《重構-改善既有代碼的設計》筆記

《重構-改善既有代碼的設計》筆記

在這裏插入圖片描述

重構的定義

在不改變代碼外在行爲的前提下,對代碼做出修改,以改進程序的內部結構。本質上說重構就是在代碼寫好之後改進它的設計。

第一章 重構,第一個案例

1、作者以一個影片出租店用的程序,計算每一位顧客的消費金額並打印詳單,來重構程序,告訴我們發現痛點果斷重構。

如果你發現自己需要爲程序添加一個特性,而代碼結構使你無法很方便地達成目的,那就先重構那個程序,使特性的添加比較容易進行,然後再添加特性。

2、重構前,先檢查自己是否有一套可靠的測試機制,這些測試必須有自我檢驗能力。

3、更改變量名是絕對值得的行爲,好的代碼應該清楚表達出自己的功能,變量名是代碼清晰的關鍵。

任何一個傻瓜都能寫出計算機可以理解的代碼。唯有寫出人類容易理解的代碼,纔是優秀的程序員 。

第二章 重構原則

1、兩頂帽子:添加新功能和重構。首先嚐試添加新功能,然後意識到如果把程序結構改一下,添加新功能會方便很多,於是你換頂帽子,做一會重構。然後把帽子換回來,繼續添加新功能,如此循環往復。

2、重構改進軟件設計。良好的設計是快速開發的根本。

3、重構使軟件更容易理解。

4、重構幫你提高編程速度。

5、重構的時機:不要安排固定的時間進行重構,而是開發階段隨時隨地進行重構。
三次法則:第三次再做類似的事情,你就必須要重構了。
添加新功能的時候是最常見的重構的時機。

6、重構的難題

  • 數據庫的結構很難改變,一旦改變意味着你不得不遷移數據。
  • 修改接口
  • 對於已經發布的接口需要可能需要維護舊接口和新接口,用 deprecated不建議使用)修飾舊接口;

7、重構與設計二者不是互斥的關係,而是不同的項目階段不同的選擇、重點。

8、性能優化放在開發的後期,通過分析工具找出消耗大量時間空間的地方,然後集中精力優化這些地方;

第三章 代碼的壞味道

1、壞味道首當其衝的就是Duplicated Code,idea現在都會對duplicate code默認給出警告。最單純的duplicate code就是同一個類的兩個函數或者兩個互爲兄弟的子類內有相同的表達式,這時就需要提煉出重複的代碼,然後都調用被提煉出來的代碼。

2、Long Method(過長函數)

3、Large Class(過大的類)

4、Long Parameter List (過長的參數列表)

5、發散式變化
有的類因爲不同原因在不同方向上發生變化被稱爲發散式變化(Divergent Change)。比如一個類,新加入數據庫要修改3個函數,新出現一個工具要修改4個函數,那就意味着這個對象分成兩個會比較好。針對某一個外界變化的所有相應修改,都只應該發生在單一類中。(對應設計模式裏的單一職責原則)。

6、霰彈式修改
遇到某種變化,你必須在許多不同的類做出許多小修改。這種情況你應該把需要修改的方法和變量放進同一個類,如果這個類不存在,那就新建一個。目的都是讓外界變化和需要修改的類一一對應。

7、依戀情節
如果一個函數中只有部分代碼出現這種依戀,那麼就把那部分獨立成一個函數再遷移。

8、數據泥團
兩個類中相同的字段,函數中相同的參數。這些一起出現的數據應該擁有屬於他們自己的對象。

9、基本類型偏執
可以嘗試着把2個類組成一個小類,這樣就有機會把修改函數也放入這個小類。

10、switch驚悚現身
可以考慮用多態來替換switch

11、平行繼承體系
讓一個繼承體系的實例引用另一個繼承體系的實例。

12、Lazy Class冗贅類
如果某個類不再需要了,可以刪除它或者Inline Class去掉。

13、誇誇其談未來性
如果實際用不到,就把這些抽象的設計去掉。

14、令人迷惑的臨時字段
如果不希望傳遞一長串參數,把這些變量和相關函數提煉到一個獨立類中。

15、過度耦合的消息鏈
代碼結構緊密耦合,一旦對象間的關係發生變化,客戶端就需要改。

16、middle man 中間人
人們可能過度運用委託。應該remove middle man,直接和真正負責的對象打交道。

17、Inappropriate intimacy狎暱關係
有時兩個類過於親密,經常訪問對方的private變量。我們需要把共同點提煉到一個安全的地方供兩個類使用。

18、異曲同工的類
如果兩個函數做同一件事情,卻有不同的簽名,請運用rename method重新命名。

19、不完美的庫類
如果你只想修改庫類的一兩個函數,可以Introduce foreign method,如果要添加一大堆額外行爲,就用Introduce Local extension。

20、data class純稚的數據類
嘗試把get和set方法的調用代碼搬移到data class,這樣你就可以用hide method把這些函數隱藏起來。

