要你命三千:老代碼中的那些坑

最近在給以前的老項目維護,說起來工作很簡單,一個字:改Bug。這看起來平淡無常的工作,實際上兇險無比,藏坑無數。時至今日,感覺整個人都得到了昇華。在睡覺前抽空寫篇博客,和各位分享一下踩坑經歷,一起品味其中的種種酸苦辣 (沒甜)。

爲保證個碼隱私,文中代碼均爲化名,還望諒解。如有雷同,純屬巧合 (可以通過 git blame 查看是誰寫的)。

第一回:變量命名沒點數,有時寫着還手誤

如果要折磨一個強迫症,最好的方法就是用各種噁心的變量名噁心死他。

什麼?你說首字母要大寫?

1
@property (nonatomic, assign) PERSONTYPE personType;

什麼?你說單詞裏面要小寫?

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

什麼?你說要用英文單詞命名?

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

什麼?你說類前面要加前綴避免衝突?

1
2
3
4
5
@interface PMWLogger : NSObject
...
@interface PMTool : NSObject
...
@interface MainControler : NSObject

什麼?你說文件要按照目錄存放?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- Classes
    - MainControllers
        - MyController
        - Controllers
        - SettingControllers
        - ChatModel.h
        - ChatModel.m
        - SettingControllers (不是手誤)
    - Chatting
    - SearchView.h
    - SearchView.m
    - Voice
    - AgentModels
- Public 
    - Common
    - PublicDef.h
    - PublicDef.m

什麼?聽說OC可以用宏定義?

1
#define STRHASSBUSTR(str,subStr) ...

各位看官,這,能忍?

正所謂:

命名拼寫看心情,文件目錄不分明。
隨機摻雜宏定義,雞不安也犬不寧。

第二回:界面全靠神奇數,保準看到就發怵

前陣子在做 iPhone4 和 iPhone6 以及 iPhone6 Plus 的適配工作。

由於歷史原因沒有用 AutoLayout ,也由於歷史原因老代碼的佈局全是用數字一個一個寫死的。這就給適配帶來了莫大的困難。

隨便揀點代碼給大家欣賞欣賞:

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

0 這種數字還好說,241 就完全讓人摸不着頭腦,至於 320 這個改成屏幕寬度倒也就還好,但是 28 這種神奇數字又是什麼呢?

這種代碼就是衝着乾死隊友的不償命的態度去的。雖然寫起來容易,但是維護困難,可讀性極差,尤其是有多個控件佈局的時候,依賴關係不明顯,如果調整佈局需要挨個重新計算並設置值,維護起來的酸爽,誰用誰知道。

要說神奇數字,集大成者莫過於此:

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

那天早上看到這代碼差點就抱着鍵盤委屈的哭了出來。

正所謂:

界面寫法各不同,歪門邪道千萬種。
有朝一日被辭了,你的代碼我不懂。

第三回:私有公有混一處,代理委託亦糊塗

在聊天的時候有這樣一個數據類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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

纖弱的頭文件裏塞滿了各種屬性定義和方法定義,彷彿可以聽到頭文件的不滿和嬌喘。

給大家出個題:看下下面的內容,猜一下這個類的文件名是什麼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
... // 此處省略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 調用業務邏輯的時候,它這樣發了一條命令:

1
2
// 喂,LOGIN_MODEL,幫我查下有沒有更新
[LOGIN_MODEL versionCheckFromAbout:YES];

這個業務是用 GCD 開了新線程來做的,在後臺檢查有沒有更新,如果有更新那麼版本號後面會加個感嘆號。那麼問題來了:你咋告訴我你檢查的結果是有更新還是沒更新吶?難道要寫 個委託?然後定義個方法?然後更新的時候指認委託?然後有了結果再告訴委託?聽起來就很煩躁嘛那乾脆就用通知好了:

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

然後在需要做處理的類裏面添加 Observer 就可以了:

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

哈哈哈哈搞定了。

哈哈哈哈你個頭啊!整個項目裏類似於這種的通知就有十來個,這還是有宏定義的,好追殺一點。對於那些沒有宏定義的,隨手一寫複製粘貼的,不知道還要填坑多少。

通知雖好,但也不要貪杯啊。

看起來輕鬆,只是 post 了一下就搞定了,但是在 Debug 的時候有點麻煩。尤其是如果有多個 Observer ,改動的時候牽一髮而動全身。如果真的是有這樣使用的必要倒也罷了,但是本來一個 block 或者 delegate 就能簡單清晰的解決,現在卻被搞得這麼繁重,實在是沒有必要。

而且 NSNotificationCenter 的代碼基本是一種變相的複製粘貼,十分的不工整。這是個人恩怨了,撇開不提。

NSNotificationCenter 這種只是不痛不癢的小問題,僅僅是邏輯不夠優雅,關係不夠清晰罷了。但是如果委託使用不當那是噁心的不行。看下這個聊天頁面:

1
@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行這樣低調的位置裏,靜靜的插上了自己的方法,還在上面認認真真的寫上了準確的註釋:

1
2
3
4
5
/*擴展*/
-(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 ,簡直就是個多面手。

上能和服務器聊天,上傳聊天消息同步聊天記錄:

1
2
3
4
5
6
- (void)reSendMessages;
- (void)receiveSecretaryMessage:(MessageEntity *)msgEntity;
- (void)deleteMessagesByUserInfo:(UserInfo *)user;
- (void)setAudioMessageBePlayed:(AudioMessageEntity *)audioMessage;
- (void)sendBubbleReplyWithCallMessage:(CallMessageEntity *)callMessage;
- (int)saveMessage:(MessageEntity *)message;

下能做本地緩存管理,增刪改查樣樣精通:

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

至於什麼彈窗提醒,上傳進度,完成提示,亦是輕鬆拿下。

以至於你改着改着不知不覺都會走到這裏,因爲它處理了太多太多的業務邏輯,每次 DEBUG 追殺斷點回到這裏,都像是一場久別重逢時的相遇,似曾相識。

正所謂:

一人做事一人當,切忌都往類裏裝。
開發人員乾的爽,維護人員很受傷。

第七回:產品突增新功能,一行代碼變大神

有時候需求來也匆匆去也匆匆,讓人猝不及防。比如一個簡單的登錄邏輯:

1
2
3
4
5
@interface LoginModel ()
@property (nonatomic, strong)NSString *tcpURL;
@property (nonatomic, strong)UserInfo *offlineCallUser;
@property (nonatomic, assign)VersionStatus versionStatus;
@end

突然發現需要在版本更新的時候多個 API 檢查,簡單,加個 BOOL ,需要的時候設置成 YES 就行:

1
@property (nonatomic, assign)BOOL isShowVersionUpdate;

但是這個功能在 About 頁面又有點改動,簡單,再加個 BOOL 就行:

1
@property (nonatomic, assign)BOOL checkVersionFromAbout;

然後如果已經顯示了註冊頁面又要少一些請求,行,那再加個 BOOL 值:

1
@property (nonatomic, assign)BOOL isRegisterShow;

得了,這代碼只有你能懂了:

1
2
3
4
5
6
7
8
@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 即可。

此時的代碼便已經不再是一件藝術品,而只是一個平凡普通、毫無生機的花瓶了。

小結

原本還是挺歡快的吐槽,突然就不想寫了。

看着以前的人寫的代碼,不禁有些淒涼。

在項目裏用盡了各種低級下流的手段,只爲了實現自己的業務。

這是對藝術的侮辱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章