重構,改善既有代碼的設計

最近在讀《重構,改善既有代碼的設計》這本書,整理了些書中對於一些代碼優化的筆記,暫時先更這些吧。

1,重複代碼(Duplicated Code):

  1,同一個類的兩個函數含有相同的表達式
     方法:1 提煉函數(Extract Method)
            1,無局部變量
            2,有局部變量
  2,
     1> 兩個互爲兄弟的子類內含有相同的表達式
        方法:第一步:使用 1
             第二步:2 函數上移(pull up Method)
     2> 代碼之間只是類似,並非完全相同
        方法:第一步:使用 1 將相似部分和差異部分分割開,構成單獨的一個函數
             第二步:3 塑造模板函數(Form Template Method)獲得一個模板設計模式
             如果有些函數以不同的算法做相同的事,你可以選擇其中一個,使用替換算法(Substitute Algorithm)將其他的函數替換掉
  3,兩個毫不相關的類出現Duplicated Code
        方法:對其中一個使用 4 提煉類(Extract Class),將重複代碼提煉到一個獨立類中,然後在另一個類中使用這個新類。

2,過長函數(Long Method)

注:原則:每當感覺需要以註釋來說明點什麼的時候,我們就把需要說明的東西寫進一個函數中,並以其用途(而非實現方法)命名                  
  1,函數中有大量的參數和臨時變量
     方法:5 以查詢取代臨時變量(Replace Temp with Query)來消除臨時元素
  2,過長的參數列表
     方法:6 引入參數對象(Introduce parameter Object)和
          7 保持對象完整(Preserve Whole Object)
  3,如果以上都做了還是不行,殺手鐗: 
     方法:8 以函數對象取代函數(Replace Method with Method Object)
  4,條件表達式
     方法:9 分解條件表達式(Decompose Conditional)
  5,循環
     方法:將循環和循環體內的代碼提煉到一個獨立的函數中

 注:怎樣去確定應該提煉哪一段代碼:*
     1,尋找註釋
     2,條件表達式和循環

3,過大的類

  1,太多的實例變量
     方法: 4 將幾個變量一起提煉至新類內。提煉時彼此相關的變量,將它們放在一起,
             通常如果類內的數個變量有着相同的前綴或字尾,這就意味着有機會把他們提煉到某個組件中,
             如果這個組件適合作爲一個子類,使用 10 提煉子類(Extract Subclass)
  2,一個類擁有太多的代碼
     方法: 4 和10,技巧:先確定客戶端 如何使用他們,然後運用11 提煉接口(Extract Interface)爲每一種使用方式提煉一個接口,這或許能讓你看清楚如何分解這個類,
  3,GUI類
     方法: 將數據和行爲移到一個獨立的領域對象去,可能需要兩邊個保留一些重複數據,並保持兩遍同步,12 複製“被監視數據”(Duplicate Observed Data)

4,過長參數列

  1,如果向一個已有的對象發出一條請求就可以取代一個參數,(已有的對象可能是函數所屬類內的一個字段,也有可能是另一個參數)
     方法: 13 以函數取代參數(Replace parameter with Method)
  2,來自同一對象的一堆數據,以對象替換他們
     方法: 7
  3,某些數據缺乏很合理的對象歸屬
     方法: 6 爲他們製造出一個對象
  4,不希望造成“被調用對象”與“較大對象”間的某種依賴關係,這時候將數據從對象中拆解出來單獨作爲參數,也是合理的

5,發散式變化(Divergent Change)

  1,某個類經常因爲不同的原因在不同的方向上發生變化:比如:如果新加入一個數據庫必須要修改這三個函數,如果新出現一種金融工具,必須要修改這四個函數
     方法: 將這個對象分成兩個會很好,這麼一來每個對象就可以只因一種變化而需改  4 

6,霰彈式修改(Shotgun Surgery)

  1, 如果每遇到某種變化,都必須在許多不同的類內做出許多小修改⑯
      方法:14 搬移函數(Move Method)和 15 搬移字段(Move Field)把所需修改的代碼放進同一個類中,
           如果沒有合適的類可以安置這些代碼,那就創建一個,通常可以使用 16 將類內聯化(Inline Class)把一系列相關行爲放進同一個類

7,依戀情結(Feature Envy)

  1,函數對某個類的興趣高於對自己所處類的興趣。比如:我們看到某個函數爲了計算某個值,從另一個對象那調用幾乎半打的取值函數。
     方法:把這個函數移至另一個地點,14 把它移到它該去的地方,有時候函數只有一部分受這種依戀之苦,1 把這一部分提煉到獨立函數中,再使用⑭把它帶到他該去的地方。
     注:一個函數往往會用到幾個類的功能,那麼他究竟該被處置在哪?原則:判斷哪個類擁有最多被此函數使用的珊瑚橘,然後就把這個函數和那些數據擺在一起。使用 1 將這個函數分解爲數個較小的函數並分別置於不同的地方。

