1. 程式人生 > >重構 改善既有程式碼的設計(要點總結一)

重構 改善既有程式碼的設計(要點總結一)

任何一個傻瓜都能寫出計算機能夠理解的程式,唯有寫出人類容易理解的程式,才是優秀的程式設計師。

定義:

對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本

目的:

使軟體更容易被理解和修改。 與之形成對比的是效能優化,但是兩者出發點不同,效能優化往往使程式碼較難理解。

效能優化:

不改變程式的外在行為(除了執行速度),只改變內部結構。

重構過程:

在不改變程式碼外在行為的前提下,對程式碼做出修改,以改程序序內部的結構。

重構基本技巧

小步前進,頻繁測試

重構基本技巧說明

  1. 如果發現需要為程式新增一個特性,而程式碼結構使你無法很方便的達成目的,
    那就先重構程式碼,再新增特性。
  2. 重構之前,首先檢查自己是否有一套可靠的測試機制,這些測試必須有自我檢驗能力。
    任何不會被修改的變數我會把它當做引數傳入新的方法,
  3. 而會被修改的變數,如果只有一個,我會把它當做返回值。
  4. 重構步驟的本質,由於重構每次修改的幅度都很小,所以任何錯誤都很容易被發現。
    而會被修改的變數,如果只有一個,我會把它當做返回值。=

重構時機

新增功能時重構
修補錯誤時重構
複審程式碼時重構

程式有兩面價值:

今天能為你做什麼
明天能為你做什麼
————Kent Beck

程式為什麼難以相與?

難以閱讀的程式,難以修改
邏輯重複的程式,難以修改
新增新行為時需要修改已有的程式,難以修改
帶複雜條件邏輯的程式,難以修改

因此,我們希望程式:

容易閱讀
所有邏輯都只能在唯一地點指定
新的改動不會危及現有行為
儘可能簡單表達條件邏輯

間接層價值

允許邏輯共享
分開解釋意圖和實現
隔離變化
封裝條件邏輯

壞味道:

  1. 重複程式碼 Duplicated Code
    a. 將相似部分和差異部分分開,構成單獨一個函式。
  2. 過長函式 Long Method
    a. 間接層帶來的利益——解釋能力、共享能力、選擇能力——都是由小型函式支援的
    b. 每當感覺需要註釋來說明點什麼的時候,我們就把需要說明的東西寫進一個獨立的函式中,並以其用途命名。
    c. 條件表示式和迴圈也常常是提煉的訊號,迴圈和其內的程式碼可以被提煉到一個獨立的函式中。
  3. 過大的類 Large Class
    a. 單個類做太多事情
  4. 過長的引數列表 Long Parameter List
    a. 如果向已有物件發出一條請求就可以取代一個引數,那你應該計劃重構手法。
  5. 發散式變化 Divergent Change
    a. 某個類經常因為不同原因在不同方向上發生改變,應將類拆分
  6. 霰彈式修改 Shotgun Surgery
    a. 與發散式變化類似也恰恰相反
    b. 遇到某種變化需要在許多不同的類中做出小修改,需要將一系列相關行為放入一個類
    c. 使“外界變化”和“需要修改的類”趨於一一對應。
  7. 依戀情結 Feature Envy
    a. 將資料和對資料的操作行為包裝在一起。
    b. 將總是一起變化的東西放在一塊兒
  8. 資料泥團 Data Clumps
    a. 你常常可以在很多地方看到相同的三四項資料:兩個類中相同的欄位、許多函式簽名中相同的引數。
  9. 基本型別偏執 Primitive Obsession
    a. 模糊(甚至打破)了橫亙於基本資料和體積較大的類之間的界限。
  10. switch驚悚現身 Switch Statements
    a. 大多數時候可用多型替代
    b. 在單一函式中使用則顯殺雞用牛刀
  11. 平衡繼承體系 Parallel Inheritance Hierarchies
    a. 每當為一個類增加一個子類,也要為另一個類增加一個子類。
    b. 策略:讓一個繼承體系的示例引用另一個繼承體系的示例。
  12. 冗贅類 Lazy Class
    a. 如果一個類的所得不值其身價,它就應該消失。
    b. 對於幾乎沒有用的元件,可使用類的內聯化處理。
  13. 誇誇其談未來性 Speculative Generality
    a. 使用各種各樣的鉤子和特殊情況來處理一些非必要的事情。
    b. 過度設計未來不需要使用的功能。
  14. 令人迷惑的欄位 Temporary Field
    a. 某個例項變數僅為特定情況而設,通常人們會以為對於全域性適用。
    b. 把所有和這個類相關的程式碼都放入一個類。
  15. 過度耦合的訊息鏈 Message Chains
    a. 物件請求一個物件,這個物件又請求另一個。
    b. z拆分或加中間層
  16. 中間人 Middle Man
    a. 某個類介面有一半的函式都是委託給其他類,不幹實事
    b. 可使用行內函數把他們放進呼叫端,其它行為放入實責物件的子類
  17. 狎暱關係 Inappropriate Intimacy
    a. 兩個類過於耦合
    b. 把兩者共同點提煉到新類,或使用代理代替繼承關係
  18. 異曲同工的類 Alternative Classes with Different Interfaces
    a. 合併程式碼或建立父類
  19. 不完美的庫類 Incomplete Library Class
    a. 對於庫類的豐富與擴充套件方法
  20. 純稚的資料類 Data Class
    a. 擁有一些欄位和訪問這些欄位的函式
    b. 嘗試把呼叫取值、設值的方法放進來,將資料及取值設值的函式隱藏
  21. 被拒絕的遺贈 Refused Bequest
    a. 傳統方法:新建兄弟類,將父類的非通用的方法push down
    b. 建議使用委託代替
  22. 過多的註釋 Comments
    a. 長長的程式碼之所以存在乃是因為程式碼很糟糕。