21、被拒絕的遺贈
如果子類複用了超類的實現,卻又不願意支持超類的接口,壞味道就會變得濃烈。

22、過多的註釋
找到壞味道並用重構把壞味道去除之後,我們會發現註釋已經變得多餘了,因爲代碼已經說明了一切。

第四章 構建測試體系

價值:一套測試就是一個強大的bug偵測器 能夠大大縮減查找bug所需要的時間。

Junit 測試框架的運用
每當你收到bug報告,請先寫一個單元測試來暴露bug。

第五章 重構列表

記錄格式:名稱,概要,動機,做法,範例

第六章 重新組織函數

1、extract method提煉函數
提煉函數遇到的麻煩的問題是 有局部變量,對局部變量再賦值。

2、inline method 內聯函數
有時候你遇到某些函數,內部代碼和函數名同樣清晰易讀,你就應該去掉這個函數。
例外一種情況是有一羣組織不合理的函數,你可以將它們都內聯到一個大型函數中,再從中提煉出合理的小型函數。

3、inline temp 內聯臨時變量
內聯臨時變量多半是作爲replace temp with query的一部分使用的,如果這個臨時變量妨礙了其他的重構手法,比如extract method,就應該將它內聯化。

4、replace temp with query 以查詢取代臨時變量
多創建幾個函數,把所有臨時變量都替換爲查詢。

5、introduce explaining variable 引入解釋性變量
在條件邏輯中,可以將每個條件子句提煉出來,以一個良好命名的臨時變量來解釋對應條件子句的意義。

6、Split Temporary Variable 分解臨時變量
有很多臨時變量用於保存一段代碼的運算結果。如果它們被賦值超過一次,說明承擔了一個以上的責任,應該被替換爲多個臨時變量。

7、remove assignments to parameters 移除對參數的賦值
在java中,不要對參數賦值,那會混淆了值傳遞和引用傳遞。

8、replace method with method object 以函數對象取代函數
考慮新建一個類,把所有局部變量變成新類的字段,然後把函數的代碼複製過來,源函數改爲調用新類的同名方法,這個新類的實例就稱爲method object,新類裏面就可以做extract method了。

9、substitute algorithm 替換算法
可以考慮替換掉原來的算法。不過在進行該重構前,確認你對原算法非常瞭解。

第七章 在對象之間搬移特性

1、Move method 搬移函數
如果一個類有太多行爲,或者與另一個類有太多合作形成高度耦合,就可以搬移函數。

2、move field 搬移字段
對於一個字段,另一個類有更多函數使用了它,就可以考慮搬移這個字段。
如果是public的字段,考慮先封裝起來。

3、extract class 提煉類
類變得過分複雜,此時你需要考慮哪些部分可以分離出去,形成一個單獨的類。某些數據和函數總是一起出現,那麼它們就應該分離出去。
還有一個extract class的信號:如果你發現子類化隻影響類的部分特性,或者某些特性需要以另一種方式來子類化,這就意味着你需要分解原來的類。

4、inline class將類內聯化
如果一個類不再承擔足夠責任,挑選這一萎縮類的最頻繁用戶,以inline class手法將萎縮類塞進另一個類中。

5、hide delegate 隱藏委託關係
如果某個客戶先通過服務對象的字段得到另一個對象,然後調用後者的函數,那麼客戶就必須知曉這一層委託關係。萬一委託關係變化,客戶也得相應變化。可以在服務對象上放置一個委託函數,將委託關係隱藏起來。這樣即使委託關係發生變化,變化也被限制在服務對象中,不會波及客戶。

6、remove middle man 移除中間人
如果頻繁使用委託,會導致委託函數越來越多,服務類完全變成了一箇中間人,此時你就應該讓客戶直接調用受託類。

7、introduce foreign method引入外加函數
如果你在多處需要這段代碼,就應該抽成一個函數來調用,避免重複代碼。

8、 Introduce Local Extension 引入本地擴展
如果引入了很多外加函數,就新建一個類來包括這些函數,可以用子類化和包裝的標準對象技術來做。

第八章 重新組織數據

1、自封裝字段
間接訪問變量,代碼比較容易閱讀,子類可以通過覆寫一個函數而改變獲取數據的途徑。

2、以對象取代數據值

3、將值對象改爲引用對象
引用對象:每個對象對應真實世界中的一個實物,比如客戶對象,賬戶對象。
值對象:對象是完全由值來定義,比如日期,錢。

4、將引用對象改爲值對象
值對象有個非常重要的特性:它們應該是不可變。

5、以對象取代數組
人們很難記住“數組的第一個元素是人名”這樣的約定。如果你用對象就不一樣,你可以運用字段名稱和函數名稱來傳達這樣的信息。

6、複製被監控數據
將該數據複製到一個領域對象中,建立一個observer模式。

7、將單向關聯改成雙向關聯
兩個類需要使用對方特性,需要添加雙向連接。

8、將雙向關聯改成單向關聯
兩個類的其中一個類不再需要另一個類的特性,去除不必要的關聯。

