一、重構原則
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(過多的註釋)
- 常常會有這樣的情況:你看到一段程式碼有著長長的註釋,然後發現,這些註釋之所以存在乃是因為程式碼很糟糕。
- 如果你不知道該做什麼,這才是註釋的良好運用時機。
三、兩句重要的話
- 重構的基本技巧——小步前進,頻繁測試。
- 模式是你希望到達的目標,重構則是到達之路。