找出那些代碼裏的壞味道吧——《重構》筆記(一)

寫在前面

重構起源於smalltalk,發揚於java和C#,它們都有成熟的重構工具。有一種說法是,《重構》和設計模式是java行業的聖經。我我的以爲,重構就像修繕忒休斯之船同樣,只是咱們是將船上的木板所有替換成了鋼板。一個程序員若是看本身一年前寫的代碼而沒有重構的念頭,那麼這個程序員可能這一年沒有什麼進步,固然也可能這塊代碼已經不須要重構了,但我想這種機率挺低的。java

由於各類緣由,沒有人能在框架設計一開始就能套用設計模式寫好,因此咱們的代碼須要不斷的重構。Gof的設計模式就是重構的目標。可是重構也是必須有理論準備的,必須系統化的進行,不然可能引入不可察覺的錯誤,風險更大。程序員

本文記錄的重點只在於指出代碼裏的壞味道,若是在你的代碼中「聞到」了這些壞味道就說明這塊的代碼可能須要重構了,至於怎麼重構,書中有一套系統的方法,請期待後續的文章。設計模式

最後,但願重構能變成像空氣和水同樣普通的技術。session

代碼的壞味道有如廚房的油污,開始時不會以爲有多大的影響,但時間長了就會累積成「噁心」又難以「清除」的污漬。咱們須要保持天天的清掃,而不是按期的「大掃除」。上面的「味道」就是一點一點的「油星」濺在「廚房」裏,看到它們就順手擦掉吧!

1、重構原則

1.重構的定義

  • 重構(名詞):對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。
  • 重構(動詞):使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構。
  • 關於重構須要強調的兩點:app

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

2.重構的目標

  • 進行重構以後,咱們但願咱們的程序能達到如下幾點目標:框架

    • 容易閱讀
    • 全部邏輯都只在惟一地點指定
    • 新的改動不會危及現有行爲
    • 儘量簡單表達條件邏輯

3.什麼時候不應重構

  • 現有代碼根本不能正常運行時,應該重寫,而不該重構。
  • 若是項目已近最後期限,你也應該避免重構。

2、代碼的壞味道

  • 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(基本類型偏執)

    • 對象的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本類型和體積較大的類之間的界限。你能夠輕鬆編寫一些與語言內置(基本)類型無異的小型類。
    • 對象技術的新手一般不肯意在小任務上運用小對象——像是結合數值和幣種的money類、由一個起始值和一個結束值組成的range類、電話號碼或郵政編碼等的特殊字符串。你能夠運用Replace Data Value With Object將原來單獨存在的數據值替換爲對象,從而走出傳統的洞窟,進入煊赫一時的對象世界。
  • Switch Statements(switch驚悚現身)

    • 面向對象程序的一個最明顯特徵就是:少用switch(或case)語句。
    • 大多數時候,一看到switch語句,你就應該考慮以多態來替換它。
    • 若是你只是在單一函數中有些選擇事例,且並不想改動它們,那麼多態就有點殺雞用牛刀了。
  • Parallel Inheritance Hierarchies(平行繼承體系)

    • 每當你爲某個類增長一個子類,必須也爲另外一個類相應增長一個子類。
    • 消除這種重複性的通常策略是:讓一個繼承體系的實例引用另外一個繼承體系的實例。
  • Lazy Class(冗贅類)

    • 你所建立的每個類,都得有人去理解它,維護它,這些工做都是要花錢的,若是一個類的所得不值其身價,它就應該消失。
  • Speculative Generality(誇誇其談將來性)

    • 當有人說「嗷,我想咱們總有一天須要作這事」,並於是企圖以各類各樣的鉤子和特殊狀況來處理一些非必要的事情,這種壞味道就出現了。
    • 若是全部裝置都會被用到,那就值得那麼作;若是用不到,就不值得。用不上的裝置只會擋你的路,因此,把它搬開吧。
  • Temporary Field(使人迷惑的暫時字段)

    • 有時你會看到這的對象:其內某個實例變量僅爲某個特定狀況而設。
    • 請使用Extract Class給這個可憐的孤兒創造一個家,而後把全部和這個變量相關的代碼都放進這個新家。
  • Message Chains(過分耦合的消息鏈)

    • 若是你看到用戶向一個對象請求另外一個對象,而後再向後者請求另外一個對象,而後再請求另外一個對象……這就是消息鏈。
    • 先觀察消息鏈最終獲得的對象是用來幹什麼的,看看可否以Extract Method把使用該對象的代碼提煉到一個獨立函數中,再運用Move Method把這個函數推入消息鏈。若是這條鏈上的某個對象有多位客戶打算航行此航線的剩餘部分,就加一個函數來作這件事。
  • Middle Man(中間人)

    • 人們可能過分運用委託。你也許會看到某個類接口有一半的函數都委託給其餘類,這樣就是過分運用。這時應該使用Remove Middle Man,直接和真正負責的對象打交道。
  • Inappropriate Intimacy(狎暱關係)

    • 有時你會看到兩個類過於親密,花費太多時間去探究彼此的private成分。
    • 就像古代戀人同樣,過度狎暱的類必須拆散。你能夠採用Move Method和Move Field幫它們劃清界線,從而減小狎暱行徑。
    • 繼承每每形成過分親密,由於子類對超類的瞭解老是超事後者的主觀願望。
  • Alternative Classes With Different Interfaces(殊途同歸的類)

    • 若是兩個函數作同一件事,卻有着不一樣的簽名。請運用Rename Method根據它們的用途從新命名。但這每每不夠,請反覆運用Move Method將某些行爲移入類,直到二者的協議一致爲止。
  • Incomplete Library Class(不完美的類庫)

    • 麻煩的是庫每每構造得不夠好,並且每每不可能讓咱們修改其中的類使它完成咱們但願完成的工做。
    • 若是你只想修改類庫的一兩個函數,能夠運用Introduce Foreign Method;若是想要添加一大堆額外行爲,就得運用Introduce Local Extension。
  • Data Class(純稚的數據類)

    • 所謂Data Class是指:它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。
    • Data Class就行小孩子,做爲一個起點很好,但若要讓它們像成熟的對象那樣參與整個系統的工做,它們就必須承擔必定責任。
  • Refused Bequest(被拒絕的遺贈)

    • 若是子類複用了超類的行爲(實現),卻又不肯意支持超類的接口,Refused Bequest的壞味道就會變得濃烈。拒絕繼承超類的實現,這一點咱們不介意;但若是拒絕繼承超類的接口,咱們不覺得然。不過即便你不肯意繼承接口,也不要胡亂修改繼承體系,應該運用Replace Inheritance With Delegation來達到目的。
  • Comments(過多的註釋)

    • 經常會有這樣的狀況:你看到一段代碼有着長長的註釋,而後發現,這些註釋之因此存在乃是由於代碼很糟糕。
    • 若是你不知道該作什麼,這纔是註釋的良好運用時機。

3、兩句重要的話

  • 重構的基本技巧——小步前進,頻繁測試。
  • 模式是你但願到達的目標,重構則是到達之路。
相關文章
相關標籤/搜索