9、以字面常量取代魔法數
魔法數:擁有特殊意義,卻不能明確表現出這種意義的數字。這些數字發生改變,就必須在程序中找到所有魔法數修改,噩夢。
解決方案:聲明一個常量來表示魔法數。

10、封裝字段
將字段聲明爲private,並提供相應的訪問函數。

11、封裝集合
返回集合的只讀副本,可以返回一個集合的複製給client。可以提供集合的增刪方法,但不要提供set集合的方法。

12、以數據類取代記錄
你可能面對一個遺留的用非面嚮對象語言寫的程序,或者從數據庫讀出的記錄。創建一個數據類,以便日後在類中都有一個對應的字段。

13、以類取代類型碼
用Enum。

14、以字段取代子類
如果子類只是用來返回常量數據,那就可以消除它,避免繼承帶來的額外複雜性。

15、以state/strategy取代類型碼

16、以字段取代子類

第九章 簡化條件表達式

1、分解條件表達式
如果有複雜的條件語句,從if、else段落中分別提煉出獨立函數。

2、合併條件表達式
如果有一系列條件測試,都得到相同結果,合併成一個條件表達式,並提煉成一個獨立函數。

3、合併重複的條件片段
將重複代碼搬到條件表達式之外。

4、移除控制標記
以break或者return代替標記

5、以衛語句取代嵌套條件表達式
條件表達式:用if else嵌套的條件表達式。
衛語句:if之後馬上return,如果某個條件極其罕見,就應該單獨檢查該條件,並在條件爲真時立刻從函數中返回。這樣的單獨檢查被稱爲衛語句(guard clauses)

6、以多態取代條件表達式
條件表達式根據對象類型的不同而選擇不同的行爲。將每個分支放進一個子類的覆寫函數,將原始函數抽象。

7、引入Null對象
反覆地判斷一個對象是否爲null是非常繁瑣的。空對象需要有一個能被識別出是空對象的方法,比如isNull()。

8、引入斷言
斷言不是用來檢查“你認爲應該爲真”的條件,它是用來檢查“一定必須爲真”的條件的。

第十章 簡化函數調用

1、函數改名
將複雜的處理過程分解成小函數,但要明確小函數的用途關鍵就是給函數起一個好名字。

2、添加參數
你必須修改一個函數,修改後的函數需要 一些過去沒有的信息,所以你需要添加參數。

3、刪除參數
當一個參數不再需要的時候,刪除它。

4、將查詢函數和修改函數分離

5、令函數攜帶參數
函數做着類似的工作,只是因爲少數幾個值導致行爲略有不同,你可以把那幾個值用參數來表示,把兩個函數合併成一個。

6、以明確函數取代參數
跟上面的優化相反。

7、保持對象完整
有時候,你會將來自同一個的對象中取出若干數據作爲參數,將它們作爲某次函數調用。如果把對象傳給函數,以防將來增減函數參數。

8、以函數取代參數
如果函數可以通過其他途徑獲得參數值,那麼就不應該通過參數取得該值。過長的參數列會增加理解難度。

9、引入參數對象
當你把這些參數組織到一起後,往往會發現一些可被移至新建類的行爲。調用函數一般會對這一組參數有一些共通的處理,如果把這些共通行爲移到新對象中,可以減少很多重複代碼。

10、移除設值函數
如果類的某個字段在對象被創建的時候被設值,然後就不再改變,去掉該字段的所有設值函數。

11、隱藏函數
函數沒有被用到,設置private

12、以工廠函數取代構造函數

13、封裝向下轉型
Downcast

14、以異常取代錯誤碼
返回特定的代碼改成異常。

15、以異常取代異常
異常不應該被濫用。它只應該被用於異常的、罕見的行爲,不應該成爲條件檢查的替代品。

第十一章 處理概括關係

1、字段上移
兩個類擁有相同的字段 將該字段移至超類

2、函數上移
兩個函數,在各個子類中產生完全相同的結果,將該函數移至超類

3、構造函數本體上移
在子類的構造函數中調用它

4、函數下移

5、字段下移

6、提煉子類
類中的某些特性只是部分實例用到,移到子類。

7、提煉超類
兩個類有相似特性,移至超類

8、提煉接口
若干客戶使用類接口中的同一個子集

9、摺疊繼承體系
超類和子類無太大區別

10、塑造模塊函數
你有一些子類,其中相應的某些函數以相同順序執行類似的操作,但操作的細節不同。

11、以委託取代繼承
超類中的一些函數你不需要,這時需要新建一個子類委託繼承你需要的函數,然後再繼承你需要的類

12、以繼承取代委託
兩個類之間使用委託關係,並經常爲整個接口編寫許多簡單的委託函數。

第十二章 大型重構

1、梳理並分解繼承體系

2、將過程化設計轉換爲對象設計

3、將領域和表述/顯示分離

4、提煉繼承體系

第十三章 重構,複用與現實

現實中的重構。

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