壞的味道:指的是應(yīng)該被修改,被重構(gòu)的代碼,不具有可讀性,難理解,冗余代碼。應(yīng)該使用各種重構(gòu)的手法去改變它!
[TOC]
Duplicated Code(重復(fù)代碼)
如果你在一個以上的地點看到相同的程序結(jié)構(gòu),那么可以肯定的:設(shè)法將他們合而為一,程序會變得更好。
- 同一個類的兩個函數(shù)含有相同的表達式
- 兩個互為兄弟子類內(nèi)含相同表達式
- 如果兩個毫不相關(guān)的類出現(xiàn)
Duplicated Code應(yīng)該考慮對其中一個將重復(fù)代碼提煉到一個獨立類種,然后在另一個類內(nèi)使用這個新類。
Long Method(過長函數(shù))
我們應(yīng)當(dāng)遵循這樣一條原則:每當(dāng)感覺需要以注釋來說明點什么的時候,我們就需要說明的東西寫進一個獨立的函數(shù)中,并以器用途(而非實現(xiàn)手法)命名。
- 我們可以對一組甚至短短一行代碼做這件事。哪怕替換后的函數(shù)調(diào)用動作比函數(shù)自身還長,只要函數(shù)名稱能夠解釋其用途,我們也該毫不猶豫得那么做。關(guān)鍵不在于函數(shù)得長度,而在于函數(shù):“做什么”和“如何做”之間得語義距離。
- 如何確定該提煉哪一段代碼呢?一個很好得技巧是:尋找注釋。它們通常能指出代碼用途和實現(xiàn)手法之間的語義距離。如果代碼前方有一行注釋,就是再提醒你L可以將這段代碼替換成一個函數(shù),而且可以在注釋得基礎(chǔ)上給這個函數(shù)命名。<font color="#dd0000"> 就算只有一行代碼,如果他需要以注釋來說明,那也值得將它提煉導(dǎo)獨立函數(shù)去。</font>
- 條件表達式和循環(huán)常常也是提煉得信號??梢允褂?code>Decompose Conditional處理條件表達是。至于循環(huán),你應(yīng)該將循環(huán)和期內(nèi)的代碼提煉導(dǎo)一個獨立的函數(shù)中
Large Class(過大的類)
- 如果類中有太多的實例變量,可以將幾個變量提煉至新類內(nèi)。提煉時應(yīng)該選擇類內(nèi)彼此相關(guān)的變量,將它們放在一起
- 類內(nèi)如果有太多代碼,把多余的東西消弭于類內(nèi)部。如果有五個“百行代碼”,它們之中很多代碼都相同,那么或許你可以把它們變成五個“十行函數(shù)”和十個提煉出的“雙行函數(shù)”。
Long Parameter List(過長的參數(shù)列)
如果已有的對象發(fā)出一條請求就可以取代一個參數(shù),那么你應(yīng)該激活重構(gòu)手法Replace Paramter with Method。在這里,“已有的對象”可能時函數(shù)所屬類內(nèi)一個字段,也可能是另一個參數(shù)。你也可以運用Preserve Whole Object將來自同一個對象的一堆數(shù)據(jù)收集起來,并以該對象替換它們。如果某些數(shù)據(jù)缺乏合理的對象歸屬,可以使用Introduce Parameter Object 為它們制造出一個“參數(shù)對象”。
Divergent Change(發(fā)散式變化)
一旦需要修改,我們希望能夠跳到系統(tǒng)的某一點,只在該處做修改。如果不能做到這點,你就嗅出兩種相關(guān)的刺鼻味道中的一種了。
- 針對某一外界變化的所有相應(yīng)修改,<font color="#dd0000"> 都只應(yīng)發(fā)生在單一類中</font>,而這個新類內(nèi)的所有內(nèi)容都應(yīng)該反應(yīng)此變化,為此,你應(yīng)該找出某特定原因而造成的所有變化,然后運用
Extract Class將它們提煉導(dǎo)另一個類中。
Shotgun Surgery(散彈式修改)
遇到某種變化,你都必須在不同類內(nèi)做出許多小修改,你所面臨的壞味道就是shotgun Surgery,如果需要修改的代碼散布四處,你不但很難找到它們,也很容易忘記某個重要的修改
- 這種情況下應(yīng)該使用
Move Method和Move Field把所有需要修改的代碼放進同一個類。如果眼下沒有合適的類可以安置這些代碼,就創(chuàng)造一個。通??梢赃\用inline class把一系列相關(guān)行為放進同一個類。 -
Divergent change是指<font color="#dd0000">“一個類受多種變化影響”</font>,shotgun surgery則是指<font color="#dd0000">“一種變化引發(fā)多個類相應(yīng)修改”</font>。者兩種情況下你都會希望整理代碼,使“外界變化”與“需要修改的類”趨于一一對應(yīng)。
Feature Envy(依戀情結(jié))
函數(shù)對某個類的興趣高過對自己所處類的興趣,通常焦點便是數(shù)據(jù),某個函數(shù)為了計算某個值,從另一個對象那調(diào)用幾乎半打的額取值函數(shù)。
- 療法顯而易見:把這個函數(shù)移至另一個地點。
- 一個函數(shù)往往會用到幾個類的功能,那么它究竟該被置于何處呢?我們的原則是判斷哪個類擁有最多被此函數(shù)使用的數(shù)據(jù),然后把這個函數(shù)和那些數(shù)據(jù)擺在一起。
Data Clumps(數(shù)據(jù)泥團)
- 兩個類中相同的字段、許多函數(shù)簽名中相同的參數(shù)。這些總是綁在一起出現(xiàn)的數(shù)據(jù)真應(yīng)該擁有屬于它們自己的對象
- 一個好的評判方法是:刪掉眾多數(shù)據(jù)中的一項。這么做,其他數(shù)據(jù)有沒有因而失去意義?如果它們不在有意義,這就是個明確信號:<font color="#dd0000">你應(yīng)該為它們產(chǎn)生一個新對象</font>。
Primitive Obsession(基本類型偏執(zhí))
對象的一個極大的價值在于:它們模糊(甚至打破)了橫亙于基本類型數(shù)據(jù)和體積較大類之間的界限
- 可以運用
Replace Data Vlaue with Object將原本單獨存在的數(shù)據(jù)值替換為對象 - 如果想要替換的數(shù)據(jù)值是類型碼,而它并不影響行為,則可以運用
Replace type Code with class將它替換 - 如果有與類型碼有關(guān)的條件表達式,可運用
Replace Type Code with Subclass 或 Replace Type Code with State/Strategy
Switch Statements(switch 驚悚現(xiàn)身)
大多數(shù)時候,一看到switch語句,<font color="#dd0000">你就應(yīng)該考慮以多態(tài)來替換它</font>。問題是多態(tài)應(yīng)該出現(xiàn)在哪兒?switch語句常常根據(jù)類型碼進行選擇,你要的是“與該類型碼相關(guān)的函數(shù)或類”
Parallel Inheritance Hierarchies(平行繼承體系)
是
Shotgun Surgery的特殊情況,在這種情況下,每當(dāng)你為某個類增加一個子類,也必須為另一個類相應(yīng)的增加一個子類。<font color="#00dd00">如果你發(fā)現(xiàn)某個繼承體系的名稱前綴和另一個繼承體系名稱前綴完全相同</font>,便是聞到了這種壞味道
消除這種重復(fù)性的一般策略是:<font color="#00dd00">讓一個繼承體系的實例引用另一個繼承體系的實例</font>。再運用Move Method 和Move Field,就可以就將引用端的繼承體系消弭于無形
Lazy Class(冗贅類)
如果一個類的所得不值其身價,它就該消失
如果某些子類沒有做足夠的工作,試試Collapse Hierarchy。對于幾乎沒用的組件,你應(yīng)該以Inline Class對付它們
Speculative Generality(夸夸其談的未來性)
<font color="#000066">不要過分對的為未來考慮</font>
如果你的某個抽象類其實沒有太大作用,請運用Collapse Hierarchy。不必要的委托可運用Inline Class除掉。如果函數(shù)的某些參數(shù)違背用上,可對它實施Remove Parameter。如果函數(shù)名稱帶有多余的抽象意味,應(yīng)該對它實施rename Method,讓它現(xiàn)實一點
Temporary Field (令人迷惑的暫時字段)
某個實例變量僅為某種特定的情況而設(shè)。這樣的代碼讓人不易理解,因為你通常認為對象在所有時候都需要它的所有變量,在未被使用的情況下猜測當(dāng)初其設(shè)置目的,會讓你發(fā)瘋的。<font color="#dd0000">把所有和這個變量相關(guān)的代碼新建一個類放入</font>。
Message Chains(過度耦合的消息鏈)
如果你看到用戶一個對象請求另一個對象,然后后者請求另一個對象,然后再請求另一個對象這就是消息鏈
先觀察消息鏈最終得到的對象用來干什么的,看看能否以Extract Method 把使用該對象的代碼提煉到一個獨立的函數(shù)中再運用Move Method 把這個函數(shù)推入消息鏈
Middle Man(中間人)
人們可能過度運用委托,會有某個接口有一半的函數(shù)都委托給其他類,這樣就是過度運用, 使用
Remove Middle Man,<font color="#dd0000">直接和真正負責(zé)的對象打交道</font>
Inappropriate Intimacy(狎昵關(guān)系)
- 過分狎昵的類必須拆散. 你可以采用
Move Method和Move Field幫它們劃分界限, 從而減少狎昵行徑. 你可以可以看看是否可以運用Change Bidirectional Association to Unidirectional(將雙向關(guān)聯(lián)改為單向關(guān)聯(lián))讓其中一個類對另一個斬斷情絲. - 如果兩個類實在是情投意合, 可以運用
Extract Class把兩者的共同點提煉到一個安全的地點,讓它們坦蕩地使用這個新類. 或者也可以嘗試運用Hide Delegate讓另一個類來為它們傳遞相思情. - 繼承往往造成過度親密, 因為子類對超類的了解總是超過超類的主管愿望. 如果你覺的該讓這個孩子獨立生活了, 請運用
Replace Inheritance with Delegation讓它離開繼承體系.
Alternative Class with Different Interfaces(異曲同工的類)
如果兩個函數(shù)做同一件事,卻有著不同的簽名,請運用Rename Method根據(jù)它們的用途重新命名。但這往往不夠,請反復(fù)運用 Move Method將某些行為移入類,直到2者的協(xié)議一致為止。如果你必須反復(fù)而贅余的移入代碼才能完成這些,或許可運用Extract SuperClass
Incomplete Library Class(不完美的類庫)
如果只想修改類庫的一兩個函數(shù),可以運用Introduce Foreign Method 如果想要添加一大堆額外行為,就得運用Introduce Local Extension
Data Class(純稚的數(shù)據(jù)類)
所謂Data Class是指:它們擁有一些值域(fields),以及用于訪問(讀寫〕這些值域的函數(shù),除此之外一無長物。
- 這樣的classes只是一種「不會說話的數(shù)據(jù)容器」,它們幾乎一定被其他classes過份細瑣地操控著。這些classes早期可能擁有public值域,果真如此你應(yīng)該在別人注意到它們之前,立刻運用Encapsulate Field (封裝值域)將它們封裝起來。如果這些classes內(nèi)含容器類的值域(collection fields),你應(yīng)該 檢査它們是不是得到了恰當(dāng)?shù)姆庋b;如果沒有,就運用
Encapsulate Collection(封裝群集) 把它們封裝起來。對于那些不該被其他classes修改的值域,請運用Remove Setting Method(移除設(shè)置函數(shù))。 - 然后,找出這些「取值/設(shè)值」函數(shù)(
getting and setting methods)被其他classes運用的地點。嘗試以Move Method(搬移函數(shù)) 把那些調(diào)用行為搬移到Data Class來。如果無法搬移整個函數(shù),就運用Extract Method(提煉函數(shù)) 產(chǎn)生一個可被搬移的函數(shù)。不久之后你就可以運用Hide Method(隱藏某個函數(shù))把這些「取值/設(shè)值」函數(shù)隱藏起來了。
Refused Bequest(被拒絕的遺贈)
指的是一個子類,不需要父類中的過多方法
這樣我們可以為這個子類創(chuàng)建一個兄弟類,把父類中不需要的方法下移到兄弟類中去。 如果子類復(fù)用了超類的行為(實現(xiàn)),卻又不愿意支持超類的接口。不過即使不愿意繼承接口,也不要胡亂修改繼承體系,應(yīng)該運用Replace Inheritance with Delegation 來達到目的
Comments(過多的注釋)
引用作者的一句話<font color="#dd0000">"當(dāng)你感覺需要撰寫注釋,請先嘗試重構(gòu),試著讓所有注釋都變得多余。"</font>
并不是說寫注釋不好,而是當(dāng)你寫一段很長的注釋來說明代碼邏輯的時候,說明這段代碼真的很糟糕,你就要考慮重構(gòu)了。