一. Duplicated Code(重復(fù)代碼)
- 如果你在一個以上的地點看到相同的程序結(jié)構(gòu),設(shè)法將他們合而為一,程序會變得更好。
- 同一個類的兩個函數(shù)含有相同的表達(dá)式,采用
Extract Method(提煉函數(shù))提煉出重復(fù)的代碼。 - 兩個互為兄弟的子類含有相同的表達(dá)式,首先對兩個類都使用
Extract Method(提煉函數(shù)),然后再對提煉出來的代碼使用Pull Up Method(函數(shù)上移),將它推入超類。 - 如果代碼之間只是類似,并非完全相同,運用
Extract Method(提煉函數(shù))將相似部分和差異部分分割開,然后可以運用Form Template Method(塑造模板函數(shù))獲取一個莫模板方法。 - 如果有些函數(shù)以不同算法做相同的事情,你可與選擇其中較清晰的一個,使用
Substitute Algorithm(替換算法)將其他函數(shù)的算法替換掉。 - 如果兩個毫不相關(guān)的類出現(xiàn)重復(fù)代碼,應(yīng)考慮對其中一個使用
Extract Class(提煉類),將重復(fù)代碼提煉到一個獨立的類中。 - 重復(fù)代碼所在的函數(shù)應(yīng)該只屬于某一個類,另一個類調(diào)用它;或者應(yīng)該屬于第三個類,另兩個類引用這第三個類。決定重復(fù)函數(shù)的最合適位置,確保只有一份。
二. Long Method(過長函數(shù))
- 擁有短函數(shù)的對象會活的比較好、比較長。
- 絕大部分情況下,要把函數(shù)變小,只需要使用
Extract Method(提煉函數(shù))。 - 使用
Extract Method(提煉函數(shù))時,如果函數(shù)中有個別參數(shù)和臨時變量,可以把他們當(dāng)做參數(shù),傳遞給被提煉出來的新函數(shù)。 - 如果被提煉函數(shù)內(nèi)有大量的參數(shù)和臨時變量,可以運用
Replace Temp with Query(以查詢?nèi)〈R時變量)來消除這些臨時元素。 - 使用
Introduce Parameter Object(引入?yún)?shù)對象)可以將過長的參數(shù)列變得更簡潔一些。 - 如果被提煉函數(shù)仍然有太多臨時變量和參數(shù),可以使用
Replace Method with Method Object(以函數(shù)對象取代函數(shù))。 - 注釋通常能夠指出應(yīng)該被提煉的代碼。就算只有一行代碼,如果它需要以注釋來說明,那也值得將它提煉到獨立函數(shù)去。
- 你可以使用
Decompose Conditional(分解條件表達(dá)式)處理條件表達(dá)式。 - 你應(yīng)該將循環(huán)和其內(nèi)的代碼提煉到一個獨立的函數(shù)中。
三. Large Class(過大的類)
- 如果想利用單個類做太多事情,其內(nèi)往往就會出現(xiàn)太多實例變量。一旦如此,重復(fù)代碼也就接踵而至了。
- 可以運用
Extract Class(提煉類)將幾個彼此相關(guān)的實例變量一起提煉至新類內(nèi)。如果被提煉出的類適合作為一個子類,使用Extract Subclass(提煉子類)往往比較簡單。 - 有時候類并非所有時刻都使用所有實例變量。你可以多次使用
Extract Class(提煉類)或Extract Subclass(提煉子類)。 - 和“太多實例變量”一樣,類內(nèi)如果有太多代碼,也是代碼重復(fù)、混亂并最終走向死亡的源頭。
- 如果有五個“百行函數(shù)”,他們之中有很多代碼相同,那么你也許可以把他們變成五個“十行函數(shù)”和十個提煉出的“雙行函數(shù)”。
- 和“太多實例變量”一樣,類內(nèi)如果有太多代碼,往往也適合使用
Extract Class(提煉類)或Extract Subclass(提煉子類)。 - 如果你的過大的類是一個
GUI類,你可能需要把數(shù)據(jù)和行為移到一個獨立的類中。
四. Long Parameter List(過長參數(shù)列)
- 太長的參數(shù)列難以理解,太多參數(shù)會造成前后不一致、不易使用。
- 剛開始學(xué)習(xí)編程時,老師教我們:把函數(shù)所需的所有東西都以參數(shù)傳遞進(jìn)去。這可以理解,因為除此之外只能選擇全局?jǐn)?shù)據(jù),而全局?jǐn)?shù)據(jù)是邪惡的東西。
- 對象技術(shù)改變了這一情況:如果你手上沒有所需的東西,總可以叫另一個對象給你。有了對象,函數(shù)需要的東西多半可以在函數(shù)的宿主類中找到。面向?qū)ο蟪绦蛑械暮瘮?shù),其參數(shù)列通常比在傳統(tǒng)程序中短的多。
- 如果向已有的對象發(fā)出一條請求就可以取代一個參數(shù),那么你應(yīng)該激活重構(gòu)手法
Replace Parameter with Method(以函數(shù)取代參數(shù))。 - 你還可以運用
Preserve Whole Object(保持對象完整)將來自同一對象的一堆數(shù)據(jù)收集起來,并以該對象替換他們。 - 如果某些數(shù)據(jù)缺乏合理的對象歸屬,可使用
Introduce Parameter Object(引入?yún)?shù)對象)為他們制造出一個“參數(shù)對象”。 - 這里有一個例外:有時候你明顯不希望造成“被調(diào)用對象”與“較大對象”間的某種依賴關(guān)系。這時候?qū)?shù)據(jù)從對象拆解出來單獨作為參數(shù),也很合情合理。但是請權(quán)衡其所引發(fā)的代價。
五. Divergent Change(發(fā)散式變化)
- 我們希望軟件能夠更容易被修改。一旦需要修改,我們希望能夠跳到系統(tǒng)的某一點,只在該處做修改。
- 如果某個類經(jīng)常因為不同的原因在不同的方向上發(fā)生變化,
Divergent Change(發(fā)散式變化)就出現(xiàn)了。 - 針對某一外界變化的所有相應(yīng)修改,都只應(yīng)該發(fā)生在單一類中。為此,你應(yīng)該找出某特定原因而造成的所有變化,運用
Extract Class(提煉類)將他們提煉到另一個類中。
六. Shotgun Surgery(散彈式修改)
-
Shotgun Surgery(散彈式修改)類似Divergent Change(發(fā)散式變化),但恰恰相反。 - 如果每遇到某種變化,你都必須在許多不同的類內(nèi)做出許多小修改,你所面臨的壞味道就是
Shotgun Surgery(散彈式修改)。 - 如果需要修改的代碼散布四處,你不但很難找到他們,也很容易忘記某個重要的修改。
- 你應(yīng)該使用
Move Method(搬移函數(shù))和Move Field(搬移字段)把所有需要修改的代碼放進(jìn)同一個類。 - 如果眼下沒有合適的類可以安置這些代碼,就創(chuàng)造一個。通常可以運用
Inline Class(將類內(nèi)聯(lián)化)把一系列相關(guān)行為放進(jìn)同一個類。 -
Divergent Change(發(fā)散式變化) 是指“一個類受多種變化的影響”。Shotgun Surgery(散彈式修改)則是指“一種變化引起多個類響應(yīng)修改”。這兩種情況你都希望整理代碼,使“外界變化”與“需要修改的類”趨于一一對應(yīng)。
七. Feature Envy(依戀情節(jié))
- 對象技術(shù)即是一種“將數(shù)據(jù)和對數(shù)據(jù)的操作行為包裝在一起”的技術(shù)。
-
Feature Envy(依戀情節(jié))指的是:函數(shù)對某個類的興趣高過對自己所處類的興趣。 - 我們常??吹侥硞€函數(shù)為了計算某個值,從另一個對象那兒調(diào)用了幾乎半打的取值函數(shù)。此時,你應(yīng)該使用
Move Method(搬移函數(shù))把它移到它該去的地方。 - 先使用
Extract Method(提煉函數(shù)),將這個函數(shù)分解為數(shù)個較小函數(shù)并分別置于不同地點,有助于Move Method(搬移函數(shù))重構(gòu)手法的實施。 - 如果一個函數(shù)用到幾個類的功能,那么需要判斷哪個類擁有最多被此函數(shù)使用的數(shù)據(jù),然后就把這個函數(shù)和那些數(shù)據(jù)擺在一起。
八. Data Clumps(數(shù)據(jù)泥團(tuán))
- 數(shù)據(jù)項就像小孩子,喜歡成群結(jié)隊地待在一塊兒。這些總是綁在一起出現(xiàn)的數(shù)據(jù)真應(yīng)該擁有屬于它們自己的對象。
- 首先運用
Extract Class(提煉類)將他們提煉到一個獨立對象中,然后將注意力轉(zhuǎn)移到函數(shù)簽名上,運用Introduce Parameter Object(引入?yún)?shù)對象)或Preserve Whole Object(保持對象完整性)為它減肥。這樣做可以縮短參數(shù)列,簡化函數(shù)調(diào)用。 - 如果刪掉眾多數(shù)據(jù)中的一項,其他數(shù)據(jù)不再有意義,那么他們應(yīng)該以一個對象的形式存在。
- 一旦擁有新對象,你就有機(jī)會讓程序散發(fā)出一種芬芳。可以將適當(dāng)?shù)某绦蛐袨橐浦列骂?。不必太久,所有的類都將在他們的小小社會中發(fā)揮價值。
九. Primitive Obsession(基類類型偏執(zhí))
- 大多數(shù)編程環(huán)境都有兩種數(shù)據(jù):結(jié)構(gòu)類型允許你將數(shù)據(jù)組織成有意義的形式;基本類型則是構(gòu)成結(jié)構(gòu)類型的積木塊。
- 對象技術(shù)的新手通常不愿意在小任務(wù)上運用小對象——像是結(jié)合數(shù)值和幣種
money類、由一個起始值和一個結(jié)束值組成的range類、電話號碼或郵政編碼等的特殊字符串。 - 你可以運用
Replace Data Value with Object(以對象取代數(shù)據(jù)值)將原本單獨存在的數(shù)據(jù)值替換為對象,從而走出洞窟,進(jìn)入炙手可熱的對象世界。 - 如果想要替換的數(shù)據(jù)值是類型碼,而它并不影響行為,則可以運用
Replace Type Code with Class(以類取代類型碼)將它換掉。 - 如果你有與類型碼相關(guān)的條件表達(dá)式,可運用
Replace Type Code with Subclass(以子類取代類型碼)或Replace Type Code with State/Strategy(以State/Strategy取代類型碼)加以處理。 - 如果你有一組應(yīng)該總是被放在一起的字段,可運用
Extract Class(提煉類)。 - 如果你在參數(shù)列中看到基本類型數(shù)據(jù),不妨試試
Introduce Parameter Object(引入?yún)?shù)對象)。 - 如果你發(fā)現(xiàn)自己正從數(shù)組中挑選數(shù)據(jù)(數(shù)組中的元素各自代表不同的東西),可運用
Replace Array with Object(以對象取代數(shù)組)。
十. Switch Statements(Switch驚悚現(xiàn)身)
- 面向?qū)ο蟪绦虻囊粋€最明顯特征就是:少用
switch語句。從本質(zhì)上說,switch語句的問題在于重復(fù)。面向?qū)ο笾械亩鄳B(tài)概念可為此帶來優(yōu)雅的解決辦法。 - 使用
Extract Method(提煉函數(shù))將switch語句提煉到一個獨立函數(shù)中,再以Move Method(搬移函數(shù))將它搬移到需要多態(tài)性的那個類里。 - 你必須決定是否使用
Replace Type Code with Subclass(以子類取代類型碼)或Replace Type Code with State/Strategy(以State/Strategy取代類型碼)。一旦這樣完成繼結(jié)構(gòu)之后,你就可以運用Replace Conditional with Polymorphism(以多態(tài)取代條件表達(dá)式)了。 - 如果你只是在單一函數(shù)中使用
switch語句,多態(tài)就有點殺雞用牛刀了。這種情況下Replace Parameter with Explicit Methods(以明確函數(shù)取代參數(shù))是個不錯的選擇。如果你的選擇條件之一是null,可以試試Introduce Null Object(引入null對象)。
十一. Parallel Inheritance Hierarchies(平行繼承體系)
-
Parallel Inheritance Hierarchies(平行繼承體系)其實是Shotgun Surgery(散彈式修改)的特殊情況。在這種情況下,每當(dāng)你為某個類添加一個子類,必須也為另一個類相應(yīng)增加一個子類。 - 讓一個繼承體系的實例引用另一個繼承體系的實例。如果再接再厲運用
Move Method(搬移函數(shù))和Move Field(搬移字段),就可以將引用端的繼承體系消弭于無形。
十二. Lazy Class(冗贅類)
- 你創(chuàng)建的每一個類,都得有人去維護(hù)它。如果一個類的所得不值其身價,就應(yīng)該消失。
- 如果某些子類沒有做足夠的工作,試試
Collapse Hierarchy(折疊繼承體系)。 - 對于幾乎沒用的組件,你應(yīng)該以
Inline Class(將類內(nèi)聯(lián)化)對付他們。
十三. Speculative Generality(夸夸其談未來性)
- 當(dāng)有人說“噢,我想我們總有一天需要做這事”,并企圖以各式各樣的鉤子和特殊情況來處理一些非必要的事情,這種壞味道就出現(xiàn)了。
- 如果所有裝置都會被用到,那就值得那么做;如果用不到,就不值得。用不上的裝置只會擋你的路,所以,把它搬開吧。
- 如果你的某個抽象類其實沒有太大作用,請運用
Collapse Hierarchy(折疊繼承體系)。 - 不必要的委托可運用
Inline Class(將類內(nèi)聯(lián)化)除掉。 - 如果函數(shù)的某些參數(shù)未被用上,可對它實施
Remove Parameter(移除參數(shù))。 - 如果函數(shù)名稱帶有多余的抽象意味,應(yīng)該對它實施
Rename Method(函數(shù)改名),讓它更現(xiàn)實一些。
十四. Temporary Field(令人迷惑的暫時字段)
- 有時你會看到這樣的對象:其內(nèi)某個實例變量僅為某種特定情況而設(shè)。這樣的代碼讓人不宜理解,因為你通常認(rèn)為對象在所有時候都需要它的所有變量。
- 請使用
Extract Class(提煉類)給這個可憐的孤兒創(chuàng)造一個家,然后把所有和這個變量相關(guān)的代碼都放進(jìn)這個新家。 - 或許你還可以使用
Introduce Null Object(引入Null對象)在變量不合法的情況下創(chuàng)建一個Null對象,從而避免寫出條件式代碼。 - 如果類中有一個復(fù)雜算法,需要好幾個變量,實現(xiàn)者不希望傳遞一長串參數(shù),所以他把這些參數(shù)放進(jìn)字段中,導(dǎo)致壞味道。這些字段只在使用該算法時才有效,你可以利用
Extract Class(提煉類)把這些變量和其相關(guān)函數(shù)提煉到一個獨立類中。提煉后的新對象將是一個函數(shù)對象。
十五. Message Chains(過度耦合的消息鏈)
- 如果你看到用戶向一個對象請求另一個對象,然后再向后者請求另一個對象,然后再請求另一個對象……這就是消息鏈。
- 你應(yīng)該使用
Hide Delegate(隱藏“委托關(guān)系”)。 - 先觀察消息鏈最終得到的對象時用來干什么的,看看能否以
Extract Method(提煉函數(shù))把使用該對象的代碼提煉到一個獨立函數(shù)中,再運用Move Method(搬移函數(shù))把這個函數(shù)推入消息鏈。
十六. Middle Man(中間人)
- 對象的基本特征之一就是封裝——對外部世界隱藏其內(nèi)部細(xì)節(jié)。封裝往往伴隨委托。
- 人們可能過度運用委托。你也許會看到某個類接口有一半的函數(shù)都委托給其他類,這樣就是過度運用。
- 這時應(yīng)該使用
Remove Middle Man(移除中間人),直接和真正負(fù)責(zé)的對象打交道。 - 如果這樣“不干實事”的函數(shù)只有少數(shù)幾個,可以運用
Inline Method(內(nèi)聯(lián)函數(shù))把他們放進(jìn)調(diào)用端。 - 如果這些中間人還要其他行為,可以運用
Replace Delegation with Inheritance(以繼承取代委托)把它變成實責(zé)對象的子類,這樣你既可以擴(kuò)展原對象的行為,又不必負(fù)擔(dān)那么多的委托動作。
十七. Inappropriate Intimacy(狎昵關(guān)系)
- 有時你會看到兩個類過于親密,花費太多時間去探究彼此的私有成分。如果這發(fā)生在兩個“人”之間,我們不必做衛(wèi)道士;但對于類,我們希望他們嚴(yán)守清規(guī)。
- 可以采取
Move Method(搬移函數(shù))和Move Field(搬移字段)幫他們劃清界限,從而減少狎昵關(guān)系。 - 你也可以看看是否可以運用
Change Bidirectional Association to Unidirectional(將雙向關(guān)聯(lián)改為單向關(guān)聯(lián)),讓其中一個類對另一個斬斷情絲。 - 如果兩個類實在是情投意合,可以運用
Extract Class(提煉類)把兩者共同點提煉到一個安全地點,讓他們坦蕩地使用這個新類。或者也可以嘗試運用Hide Delegate(隱藏“委托關(guān)系”)讓另一個類來為他們傳遞相思情。 - 繼承往往造成過度親密,因為子類對超類的了解總是超過后者的主觀愿望。如果你覺得是該讓這個孩子獨自生活了,請運用
Replace Inheritance with Delegation(以委托取代繼承)讓它離開繼承體系。
十八. Alternative Classes with Different Interfaces(異曲同工的類)
- 如果兩個函數(shù)做同一件事,卻有著不同的簽名,請運用
Rename Method(函數(shù)改名)根據(jù)他們的用途重新命名。 - 反復(fù)運用
Move Method(搬移函數(shù))將某些行為移入類,直到兩者的協(xié)議一致為止。如果你必須重復(fù)而贅余地移入代碼才能完成這些,或許可運用Extract Superclass(提煉超類)為自己贖點罪。
十九. Incomplete Library Class(不完美的庫類)
- 復(fù)用常被視為對象的終極目的。許多編程技術(shù)都建立在程序庫的基礎(chǔ)上。
- 庫類構(gòu)筑者沒有未卜先知的能力,我們不能因此責(zé)怪他們。庫往往構(gòu)造的不夠好,而且往往不可能讓我們修改其中的類使它完成我們希望完成的工作。
- 如果你只想修改庫類的一兩個函數(shù),可以運用
Introduce Foreign Method(引入外加函數(shù));如果想要添加一大堆額外行為,就得運用Introduce Local Extension(引入本地擴(kuò)展)。
二十. Data Class(幼稚的數(shù)據(jù)類)
- 幼稚的數(shù)據(jù)類是指:他們擁有一些字段,以及用于訪問(讀寫)這些字段的函數(shù),除此之外一無長物。
- 這樣的類只是一種不會說話的數(shù)據(jù)容器,他們幾乎一定被其他類過分細(xì)鎖地操縱著。
- 你應(yīng)該運用
Encapsulate Collection(封裝集合)把他們封裝起來。對于那些不該被其他類修改的字段,請運用Remove Setting Method(移除設(shè)值函數(shù))。 - 找出這些取值/設(shè)值函數(shù)被其他類運用的地點。嘗試
Move Method(搬移函數(shù))把那些調(diào)用行為搬移到Data Class(幼稚的數(shù)據(jù)類)來。如果無法搬移整個函數(shù),就運用Extract Method(提煉函數(shù))產(chǎn)生一個可被搬移的函數(shù)。不久以后你就可以運用Hide Method(隱藏函數(shù))把這些取值/設(shè)置函數(shù)隱藏起來了。 -
Data Class(幼稚的數(shù)據(jù)類)就像小孩子。作為一個起點很好,但若要讓它們像成熟的對象那樣參與整個系統(tǒng)的工作,它們就必須承擔(dān)一定責(zé)任。
二十一. Refused Bequest(被拒絕的遺贈)
- 子類應(yīng)該繼承超類的函數(shù)和數(shù)據(jù)。但如果他們得到所有禮物,卻只從中挑選幾樣來玩!又該怎么辦呢?
- 按傳統(tǒng)做法,你需要為這個子類新建一個兄弟類,再運用
Push Down Method(函數(shù)下移)和Push Down Field(字段下移)把所有用不到的函數(shù)從超類下推給那個兄弟。這樣,超類就只持有所有子類共享的東西。 - 我們不建議你胡亂修改繼承體系,應(yīng)該運用
Replace Inheritance with Delegation(以委托取代繼承)來達(dá)到目的。
二十二. Comments(過多的注釋)
- 注釋本身不是一種壞味道,事實上他們還是一種香味呢。
- 有時候,注釋之所以存在乃是因為代碼很糟糕。把注釋當(dāng)做除臭劑是一種壞味道。
- 很多時候,注釋可以幫助我們找到代碼的壞味道。找到壞味道之后,我們首先應(yīng)該以各種重構(gòu)手法把壞味道去除。完成之后我們常常會發(fā)現(xiàn):注釋已經(jīng)變得多余了,因為代碼已經(jīng)清晰說明了這一切。
- 如果你需要注釋來解釋一塊代碼做了什么,試試
Extract Method(提煉函數(shù));如果函數(shù)已經(jīng)提煉出來,但還是需要注釋來解釋其行為,試試Rename Method(函數(shù)改名);如果你需要注釋說明某些系統(tǒng)的需求規(guī)格,試試Introduce Assertion(引入斷言)。 - 當(dāng)你感覺需要撰寫注釋時,請先嘗試重構(gòu),試著讓所有注釋都變得多余。
- 注釋應(yīng)該用來記述將來的打算、標(biāo)記你并無十足把握的區(qū)域。你可以在注釋里寫下自己“為什么做某某事”。這類信息可以幫助將來的修改者,尤其是那些健忘的家伙。