找出那些程式碼裡的壞味道吧——《重構》筆記

吳尼瑪發表於2017-12-19

找出那些程式碼裡的壞味道吧——《重構》筆記

一、重構原則

1.重構的定義

  • 重構(名詞):對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本。

  • 重構(動詞):使用一系列重構手法,在不改變軟體可觀察行為的前提下,調整其結構。

  • 關於重構需要強調的兩點:

    • 重構的目的是使軟體更容易被理解和修改。
    • 重構不會改變軟體可觀察的行為——重構之後軟體功能一如以往。

2.重構的目標

  • 進行重構之後,我們希望我們的程式能達到以下幾點目標:
    • 容易閱讀
    • 所有邏輯都只在唯一地點指定
    • 新的改動不會危及現有行為
    • 儘可能簡單表達條件邏輯

3.何時不該重構

  • 現有程式碼根本不能正常執行時,應該重寫,而不應重構。
  • 如果專案已近最後期限,你也應該避免重構。

二、程式碼的壞味道

  • Duplicate Code(重複程式碼)
    • 如果你在一個以上的地點看到相同的程式結構,那麼可以肯定,設法將它們合二為一,程式會變得更好。
  • Long Method(過長函式)
    • 擁有短函式的物件會活的比較好,比較長。
    • 我們遵循這樣一條原則:每當感覺需要以註釋來說明點什麼的時候,我們就把需要說明的東西寫進一個獨立函式中,並以其用途(而非實現手法)命名。
    • 條件表示式和迴圈常常也是提煉的訊號。
  • Large Class(過大的類)
    • 如果想利用單個類做太多事情,其內往往就會出現太多例項變數。
    • 和“太多例項變數”一樣,類內如果有太多程式碼,也是程式碼重複、混亂並最終走向死亡的源頭。最簡單的解決方案是把多餘的東西消弭於類內部。
    • 這裡有個技巧:先確定客戶端如何使用它們(指“太多的例項變數”),然後運用Extract Interface為每一種使用方式提煉出一個介面。這或許可以幫助你看清楚如何分解這個類。
  • Long Parameter List(過長引數列)
    • 如果你手上沒有所需的東西,總可以叫另一個物件給你。因此,有了物件,你就不必把函式需要的所有東西都以引數傳遞給它了,只需傳給它足夠的、讓函式能從中獲得自己需要的東西就行了。
  • Divergent Change(發散式變化)
    • 針對某一外界變化的所有相應修改,都只應該發生在單一類中,而這個新類內的所有內容都應該反應此變化。
  • Shotgun Surgery(霰彈式修改)
    • Divergent Change是指“一個類受多種變化的影響”,Shotgun Surgery則是指“一種變化引發多個類相應的修改”。這兩種情況下你都會希望整理程式碼,使“外界變化”與“需要修改的類”趨於一一對應。
  • Feature Envy(依戀情結)
    • 函式對某個類的興趣高過對自己所處類的興趣。
    • 判斷哪個類擁有最多被此函式使用的資料,然後就把這個函式和那些資料擺在一起。
    • 最根本的原則是:將總是一起變化的東西放在一塊兒。資料和引用這些資料的行為總是一起變化的,但也有例外。如果例外出現,我們就搬移那些行為,保持變化值在一地發生。
  • Data Clumps(資料泥團)
    • 你常常可以再很多地方看到相同的三四項資料:兩個類中相同的欄位、許多函式簽名中相同的引數。這些總是綁在一起出現的資料真應該擁有屬於它們自己的物件。
  • Primitive Obsession(基本型別偏執)
    • 物件的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本型別和體積較大的類之間的界限。你可以輕鬆編寫一些與語言內建(基本)型別無異的小型類。
  • Switch Statements(switch驚悚現身)
    • 物件導向程式的一個最明顯特徵就是:少用switch(或case)語句。
    • 大多數時候,一看到switch語句,你就應該考慮以多型來替換它。
    • 如果你只是在單一函式中有些選擇事例,且並不想改動它們,那麼多型就有點殺雞用牛刀了。
  • Parallel Inheritance Hierarchies(平行繼承體系)
    • 每當你為某個類增加一個子類,必須也為另一個類相應增加一個子類。
    • 消除這種重複性的一般策略是:讓一個繼承體系的例項引用另一個繼承體系的例項。
  • Lazy Class(冗贅類)
    • 你所建立的每一個類,都得有人去理解它,維護它,這些工作都是要花錢的,如果一個類的所得不值其身價,它就應該消失。
  • Speculative Generality(誇誇其談未來性)
    • 如果所有裝置都會被用到,那就值得那麼做;如果用不到,就不值得。用不上的裝置只會擋你的路,所以,把它搬開吧。
  • Temporary Field(令人迷惑的暫時欄位)
    • 有時你會看到這的物件:其內某個例項變數僅為某個特定情況而設。
    • 請使用Extract Class給這個可憐的孤兒創造一個家,然後把所有和這個變數相關的程式碼都放進這個新家。
  • Message Chains(過度耦合的訊息鏈)
    • 如果你看到使用者向一個物件請求另一個物件,然後再向後者請求另一個物件,然後再請求另一個物件……這就是訊息鏈。
  • Middle Man(中間人)
    • 人們可能過度運用委託。你也許會看到某個類介面有一半的函式都委託給其他類,這樣就是過度運用。
  • Inappropriate Intimacy(狎暱關係)
    • 有時你會看到兩個類過於親密,花費太多時間去探究彼此的private成分。
    • 就像古代戀人一樣,過分狎暱的類必須拆散。
    • 繼承往往造成過度親密,因為子類對超類的瞭解總是超過後者的主觀願望。
  • Alternative Classes With Different Interfaces(異曲同工的類)
    • 如果兩個函式做同一件事,卻有著不同的簽名。
  • Incomplete Library Class(不完美的類庫)
    • 如果你只想修改類庫的一兩個函式,可以運用Introduce Foreign Method;如果想要新增一大堆額外行為,就得運用Introduce Local Extension。
  • Data Class(純稚的資料類)
    • 所謂Data Class是指:它們擁有一些欄位,以及用於訪問(讀寫)這些欄位的函式,除此之外一無長物。
    • Data Class就行小孩子,作為一個起點很好,但若要讓它們像成熟的物件那樣參與整個系統的工作,它們就必須承擔一定責任。
  • Refused Bequest(被拒絕的遺贈)
    • 如果子類複用了超類的行為(實現),卻又不願意支援超類的介面,Refused Bequest的壞味道就會變得濃烈。
  • Comments(過多的註釋)
    • 常常會有這樣的情況:你看到一段程式碼有著長長的註釋,然後發現,這些註釋之所以存在乃是因為程式碼很糟糕。
    • 如果你不知道該做什麼,這才是註釋的良好運用時機。

三、兩句重要的話

  • 重構的基本技巧——小步前進,頻繁測試。
  • 模式是你希望到達的目標,重構則是到達之路。

相關文章