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