本文做者:Wang Hyde
代碼倉庫:
https://gitcafe.com/callmewhy
博客地址:
http://callmewhy.gitcafe.iogit
最近在給之前的老項目維護,提及來工做很簡單,一個字:改 Bug。這看起來平淡無常的工做,實際上兇險無比,藏坑無數。時至今日,感受整我的都獲得了昇華。在睡覺前抽空寫篇博客,和各位分享一下踩坑經歷,一塊兒品味其中的種種酸苦辣(沒甜)。程序員
爲保證個碼隱私,文中代碼均爲化名,還望諒解。若有雷同,純屬巧合(能夠經過 git blame
查看是誰寫的)。緩存
若是要折磨一個強迫症,最好的方法就是用各類噁心的變量名噁心死他。安全
什麼?你說首字母要大寫?服務器
@property (nonatomic, assign) PERSONTYPE personType;
什麼?你說單詞裏面要小寫?網絡
typedef enum tagPersonType{ person_type = 1, group_type, } PERSONTYPE;
什麼?你說要用英文單詞命名?異步
- (void)uploadSeccess:(MessageEntity *)message;
什麼?你說類前面要加前綴避免衝突?tcp
@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 能夠用宏定義?post
#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/