8,數據泥團(Data Clumps)

  1,常常可以在很多地方看到相同的三四個數據項:兩個類中相同的字段、許多函數簽名中相同的參數。
     方法:先找出這些數據以字段形式出現的地方,運用 4  將它們提煉到一個獨立對象中,然後將注意力轉移到函數簽名上,運用6 或 7 爲他減肥,這麼做的直接好處是將參數列表縮短,簡化函數調用,你不必在意Data Clumps只用上新對象的一部分字段,只要以新對象取代兩個(更多)字段。
     注:一個好的評判的辦法:刪掉衆多數據中的一項。這麼做,其他數據有沒有因而失去意義?如果他們不再有意義,這就是一個明確的信號:你應該爲他們產生一個新對象。

9,基本類型偏執(Primitive Obsession)

大多數編程環境都有兩種數據:結構類型允許你將數據組織稱有意義的形式,基本類型則是構成結構類型的積木塊。結構總是會帶來一定的額外開銷,他們可能代表着數據庫中的表,如果只爲一兩件事情而創建結構類型也可能顯得太麻煩.對象的一個極大的價值在於:他們模糊(基本打破)了橫亙於基本數據和體積較大的類之間的界限。
  1,對象技術的新手通常不願意在小任務上運用一個大對象 ---像是結合數值呵呵幣種的money類,由一個起始值和結束值組成的rang類、電話號碼或郵政編碼等的特殊字符串。
     方法:17 以對象取代數據值(Replace Data Value with Object)將原本單獨存在的數據值替換成對象。
  2,如果想要替換的數據值是類型碼,而他並不影響行爲,
     方法:則可以運用18以類取代類型碼(Replace Type Code with Class)將它替換掉
  3,如果你有與類型碼相關的條件表達式,
     方法:可運用19 以子類取代類型碼(Replace Type Code with subclass)或 20 以State/Strategy取代類型碼(Replace Type Code with State/Strategy)
  4,如果你有一組應該總是被放在一起的字段
     方法:4 
  5,如果你在參數列中看到基本類型數據
     方法: 試試6
  6,如果你發現自己正從數組中挑選數據           
     方法:21 以對象取代數組(Replace Array with Object)

10,Switch 驚悚現身(Switch Statements)

  1,以多態來取代他
     方法:使用 1 將switch語句提煉到一個獨立的函數中,再以 14 將它搬移到需要多態性的那個類裏。此時,你必須決定是否使用 19 或 20 ,一旦這樣完成繼承結構之後,可以運用 22 以多態取代條件表達式(Replace Conditional with Polymorphism)
  2,如果你只是在單一函數中有些選擇事例 ,且不想改動他們,那麼多態就有點殺雞牛刀了
     方法:使用 23 以明確函數取代參數(Replace Parameter with Explicit Methods)
  3,如果你的選擇條件之一是null
     方法:試試 24 引入Null對象(Introduce Null Object)

11,平行繼承體系(Parallel Inheritance Hierarchies)

 是霰彈式修改(Shotgun Surgery)的特殊情況。
  1,每當你爲某各類增加一個子類,必須也爲另一個類相應增加一個子類,如果你發現某個繼承體系的類名稱前綴和另一個繼承體系的類名稱前綴完全相同
     方法:讓一個繼承體系的實例引用另一個繼承體系的實例,再運用 14 和 15

12,冗贅類(Lazy Class)

  1,如果某各類沒有做足夠的工作
     方法:試試 25 摺疊繼承體系(Collapse Hierarchy),
  2,對於幾乎沒用的組件
     方法:你應該以 16 應對

13,誇誇其談未來性(Speculative Generality)

  1,如果你的某個抽象類其實沒有太大作用
     方法:請運用25
  2,不必要的委託
     方法:16
  3,如果函數的某些參數未被用上
     方法:26 移除參數(Remove Parameter)
  4,如果函數名稱帶有多餘的抽象意味
     方法: 27 函數改名(Rename Method)

14,令人迷惑的暫時字段

  1,其內某個實例變量僅爲某種特定情況而定。
     方法:4  還可以使用 24  在“變量不合法”的情況下創建一個Null 對象,從而避免寫出條件式代碼
  2,如果類中有一個複雜的算法,需要好幾個變量
     方法:4 把這些變量和其他相關函數提煉到一個獨立類中,提煉後的新對象將是一個函數對象

