寫在前面
重構起源於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、兩句重要的話
- 重構的基本技巧——小步前進,頻繁測試。
- 模式是你但願到達的目標,重構則是到達之路。