決定何時(shí)重構(gòu)庇谆、何時(shí)停止和知道如何重構(gòu)一樣重要!
開發(fā)者必須通過(guò)實(shí)踐培養(yǎng)自己的經(jīng)驗(yàn)和直覺凭疮,培養(yǎng)出自己的判斷力:學(xué)會(huì)判斷一個(gè)類內(nèi)有多少個(gè)實(shí)例變量算是太大饭耳、學(xué)會(huì)判斷一個(gè)函數(shù)內(nèi)有多少行代碼才算太長(zhǎng)。
重復(fù)代碼(Duplicated Code)
如果你在一個(gè)以上的地方看到相同的程序結(jié)構(gòu)执解,那么可以肯定:設(shè)法將它們合而為一寞肖,程序會(huì)變得更美好!你需要決定這個(gè)重復(fù)的代碼放在哪里比較合適衰腌,并確保它被安置之后就不會(huì)在別的地方再次出現(xiàn)新蟆。過(guò)長(zhǎng)函數(shù)(Long Method)
程序越長(zhǎng)越難以理解。現(xiàn)代OO語(yǔ)言幾乎完全免去了進(jìn)程內(nèi)的函數(shù)調(diào)用開銷右蕊,因此栅葡,你應(yīng)該積極地分解函數(shù)。我們應(yīng)該遵循原則:每當(dāng)需要以注釋來(lái)說(shuō)明點(diǎn)什么的時(shí)候尤泽,我們就需要把需要說(shuō)明的東西寫進(jìn)一個(gè)獨(dú)立函數(shù)中欣簇,并以其用途(而非實(shí)現(xiàn)手法)命名。如何確定需要提煉哪一段代碼:尋找注釋坯约、條件表達(dá)式熊咽、循環(huán)。過(guò)大的類(Large Class)
如果你發(fā)現(xiàn)一個(gè)類試圖做太多事情闹丐,其內(nèi)部就會(huì)出現(xiàn)很多不相關(guān)的實(shí)例變量横殴,此時(shí)這個(gè)類的職責(zé)就不明確了。過(guò)長(zhǎng)參數(shù)列(Long Parameter List)
太長(zhǎng)的參數(shù)隊(duì)列難以理解,太多參數(shù)的接口對(duì)于使用者來(lái)說(shuō)十分不友好衫仑,而且容易出錯(cuò)梨与。如果可以使用一個(gè)對(duì)象代替參數(shù)列表,那么就應(yīng)該這么做文狱。發(fā)散式變化(Divergent Change) VS 霰彈式修改(Shotgun Surgery)
一旦需要修改粥鞋,我們希望只在系統(tǒng)的一個(gè)地方進(jìn)行修改,否則瞄崇,就屬于兩種非常相似的壞味道的一種:如果某個(gè)類經(jīng)常因?yàn)椴煌脑蛟诓煌牡胤桨l(fā)生變化呻粹,那么Divergent Change就出現(xiàn)了;如果系統(tǒng)每遇到一個(gè)小變化苏研,就需要在多個(gè)不同的類內(nèi)進(jìn)行許多小修改等浊,這屬于Shotgun Surgery。Divergent Change是指“一個(gè)類受多種變化的影響”摹蘑,Shotgun Surgery則指的是“一種變化引發(fā)多個(gè)類的修改”筹燕。平行繼承體系(Parallel Inheritance Hierarchies)
這其實(shí)是Shotgun Surgery的特殊情況——每當(dāng)你為某個(gè)類添加一個(gè)子類,你也必須為它的兄弟類加一個(gè)子類衅鹿。如果你發(fā)現(xiàn)某個(gè)繼承體系的類名稱前綴和另一個(gè)繼承體系的類名稱前綴完全相同撒踪,就屬于這種情況。依戀情結(jié)(Feature Envy)
面向?qū)ο蟮木柙谟冢骸皩?shù)據(jù)和對(duì)數(shù)據(jù)的操作行為包裝在一起”塘安。有一種氣味是:函數(shù)對(duì)某個(gè)類的興趣高過(guò)自己所處的類的興趣糠涛。有很多時(shí)候援奢,我們看到一個(gè)函數(shù)為了計(jì)算某個(gè)值兼犯,從另一個(gè)對(duì)象那兒調(diào)用了幾乎半打的取值函數(shù)。最根本的原則是:將總是一起變化的東西放在一起集漾。數(shù)據(jù)泥團(tuán)(Data Clumps)
你常城星可以在很多地方看到相同的三四項(xiàng)數(shù)據(jù):兩個(gè)類中相同的字段、許多函數(shù)簽名中相同的參數(shù)具篇。這些綁在一起出現(xiàn)的數(shù)據(jù)真應(yīng)該擁有屬于它們自己的對(duì)象纬霞。減少字段和參數(shù)的個(gè)數(shù),當(dāng)然可以去除一些壞味道驱显,但更重要的是:一旦擁有新對(duì)象诗芜,你就有機(jī)會(huì)尋找Feature Envy,這可以幫你指出能夠移至新類中的種種方法埃疫。基本類型偏執(zhí)(Primitive Obsession)
對(duì)象技術(shù)的新手通常不愿意在小任務(wù)上運(yùn)用對(duì)象——例如結(jié)合數(shù)值和幣種的Money類伏恐、由一個(gè)起始值和一個(gè)結(jié)束值構(gòu)成的Range類、電話號(hào)碼或郵政號(hào)碼等的特殊字符串栓霜。switch語(yǔ)句(Switch Statements)
從本質(zhì)上講翠桦,switch語(yǔ)句的問題在于重復(fù),面向?qū)ο蟮亩鄳B(tài)特性可以優(yōu)雅地解決這個(gè)問題胳蛮。如果你只是在單一函數(shù)內(nèi)有些選擇事例销凑,那么用多態(tài)就屬于殺雞用牛刀了丛晌,這種情況下Replace Parameter with Explicit Methods是個(gè)不錯(cuò)的選擇,如果你的選擇之一是null斗幼,記得使用Introduce Null Object澎蛛。多余的類(Lazy Class)
你所創(chuàng)建的每一個(gè)類,都得有人去理解它孟岛、維護(hù)它瓶竭,這些工作都是要花錢的。如果一個(gè)類的所得不值得其身價(jià)渠羞,就應(yīng)該消除這個(gè)類斤贰。過(guò)度設(shè)計(jì)(Speculative Generality)
當(dāng)有人說(shuō)“噢,我想我們有一天需要做這個(gè)事情”次询,并因此而企圖以各種各樣的鉤子和特殊情況來(lái)處理一些非必要的事情荧恍,這種壞味道就出現(xiàn)了。軟件設(shè)計(jì)不可過(guò)度設(shè)計(jì)屯吊,否則會(huì)使得系統(tǒng)難以理解和維護(hù)送巡。令人迷惑的暫時(shí)字段(Temporary Field)
有時(shí)你會(huì)看到這樣的現(xiàn)象:類內(nèi)的某個(gè)實(shí)例變量?jī)H為某種特定情況而設(shè)。這樣的代碼不易理解盒卸,因?yàn)橥ǔUJ(rèn)為對(duì)象在所有時(shí)候都需要它的所有變量骗爆。過(guò)度耦合的消息鏈(Message Chains)
如果你看到用戶向一個(gè)對(duì)象請(qǐng)求另一個(gè)對(duì)象,然后再向后者請(qǐng)求另一個(gè)對(duì)象蔽介,然后再請(qǐng)求另一個(gè)對(duì)象……這就是消息鏈摘投。實(shí)際代碼中你看到的可能是一長(zhǎng)串getXXX()調(diào)用,意味著客戶代碼將與查找目標(biāo)對(duì)象過(guò)程中的導(dǎo)航結(jié)構(gòu)緊密耦合虹蓄,一旦對(duì)象間的關(guān)系發(fā)生任何變化犀呼,客戶端就會(huì)受到影響。中間人(Middle Man)
對(duì)象的基本特征之一是封裝——對(duì)外部世界隱藏其內(nèi)部細(xì)節(jié)薇组。封裝往往伴隨著委托外臂,比如你問主管是否有時(shí)間參加一個(gè)會(huì)議,他就把這個(gè)消息“委托”給他的記事簿律胀,然后才能回答你——你沒有必要這位主管到底是使用傳統(tǒng)記事簿或電子記事簿或秘書來(lái)記錄自己的約會(huì)宋光。但是,不要過(guò)度使用委托——你也許會(huì)看到某個(gè)類有一半接口都委托給其他類炭菌。狎昵關(guān)系(Inappropriate Intimacy)
類與類之間過(guò)分緊密的關(guān)系必須拆散——可以引入第三方類或者利用委托罪佳。異曲同工的類(Alternative Classes with Different Interfaces)
如果兩個(gè)函數(shù)做同一件事,卻有著不同的簽名娃兽,請(qǐng)運(yùn)用Rename Method根據(jù)它們的用途重新命名菇民。但這往往不夠,請(qǐng)反復(fù)運(yùn)用Move Method將某些行為移入類,知道這兩個(gè)函數(shù)的協(xié)議一致為止第练。如果你必須移動(dòng)大量代碼才可以完成這個(gè)工作阔馋,那還不如直接構(gòu)建一個(gè)父類。不完美的庫(kù)類(Incomplete Library Class)
復(fù)用常常被認(rèn)為是面向?qū)ο蠹夹g(shù)的終極目標(biāo)娇掏。很多第三方庫(kù)提供的接口經(jīng)常不能恰如其分得滿足我們的需求呕寝,這時(shí)候就需要對(duì)第三方接口做一層轉(zhuǎn)換,或者給它添加一定的行為婴梧。數(shù)據(jù)類(Data Class)
所謂Data Class下梢,指的是:這種類擁有一些字段,以及用于訪問(讀塞蹭、寫)的函數(shù)孽江,除此之外啥都沒有。這樣的類只是一種不會(huì)說(shuō)話的數(shù)據(jù)容器番电,它們一定被其他類過(guò)分細(xì)碎得控制著岗屏。
Data Class就像小孩子,作為一個(gè)起點(diǎn)很好漱办,但若要讓它們像成熟的對(duì)象那樣參與整個(gè)系統(tǒng)的工作这刷,它們就必須承擔(dān)一定責(zé)任。但是娩井,在Spring框架開發(fā)中暇屋,我們經(jīng)常需要定義很多domain對(duì)象。被拒絕的遺囑(Refused Request)
子類應(yīng)該繼承超類的函數(shù)和數(shù)據(jù)洞辣,但如果它們不想或者不需要繼承咐刨,又該怎么辦呢?按照傳統(tǒng)說(shuō)法屋彪,這就意味著繼承體系的設(shè)計(jì)錯(cuò)誤所宰。你需要為這個(gè)子類新建一個(gè)兄弟類绒尊,然后讓父類只包括兩個(gè)子類共享的部分畜挥。
一般而言,這就足夠了婴谱,但是如果子類不愿意支持超類提供的接口蟹但,則說(shuō)明不能使用繼承處理,應(yīng)該使用委托谭羔。過(guò)多的注釋(Comments)
常常會(huì)有這樣的情況:你看到一段代碼有著長(zhǎng)長(zhǎng)的注釋华糖,然后發(fā)現(xiàn),這些注釋之所以存在乃是因?yàn)榇a很糟糕瘟裸。當(dāng)你需要些注釋時(shí)客叉,要先嘗試重構(gòu)下代碼,爭(zhēng)取讓代碼擁有自說(shuō)明性。