以前做個(gè)人項(xiàng)目的時(shí)候,簡(jiǎn)歷上寫過重構(gòu)了三次,后來在扇貝面試的時(shí)候,面試官問三次分別重構(gòu)了什么,仔細(xì)想想那時(shí)候的重構(gòu)并不算重構(gòu),第一次是 UI 改版,但是項(xiàng)目結(jié)構(gòu)沒什么大的變化,第二次是整體遷移到了 CocoaPods,這次勉強(qiáng)能算重構(gòu),第三次僅僅是變量名方法名空行這些地方的風(fēng)格統(tǒng)一而已。
在現(xiàn)在工作的地方,接手這些項(xiàng)目之后,主要工作做的是重構(gòu),而重構(gòu)工作,本來想寫成一行,結(jié)果發(fā)現(xiàn)挺多,我列個(gè)列表吧:
刪除沒用到的第三方庫(kù)
刪除不合理的第三方庫(kù),使用系統(tǒng)自帶的或者自己造輪子
刪除定義好但是沒有用到的變量
刪除 import 進(jìn)來但是沒有用到的頭文件
刪除更舊項(xiàng)目留下來的用不到的邏輯
Controller 層不合理的層級(jí)結(jié)構(gòu)重構(gòu),無用代碼清理
View 層不合理的結(jié)構(gòu)重構(gòu)
Service 層冗余的寫法重構(gòu)
Model 層不合理的寫法重構(gòu)
拆開不合理的耦合
耦合一個(gè)類別的模塊
修復(fù)了多處內(nèi)存泄露
修復(fù)了多處循環(huán)引用
優(yōu)化編譯速度
消除項(xiàng)目中的 warning
關(guān)于刪除代碼,在某個(gè)項(xiàng)目里,Pods 文件夾那些第三方庫(kù)的代碼刪了 9 萬多行(那個(gè)目錄沒有被 git ignore 掉),項(xiàng)目里面刪除了大約 4 萬行,其中大量代碼是該項(xiàng)目之前的項(xiàng)目里面留下來的東西,只不過沒人清理。在刪了 4 萬行之后,程序仍然能完整的跑。
接下來是做了部分重構(gòu),把一些第三方庫(kù)刪掉,自己造輪子,在這個(gè)過程中,累計(jì)刪除了 1.2 萬行代碼,增加了 1100 行左右。
整個(gè)重構(gòu)工作下來,編譯速度從 2-3 分鐘減小到了 40 多秒,warning 從 70 多減少到了 0,第三方庫(kù)的數(shù)量從 51 個(gè)減少到了 13 個(gè),安裝包從 22.1M 減小到了 3.7M,功能反而比之前還要多。
內(nèi)存泄露方面,因?yàn)闆]人在意這件事,有一個(gè)功能使用一次,就會(huì)增加好幾百 kb 內(nèi)存,那部分代碼是用 C 寫的,所以及時(shí)釋放內(nèi)存,并且優(yōu)化下調(diào)用方式,內(nèi)存泄露的問題就完美解決。
循環(huán)引用方面,是因?yàn)橛腥税?Xcode 的 warning 關(guān)了,后來打開的時(shí)候,發(fā)現(xiàn)了四個(gè)循環(huán)引用 + 幾十個(gè) warning,并且測(cè)試過程中發(fā)現(xiàn)那個(gè)頁面不斷打開退出,程序會(huì) crash。
籠統(tǒng)的就這么多,我再來分享幾個(gè)具體的點(diǎn)。
單例用著確實(shí)爽,但是程序退出之前是不會(huì)被回收的,如果是整個(gè)生命周期基本用不到的模塊做成單例,那么只會(huì)浪費(fèi)內(nèi)存而已。
具體是什么意思呢,用網(wǎng)絡(luò)層舉例子,封裝 AFN 是一層,API 的后綴字符串放一層,構(gòu)造請(qǐng)求放一層,OAuth 授權(quán)放一層,發(fā)普通請(qǐng)求又是一層。增加一個(gè) API,至少要修改 6 個(gè)文件。寫著也很痛苦,看著也很痛苦啊。
網(wǎng)絡(luò)層就只設(shè)計(jì)一層,封裝 AFN,發(fā)請(qǐng)求的函數(shù)也在里面,API 地址直接用字符串寫進(jìn)去,搞那么多層沒實(shí)際意義,在這么小的一個(gè)項(xiàng)目里面。
除此之外,關(guān)于項(xiàng)目文件結(jié)構(gòu),一兩個(gè)文件的建議不要新建文件夾放進(jìn)去,這個(gè)主要是個(gè)人習(xí)慣,其實(shí)無大礙。
- (void)requestAtPathForRouteNamed:(NSString *)routeName object:(id)object parameters:(NSDictionary *)parameters
和
- (void)requestWithMethod:(XXHTTPMethod)method path:(NSString *)path params:(id)params paramsType:(XXParamType)paramsType
之前這樣設(shè)計(jì)的目的是 param 放 form data 類型數(shù)據(jù),object 放 json 格式,顯然不合理,同一個(gè) API 不應(yīng)該允許同時(shí)存在 form data 和 json,如果采用第一種,新來的同事可能會(huì)認(rèn)為這兩個(gè)都可以填數(shù)據(jù),這是不符合我們期望的。
甚至再極端一點(diǎn),某天我們需要傳文件過去,是不是還得再擴(kuò)充字段。
如果采用第二種,param 是 id 類型,如果是 json,type 傳入 json 枚舉類型,如果是二進(jìn)制,type 傳入二進(jìn)制枚舉類型,只留一個(gè)字段暴露給開發(fā)者更合理。
我們的項(xiàng)目中有一條漸變顏色的線要到處用到,這條線我們放在了 UIImage+XXUtil.h 里面,之前的設(shè)計(jì)是這樣的:
+ (instancetype)xx_navigationBarShadowImage
在 .m 的實(shí)現(xiàn)中,還把 UIColor+XXTheme 耦合進(jìn)去了,并且這個(gè)方法已經(jīng)脫離了類名 Util 的實(shí)質(zhì),他已經(jīng)不是一個(gè)通用的工具了,重構(gòu)之后的命名是這樣的:
+ (instancetype)xx_gradientImageWithStartColor:(UIColor *)aColor endColor:(UIColor *)bColor andWidth:(CGFloat)width
這樣就很符合 Util 這個(gè) category 名字。
繼承確實(shí)很好用,帶來的后果就是子類會(huì)把父類的方法挨個(gè)執(zhí)行一遍,乍一看沒什么,但是如果這個(gè)方法很消耗性能呢。
我們這個(gè)項(xiàng)目就遇到了,app 經(jīng)??ㄋ?,用著用著,就 freeze 了,點(diǎn)哪里都沒反應(yīng)。因?yàn)樗许撁娑祭^承自基類的一個(gè)設(shè)計(jì),恰好基類里面有一個(gè)比較耗時(shí)的操作,每個(gè)頁面都會(huì)執(zhí)行至少三次,就導(dǎo)致了頁面假死。
重構(gòu)后的做法是設(shè)計(jì)成一個(gè) category,只是給 UIViewController 添加了幾個(gè)方法,按需調(diào)用,不需要在每個(gè)頁面都調(diào)用,于是解決了這個(gè)詭異的 bug。
如果有一個(gè)功能,迫于各種原因,不得不采用第三方庫(kù),至少也要選一個(gè) GitHub 上 star 比較多的吧,其次是看看 issue 列表有沒有什么很嚴(yán)重的 bug 沒修好,以及兼容性問題,多養(yǎng)成好習(xí)慣,慢慢就能篩選出來最合適的庫(kù)了。
我們的項(xiàng)目之前有用到 YYText 這個(gè)庫(kù),就為了一段文字里面加一張圖片,活動(dòng)當(dāng)天 iOS9 設(shè)備出現(xiàn)好幾百次 crash,實(shí)際上這段代碼用 NSAttributedString attributedStringWithAttachment 寫一下,七行就夠了,七行替代掉一個(gè)不穩(wěn)定的第三方庫(kù),還是很劃算的。
不知道因?yàn)槭裁丛?,可能是更舊的項(xiàng)目里面用了 PSCollectionView,能跑就沒去重構(gòu),這類庫(kù)也是屬于完全沒必要的,系統(tǒng)自帶的足夠好用,并且更安全。
數(shù)據(jù)的處理,比如字符串進(jìn)行 UTF8 編碼,時(shí)間戳轉(zhuǎn)成 YYYY-MM-DD 字符串,這些都放在 Model 層來處理,各司其職,Model 層就是做數(shù)據(jù)處理的。
后端把各種 ID 用 long 型來記錄,是因?yàn)樗麄円鏊饕?,為了索引速度。而客戶端完全沒這個(gè)需求,直接用 string 就好,還不用擔(dān)心長(zhǎng)度不夠的溢出,做展示的時(shí)候還不用轉(zhuǎn)類型。
同樣的,金額按理說應(yīng)該用雙精度浮點(diǎn)型,因?yàn)?float 的精度不夠,結(jié)合我的開發(fā)經(jīng)驗(yàn)看,金額很少要客戶端做加減,直接用 string 即可,需要計(jì)算的時(shí)候再轉(zhuǎn)換,只轉(zhuǎn)換一次,避免丟失精度。
蘋果的 UIKit 就是最好的例子,寫什么組件不知道名字怎么起的時(shí)候,就想想蘋果有沒有類似的組件,去找找靈感。
OC 的方法名本身就很長(zhǎng)很清晰了,只是給方法名中間加幾個(gè)空格,然后作為注釋,跟沒寫一樣吧。
Git 的作用就是隨時(shí)回溯以前版本,代碼都是能找到的,把代碼注釋掉,再寫一行類似的,除了增加閱讀成本,容易引起歧義,應(yīng)該沒什么用了。一個(gè)文件一共一兩百行,打開之后發(fā)現(xiàn)七八十行代碼被注釋了,這種感覺相當(dāng)操蛋,影響閱讀。
重構(gòu)祖?zhèn)鞔a真的會(huì)有一種活久見的感覺,上面提到的那些是印象比較深刻的,還有一些小問題已經(jīng)悄悄解決了,希望我寫的代碼不會(huì)讓后面的同學(xué)也這樣認(rèn)為吧。
聯(lián)系客服