重構的定義
重構:對軟件內部結構的一種調整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。
重構與添加新功能
- 添加新功能:不修改既有代碼,只添加新功能
- 重構:不添加功能,只改進程序結構,不再添加任何測試
為何重構
- 改進軟件設計
沒有重構的代碼會逐漸腐敗變質
- 使軟件更容易理解
重構方便后續維護者理解自己寫的代碼,方便自己理解不熟悉的代碼。
幫助找到 bug
提高編程速度
良好的設計是維持軟件開發速度的根本。
何時重構
- 添加新功能時重構
- 修補錯誤時重構
- 復審代碼時重構
重構的難題
- 數據庫的重構
- 修改接口:不過早發布接口,不發布冗余接口
- 難以通過重構完成的設計改動
- 現有代碼無法正常運行時,重寫而非重構
重構與性能
重構短期內可能會影響性能,但是重構后的代碼更容易調整和優化。
性能優化階段,對性能進行具體監控后,針對性關注并優化。
代碼的壞味道
這里列舉 22 種壞味道的代碼
1. Duplicated Code(重復代碼)
同一個類的兩個函數含有相同的表達式;
互為兄弟的子類內含有相同表達式;
毫不相干的類出現重復代碼。
2. Long Method(過長函數)
小函數使間接層的解釋能力、共享能力、選擇能力得到支持;
小函數容易理解的關鍵在于一個好名字;
需要以注釋說明的時候,將說明寫進獨立函數,哪怕替換后的函數調用動作比函數自身還長,只要函數名稱能夠解釋其用途就該毫不猶豫地做。因此尋找注釋能定位到需要提煉的代碼。
3. Large Class(過大的類)
太多實例變量,太多代碼,都需要重構。
4. Long Parameter List(過長參數列)
太長的參數列難以理解;
太多參數會造成前后不一致、不易使用,一旦需要更多數據,只能修改簽名;
用對象代替參數列。
5. Divergent Change(發散式變化)
某一個特定的類,會由于不同的外部原因,導致不同的變化時,應當拆分此類;
針對某一外界變化的所有相應修改都應只發生在單一類中,且新類的所有內容都應反應此變化。
6. Shotgun Surgery(霰(xiàn)彈式修改)
遇到變化時需要修改多處不同的類,則將修改點提煉成獨立的類。
7. Feature Envy(依戀情節)
類中的函數關注其他類勝過自身,則將函數移位到其他類。
8. Data Clumps(數據泥團)
多處類出現相同的字段、相同的函數參數,且互相關聯互相影響,則提煉獨立對象。
9. Primitive Obsession(基本類型偏執)
將基本類型字段替換為對象字段。
10. Switch Statements(switch驚悚現身)
面向對象程序的一個最明顯特征:少用 switch 或 case 語句。switch 的問題在于重復,且不易修改;
嘗試用多態或不同函數簽名代替 switch。
11. Parallel Inheritance Hierarchies(平行繼承體系)
每當為一個類添加子類時,都必須為另一個類相應增加子類;
嘗試用實例引用的方式去除重復性。
12. Lazy Class(冗贅類)
刪除無足夠工作或幾乎沒用的冗贅類。
13. Speculative Generality(夸夸其談未來性)
用不到的類或函數均需移除。
14. Temporary Field(令人迷惑的暫時字段)
類中某個實例變量僅為特定情況、特定函數而設置,則將變量和相關代碼提煉為單獨的類;
通常出現在類中的復雜算法,需要多個變量,同時不易于用參數傳遞,因而采用了類實例變量的方式。
15. Message Chains(過渡耦合的消息鏈)
消息鏈表示由一串對象前后調用的邏輯,代碼與導航結構緊密耦合,一旦關系發生變化,則代碼就需要修改。
16. Middle Man(中間人)
中間人僅僅承擔接口委托的職責,則應該直接與負責對象打交道,去掉中間人。
17. Inappropriate Intimacy(狎昵關系)
類之間耦合嚴重,關系過于親密,需要解耦合。
18. Alternative Classed with Different Interfaces(異曲同工的類)
兩個邏輯相同而簽名不同的函數,需要合并。
19. Incomplete Library Class(不完美的庫類)
針對庫類的不合理處也需要彌補。
20. Data Class(純稚的數據類)
數據類除了擁有字段和訪問函數外一無長物,需要增添職責,封裝和隱藏字段。
21. Refused Bequest(被拒絕的遺贈)
子類繼承了超類,而沒有完全繼承超類的函數和數據;
嘗試新建兄弟類,轉移函數和數據。
22. Comments(過多的注釋)
注釋常常意味著代碼需要重構,重構使注釋變得多余。