重構,須要培養出本身的判斷力,學會判斷一個類內有多少實例變量算是太大,一個函數內有多少行代碼纔算太長。數據庫
一、重複代碼設計模式
(1)最單純的重複代碼是「同一個類的兩個函數含有相同的表達式」,只須要抽出相同的代碼,兩個地方都調用就能夠了。若是是兩個絕不相干的類出現重複代碼,你應該考慮將代碼抽到一個獨立類當中。可是,重複代碼所在的函數也可能的確只屬於某個類,另一個類只能調用它。你必須決定這個函數放在哪兒最合適,並確保它被安置後就不會再在其餘任何地方出現。session
(2)若是兩個互爲兄弟的子類內含相同表達式,須要提煉出代碼推入超類內。若是代碼只是類似,並不是徹底相同,那麼就得分離類似部分和差別部分。(這裏提到一個Template Method設計模式,補上)app
二、過長函數函數
程序越長越難理解。「間接層」所能帶來的所有利益--解釋能力、共享能力、選擇能力都是由小型函數支持的。現代OO語言幾乎已經徹底免除了進程內的函數調用開銷。不過代碼閱讀者仍是得多費力氣,由於他必須常常轉換上下文去看看子程序作了什麼。若是你能給函數起個好名字,讀者就能夠經過名字瞭解函數的做用,根本就沒必要去看其中寫了什麼。條件表達式和循環經常是提煉的信號。你可使用Decompose Conditional(這應該是一種處理方法,分解條件?)處理條件表達式。至於循環,你應該將循環和其內的代碼提煉到一個獨立函數中。工具
三、過大的類設計
若是想利用單個類作太多事情,其內每每就會出現太多實例變量。能夠講幾個變量一塊兒提煉至新類內。提煉時應該選擇類內彼此相關的變量,將它們放在一塊兒。例如depositAmount和depositCurrency可能應該隸屬同一個類。一般若是類內的數個變量有着相同的前綴或字尾,這就意味着有機會把它們提煉到某個組件內。若是這個組件適合做爲一個子類,你會發現Extract Subclass每每比較簡單。若是類內有太多代碼,最簡單的解決方案是把東西消弭於類內部,好比有五個「百行函數」,它們之中不少代碼都相同,那麼或許你能夠把它們變成五個「十行函數」和十個提煉出的「雙行函數」。若是你的Large Class是一個GUI類,你可能須要把數據和行爲移到一個獨立的領域對象去。你可能須要兩邊各保留一些重複數據,並保持兩邊同步。對象
四、過長的參數列表繼承
過長的參數列表難以理解。函數所需的全部東西都以參數傳遞進去,除此以外只能選擇全局數據,而全局數據是邪惡的東西。對象技術改變了這一狀況:若是你手上沒有所需的東西,總能夠叫另外一個對象給你。此爲對象參數。接口
五、Divergent Change(發散式變化)
咱們但願軟件可以更容易被修改。一旦須要修改,咱們但願可以跳到系統的某一點,只在該處作修改。若是某個類常常由於不一樣的緣由在不一樣方向上發生變化,Divergent Change就出現了。好比:新加入一個數據庫,必須修改三個函數;新出現一種金融工具,必須修改四個函數。那麼此時也許將這個對象分紅兩個會更好,這樣一來每一個對象就能夠只因一種變化而須要修改。固然,每每在加入新數據庫或新金融工具後,你才能發現這一點。
六、Shotgun Surgery(霰彈式修改)
若是每遇到某種變化,你都必須在許多不一樣的類中作出許多小修改,你所面臨的壞味道就是Shotgun Surgery。若是須要修改的代碼三步四處,你不但很難找到它們,也很容易忘記某個重要的修改。這種狀況下你應該把全部須要修改的代碼放進同一個類,若是眼瞎沒有合適的類能夠安置這些代碼,就創造一個。
七、Feature Envy(依戀情結)
對象技術的所有要點在於:這是一種「將數據和對數據的操做行爲包裝在一塊兒」的技術。函數可能由於數據,對某個類的興趣高過對本身所處類的興趣。這時候須要把它移到該去的地方。若是複雜一點,一個函數每每會用到幾個類的功能,那麼咱們的原則是:判斷哪一個類擁有最多被此函數使用的數據,而後就把這個函數和那些數據擺在一塊兒。這裏再次提到Strategy模式(複習),還有Visitor模式(補上)
八、Data Clumps(數據泥團)
你經常能夠在不少地方看到相同的三四項數據:兩個類中相同的字段、許多函數簽名中相同的參數。這些老是綁在一塊兒出現的數據真應該擁有屬於它們本身的對象。可能在使用時,只用上了新對象的部分字段,可是隻要以新對象取代兩個(或更多)字段,你就值回票價了。獲得新對象以後,你就能夠着手尋找Feature Envy,這能夠幫你指出可以移至新類中的種種程序行爲。沒必要過久,全部類都將在它們的小小社會中充分發揮價值。
九、Primitive Obsession(基本類型偏執)
大多數變成環境都有兩種數據:結構類型容許你將數據組織成有意義的形式;基本類型則是構成結構類型的積木塊。對象的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本數據和體積較大的類之間的界限。你能夠嘗試在小任務上用小對象--像是結合數值和幣種money類、有一個起始值和一個結束值組成的range類。將單獨存在的數據值替換爲對象,從而走出傳統的窟窿,進入煊赫一時的對象世界。
十、Switch Statements(switch驚悚現身)
面向對象程序的一個最明顯特徵就是:少用switch(或case)語句。從本質上說,switch語句的問題在於重複。你常會發現一樣的switch語句散佈於不一樣地點。若是要爲它添加一個新的case子句,就必須找到全部switch語句並修改它們。面向對象的多態概念可爲此帶來優雅的解決辦法。(具體事例在這本書的第一章)
十一、Parallel Inheritance Hierarchies(平行繼承體系)
Parallel Inheritance Hierarchies實際上是Shotgun Surgery的特殊狀況。在這種狀況下,每當你爲某個類增長一個子類,必須也爲另外一個類相應增長一個子類。若是你發現某個繼承體系的類名稱前綴和另外一個繼承體系的類名稱前綴徹底相同,即是聞到了這種壞味道。消除這種重複性的通常策略是:讓一個繼承體系的實例引用另外一個繼承體系的實例。
十二、Lazy Class(冗贅類)
你所建立的每個類,都得有人去理解它、維護它,這些工做都是要花錢的。若是一個類的所得不值其身價,它就應該消失。項目中常常會出現這樣的狀況,某個類本來對得起本身的身價,但重構使它身形縮水,再也不作那麼多工做;或開發者事先規劃了某些變化,並添加一個類來應付這些變化,但變化實際上沒有發生。
1三、Speculative Generality(誇誇其談將來性)
有些代碼若是暫時用不到,就把它去掉。而不是說「我總有一天會用到」,這麼作的結果每每形成系統更難理解和維護。
1四、Temporary Field(使人迷惑的暫時字段)
有時你會看到這樣的對象:其內某個實例變量(就是非static修飾的變量,也被稱爲成員變量。對應的是類變量,static修飾的變量)僅爲某種特定狀況而設。這樣的代碼讓人不易理解,由於你一般認爲對象在全部時候都須要它的全部變量。在變量未被使用的狀況下猜想當初其設置目的,會讓你發瘋的。把全部和這個變量相關的代碼都放進一個新類。也許你還能夠在變量不合法的狀況下,建立一個null對象(空對象,應該是這個意思),從而避免寫出條件式代碼
1五、Message Chains(過渡耦合的消息鏈)
若是你看到用戶向一個對象請求另外一個對象,而後再向後者請求另外一個對象,而後再請求另外一個對象。。。。。。這就是消息鏈。實際代碼中你看到的多是一長串的getThis()或一長串臨時變量。採用這種方式,意味客戶代碼將與查找過程當中的導航結構緊密耦合。一旦對象間的關係發生任何變化,客戶端就不得不作出相應修改。你能夠再消息鏈的不一樣位置進行這種重構手法。理論上能夠重構消息鏈上的任何一個對象,但這麼作每每會把一系列對象都變成Middle Man。一般更好的選擇是:先觀察消息鏈最終獲得的對象是用來幹什麼的,看看可否把使用該對象的代碼提煉到一個獨立函數中,而後把這個函數推入消息鏈。若是這條鏈上的某個對象有多位客戶打算航行此航線的剩餘部分,就加一個函數來作這件事。
1六、Middle Man(中間人)
對象的基本特徵之一就是封裝--對外部世界隱藏其內部細節。封裝每每伴隨委託。好比說你問主管是否有時間參加一個會議,他就把這個消息「委託」給他的記事簿,而後才能回答你。你沒有必要知道這位主管到底使用傳統記事簿或電子記事簿亦或祕書來記錄本身的約會。可是人們可能過渡運用委託。你也許會看到某個類接口有一半的函數都委託給其餘類,這樣就是過渡運用。這時應該去掉中間人,直接和真正負責的對象打交道。若是這樣「不幹實事」的函數只有少數幾個,能夠把它們放進調用端。若是這些Middle Man還有其餘行爲,能夠把它變成實則對象的子類,這樣你既能夠擴展原對象的行爲,又沒必要負擔那麼多的委託動做。
1七、Inappropriate Intimacy(狎暱關係)
有時你會看到兩個類過於親密,花費太多時間去探究彼此的private成分,你就須要幫它們劃清界限,減小狎暱行徑。若是它們有一些共同點,能夠將共同點提煉到一個新類中。繼承每每形成過分親密,由於子類對超類的瞭解老是超事後者的主觀願望。若是你以爲該讓這個孩子獨自生活了,可讓它離開繼承體系。
1八、Alternative Classes with Different Interfaces(殊途同歸的類)
若是兩個函數作同一件事,卻有着不一樣的簽名(應該是函數名的意思),根據它們的用途從新命名。但這每每不夠,請反覆移動方法將某些行爲移入類,直到二者的協議一致爲止。(協議?)。若是你必須重複而贅餘地移入代碼才能完成這些,或許能夠嘗試抽出父類。
1九、Incomplete Library Class(不完美的庫類)
所謂Data Class是指:它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。這樣的類只是一種不會說話的數據容器,它們幾乎必定被其餘類過度細瑣地操控着。這些類可能擁有public字段,注意封裝。若是這些類內含容器類的字段,你應該檢查他們是否是獲得了恰當的封裝。找出這些取值/設置函數被其餘類運用的地點,嘗試將那些調用行爲搬移到Data Class來。若是沒法搬移整個函數,就抽出一個可被搬移的函數。不久以後,你就能夠把這些取值/設置函數隱藏起來了。Data Class就像小孩子。做爲一個起點很好,但若要讓它們像成熟的對象那樣參與整個系統的工做,它們就必須承擔必定責任。
20、Comments(過多的解釋)
經常會有這樣的狀況:你看到一段代碼有着經常的註釋,而後發現,這些註釋之因此存在乃是由於代碼很糟糕。當你感受須要撰寫註釋時,請先嚐試重構,試着讓全部註釋都變得多餘。若是你不知道該作什麼,這纔是註釋的良好運用時機。除了用來記述未來的打算以外,註釋還能夠用來標記你並沒有十足把握的區域。你能夠在註釋裏寫下本身「爲何作某某事」。這類信息能夠幫助未來的修改者,尤爲是那些健忘的傢伙。
這一章主要講述代碼的壞味道,哪些地方可能須要重構,增長本身重構的判斷力。