15,過度耦合的消息鏈(Message Chains)

  1,實際代碼中你看到的可能是一長串的getThis()或一長串的臨時變量
     方法: 使用 28 隱藏“委託關係”(Hide Delegate),可以在消息鏈的不同位置進行這種重構手法
           更合適的做法 :先觀察消息鏈最終得到的對象是用來幹什麼的,看看能否以 1 把使用該對象的代碼提煉到一個獨立函數中,在運用 14 把這個函數推入消息鏈,如果這條鏈上的某個對象有多位客戶打算航行此航線的剩餘部分,就加一個函數來做這個事情

16,中間人(Middle Man)

對象的基本特徵之一就是封裝 --- 對外部世界隱藏其內部的實現細節。封裝往往伴隨着委託。
  1,過度的委託,你也許會看到某個類或接口有一半的函數都委託給其他類,
     方法:29 移除中間人(Remove  Middle Man),直接和真正負責的對象打交道,如果祝賀樣“不幹實事”的函數只有少數幾個,可以運用 16 把他們放進調用端。如果Middle Man還有其他行爲,可以運用 30 以繼承取代委託(Replace Delegation  with Inheritance)把它變成實責對象的子類,這樣你即可以擴展對象的行爲,又不用但系負擔那麼多的委託對象。

17,狎暱關係(Inappropriate Intimacy)

  1,兩個類過於親密,花費太多的時間去探究彼此的private 成分
     方法: 14 和15 幫他們劃分界限,也可以運用 30將雙向關聯改成單向關聯(Change Bidirectional Association to Undirectional)讓類分開,如果兩個類實在是情投意合,可以運用 4 把兩者共同點提煉到一個安全地點,讓他們坦蕩的使用這個新類。也可以嘗試運用 28 讓另一個類來爲他們傳遞關係
  2,繼承往往造成過度的親密,因爲子類對超類的瞭解總是超過後者的主觀願望,如果你覺得這個孩子可以獨立生活了,運用 31 以委託取代繼承(Replace Inhheritance with Delegation),讓他離開繼承體系。

18,異曲同工的類(Alternative Classes with Different Interfaces)

  1,如果兩個函數做同一件事情,卻有着不同的簽名
     方法:27 根據他們的用途重新命名,但這往往不夠,請反覆運用 14 將某些行爲移入類,直到兩者的協議一致爲止,如果你必須重複而贅餘地移入代碼才能完成這些,可運用 32 提煉超類(Extract Superclass)

19,不完美的庫類

  1,如果你只想修改類庫的一兩個函數
     方法:可以運用 33 引入外加函數(Introduce Foreign Method)
  2,如果想要添加一大堆額外行爲
     方法:運用 34 引入本地擴展(Introduce Local Extension)

20,純稚的數據類(Data Class)

他們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。
  1,這種類只是一中數據容器,他們幾乎一定被其他類過分細鎖的控制着,這些類早起可能擁有public字段
     方法: 35 封裝字段(Encapsulate Field)將他們封裝起來。
  2,如果這些類內含容器類的字段
     方法:檢查他是不是得到了恰當的封裝:如果沒有,就運用 36 封裝集合(Encapsulate Collection)把他們封裝起來。
  3,對於那些不該被其他類修改的字段
     方法:運用 37 移除設值函數(Remove Setting Method),然後找出這些取值/設值函數給其他類運用的地方。嘗試以 14 把那些調用行爲搬移到Data Class ,如果無法搬移整個函數,就運用 1 產生一個可被搬移的函數,不久之後,就可以運用 38 隱藏函數(Hide Method)把這些取值設值函數隱藏起來。

21,被拒絕的遺贈(Refused Bequest)

  1,子類應該繼承超類的函數和數據,但如果他們不想或不需要繼承,又該怎麼辦,他們得到所有禮物,卻只從中挑選幾樣來完
     方法:你需要爲這個子類新建一個兄弟類,再運用 39 函數下移(Push Down Method)和 40 字段下移(Push Down Filed)把所有用不到的函數下推給那個兄弟,這樣一來,超類就只持有所有子類共享的東西
  2,子類複用了超類的行爲(實現),但又不願意支持超類的接口
     方法: 31

22,過多的註釋

  1,如果你需要註釋來解釋一段代碼做了什麼              
     方法: 1 
  2,如果函數已經提取出來了,但還是需要註釋來解釋其行爲 
     方法:27
  3,如果你需要註釋說明某些系統的需求規格
     方法: 41 引入斷言(Introduce Assertion)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章