如何實施代碼重構?

閱讀《重構》的筆記獻上。算法

重構的定義

重構是在不改變軟件可觀察行爲的前提下改善其內部結構。編程

重構的節奏

以微小的步伐修改程序。若是你犯下錯誤,很容易即可發現它。數組

  • 一個方法裏面,不該該有不少的代碼,咱們能夠經過分解後重組。session

  • 好的代碼應該清楚的表達出本身的功能,變量名稱是代碼清晰的關鍵。app

  • 儘可能減小臨時變量,大量參數被傳來傳去,很容易跟丟,可讀性差。dom

  • 提煉出邏輯代碼,以便功能複用。ide

重構(名詞):對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。函數

重構(動詞):使用一系列重構首發,在不改變軟件可觀察行爲的前提下,調整其結構。單元測試

爲什麼重構?

  • 重構改進軟件設計測試

  • 重構是軟件更容易理解

  • 重構幫助找到bug

  • 重構提升編程速度

什麼時候重構?

幾乎任何狀況下我都反對專門拔出時間進行重構。在我看來重構原本就不是一件應該特別撥出時間作的事情,重構應該隨時隨地的進行。你不該該爲重構而重構,你之因此重構,是由於你想作別的什麼事,而重構能夠幫助你把那些事作好。

三次法則

第一次作某件事只管去作,第二次作相似的事情會有反感,第三次再作相似的事,你就應該重構。
事不過三,三則重構。

  • 添加功能時重構

  • 修補錯誤時重構

  • 複審代碼時重構

爲何重構有用?

難以修改的程序

  • 難以閱讀的

  • 邏輯重複的

  • 添加新行爲須要修改已有代碼的

  • 帶複雜條件邏輯的

好的程序

  • 容易閱讀

  • 全部邏輯都只是在惟一地點指定

  • 新的改動不會危及現有行爲

  • 儘量簡單表達條件邏輯

重構是這樣一個過程:它在一個目前可運行的程序上進行,在不改變程序行爲的前提下使其具有上述美好性質,使咱們可以繼續保持高速開發,從而新增程序的價值。

什麼時候不應重構?

  • 沒法穩定運行直接重寫不用重構

  • 項目以及接近最後期限,不該該重構,雖然重構可以提升生產力,可是你沒有足夠的時間,一般標示你其實早該進行重構了。

代碼的壞味道

  • Duplicated Code 重複代碼

  • Long Method 過長函數

  • Large Class 過大的類

  • Long Parameter List 過長參數列

  • Divergent Change 發散式變化

  • Shotgun Surgery 散彈式修改

  • Feature Envy 依戀情結 (StrategyVisitor)

  • Data Clumps 數據泥團

  • Primitive Obsession 基本類型偏執

  • Switch Statements switch驚悚現身 (使用多態性替換)

  • 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所須要的時間。

  • 頻繁地運行測試。每次編譯請把測試也考慮進去—— 天天至少執行每一個測試一次。

  • 每當你收到bug報告,請先寫一個單元測試來暴露這個bug。

  • 編寫未完善的測試並執行,好過對完美測試的無盡等待。

  • 考慮可能出錯的邊界條件,把測試火力集中在那兒。

  • 當事情被認爲應該會出錯時,別忘了檢查是否拋出了預期的異常。

  • 不要由於測試沒法捕捉全部bug就不寫測試,由於測試能夠捕捉到大多數的bug。

重構列表

重構記錄格式

  • 名稱

  • 概要

    • 描述解決的問題

    • 描述要作的事情

    • 速寫圖展現重構前和重構後的示例

  • 動機

  • 作法

  • 範例

重構的基本技巧—小步前進、頻繁測試

模式和重構之間有着一種與生俱來的關係。模式是你但願到達的目標,重構則是到達之路。

