前言
最近公司的項(xiàng)目要更新所有界面的 UI 風(fēng)格,趁此機(jī)會(huì)正好把項(xiàng)目重構(gòu)一遍,本文主要記錄重構(gòu)時(shí)的一些選擇和解決的問題。
背景
首先說說背景,也就是為什么要重構(gòu)熟吏,因?yàn)橹貥?gòu)是需要成本的,一不小心修改錯(cuò)了玄窝,就會(huì)讓原來完好的產(chǎn)品出各種問題牵寺,所以先總結(jié)下為什么,主要是以下四個(gè)問題:
- 1.沒有統(tǒng)一的代碼風(fēng)格
這個(gè)就比較痛苦了恩脂,因?yàn)闅v史的原因帽氓,或者說這個(gè)項(xiàng)目初期就沒有代碼規(guī)范,在接手項(xiàng)目前俩块,代碼風(fēng)格就非常隨意黎休、天馬行空。在此期間我重構(gòu)了一部分玉凯,但仍有一大半不規(guī)范的代碼势腮。 - 2.臃腫的 ViewController
這個(gè)好理解,一個(gè)類幾千行代碼漫仆,應(yīng)該的不應(yīng)該的都擠在一起捎拯。 - 3.難以理解的邏輯代碼
業(yè)務(wù)邏輯代碼混亂不堪,看的頭疼盲厌,還不敢亂刪署照。 - 4.混亂的類調(diào)用
沒有明確一個(gè)類該做什么,全都堆在一起吗浩。
追求代碼質(zhì)量是一個(gè)優(yōu)秀程序員對(duì)自己的要求建芙,所以趁這個(gè)機(jī)會(huì),決定把整個(gè)項(xiàng)目重構(gòu)一遍懂扼。
架構(gòu)的選擇
當(dāng)前在 iOS 開發(fā)上有很多熱門的架構(gòu)模式禁荸,如 MVC、MVVM阀湿、MVCS屡限、VIPER 等等。對(duì)此我的觀點(diǎn)是:架構(gòu)的選擇要結(jié)合具體的情況炕倘,比如業(yè)務(wù)的復(fù)雜度、開發(fā)人員的接受程度翰撑,以及重構(gòu)的時(shí)間周期等等罩旋,選擇一個(gè)對(duì)的架構(gòu)比選擇一個(gè)復(fù)雜的架構(gòu)更為重要啊央。
在老項(xiàng)目里選擇的是 MVC,對(duì)于項(xiàng)目中的絕大部分場(chǎng)景來說涨醋,MVC 沒有成為制約業(yè)務(wù)發(fā)展的瓶頸瓜饥,同時(shí)考慮到新項(xiàng)目的學(xué)習(xí)成本以及重構(gòu)時(shí)間周期的問題,所以在新項(xiàng)目上還是選擇了 MVC浴骂。
統(tǒng)一的代碼風(fēng)格
無規(guī)矩不成方圓乓土,在老項(xiàng)目里由于流程的缺失和開發(fā)人員的更迭,一直沒有一個(gè)統(tǒng)一的編碼規(guī)范溯警。而在多人開發(fā)時(shí)趣苏,保持統(tǒng)一的編碼規(guī)范是很有必要的,這樣易于保持代碼一致性和 Code Review梯轻。所以確定了架構(gòu)之后食磕,接著就是確定編碼規(guī)范。
下面是編碼規(guī)范里一些需要注意的點(diǎn):
命名
使用可讀的駝峰命名法去給類喳挑、方法彬伦、變量命名。
常量應(yīng)該使用駝峰式命名規(guī)則伊诵,所有的單詞首字母大寫和加上與類名有關(guān)的前綴单绑。
代碼分塊
在函數(shù)分組和protocol/delegate實(shí)現(xiàn)中使用#pragma mark -來分類方法,要遵循以下一般結(jié)構(gòu):
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Protocol conformance
留空白
- 建議使用tabs 而不是使用空格,tabs縮進(jìn)使用4個(gè)空格曹宴,確保在Xcode偏好設(shè)置來設(shè)置搂橙。
- 文件結(jié)束時(shí)留一行空白
- 用足夠的空行把代碼分割為合理的邏輯塊,而不是非常緊湊
- 不要在一行代碼結(jié)尾處留空格
- 更不要在空行(\n)中使用縮進(jìn)(\t)
代碼塊縮進(jìn)
(if/else/switch/while etc.)或者method function 的大括號(hào)留在當(dāng)前行浙炼,并前保留一個(gè)空格 份氧,能省略的不要添加
如
if user.isHappy {
// Do something
} else {
// Do something else
}
不推薦
if (user.isHappy ) 多余空格
{ 換行位置不對(duì)
// Do something
}
else {
// Do something else
}
項(xiàng)目層級(jí)劃分
物理層級(jí)
上文確認(rèn)了項(xiàng)目架構(gòu)是以 MVC 為主,所以這里推薦使用 MVC + 按業(yè)務(wù)劃分項(xiàng)目層級(jí)的方式弯屈。具體的如下圖:
整個(gè)項(xiàng)目主要分為4個(gè)文件夾蜗帜,分別是:
- AppDelegate: 程序入口。
- Classes: 存放主要代碼文件资厉。
- Resources: 存放資源文件厅缺,如圖片、音頻宴偿、視頻湘捎、 HTML 文件等。
- Supporting Files: 存放配置文件窄刘,如 Info.plist窥妇、main.m、pch 文件等娩践。
而其中 Classes 目錄下活翩,又細(xì)分如下圖:
- General:存放一些通用的類烹骨,如基類、Category材泄、Model 等沮焕。
- Sections:按業(yè)務(wù)模塊細(xì)分 MVC。
- Helpers:存放各種工具類拉宗。
- Venders:存放需要手動(dòng)引入的第三方庫峦树。
- Macro:存放全局頭文件,各種宏定義旦事,常量等魁巩。
代碼邏輯層級(jí)
從代碼邏輯上,大致分為 5 層
- 網(wǎng)絡(luò)層:負(fù)責(zé)和服務(wù)器通訊獲取數(shù)據(jù)族檬。
- 數(shù)據(jù)層:存儲(chǔ)用戶的數(shù)據(jù)歪赢,包括內(nèi)存cache。
- 業(yè)務(wù)層:包含各種業(yè)務(wù)邏輯单料。
- UI數(shù)據(jù)層:負(fù)責(zé)提供UI層所需要的數(shù)據(jù)埋凯,UI只和這層打交道。
- UI層:包括ViewController和View扫尖,處理用戶的輸入白对。
分層使項(xiàng)目更清晰,開發(fā)時(shí)做到各個(gè)層獨(dú)立换怖,高內(nèi)聚甩恼、低耦合。
布局規(guī)范
AutoLayout
老項(xiàng)目是純 Frame 布局沉颂,代碼里有一大堆 Magic Number条摸,一大堆屏幕尺寸判斷,而這些造成了項(xiàng)目里有過多的布局代碼铸屉,對(duì)于剛上手項(xiàng)目的人來說也不太容易看懂钉蒲。所以這次重構(gòu)整體采用 AutoLayout 布局,摒棄老項(xiàng)目里的純 Frame 布局方式彻坛,精簡(jiǎn)代碼顷啼。
Xib/Storyboard
手寫 UI 和 Xib/Storyboard 誰優(yōu)誰劣這里不做討論,但有一點(diǎn)我認(rèn)為 Xib/Storyboard 是比手寫 UI 要快的昌屉。而這次重構(gòu)工作量大工期緊钙蒙,所以摒棄老項(xiàng)目里的手寫 UI ,改用 Xib/Storyboard 间驮。
精簡(jiǎn) ViewController
這一部分我認(rèn)為是本次重構(gòu)的重頭戲躬厌,也是本次重構(gòu)的主要目的所在,精簡(jiǎn) ViewController 里的代碼竞帽,解決老項(xiàng)目中 Massive ViewController 的問題烤咧。
主要工作分為以下幾步:
- 1.保留最重要的任務(wù)偏陪,拆分其它不重要的任務(wù)。
- 2.拆分后的模塊要盡可能提高可復(fù)用性煮嫌,盡量做到DRY
- 3.提高拆分模塊后的抽象度
胖 Model 的問題
上文精簡(jiǎn) ViewController 把代碼都加入到 Model 里去,所以會(huì)造成胖 Model 的問題抱虐。對(duì)此我的理解是昌阿,除了優(yōu)化外,代碼的總量是確定的恳邀,一方的精簡(jiǎn)必然會(huì)造成一方的增加懦冰,而 Model 在這里是用來幫 ViewController 處理業(yè)務(wù)邏輯的,也就是說胖 Model 包含了部分弱業(yè)務(wù)邏輯谣沸。胖 Model 要達(dá)到的目的是刷钢,Controller從胖 Model 這里拿到數(shù)據(jù)之后,不用額外做操作或者只要做非常少的操作乳附,就能夠?qū)?shù)據(jù)直接應(yīng)用在 View 上内地,從這點(diǎn)上看胖 Model 也是可以接受的。
單元測(cè)試
老項(xiàng)目是不寫測(cè)試代碼的赋除,也沒有寫單元測(cè)試的條件阱缓。想想一個(gè)類堆砌幾千行代碼,想寫也不好下手举农,但既然重構(gòu)了荆针,單元測(cè)試也得補(bǔ)上。
單元測(cè)試對(duì)于目前來說颁糟,就是為了方便測(cè)試一些功能是否正常運(yùn)行航背,還有調(diào)試接口是否能正常使用。
有時(shí)候你可能是為了測(cè)試某一個(gè)網(wǎng)絡(luò)接口棱貌,然后每次都重新啟動(dòng)并且經(jīng)過很多操作之后才測(cè)試到了那個(gè)網(wǎng)絡(luò)接口玖媚,如果使用了單元測(cè)試,就可以直接測(cè)試那個(gè)方法键畴,相對(duì)方便很多最盅。
比如由于修改較多,想測(cè)試一下分享功能是否正常起惕,這時(shí)候就有用了涡贱。或者直接看一些接口返回的數(shù)據(jù)也會(huì)非常直觀惹想,不用啟動(dòng)整個(gè)工程问词。
TODO
在這也列舉兩個(gè)接下來可以做的事,先添加到 ToDoList 里嘀粱。
URLRoute
模塊化
最后
其實(shí)總結(jié)起來就三點(diǎn)激挪,盡量保證:
- Controller 里的代碼盡可能的少
- Model 的功能盡可能的完整
- View 盡可能獨(dú)立
就能構(gòu)建一個(gè)容易維護(hù)辰狡,便于協(xié)同的項(xiàng)目。
本文只是在項(xiàng)目重構(gòu)中總結(jié)的一些比較重要的點(diǎn)垄分,并不意味著都是最優(yōu)解宛篇。而我在項(xiàng)目的架構(gòu)和代碼的組織上經(jīng)驗(yàn)尚淺,若本文有什么錯(cuò)誤或是有更好的方法請(qǐng)直接指出薄湿,歡迎交流討論叫倍。