V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GuBonjour
V2EX  ›  GitCafe

要你命三千——老代码中的那些坑

  •  
  •   GuBonjour · 2015-01-23 16:38:49 +08:00 · 4381 次点击
    这是一个创建于 3592 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文作者:Wang Hyde
    代码仓库:
    https://gitcafe.com/callmewhy
    博客地址:
    http://callmewhy.gitcafe.io

    最近在给以前的老项目维护,说起来工作很简单,一个字:改 Bug。这看起来平淡无常的工作,实际上凶险无比,藏坑无数。时至今日,感觉整个人都得到了升华。在睡觉前抽空写篇博客,和各位分享一下踩坑经历,一起品味其中的种种酸苦辣(没甜)。

    为保证个码隐私,文中代码均为化名,还望谅解。如有雷同,纯属巧合(可以通过 git blame 查看是谁写的)。

    第一回:变量命名没点数,有时写着还手误

    如果要折磨一个强迫症,最好的方法就是用各种恶心的变量名恶心死他。

    什么?你说首字母要大写?

    @property (nonatomic, assign) PERSONTYPE personType;
    

    什么?你说单词里面要小写?

    typedef enum tagPersonType{
      person_type = 1,
      group_type,
    } PERSONTYPE;
    

    什么?你说要用英文单词命名?

    - (void)uploadSeccess:(MessageEntity *)message;
    

    什么?你说类前面要加前缀避免冲突?

    @interface PMWLogger : NSObject
    ...
    @interface PMTool : NSObject
    ...
    @interface MainControler : NSObject
    

    什么?你说文件要按照目录存放?

    - Classes
          - MainControllers
            - MyController
            - Controllers
            - SettingControllers
            - ChatModel.h
            - ChatModel.m
            - SettingControllers (不是手误)
          - Chatting
          - SearchView.h
          - SearchView.m
          - Voice
          - AgentModels
       - Public 
         - Common
         - PublicDef.h
         - PublicDef.m
    

    什么?听说 OC 可以用宏定义?

    #define STRHASSBUSTR(str,subStr) ...
    

    各位看官,这,能忍?

    正所谓:

    命名拼写看心情,文件目录不分明。
    随机掺杂宏定义,鸡不安也犬不宁。

    第二回:界面全靠神奇数,保准看到就发怵

    前阵子在做 iPhone4 和 iPhone6 以及 iPhone6 Plus 的适配工作。

    由于历史原因没有用 AutoLayout ,也由于历史原因老代码的布局全是用数字一个一个写死的。这就给适配带来了莫大的困难。

    随便拣点代码给大家欣赏欣赏:

    UILabel *infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 241, 320, 28)];
    

    0 这种数字还好说,241 就完全让人摸不着头脑,至于 320 这个改成屏幕宽度倒也就还好,但是 28 这种神奇数字又是什么呢?

    这种代码就是冲着干死队友的不偿命的态度去的。虽然写起来容易,但是维护困难,可读性极差,尤其是有多个控件布局的时候,依赖关系不明显,如果调整布局需要挨个重新计算并设置值,维护起来的酸爽,谁用谁知道。

    要说神奇数字,集大成者莫过于此:

    CGRect rect = CGRectMake(12.2+(page-1)*320+42.5*(i%7),((totalRows-1)%3)*55+2,42.5,42.5);
    

    那天早上看到这代码差点就抱着键盘委屈的哭了出来。

    正所谓:
    界面写法各不同,歪门邪道千万种。
    有朝一日被辞了,你的代码我不懂。

    第三回:私有公有混一处,代理委托亦糊涂

    在聊天的时候有这样一个数据类:

    @interface HBTalkData : NSObject
        {
          UIImage *_firstImage;
          NSArray *_imageArry;
          id _contents;
        }
        @property (nonatomic, assign) NSInteger messageId;
        @property (nonatomic, strong) id contents;
        @property (nonatomic, assign) NSTimeInterval timeInterval;
        @property (nonatomic) BOOL fromSelf;
        @property (nonatomic) BOOL isGroup;
        @property (nonatomic, assign) HBTalkDataStatus talkDataStatus;
        @property (nonatomic) HBTalkDataContentType contentType;
        @property (nonatomic, strong) PersonInfo *personInfo;
        @property (nonatomic, strong) UserInfo *cardUser;
        @property (nonatomic, assign) CallType callType;
        @property (nonatomic, strong) NSString *duartion;
        @property (nonatomic, strong) NSString *mPhoneNumber;
        @property (nonatomic, strong) NSString *imageList;
        @property (nonatomic, strong) NSString *msgDesc;
        @property (nonatomic, readonly) UIImage *firstImage;
        @property (nonatomic, readonly) NSArray *imageArry;
        @property (nonatomic, assign) float     cellHeight;
        @property (nonatomic, assign) CGSize    textSize;
        @property (nonatomic) NSTimeInterval voiceDuration;
        @property (nonatomic) CGFloat dataSize;
        @property (nonatomic) NSUInteger bubbleCount;
        @property (nonatomic, copy) NSString *chatUserName;
        @property (nonatomic, strong) MessageEntity *originalMessage;
        @property (nonatomic, strong) HBTalkDataRegisterInfo *registerInfo;    
        -(void)reset;
        -(NSString *)bubbleDescription;
        ...
        @end
    

    纤弱的头文件里塞满了各种属性定义和方法定义,仿佛可以听到头文件的不满和娇喘。

    给大家出个题:看下下面的内容,猜一下这个类的文件名是什么:

    ... // 此处省略20行
    
    @interface PersonInfo : NSObject
    ... // 此处省略20行
    @property (nonatomic, assign)BOOL     isGrey;
    @property (nonatomic, assign)BOOL     isBlack;
    @property (nonatomic, assign)BOOL     isTop;
    @property (nonatomic, assign)BOOL     isStar;
    
    - (BOOL)isStranger;
    - (BOOL)isIndividual;
    - (BOOL)isDuDuSecretary;
    
    @end
    
    @interface UserInfo : PersonInfo
    ... // 此处再省20行
    @property (nonatomic, assign)BOOL     mobileVerified;
    @property (nonatomic, strong)NSString *countryCode;
    @property (nonatomic, readonly)NSString *dialogName;
    @end
    
    @interface GroupInfo : PersonInfo
    ... // 此处又省20行
    @property (nonatomic, strong)NSString *creater;
    @property (nonatomic, assign)int      memberCount;
    @property (nonatomic, strong)NSString *members;
    @end
    

    嗯然后这个文件叫做 UserInfo.h ,头文件将近 100 行。大兄弟,我读书少,你不要骗我。把三个类塞在一个文件里这种行为,除了难为队友,实在是没看出来有什么其他动机可言。

    正所谓:

    头文件里地方小,塞到一处并不好。
    外部对象都知道,安全问题可不小。

    第四回:消息通知满天飞,委托方法一大堆

    我一直在想,到底是什么,让这个项目的开发人员对 NSNotificationCenter 如此痴迷,痴迷的令人陶醉。

    在通过 Model 调用业务逻辑的时候,它这样发了一条命令:

    // 喂,LOGIN_MODEL,帮我查下有没有更新
    [LOGIN_MODEL versionCheckFromAbout:YES];
    

    这个业务是用 GCD 开了新线程来做的,在后台检查有没有更新,如果有更新那么版本号后面会加个感叹号。那么问题来了:你咋告诉我你检查的结果是有更新还是没更新呐?难道要写 个委托?然后定义个方法?然后更新的时候指认委托?然后有了结果再告诉委托?听起来就很烦躁嘛那干脆就用通知好了:

    if (self.versionStatus != VersionStatusNormal) {
        [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFY_HAS_NEW_VERSION object:nil];
    }
    

    然后在需要做处理的类里面添加 Observer 就可以了:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myIconShouldChange) name:NOTIFY_HAS_NEW_VERSION object:nil];
    

    哈哈哈哈搞定了。

    哈哈哈哈你个头啊!整个项目里类似于这种的通知就有十来个,这还是有宏定义的,好追杀一点。对于那些没有宏定义的,随手一写复制粘贴的,不知道还要填坑多少。

    通知虽好,但也不要贪杯啊。

    看起来轻松,只是 post 了一下就搞定了,但是在 Debug 的时候有点麻烦。尤其是如果有多个 Observer ,改动的时候牵一发而动全身。如果真的是有这样使用的必要倒也罢了,但是本来一个 block 或者 delegate 就能简单清晰的解决,现在却被搞得这么繁重,实在是没有必要。

    而且 NSNotificationCenter 的代码基本是一种变相的复制粘贴,十分的不工整。这是个人恩怨了,撇开不提。

    NSNotificationCenter 这种只是不痛不痒的小问题,仅仅是逻辑不够优雅,关系不够清晰罢了。但是如果委托使用不当那是恶心的不行。看下这个聊天页面:

    @interface ChattingViewController () <UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, ChattingActionsPanelDelegate, ChatModelDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, HBTalkTableViewCellDelegate, EGORefreshTableHeaderDelegate, XTImagePickerControllerDelegate, ChattingInputPanelDelegate, VoiceRecordingButtonTrashBinViewContainer, ChattingUserDetailPanelDelegate, VoiceRecordingButtonDelegate>
    

    这是一个真实的故事 。整个类将近 3000 行,有 2000 多行是委托里定义的方法,你能信?

    在这三千行代码里漫步,万事都要小心。因为你不知道 callIn 这种方法到底是定义的私有方法,还是在委托里定义的方法。#pragma mark 自然也是看心情加的,说不定加错了你也不要当真。

    有时候委托都删了不见影子了,但是委托里的各种方法还留在以前的类里。

    没人敢动。

    How to play.

    正所谓:

    异步回调用通知,委托多的令人痴。
    反正老子看不懂,不写代码光写诗。

    第五回:第三方库无出处,随手改动无备注

    相信做 iOS 的都知道 AFNetworking 这个网络库,在我们的项目里 AFNetworking 分两种,一个是别人家的 AFNetworking ,一个是咱们的 AFNetworking。对奏是这么任性。在一个 300 行的头文件里,在 99 行这样低调的位置里,静静的插上了自己的方法,还在上面认认真真的写上了准确的注释:

    /*扩展*/
    -(void)setDDCImageWithURL:(NSURL *)url
             placeholderImage:(UIImage *)placeholderImage
                      success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                      failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
    

    扩展个头啊!你加在人家的头文件里你说你是扩展,谁信?

    这种改动遍地都是,特点是极其低调,难以察觉,甚至 TTTAttributedLabel 这种 UI 库也不能避免:改了 init 为了统一字体和颜色。。。

    你说这代码,谁敢改?

    我还曾经单纯的想给项目加上 Cocoapods 更新一下第三方库,现在想想,Naive。等以后写到新的独立模块的时候再说吧。

    正所谓:

    项目勤用三方库,随意穿插改无数。
    即使类库有更新,试问代码谁维护。

    第六回:单个对象多职责,悲伤逆向流成河

    在聊天模块有这样一个类:ChatModel,简直就是个多面手。

    上能和服务器聊天,上传聊天消息同步聊天记录:

    - (void)reSendMessages;
    - (void)receiveSecretaryMessage:(MessageEntity *)msgEntity;
    - (void)deleteMessagesByUserInfo:(UserInfo *)user;
    - (void)setAudioMessageBePlayed:(AudioMessageEntity *)audioMessage;
    - (void)sendBubbleReplyWithCallMessage:(CallMessageEntity *)callMessage;
    - (int)saveMessage:(MessageEntity *)message;
    

    下能做本地缓存管理,增删改查样样精通:

    - (void)saveCacheMsg:(NSString *)msg UserMd5:(NSString *)md5;
    - (NSString *)loadCacheMsgWithMd5:(NSString *)md5;
    - (void)clearCacheMsgWithMd5:(NSString *)md5;
    

    至于什么弹窗提醒,上传进度,完成提示,亦是轻松拿下。

    以至于你改着改着不知不觉都会走到这里,因为它处理了太多太多的业务逻辑,每次 DEBUG 追杀断点回到这里,都像是一场久别重逢时的相遇,似曾相识。

    正所谓:

    一人做事一人当,切忌都往类里装。
    开发人员干的爽,维护人员很受伤。

    第七回:产品突增新功能,一行代码变大神

    有时候需求来也匆匆去也匆匆,让人猝不及防。比如一个简单的登录逻辑:

    @interface LoginModel ()
    @property (nonatomic, strong)NSString *tcpURL;
    @property (nonatomic, strong)UserInfo *offlineCallUser;
    @property (nonatomic, assign)VersionStatus versionStatus;
    @end
    

    突然发现需要在版本更新的时候多个 API 检查,简单,加个 BOOL ,需要的时候设置成 YES 就行:

    @property (nonatomic, assign)BOOL isShowVersionUpdate;
    

    但是这个功能在 About 页面又有点改动,简单,再加个 BOOL 就行:

    @property (nonatomic, assign)BOOL checkVersionFromAbout;
    

    然后如果已经显示了注册页面又要少一些请求,行,那再加个 BOOL 值:

    @property (nonatomic, assign)BOOL isRegisterShow;
    

    得了,这代码只有你能懂了:

    @interface LoginModel ()
    @property (nonatomic, strong)NSString *tcpURL;
    @property (nonatomic, strong)UserInfo *offlineCallUser;
    @property (nonatomic, assign)VersionStatus versionStatus;
    @property (nonatomic, assign)BOOL isShowVersionUpdate;
    @property (nonatomic, assign)BOOL checkVersionFromAbout;
    @property (nonatomic, assign)BOOL isRegisterShow;
    @end
    

    想象一下实现方法里各种对 BOOL 标记的特殊处理,想象一下 N 个 if 嵌套的壮观场景。

    心塞。

    正所谓:

    凡是都要听产品,各种业务催的紧。
    天塌下来也别怕,逻辑清晰自然挺。

    第八回:来了任务有委托,多写一行都嫌多

    所谓悲哀就是,当程序员发现一个 delegate 就能访问上级的对象,于是便把各种需要通知上级的事情都放在了委托方法里,尽管这些事情与委托本身无关,但是为了实现功能已经不在意这些所谓的设计与美观。

    一个简单的 @optional,甚至可以用同一个 @protocol 获取到各种不同的上级对象,只需要每次调用的时候加个 respondsToSelector 就行了。写上十几个可选方法,取一个通俗的委托名,比如 MyDelegate ,然后如果你持有了我但是我还想调用你的方法, so easy,把你的方法扔到 MyDelegate 即可。

    此时的代码便已经不再是一件艺术品,而只是一个平凡普通、毫无生机的花瓶了。

    小结:

    原本还是挺欢快的吐槽,突然就不想写了。

    看着以前的人写的代码,不禁有些凄凉。

    在项目里用尽了各种低级下流的手段,只为了实现自己的业务。

    这是对艺术的侮辱。

    本文作者:Wang Hyde
    代码仓库:
    https://gitcafe.com/callmewhy
    博客地址:
    http://callmewhy.gitcafe.io

    原文地址: http://callmewhy.gitcafe.io/2015/01/20/wtf-in-old-code/

    14 条回复    2015-01-24 07:22:03 +08:00
    yksoft1
        1
    yksoft1  
       2015-01-23 16:58:57 +08:00
    差不多吧,公司没有能力和时间进行代码风格培训和代码质量强制检查,最后就只有这样悲惨的结果了。。。当然这样的混乱在以前的大公司写的代码里都不少,比如微软的word for windows 1.1,nt4 src pack里面那些也出现了很多奇怪的宏,magic number之类
    yyfearth
        2
    yyfearth  
       2015-01-23 17:00:46 +08:00
    看了之后确实很有感触

    不过作为一个后来进入对原有系统进行维护和改进的队员
    很多时候这种事情会不得已而为之
    有时候如果项目先后经过多人的手 但是依然没有重写
    不一致的现象到处都是 毕竟每个人的习惯和风格都不一样
    也不是很严肃的项目 也没有任何文档 更没有编码规范

    另外就是由于项目过于庞大 作为一个后来的开发者 不可能对整个项目全部了解
    往往一知半解的时候就不得不去修bug 加功能
    只是复制粘贴已经还算是不错的了 至少看的出来
    各种补丁破坏原有的设计 修修补补都完全看不出来在当初做什么了
    各种类和方法以及接口和数据库的命名 随着需求的变化 慢慢的和实际用途完全不沾边 但是谁都不敢改它

    项目做的后来 不要说艺术了 能跑起来 简直就是个奇迹
    stiekel
        3
    stiekel  
       2015-01-23 17:01:47 +08:00
    gitcafe.io + hexo,我也是这套
    sanddudu
        4
    sanddudu  
       2015-01-23 17:10:19 +08:00 via iPhone
    请不要全文转载
    GuBonjour
        5
    GuBonjour  
    OP
       2015-01-23 17:55:01 +08:00
    @yyfearth 同深有感触~~
    “我操这是什么垃圾代码,删了吧”
    “我操,怎么跑不起来了”
    “要不还是放回去吧”
    “我操,跑起来了”
    “好吧,放着吧,别动了”

    亲身经历= =
    GuBonjour
        6
    GuBonjour  
    OP
       2015-01-23 17:55:36 +08:00
    @stiekel 碰到用户了 好激动T^T
    GuBonjour
        7
    GuBonjour  
    OP
       2015-01-23 17:55:54 +08:00
    @sanddudu 作者已经授权进行全文转载了的
    Elethom
        8
    Elethom  
       2015-01-23 18:21:44 +08:00
    好奇翻看了下作者的 blog, 實在不覺得作者哪裡有資格寫文章公開批評別人的代碼風格, 雖然說得並沒有錯倒是.
    sanddudu
        9
    sanddudu  
       2015-01-23 18:27:28 +08:00
    @GuBonjour 但是 V2EX 不允许全文转载
    留下地址就可以了

    @livid
    Livid
        10
    Livid  
    MOD
       2015-01-23 18:34:32 +08:00
    @sanddudu 如果是转载自己写的东西是没问题的。
    lyklykkkkkkk
        11
    lyklykkkkkkk  
       2015-01-23 21:19:55 +08:00
    看到最后以为会是招聘广告呢…
    beimenjun
        12
    beimenjun  
       2015-01-24 00:39:21 +08:00
    作者自我感觉挺好的,挺有自信的。
    chairuosen
        13
    chairuosen  
       2015-01-24 01:26:13 +08:00
    前端狗表示代码没怎么看懂,但是作者yin得一手好湿啊
    stiekel
        14
    stiekel  
       2015-01-24 07:22:03 +08:00
    @GuBonjour 哈哈,你在gitcafe工作?还是hexo的贡献者?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2736 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 12:08 · PVG 20:08 · LAX 04:08 · JFK 07:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.