從新組織函數

  • Extract Method 提煉函數

    • 你有一段代碼能夠被組織在一塊兒並獨立出來。將這段代碼放進一個獨立函數中,並讓函數名稱解釋該函數的用途。

  • Inline Method 內聯函數

    • 一個函數,其本體應該與其名稱一樣清楚易懂。在函數調用點插入函數本體,而後移除該函數。

  • Inline Temp 內聯臨時變量

    • 你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其餘重構方法。將全部對該變量的引用動做,替換爲對它賦值的那個表達式自己。

  • Replace Temp with Query 已查詢取代臨時變量

    • 你的程序以一個臨時變量(temp)保存某一個表達式的運算結果。將這個表達式提煉到一個獨立函數(query查詢式)中。將這個臨時變量的全部被引用點替換爲對新函數的調用。新函數可被其餘函數使用。

  • Introduce Explaining Variable 引入解釋性變量

    • 你有一個複雜的表達式。將該複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。

  • Split Temporary Variable 分解臨時變量

    • 你的程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不是一個集用臨時變量(collecting temporary variable)。針對每次賦值,創造一個獨立。對應的臨時變量。

  • 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 複製「被監聽數據」

    •  你有一些domain data 置身GUI控件中,而domain method 須要訪問之。即那個該筆數據拷貝到以到domain object。創建一個observer模式,用以對domain object和GUI object 內的重複數據進行同步控制(sync).

  • Change Unidirectional Association to Bidirectional 將單向關聯改成雙向關聯

    • 兩個classes都須要使用對方特性,但其間只有一條單向鏈接。添加一個反向指針,並使修改函數可以同時更新兩條鏈接。

  • Change Bidirectional Association to Unidirectional 將雙向關聯改爲單向關聯

    • 兩個classes之間有雙向關聯,但其中一個class現在再也不須要另外一個class的特性。去除沒必要要的關聯(association)。

  • Replace Magic Number with Symbolic Constant 以字面常量取代魔法數

    • 你有一個字面值,帶有特別的含義。創造一個常量,根據其意義爲它命名,並將上述的字面數值替換爲這個常量。

  • Encapsulate Field 封裝字段

    • 你的class存在一個public值域。將它聲明爲private,並提供相應的訪問函數。

  • Encapsulate Collection 封裝集合

    •  有個函數返回一個羣集(collection)。放這個函數返回該羣集的一個只讀映件,並在這個class中提供添加移除羣集元素的函數。

  • Replace Record with Data Class 以數據類取代記錄

    • 你須要面對傳統編程環境中的record structure(記錄結構)。爲該record(記錄)建立一個啞數據對象(dumb data object)。

  • Replace Type Code with Class 以類取代類型碼

    •  class之中有一個數值別碼,但他並不影響class的行爲。以一個新的class替換數值型別碼。

  • Replace Type Code with Subclasses 以子類取代類型碼 (多態機制)

    •  你有一個不可變的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 以衛語句取代嵌套條件表達式 (單獨判斷被稱爲「衛語句」)

    • 函數中的條件邏輯令人難以看清正常的執行路徑。使用衛語句表現全部特殊的狀況。衛語句就是把複雜的條件表達式拆分紅多個條件表達式,好比一個很複雜的表達式,嵌套了好幾層的if - then-else語句,轉換爲多個if語句,實現它的邏輯,這多條的if語句就是衛語句

  • Replace Conditional with Polymorphism 以多態取代條件表達式

    • 你手上有個條件式,它根據對象型別的不一樣而選擇不一樣的行爲。將這個條件式的每一個分支放進一個subclass內的覆寫函數中,而後將原始函數聲明爲抽象函數。

  • Introduce Null Object 引入Null對象

    • 你須要再三檢查某個是否爲null value。將null value(無效值)替換爲null object(無效物)

  • Introduce Assertion 引入斷言

    • 某一段代碼須要對程序狀態作出某種假設。以assertion(斷言)明確表現這種假設。

簡化函數調用

  • Rename Method 函數更名

    • 函數的名稱未能揭示函數的用途,修改函數名稱。

  • Add Parameter 添加參數

    • 某個函數須要從調用端獲得更多信息。爲此函數添加一個對象參數,讓該對象帶進函數所需信息。

  • Remove Parameter 移除參數

    • 函數本體再也不須要某個參數,將該參數去除。

  • Separate Query from Modifier 講查詢函數和修改函數分離

    • 某個函數即返回函數對象狀態值,又修改對象狀態。未來兩個不一樣的函數,其中一個負責查詢,另外一個負責修改。

  • Parameterize Method 令函數攜帶參數

    • 若干函數作了相似的工做,但在函數本體中卻包含了不一樣的值。創建單一函數,以參數表達那些不一樣的值。

  • Replace Parameter with Explicit Methods 以明確函數取代參數

    • 你有一個函數,其內徹底取決於參數值而採起不一樣反應。針對該參數的每個可能值,創建一個獨立函數。

  • Preserve Whole Object 保持對象完整

    •  你從某個對象中取出某個值,將它們做爲某一次函數調用的參數。該引用(傳遞)整個對象。

  • Replace Parameter with Methods 以函數取代參數

    • 對象調用某個函數,並將所得結果做爲參數,傳遞給另外一個函數。而接受該參數的函數也能夠調用前一個函數。讓參數接受者去除該項參數,並直接調用前一個函數。

  • Introduce Parameter Object 引入參數對象

    • 某個參數老是很天然地同時出現。以一個對象取代這些參數。

  • Remove Setting Method 移除設置函數

    • 你的class中的某個值域,應該在對象初創時被設值,而後就再也不改變。去掉該值域的全部設置函數。

  • Hide Method 隱藏函數

    • 有一個函數,歷來沒有被其餘任何class用到。將這個函數改成private。

  • Replace Constructor with Factory Method 以工廠函數取代構造函數

    • 你但願在建立對象時不只僅是對它作簡單的構建工做,將construcotr(構造函數)替換爲factory method(工廠函數)

  • Encapsulate Downcast 封裝向下轉型

    • 某個函數返回的對象,須要由函數調用者執行向下轉型動做。將向下轉型動做移到函數中。

  • 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之間無太大區別。將它們和爲一體。

  • Form Template Method 塑造模板函數

    • 有一些subclasses,其中相應的某些函數以相同順序執行相似的措施,但各措施實際上有所不一樣。將各個措施分別放進獨立函數中,並保持它們都有相同的簽名式,因而原函數也就變得相同了。而後將原函數上移至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 將過程化設計轉化爲對象設計

    • 你手上有一些代碼,以傳統的過程化風格寫就。將數據記錄變成對象,將行爲分開,並將行爲移入相關對象中。

  • Separate Domain from Presentation 將領域和表述/顯示分離

    • 某些GUI classes之中包含了domain login(領域邏輯)。將domain loginc(領域邏輯)分離出來,爲它們創建獨立的domain classes.

  • Extract Hierarchy 提煉繼承體系

    • 你有某個class作了太多工做,其中一個部分工做以大量條件式完成的。創建繼承體系,以一個subclass表示一種特殊狀況。

相關文章
相關標籤/搜索