閱讀《重構》的筆記獻上。
重構的定義
重構是在不改變軟件可觀察行為的前提下改善其內部結構。
重構的節(jié)奏
以微小的步伐修改程序。如果你犯下錯誤,很容易便可發(fā)現(xiàn)它。
- 一個方法里面,不應該有很多的代碼,我們可以通過分解后重組。
- 好的代碼應該清楚的表達出自己的功能,變量名稱是代碼清晰的關鍵。
- 盡量減少臨時變量,大量參數被傳來傳去,很容易跟丟,可讀性差。
- 提煉出邏輯代碼,以便功能復用。
重構(名詞):對軟件內部結構的一種調整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。
重構(動詞):使用一系列重構首發(fā),在不改變軟件可觀察行為的前提下,調整其結構。
為何重構?
- 重構改進軟件設計
- 重構是軟件更容易理解
- 重構幫助找到bug
- 重構提高編程速度
何時重構?
幾乎任何情況下我都反對專門拔出時間進行重構。在我看來重構本來就不是一件應該特別撥出時間做的事情,重構應該隨時隨地的進行。你不應該為重構而重構,你之所以重構,是因為你想做別的什么事,而重構可以幫助你把那些事做好。
三次法則
第一次做某件事只管去做,第二次做類似的事情會有反感,第三次再做類似的事,你就應該重構。
事不過三,三則重構。
- 添加功能時重構
- 修補錯誤時重構
- 復審代碼時重構
為什么重構有用?
難以修改的程序
- 難以閱讀的
- 邏輯重復的
- 添加新行為需要修改已有代碼的
- 帶復雜條件邏輯的
好的程序
- 容易閱讀
- 所有邏輯都只是在唯一地點指定
- 新的改動不會危及現(xiàn)有行為
- 盡可能簡單表達條件邏輯
重構是這樣一個過程:它在一個目前可運行的程序上進行,在不改變程序行為的前提下使其具備上述美好性質,使我們能夠繼續(xù)保持高速開發(fā),從而新增程序的價值。
何時不該重構?
- 無法穩(wěn)定運行直接重寫不用重構
- 項目以及接近最后期限,不應該重構,雖然重構能夠提高生產力,但是你沒有足夠的時間,通常標示你其實早該進行重構了。
代碼的壞味道
- Duplicated Code 重復代碼
- Long Method 過長函數
- Large Class 過大的類
- Long Parameter List 過長參數列
- Divergent Change 發(fā)散式變化
- Shotgun Surgery 散彈式修改
- Feature Envy 依戀情結 (Strategy\Visitor)
- Data Clumps 數據泥團
- Primitive Obsession 基本類型偏執(zhí)
- Switch Statements switch驚悚現(xiàn)身 (使用多態(tài)性替換)
- Parallel Inheritance Hierarchies 平行繼承體系
- Lazy Class 冗贅類
- Speculative Generality 夸夸其談未來性
- Temporary Field 令人迷惑的暫時字段
- Message Chains 過渡耦合的消息鏈
- Middle Man 中間人
- Inappropriate Intimacy 狎昵關系
- Alternative Classes with Different Interfaces 異曲同工的類
- Incomplete Library Class 不完美的庫類
- Data Class 純稚的數據類
- Refused Bequest 被拒絕的遺贈
- Comments 過多的注釋
當你感覺需要撰寫注釋時,請先嘗試重構,試著讓所有注釋變得多余。
構建測試體系
- 確保所有測試都完全自動化,讓他們檢查自己的測試結果。
- 一套測試就是一個強大的bug偵察器,能夠大大縮減查找bug所需要的時間。
- 頻繁地運行測試。每次編譯請把測試也考慮進去—— 每天至少執(zhí)行每個測試一次。
- 每當你收到bug報告,請先寫一個單元測試來暴露這個bug。
- 編寫未完善的測試并執(zhí)行,好過對完美測試的無盡等待。
- 考慮可能出錯的邊界條件,把測試火力集中在那兒。
- 當事情被認為應該會出錯時,別忘了檢查是否拋出了預期的異常。
- 不要因為測試無法捕捉所有bug就不寫測試,因為測試可以捕捉到大多數的bug。
重構列表
重構記錄格式
- 名稱
- 概要
- 描述解決的問題
- 描述要做的事情
- 速寫圖展示重構前和重構后的示例
- 動機
- 做法
- 范例
重構的基本技巧—小步前進、頻繁測試
模式和重構之間有著一種與生俱來的關系。模式是你希望到達的目標,重構則是到達之路。
重新組織函數
- Extract Method 提煉函數
- 你有一段代碼可以被組織在一起并獨立出來。將這段代碼放進一個獨立函數中,并讓函數名稱解釋該函數的用途。
- Inline Method 內聯(lián)函數
- 一個函數,其本體應該與其名稱同樣清楚易懂。在函數調用點插入函數本體,然后移除該函數。
- Inline Temp 內聯(lián)臨時變量
- 你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其他重構方法。將所有對該變量的引用動作,替換為對它賦值的那個表達式本身。
- Replace Temp with Query 已查詢取代臨時變量
- 你的程序以一個臨時變量(temp)保存某一個表達式的運算結果。將這個表達式提煉到一個獨立函數(query查詢式)中。將這個臨時變量的所有被引用點替換為對新函數的調用。新函數可被其他函數使用。
- Introduce Explaining Variable 引入解釋性變量
- 你有一個復雜的表達式。將該復雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
- Split Temporary Variable 分解臨時變量
- 你的程序有某個臨時變量被賦值超過一次,它既不是循環(huán)變量,也不是一個集用臨時變量(collecting temporary variable)。針對每次賦值,創(chuàng)造一個獨立。對應的臨時變量。
- Remove Assignments to Parameters 移除對參數的賦值
- 你的代碼對一個參數進行賦值動作。以一個臨時變量取代該參數的位置。
- Replace Method with Method Object 以函數對象取代函數
- 你有一個大型函數,其中對局部變量的使用,使你無法采用Extract Method。將這個函數放在一個獨立的對象中,如此一來局部變量就變成了對象內的值域,然后你可以在同一個對象中將這個大型函數分解為數個小型函數。
- Substitute Algorithm 替換算法
- 你想要把某個算法替換為另一個更清晰的算法。將函數本體替換為另一個算法。
在對象之間搬移特性
- Move Method 搬移函數
- 你的程序中,有個函數與其所駐class之外的另一個class進行更多交流:調用后者,或被后者調用。在函數最常引用的class中建立一個有著類似行為的新函數。將舊函數變成一個單純的委托函數,或是將舊函數完全移除。
- Extract Class 提煉類
- 你的程序中,某個field(值域)被所駐class之外的另一個class更多地用到。在target class 建立一個new field ,修改source field 的所有用戶,令它們改用new field.
- Inline Class 將類內斂化
- 某個class做了應該由兩個classes做的事情。建立一個新的class,將相關的值域和函數從就class搬移到新class。
- Hide Delegate 隱藏委托類
- 客戶直接調用其server object(服務對象)的delegate class。在sever端(某個class)建立客戶所需要的所有函數,用以隱藏委托關系。
- Remove Middle Man 移除中間人
- 某個class做了過多的簡單委托(simple delegation).讓客戶直接調用delegate(受托類)。
- Introduce Foreign Method 引入外加函數
- 你所使用的server class 需要一個額外函數,但你無法修改這個class。在client class 中建立一個函數,并以一個server class實體作為第一引數(argument)。
- Introduce Local Extension 引入本地擴展
- 你所使用的server class 需要一些額外函數,但你無法修改這個class。建立一個新class,使它包含這些額外函數。讓這個擴展品成為source class的subclass(子類)或(wrapper)外覆類。
重新組織數據
-
Self Encapsulate Field 自封裝字段
- 你直接訪問一個值域(field),但與值域直接的耦合關系變得逐漸變得笨拙。為這個值域建立取值/設值函數,并且只有這些函數來訪問值域。
-
Replace Data Value with Object 以對象取代數據值
- 你有一筆數據項(data item),需要額外的數據和行為。將這筆數據項變成一個對象。
-
Change Value to Reference 將值對象改成引用對象
- 你有一個class,衍生出許多相等視圖(equal instance),你希望將它們替換為單元對象。將這個 value object(實值對象)變成一個reference object(引用對象)。
-
Change Reference to Value 將引用對象改成值對象 (equals hashCode)
- 你有一個reference object(引用對象),很小且不可變,而且不易管理。將他變成一個value object(實值對象)。
-
Replace Array with Object 以對象取代數組
- 你有一個數組(array),其中的元素個各自代表不同的東西,以對象替換數組。對于數組中的每個元素,以一個值域表示之。
-
Duplicate Observed Data 復制“被監(jiān)聽數據”
- 你有一些domain data 置身GUI控件中,而domain method 需要訪問之。即那個該筆數據拷貝到以到domain object。建立一個observer模式,用以對domain object和GUI object 內的重復數據進行同步控制(sync).
-
Change Unidirectional Association to Bidirectional 將單向關聯(lián)改為雙向關聯(lián)
- 兩個classes都需要使用對方特性,但其間只有一條單向連接。添加一個反向指針,并使修改函數能夠同時更新兩條連接。
-
Change Bidirectional Association to Unidirectional 將雙向關聯(lián)改成單向關聯(lián)
- 兩個classes之間有雙向關聯(lián),但其中一個class如今不再需要另一個class的特性。去除不必要的關聯(lián)(association)。
-
Replace Magic Number with Symbolic Constant 以字面常量取代魔法數
- 你有一個字面值,帶有特別的含義。創(chuàng)造一個常量,根據其意義為它命名,并將上述的字面數值替換為這個常量。
-
Encapsulate Field 封裝字段
- 你的class存在一個public值域。將它聲明為private,并提供相應的訪問函數。
-
Encapsulate Collection 封裝集合
- 有個函數返回一個群集(collection)。放這個函數返回該群集的一個只讀映件,并在這個class中提供添加移除群集元素的函數。
-
Replace Record with Data Class 以數據類取代記錄
- 你需要面對傳統(tǒng)編程環(huán)境中的record structure(記錄結構)。為該record(記錄)創(chuàng)建一個啞數據對象(dumb data object)。
-
Replace Type Code with Class 以類取代類型碼
- class之中有一個數值別碼,但他并不影響class的行為。以一個新的class替換數值型別碼。
-
Replace Type Code with Subclasses 以子類取代類型碼 (多態(tài)機制)
- 你有一個不可變的type code,它會影響class的行為。以一個subclass取代這個type code。
-
Replace Type Code with State/Strategy 以State/Strategy取代類型碼
- 你有一個type code,它會影響class 的行為,但你無法使用subclassing。以state object取代type code.
-
Replace Subclass with Fields 以字段取代子類
- 你的各個subclasses的唯一差別只在返回常量數據的函數身上。修改這些函數,使他們返回superclass中的某個(新增)值域,然后銷毀sublcasses。
簡化條件表達式
- Decompose Conditional 分解條件表達式
- 你有一個復雜的條件語句。從if、then、else三個段落中分別提出獨立函數。
- Consolidate Conditional Expression 合并條件表達式
- 你有一系列條件測試,都得到相同結果。將這些測試合并為一個條件式,并將這個條件式提煉成為一個獨立函數。
- Consolidate Duplicate Conditional Fragments 合并重復的條件片段
- 在條件式的每一個分支上著相同的代碼。將這個段重復代碼搬移到條件式之外。
- Remove Control Flag 移除控制標記 (break/continue/return)
- 在一系列布爾表達式中,某個變量帶有控制標記的作用,以break語句或return語句取代控制標記。
- Replace Nested Conditional with Guard Clauses 以衛(wèi)語句取代嵌套條件表達式 (單獨判斷被稱為“衛(wèi)語句”)
- 函數中的條件邏輯使人難以看清正常的執(zhí)行路徑。使用衛(wèi)語句表現(xiàn)所有特殊的情況。衛(wèi)語句就是把復雜的條件表達式拆分成多個條件表達式,比如一個很復雜的表達式,嵌套了好幾層的if - then-else語句,轉換為多個if語句,實現(xiàn)它的邏輯,這多條的if語句就是衛(wèi)語句
- Replace Conditional with Polymorphism 以多態(tài)取代條件表達式
- 你手上有個條件式,它根據對象型別的不同而選擇不同的行為。將這個條件式的每個分支放進一個subclass內的覆寫函數中,然后將原始函數聲明為抽象函數。
- Introduce Null Object 引入Null對象
- 你需要再三檢查某個是否為null value。將null value(無效值)替換為null object(無效物)
- Introduce Assertion 引入斷言
- 某一段代碼需要對程序狀態(tài)做出某種假設。以assertion(斷言)明確表現(xiàn)這種假設。
簡化函數調用
- Rename Method 函數改名
- 函數的名稱未能揭示函數的用途,修改函數名稱。
- Add Parameter 添加參數
- 某個函數需要從調用端得到更多信息。為此函數添加一個對象參數,讓該對象帶進函數所需信息。
- Remove Parameter 移除參數
- 函數本體不再需要某個參數,將該參數去除。
- Separate Query from Modifier 講查詢函數和修改函數分離
- 某個函數即返回函數對象狀態(tài)值,又修改對象狀態(tài)。將來兩個不同的函數,其中一個負責查詢,另一個負責修改。
- Parameterize Method 令函數攜帶參數
- 若干函數做了類似的工作,但在函數本體中卻包含了不同的值。建立單一函數,以參數表達那些不同的值。
- Replace Parameter with Explicit Methods 以明確函數取代參數
- 你有一個函數,其內完全取決于參數值而采取不同反應。針對該參數的每一個可能值,建立一個獨立函數。
- Preserve Whole Object 保持對象完整
- 你從某個對象中取出某個值,將它們作為某一次函數調用的參數。該引用(傳遞)整個對象。
- Replace Parameter with Methods 以函數取代參數
- 對象調用某個函數,并將所得結果作為參數,傳遞給另一個函數。而接受該參數的函數也可以調用前一個函數。讓參數接受者去除該項參數,并直接調用前一個函數。
- Introduce Parameter Object 引入參數對象
- 某個參數總是很自然地同時出現(xiàn)。以一個對象取代這些參數。
- Remove Setting Method 移除設置函數
- 你的class中的某個值域,應該在對象初創(chuàng)時被設值,然后就不再改變。去掉該值域的所有設置函數。
- Hide Method 隱藏函數
- 有一個函數,從來沒有被其他任何class用到。將這個函數改為private。
- Replace Constructor with Factory Method 以工廠函數取代構造函數
- 你希望在創(chuàng)建對象時不僅僅是對它做簡單的構建工作,將construcotr(構造函數)替換為factory method(工廠函數)
- Encapsulate Downcast 封裝向下轉型
- 某個函數返回的對象,需要由函數調用者執(zhí)行向下轉型動作。將向下轉型動作移到函數中。
- Replace Error Code with Exception 以異常取代錯誤碼
- 某個函數返回一個特定的代碼,用以表示某種錯誤情況。改用異常。
- Replace Exception with Test 以測試取代異常
- 面對一個調用者可預先加以檢查的條件,你拋出一個異常。修改調用者,使它在調用函數之前先做檢查。
處理概括關系
- Pull Up Field 字段上移
- 兩個subclass擁有相同的值域。將此一值域移至superclass。
- Pull Up Method 函數上移
- 有些函數,在各個subclass中產生完全相同的效果。將該函數移至superclass。
- Pull Up Constructor Body 構造函數本體上移
- 你的各個subclass中擁有一些構造函數,它們的本體幾乎完全一致。在superclass中新建一個構造函數,并在subclass構造函數中調用它。
- Push Down Method 函數下移
- superclass中的某個函數只與部分(而非全部)subclass有關。將這個函數移到相關的那些subclasses去。
- Push Down Field 字段下移
- superclass中的某個值域只被部分subclass用到。將這個值域到需要它的那些subclasses去。
- Extract Subclass 提煉子類
- class中某些特性只被某些而非全部實體用到。新建一個subclass,將上面所說的那一部分特性移到subclass中。
- Extract Superclass 提煉超類
- 兩個classes有相似特性。為這連個classes建立一個superclass.將相同特性移至superclass.
- Extract Interface 提煉接口
- 若干客戶使用class接口中的同一子集。或者,兩個Classes的接口有部分相同。將相同的子集提煉到一個獨立接口中。
- Collapse Hierarchy 折疊繼承體系
- superclass和subclass之間無太大區(qū)別。將它們和為一體。
- Form Template Method 塑造模板函數
- 有一些subclasses,其中相應的某些函數以相同順序執(zhí)行類似的措施,但各措施實際上有所不同。將各個措施分別放進獨立函數中,并保持它們都有相同的簽名式,于是原函數也就變得相同了。然后將原函數上移至superclass。
- Replace Inheritance with Delegation 以委托取代繼承
- 某個subclass只使用superclass接口中的一部分,或是更本不需要繼承而來的數據。在subclass中新建一個值域用以保存superclass;調整subclass函數,令它改而委托superclass;然后去掉兩者之間的繼承關系。
- Replace Delegation with Inheritance 以繼承取代委托
- 你的兩個classes之間使用了委托關系,并經常為整個接口編寫許多極其簡單的請托函數。讓請托Class繼承受托class。
大型重構
- Tease Apart Inheritance 梳理并分解繼承體系
- 某個繼承體系同時承擔兩項責任。建立兩個繼承體系,并通過委托關系讓其中一個可以調用另一個。
- Convert Procedural Design to Objects 將過程化設計轉化為對象設計
- 你手上有一些代碼,以傳統(tǒng)的過程化風格寫就。將數據記錄變成對象,將行為分開,并將行為移入相關對象中。
- Separate Domain from Presentation 將領域和表述/顯示分離
- 某些GUI classes之中包含了domain login(領域邏輯)。將domain loginc(領域邏輯)分離出來,為它們建立獨立的domain classes.
- Extract Hierarchy 提煉繼承體系
- 你有某個class做了太多工作,其中一個部分工作以大量條件式完成的。建立繼承體系,以一個subclass表示一種特殊情況。