重新組織函式

  1. 提煉函式 Extract Method
    a. 建立一個函式,根據這個函式的意圖來命名,以“做什麼”命名,不要以“怎麼做”命名。
    b. z區域性變數需改動時,使用查詢(帶返回值)
  2. 行內函數 Inline Method
    a. 間接性可能帶來幫助,但非必要的間接性總讓人不舒服
    b. 間接層有其價值,但不是所有間接層都有價值
  3. 內聯臨時變數 Inline Temp
    a. 將所有對該變數的引用動作,替換為對它賦值的那個表示式本身。
  4. 以查詢取代臨時變數 Replace Temp with Query
    a. 同一個類中所有函式都將獲得這份資訊
  5. 引入解釋性變數 Introduce Explaining Variable
    a. 將複雜的表示式(或其中一部分)的結果放入一個臨時變數,以此變數名稱來解釋表示式的用途。
    b. 可以刪除註釋,因為程式碼已經可以完美表達自己的意義了。
  6. 分解臨時變數 Split Temporary Variable
    a. 針對每次賦值,創造一個獨立、對應的臨時變數。
  7. 移除對引數的賦值 Remove Assignments to Parameters
    a. 避免對一個引數進行賦值,使用臨時變數替換,以免產生引用傳遞修改原值的問題。
  8. 以函式物件取代函式 Replace Method with Method Object
    a. 將函式放入一個單獨物件中,其區域性變數成為欄位
    b. 可在同一個物件內進行函式拆解,而不必擔心引數傳遞問題
  9. 替換演算法 Substitude Algorithm
    a. 將原演算法替換為新演算法。
    b. 拆解原演算法再進行替換並測試。

重構語錄:

  • 三次法則:事不過三,三則重構
  • 電腦科學是這樣一門科學:它相信所有問題都可以通過增加一個間接層來解決。 ——Dennis DeBruler
  • 重構往往把大型物件拆成多個小型物件,把大型函式拆成對個小型函式。 如果專案已非常接近最後期限,不應分心於重構。
  • 不過多個專案經驗顯示,重構的確能提高生產力, 如果最後沒有足夠的時間,通常表示你其實早該進行重構
  • Ward Cunningham把未完成的重構工作形容為“債務”,很多公司通過借債來使自己更有效的運轉,
  • 但是借債就得付利息,過於複雜的程式碼所造成的維護和擴充套件的額外成本就是利息。
  • 重構肩負一項特殊使命:它和設計彼此互補。
  • 你必須培養出自己的判斷力,判斷一個類內有多少例項變數算是太大,一個函式內有多少行程式碼算是太長。