重構 改善既有程式碼的設計(要點總結一)
阿新 • • 發佈:2018-11-15
任何一個傻瓜都能寫出計算機能夠理解的程式,唯有寫出人類容易理解的程式,才是優秀的程式設計師。
定義:
對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本
目的:
使軟體更容易被理解和修改。 與之形成對比的是效能優化,但是兩者出發點不同,效能優化往往使程式碼較難理解。
效能優化:
不改變程式的外在行為(除了執行速度),只改變內部結構。
重構過程:
在不改變程式碼外在行為的前提下,對程式碼做出修改,以改程序序內部的結構。
重構基本技巧
小步前進,頻繁測試
重構基本技巧說明
- 如果發現需要為程式新增一個特性,而程式碼結構使你無法很方便的達成目的,
那就先重構程式碼,再新增特性。- 重構之前,首先檢查自己是否有一套可靠的測試機制,這些測試必須有自我檢驗能力。
任何不會被修改的變數我會把它當做引數傳入新的方法,- 而會被修改的變數,如果只有一個,我會把它當做返回值。
- 重構步驟的本質,由於重構每次修改的幅度都很小,所以任何錯誤都很容易被發現。
而會被修改的變數,如果只有一個,我會把它當做返回值。=
重構時機
新增功能時重構
修補錯誤時重構
複審程式碼時重構
程式有兩面價值:
今天能為你做什麼
明天能為你做什麼
————Kent Beck
程式為什麼難以相與?
難以閱讀的程式,難以修改
邏輯重複的程式,難以修改
新增新行為時需要修改已有的程式,難以修改
帶複雜條件邏輯的程式,難以修改
因此,我們希望程式:
容易閱讀
所有邏輯都只能在唯一地點指定
新的改動不會危及現有行為
儘可能簡單表達條件邏輯
間接層價值
允許邏輯共享
分開解釋意圖和實現
隔離變化
封裝條件邏輯
壞味道:
- 重複程式碼 Duplicated Code
a. 將相似部分和差異部分分開,構成單獨一個函式。 - 過長函式 Long Method
a. 間接層帶來的利益——解釋能力、共享能力、選擇能力——都是由小型函式支援的
b. 每當感覺需要註釋來說明點什麼的時候,我們就把需要說明的東西寫進一個獨立的函式中,並以其用途命名。
c. 條件表示式和迴圈也常常是提煉的訊號,迴圈和其內的程式碼可以被提煉到一個獨立的函式中。 - 過大的類 Large Class
a. 單個類做太多事情 - 過長的引數列表 Long Parameter List
a. 如果向已有物件發出一條請求就可以取代一個引數,那你應該計劃重構手法。 - 發散式變化 Divergent Change
a. 某個類經常因為不同原因在不同方向上發生改變,應將類拆分 - 霰彈式修改 Shotgun Surgery
a. 與發散式變化類似也恰恰相反
b. 遇到某種變化需要在許多不同的類中做出小修改,需要將一系列相關行為放入一個類
c. 使“外界變化”和“需要修改的類”趨於一一對應。 - 依戀情結 Feature Envy
a. 將資料和對資料的操作行為包裝在一起。
b. 將總是一起變化的東西放在一塊兒 - 資料泥團 Data Clumps
a. 你常常可以在很多地方看到相同的三四項資料:兩個類中相同的欄位、許多函式簽名中相同的引數。 - 基本型別偏執 Primitive Obsession
a. 模糊(甚至打破)了橫亙於基本資料和體積較大的類之間的界限。 - switch驚悚現身 Switch Statements
a. 大多數時候可用多型替代
b. 在單一函式中使用則顯殺雞用牛刀 - 平衡繼承體系 Parallel Inheritance Hierarchies
a. 每當為一個類增加一個子類,也要為另一個類增加一個子類。
b. 策略:讓一個繼承體系的示例引用另一個繼承體系的示例。 - 冗贅類 Lazy Class
a. 如果一個類的所得不值其身價,它就應該消失。
b. 對於幾乎沒有用的元件,可使用類的內聯化處理。 - 誇誇其談未來性 Speculative Generality
a. 使用各種各樣的鉤子和特殊情況來處理一些非必要的事情。
b. 過度設計未來不需要使用的功能。 - 令人迷惑的欄位 Temporary Field
a. 某個例項變數僅為特定情況而設,通常人們會以為對於全域性適用。
b. 把所有和這個類相關的程式碼都放入一個類。 - 過度耦合的訊息鏈 Message Chains
a. 物件請求一個物件,這個物件又請求另一個。
b. z拆分或加中間層 - 中間人 Middle Man
a. 某個類介面有一半的函式都是委託給其他類,不幹實事
b. 可使用行內函數把他們放進呼叫端,其它行為放入實責物件的子類 - 狎暱關係 Inappropriate Intimacy
a. 兩個類過於耦合
b. 把兩者共同點提煉到新類,或使用代理代替繼承關係 - 異曲同工的類 Alternative Classes with Different Interfaces
a. 合併程式碼或建立父類 - 不完美的庫類 Incomplete Library Class
a. 對於庫類的豐富與擴充套件方法 - 純稚的資料類 Data Class
a. 擁有一些欄位和訪問這些欄位的函式
b. 嘗試把呼叫取值、設值的方法放進來,將資料及取值設值的函式隱藏 - 被拒絕的遺贈 Refused Bequest
a. 傳統方法:新建兄弟類,將父類的非通用的方法push down
b. 建議使用委託代替 - 過多的註釋 Comments
a. 長長的程式碼之所以存在乃是因為程式碼很糟糕。
重新組織函式
- 提煉函式 Extract Method
a. 建立一個函式,根據這個函式的意圖來命名,以“做什麼”命名,不要以“怎麼做”命名。
b. z區域性變數需改動時,使用查詢(帶返回值) - 行內函數 Inline Method
a. 間接性可能帶來幫助,但非必要的間接性總讓人不舒服
b. 間接層有其價值,但不是所有間接層都有價值 - 內聯臨時變數 Inline Temp
a. 將所有對該變數的引用動作,替換為對它賦值的那個表示式本身。 - 以查詢取代臨時變數 Replace Temp with Query
a. 同一個類中所有函式都將獲得這份資訊 - 引入解釋性變數 Introduce Explaining Variable
a. 將複雜的表示式(或其中一部分)的結果放入一個臨時變數,以此變數名稱來解釋表示式的用途。
b. 可以刪除註釋,因為程式碼已經可以完美表達自己的意義了。 - 分解臨時變數 Split Temporary Variable
a. 針對每次賦值,創造一個獨立、對應的臨時變數。 - 移除對引數的賦值 Remove Assignments to Parameters
a. 避免對一個引數進行賦值,使用臨時變數替換,以免產生引用傳遞修改原值的問題。 - 以函式物件取代函式 Replace Method with Method Object
a. 將函式放入一個單獨物件中,其區域性變數成為欄位
b. 可在同一個物件內進行函式拆解,而不必擔心引數傳遞問題 - 替換演算法 Substitude Algorithm
a. 將原演算法替換為新演算法。
b. 拆解原演算法再進行替換並測試。
重構語錄:
- 三次法則:事不過三,三則重構
- 電腦科學是這樣一門科學:它相信所有問題都可以通過增加一個間接層來解決。 ——Dennis DeBruler
- 重構往往把大型物件拆成多個小型物件,把大型函式拆成對個小型函式。 如果專案已非常接近最後期限,不應分心於重構。
- 不過多個專案經驗顯示,重構的確能提高生產力, 如果最後沒有足夠的時間,通常表示你其實早該進行重構
- Ward Cunningham把未完成的重構工作形容為“債務”,很多公司通過借債來使自己更有效的運轉,
- 但是借債就得付利息,過於複雜的程式碼所造成的維護和擴充套件的額外成本就是利息。
- 重構肩負一項特殊使命:它和設計彼此互補。
- 你必須培養出自己的判斷力,判斷一個類內有多少例項變數算是太大,一個函式內有多少行程式碼算是太長。