壞的味道:指的是應(yīng)該被修改,被重構(gòu)的代碼广恢,不具有可讀性凯旋,難理解,冗余代碼袁波。應(yīng)該使用各種重構(gòu)的手法去改變它瓦阐!
[TOC]
Duplicated Code(重復(fù)代碼)
如果你在一個以上的地點(diǎn)看到相同的程序結(jié)構(gòu)蜗侈,那么可以肯定的:設(shè)法將他們合而為一篷牌,程序會變得更好。
- 同一個類的兩個函數(shù)含有相同的表達(dá)式
- 兩個互為兄弟子類內(nèi)含相同表達(dá)式
- 如果兩個毫不相關(guān)的類出現(xiàn)
Duplicated Code
應(yīng)該考慮對其中一個將重復(fù)代碼提煉到一個獨(dú)立類種踏幻,然后在另一個類內(nèi)使用這個新類枷颊。
Long Method(過長函數(shù))
我們應(yīng)當(dāng)遵循這樣一條原則:每當(dāng)感覺需要以注釋來說明點(diǎn)什么的時候,我們就需要說明的東西寫進(jìn)一個獨(dú)立的函數(shù)中,并以器用途(而非實現(xiàn)手法)命名夭苗。
- 我們可以對一組甚至短短一行代碼做這件事信卡。哪怕替換后的函數(shù)調(diào)用動作比函數(shù)自身還長,只要函數(shù)名稱能夠解釋其用途题造,我們也該毫不猶豫得那么做傍菇。關(guān)鍵不在于函數(shù)得長度,而在于函數(shù):“做什么”和“如何做”之間得語義距離界赔。
- 如何確定該提煉哪一段代碼呢丢习?一個很好得技巧是:尋找注釋。它們通常能指出代碼用途和實現(xiàn)手法之間的語義距離淮悼。如果代碼前方有一行注釋咐低,就是再提醒你L可以將這段代碼替換成一個函數(shù),而且可以在注釋得基礎(chǔ)上給這個函數(shù)命名袜腥。<font color="#dd0000"> 就算只有一行代碼见擦,如果他需要以注釋來說明,那也值得將它提煉導(dǎo)獨(dú)立函數(shù)去羹令。</font>
- 條件表達(dá)式和循環(huán)常常也是提煉得信號鲤屡。可以使用
Decompose Conditional
處理條件表達(dá)是特恬。至于循環(huán)执俩,你應(yīng)該將循環(huán)和期內(nèi)的代碼提煉導(dǎo)一個獨(dú)立的函數(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ù)。你也可以運(yùn)用Preserve Whole Object
將來自同一個對象的一堆數(shù)據(jù)收集起來邻梆,并以該對象替換它們守伸。如果某些數(shù)據(jù)缺乏合理的對象歸屬,可以使用Introduce Parameter Object
為它們制造出一個“參數(shù)對象”浦妄。
Divergent Change(發(fā)散式變化)
一旦需要修改尼摹,我們希望能夠跳到系統(tǒng)的某一點(diǎn)见芹,只在該處做修改。如果不能做到這點(diǎn)蠢涝,你就嗅出兩種相關(guān)的刺鼻味道中的一種了玄呛。
- 針對某一外界變化的所有相應(yīng)修改,<font color="#dd0000"> 都只應(yīng)發(fā)生在單一類中</font>和二,而這個新類內(nèi)的所有內(nèi)容都應(yīng)該反應(yīng)此變化徘铝,為此,你應(yīng)該找出某特定原因而造成的所有變化惯吕,然后運(yùn)用
Extract Class
將它們提煉導(dǎo)另一個類中庭砍。
Shotgun Surgery(散彈式修改)
遇到某種變化,你都必須在不同類內(nèi)做出許多小修改混埠,你所面臨的壞味道就是shotgun Surgery怠缸,如果需要修改的代碼散布四處,你不但很難找到它們钳宪,也很容易忘記某個重要的修改
- 這種情況下應(yīng)該使用
Move Method
和Move Field
把所有需要修改的代碼放進(jìn)同一個類揭北。如果眼下沒有合適的類可以安置這些代碼,就創(chuàng)造一個吏颖。通成μ澹可以運(yùn)用inline class把一系列相關(guān)行為放進(jìn)同一個類。 -
Divergent change
是指<font color="#dd0000">“一個類受多種變化影響”</font>半醉,shotgun surgery
則是指<font color="#dd0000">“一種變化引發(fā)多個類相應(yīng)修改”</font>疚俱。者兩種情況下你都會希望整理代碼,使“外界變化”與“需要修改的類”趨于一一對應(yīng)缩多。
Feature Envy(依戀情結(jié))
函數(shù)對某個類的興趣高過對自己所處類的興趣呆奕,通常焦點(diǎn)便是數(shù)據(jù),某個函數(shù)為了計算某個值衬吆,從另一個對象那調(diào)用幾乎半打的額取值函數(shù)梁钾。
- 療法顯而易見:把這個函數(shù)移至另一個地點(diǎn)。
- 一個函數(shù)往往會用到幾個類的功能逊抡,那么它究竟該被置于何處呢姆泻?我們的原則是判斷哪個類擁有最多被此函數(shù)使用的數(shù)據(jù),然后把這個函數(shù)和那些數(shù)據(jù)擺在一起冒嫡。
Data Clumps(數(shù)據(jù)泥團(tuán))
- 兩個類中相同的字段拇勃、許多函數(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ù)和體積較大類之間的界限
- 可以運(yùn)用
Replace Data Vlaue with Object
將原本單獨(dú)存在的數(shù)據(jù)值替換為對象 - 如果想要替換的數(shù)據(jù)值是類型碼,而它并不影響行為辜窑,則可以運(yùn)用
Replace type Code with class
將它替換 - 如果有與類型碼有關(guān)的條件表達(dá)式钩述,可運(yù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ù)類型碼進(jìn)行選擇所禀,你要的是“與該類型碼相關(guān)的函數(shù)或類”
Parallel Inheritance Hierarchies(平行繼承體系)
是
Shotgun Surgery
的特殊情況方面,在這種情況下,每當(dāng)你為某個類增加一個子類色徘,也必須為另一個類相應(yīng)的增加一個子類恭金。<font color="#00dd00">如果你發(fā)現(xiàn)某個繼承體系的名稱前綴和另一個繼承體系名稱前綴完全相同</font>,便是聞到了這種壞味道
消除這種重復(fù)性的一般策略是:<font color="#00dd00">讓一個繼承體系的實例引用另一個繼承體系的實例</font>褂策。再運(yùn)用Move Method 和Move Field
横腿,就可以就將引用端的繼承體系消弭于無形
Lazy Class(冗贅類)
如果一個類的所得不值其身價,它就該消失
如果某些子類沒有做足夠的工作斤寂,試試Collapse Hierarchy
耿焊。對于幾乎沒用的組件,你應(yīng)該以Inline Class
對付它們
Speculative Generality(夸夸其談的未來性)
<font color="#000066">不要過分對的為未來考慮</font>
如果你的某個抽象類其實沒有太大作用遍搞,請運(yùn)用Collapse Hierarchy
罗侯。不必要的委托可運(yùn)用Inline Class
除掉。如果函數(shù)的某些參數(shù)違背用上溪猿,可對它實施Remove Parameter
钩杰。如果函數(shù)名稱帶有多余的抽象意味,應(yīng)該對它實施rename Method
诊县,讓它現(xiàn)實一點(diǎn)
Temporary Field (令人迷惑的暫時字段)
某個實例變量僅為某種特定的情況而設(shè)榜苫。這樣的代碼讓人不易理解,因為你通常認(rèn)為對象在所有時候都需要它的所有變量翎冲,在未被使用的情況下猜測當(dāng)初其設(shè)置目的垂睬,會讓你發(fā)瘋的。<font color="#dd0000">把所有和這個變量相關(guān)的代碼新建一個類放入</font>抗悍。
Message Chains(過度耦合的消息鏈)
如果你看到用戶一個對象請求另一個對象驹饺,然后后者請求另一個對象,然后再請求另一個對象這就是消息鏈
先觀察消息鏈最終得到的對象用來干什么的缴渊,看看能否以Extract Method
把使用該對象的代碼提煉到一個獨(dú)立的函數(shù)中再運(yùn)用Move Method
把這個函數(shù)推入消息鏈
Middle Man(中間人)
人們可能過度運(yùn)用委托赏壹,會有某個接口有一半的函數(shù)都委托給其他類,這樣就是過度運(yùn)用, 使用
Remove Middle Man
衔沼,<font color="#dd0000">直接和真正負(fù)責(zé)的對象打交道</font>
Inappropriate Intimacy(狎昵關(guān)系)
- 過分狎昵的類必須拆散. 你可以采用
Move Method
和Move Field
幫它們劃分界限, 從而減少狎昵行徑. 你可以可以看看是否可以運(yùn)用Change Bidirectional Association to Unidirectional
(將雙向關(guān)聯(lián)改為單向關(guān)聯(lián))讓其中一個類對另一個斬斷情絲. - 如果兩個類實在是情投意合, 可以運(yùn)用
Extract Class
把兩者的共同點(diǎn)提煉到一個安全的地點(diǎn),讓它們坦蕩地使用這個新類. 或者也可以嘗試運(yùn)用Hide Delegate
讓另一個類來為它們傳遞相思情. - 繼承往往造成過度親密, 因為子類對超類的了解總是超過超類的主管愿望. 如果你覺的該讓這個孩子獨(dú)立生活了, 請運(yùn)用
Replace Inheritance with Delegation
讓它離開繼承體系.
Alternative Class with Different Interfaces(異曲同工的類)
如果兩個函數(shù)做同一件事蝌借,卻有著不同的簽名昔瞧,請運(yùn)用Rename Method
根據(jù)它們的用途重新命名。但這往往不夠菩佑,請反復(fù)運(yùn)用 Move Method
將某些行為移入類自晰,直到2者的協(xié)議一致為止。如果你必須反復(fù)而贅余的移入代碼才能完成這些稍坯,或許可運(yùn)用Extract SuperClass
Incomplete Library Class(不完美的類庫)
如果只想修改類庫的一兩個函數(shù)酬荞,可以運(yùn)用Introduce Foreign Method
如果想要添加一大堆額外行為,就得運(yùn)用Introduce Local Extension
Data Class(純稚的數(shù)據(jù)類)
所謂Data Class是指:它們擁有一些值域(fields)瞧哟,以及用于訪問(讀寫〕這些值域的函數(shù)混巧,除此之外一無長物。
- 這樣的classes只是一種「不會說話的數(shù)據(jù)容器」勤揩,它們幾乎一定被其他classes過份細(xì)瑣地操控著咧党。這些classes早期可能擁有public值域,果真如此你應(yīng)該在別人注意到它們之前陨亡,立刻運(yùn)用Encapsulate Field (封裝值域)將它們封裝起來凿傅。如果這些classes內(nèi)含容器類的值域(collection fields),你應(yīng)該 檢査它們是不是得到了恰當(dāng)?shù)姆庋b数苫;如果沒有聪舒,就運(yùn)用
Encapsulate Collection
(封裝群集) 把它們封裝起來。對于那些不該被其他classes修改的值域虐急,請運(yùn)用Remove Setting Method
(移除設(shè)置函數(shù))箱残。 - 然后,找出這些「取值/設(shè)值」函數(shù)(
getting and setting methods
)被其他classes運(yùn)用的地點(diǎn)止吁。嘗試以Move Method
(搬移函數(shù)) 把那些調(diào)用行為搬移到Data Class來被辑。如果無法搬移整個函數(shù),就運(yùn)用Extract Method
(提煉函數(shù)) 產(chǎn)生一個可被搬移的函數(shù)敬惦。不久之后你就可以運(yùn)用Hide Method
(隱藏某個函數(shù))把這些「取值/設(shè)值」函數(shù)隱藏起來了盼理。
Refused Bequest(被拒絕的遺贈)
指的是一個子類,不需要父類中的過多方法
這樣我們可以為這個子類創(chuàng)建一個兄弟類俄删,把父類中不需要的方法下移到兄弟類中去宏怔。 如果子類復(fù)用了超類的行為(實現(xiàn)),卻又不愿意支持超類的接口畴椰。不過即使不愿意繼承接口臊诊,也不要胡亂修改繼承體系,應(yīng)該運(yùn)用Replace Inheritance with Delegation
來達(dá)到目的
Comments(過多的注釋)
引用作者的一句話<font color="#dd0000">"當(dāng)你感覺需要撰寫注釋斜脂,請先嘗試重構(gòu)抓艳,試著讓所有注釋都變得多余。"</font>
并不是說寫注釋不好帚戳,而是當(dāng)你寫一段很長的注釋來說明代碼邏輯的時候玷或,說明這段代碼真的很糟糕儡首,你就要考慮重構(gòu)了。