#
你常常可以在很多地方看到相同的三或四筆數(shù)據(jù)項(xiàng):兩個(gè)classes內(nèi)的相同值域(field)、許多函數(shù)簽名式(signature)中的相同參數(shù)。這
些[總是綁在一起出現(xiàn)的數(shù)據(jù)]真應(yīng)該放進(jìn)屬于它們自己的對象中。首先請找出這些數(shù)據(jù)的值域形式(field)出現(xiàn)點(diǎn),運(yùn)用Extract
Class(149)將它們提煉到一個(gè)獨(dú)立對象中。然后將注意力轉(zhuǎn)移到函數(shù)簽名式(signature)上頭,運(yùn)用Introduce Parameter Object(295)或Preserve Whole Object(288)為它減肥。這么做的直接好處是可以將很多參數(shù)列縮短,簡化函數(shù)調(diào)用動作。是的,不必因?yàn)?span style="font-weight: bold;">Data Clumps只用上新對象的一部分值或而在意,只要你以新對象取代兩個(gè)(或更多)值域,你就值回票價(jià)了。
一個(gè)好的評斷辦法是:刪掉眾多數(shù)據(jù)中的一筆。其他數(shù)據(jù)有沒有因而失去意義?如果它們不再有意義,這就是個(gè)明確信號:你應(yīng)該為它們產(chǎn)生一個(gè)新對象。
縮短值域個(gè)數(shù)和參數(shù)個(gè)數(shù),當(dāng)然可以去除一些壞味道,但更重要的是:一旦擁有新對象,你就有機(jī)會讓程序散發(fā)出一種芳香。得到新對象后,你就可以著手尋找Feature Envy,這可以幫你指出[可移到新class]中的種種程序行為。不必太久,所有classes都將在它們的小小社會中充分發(fā)揮自己的生產(chǎn)力。
無數(shù)次經(jīng)驗(yàn)里,我們看到某個(gè)函數(shù)為了計(jì)算某值,從另一個(gè)對象那兒調(diào)用幾乎半打的取值函數(shù)(getting method)。療法顯而易見:把這個(gè)函數(shù)移至另一個(gè)地點(diǎn)。你應(yīng)該使用Move Method(142)把它移到它該去的地方。有時(shí)侯函數(shù)中只有一部分受這種依戀之苦,這時(shí)候你應(yīng)該使用Extract Method(110)把這一部分提煉到獨(dú)立函數(shù)中,再使用Move Method(142)帶它去它的夢中家園。
當(dāng)然,并非所有情況都這么簡單。一個(gè)函數(shù)往往會用上數(shù)個(gè)classes特性,那么它究竟該被置于何處呢?我們的原則是:判斷哪個(gè)class擁有最多[被此函數(shù)使用]的數(shù)據(jù),然后就把這個(gè)函數(shù)和那些數(shù)據(jù)擺在一起。如果先以Extract Method(110)將這個(gè)函數(shù)分解為數(shù)個(gè)較小函數(shù)并分別置放于不同地點(diǎn),上述步驟也就比較容易完成了。
有數(shù)個(gè)復(fù)雜精巧的模式(patterns)破壞了這個(gè)規(guī)則。說起這個(gè)話題,[四巨頭][Gang of Four]的Strategy和Visitor立刻跳入我的腦海,Kent Beck的Self Delegation[Beck]也在此列。使用這些模式是為了對抗壞味道Divergent Change。最根本的原則是:將總是一起變化的東西放在一塊兒。[數(shù)據(jù)]和[引用這些數(shù)據(jù)]的行為總是一起變化的,但也有例外。如果例外出現(xiàn),我們就搬移那些行為,保持[變化只在一地發(fā)生]。Strategy和Visitor使你得以輕松修改函數(shù)行為,因?yàn)樗鼈儗⑸倭啃璞桓矊懀╫verridden)的行為隔離開來-當(dāng)然也付出了[多一層間接性]的代價(jià)。
Shotgun Surgery和Divergent Change,但恰恰相反。如果每遇到某種變化,你都必須在許多不同的classes內(nèi)做出許多小修改以響應(yīng)之,你所面臨的壞味道就是Shotgun Surgery。如果需要修改的代碼散布四處,你不但很難找到它們,也很容易忘記某個(gè)重要的修改。
這種情況下你應(yīng)該使用Move Method(142)和Move Field(146)把所有需要修改的代碼放進(jìn)同一個(gè)class。如果眼下沒有合適的class可以安置這些代碼,就創(chuàng)造一個(gè)。通常你可以運(yùn)用Inline Class(154)把一系列相關(guān)行為放進(jìn)同一個(gè)class。這可能會造成少量Divergent Change,但你可以輕易處理它。
Divergent Change是指[一個(gè)class受多種變化的影響],Shotgun Surgery則是指[一種變化引發(fā)多個(gè)class相應(yīng)修改]。這兩種情況下你都會希望整理代碼,取得[外界變化]與[待改類]呈現(xiàn)一對一關(guān)系的理想境地。
如果某個(gè)class經(jīng)常因?yàn)椴煌姆较蛏习l(fā)生變化,Divergent
Change就出現(xiàn)了。當(dāng)你看著一個(gè)class說[呃,如果新加入一個(gè)數(shù)據(jù)庫,我必須修改這三個(gè)函數(shù);如果新出現(xiàn)一種金融工具,我必須修改這四個(gè)函數(shù)],
那么此時(shí)也許將這個(gè)對象分成兩個(gè)會更好,這么一來每個(gè)對象就可以只因一種變化而需要修改。當(dāng)然,往往只有在加入新數(shù)據(jù)庫或新金融工具后,你才能發(fā)現(xiàn)這一
點(diǎn)。針對某一外界變化的所有相應(yīng)修改,都只應(yīng)該發(fā)生在單一class中,而這個(gè)新class內(nèi)的所有內(nèi)容都應(yīng)該外界變化。為此,你應(yīng)該找出因著某特定原因
而造成的所有變化,然后運(yùn)用Extrace Class(149)將它們提煉到另一個(gè)class中。
如果某個(gè)class經(jīng)常因?yàn)椴煌姆较蛏习l(fā)生變化,Divergent Change就出現(xiàn)了。
如果[向既有對象發(fā)出一條請求]就可以取得原本位于參數(shù)列上的一份數(shù)據(jù),那么你應(yīng)該激活重構(gòu)準(zhǔn)則Peplace Parameter with Method(292)。上述的既有對象可能是函數(shù)所屬class內(nèi)的一個(gè)值域(field),也可能是另一個(gè)參數(shù)。你還可以運(yùn)用Preserve Whole Object(288)將來自同一對象的一堆數(shù)據(jù)收集起來,并以該對象替換它們。如果某些數(shù)據(jù)缺乏合理的對象歸屬,可使用Introduce Parameter Object(295)為它們制造出一個(gè)[參數(shù)對象]。
此間存在一個(gè)重要的例外。有時(shí)侯你明顯不希望造成[被調(diào)用對象]與[較大對象]間的某種依存關(guān)系。這時(shí)候?qū)?shù)據(jù)從對象中拆解出來單獨(dú)作為參數(shù),也很合情合
理。但是請注意其所引發(fā)的代價(jià)。如果參數(shù)列太長或變化太頻繁,你就需要重新考慮自己的依存結(jié)構(gòu)(dependency structure)了。
如果想利用單一class做太多事情,其內(nèi)往往就會出現(xiàn)太多instance變量。一旦如此,Duplicated Code也就是接踵而至了。
你可以運(yùn)用Extract Class(149)將數(shù)個(gè)變量一起提煉
至新class內(nèi)。提煉時(shí)應(yīng)該選擇class內(nèi)彼此相關(guān)的變量,將它們放在一起。例如“depositAmount”和
“depositCurrency”可能應(yīng)該隸屬同一個(gè)class。通常如果class內(nèi)的數(shù)個(gè)變量有著相同的前綴或字尾,這就意味有機(jī)會把它們提煉到某
個(gè)組件內(nèi)。如果這個(gè)組件適合作為一個(gè)subclass,你會發(fā)現(xiàn)Extract Subclass(330)往往比較簡單。
有時(shí)候class并非在所有時(shí)刻都使用所有instance變量。果真如此,你或許可以多次使用Extract Class(149)或Extract Subclass(330)。
和[太多instance變量]一樣,class內(nèi)如果有太多代碼,也是[代碼重復(fù)、混亂、死亡]的絕佳滋生地點(diǎn)。最簡單的解決方案(還記得嗎,我們喜歡
簡單的解決)是把贅余的東西消弭于class內(nèi)部。如果有五個(gè)[百行函數(shù)],它們之中很多代碼都相同,那么或許你可以把它們變成五個(gè)[十行函數(shù)]和十個(gè)提
煉出來的[雙行函數(shù)]。
和[擁有太多instance變量]一樣,一個(gè)class如果擁有太多代碼,往往也適合使用Extract Class(149)和Extract Subclass(330)。這里有個(gè)有用技巧:先確定客戶端如何使用它們,然后運(yùn)用Extract Interface(341)為每一種使用方法提煉出一個(gè)接口。這或許可以幫助你看清楚如何分解這個(gè)class。
如果你的Large Class是個(gè)GUI class,你可能需要把數(shù)據(jù)和行為移到一個(gè)獨(dú)立的領(lǐng)域?qū)ο螅╠omain object)去。你可能需要兩邊各保留一些重復(fù)數(shù)據(jù),并令這些數(shù)據(jù)同步(sync.)。Duplicated Observed Data(189)告訴你該怎么做。這種情況下,特別是如果你使用舊式Abstract Windows Toolkit(AWT)組件,你可以采用這種方式去掉GUI class并代以Swing組件。
最終的效果是:你應(yīng)該更積極進(jìn)取地分解函數(shù)。我們遵循這樣一條原則:每當(dāng)感覺需要以注釋來說明點(diǎn)什么的時(shí)候,我們就把需要說明的東西寫進(jìn)一個(gè)獨(dú)立函數(shù)中,
并以其用途(而非實(shí)現(xiàn)手法)命名。我們可以對一組或甚至短短一行代碼做這件事。哪怕替換后的函數(shù)調(diào)用動作比函數(shù)自身還長,只要函數(shù)名稱能夠解釋其用途,我
們也該毫不猶豫地那么做。關(guān)鍵不在于函數(shù)長度,而在于函數(shù)[做什么]和[如何做]之間的語義距離。
百分之九十九的場合里,要把函數(shù)變小,只需使用Extract Method(110)。找到函數(shù)中適合集在一起的部分,將它們提煉出來形成一個(gè)新函數(shù)。
如果函數(shù)內(nèi)有大量的參數(shù)和臨時(shí)變量,它們會對你的函數(shù)提煉形成阻礙。如果你嘗試運(yùn)用Extract Method(110),最終就會把許多這些參數(shù)和臨時(shí)變量當(dāng)作參數(shù),傳遞給被提煉出來的新函數(shù),導(dǎo)致可讀性幾乎沒有任何提升。啊是的,你可以經(jīng)常運(yùn)用Replace Temp with Query(120)來消除這些暫時(shí)元素。Introduce Parameter Object(295)和Preserve Whole Object(288)則可以將過長的參數(shù)列變得更簡潔一些。
如果你已經(jīng)這么做了,仍然有太多臨時(shí)變量和參數(shù),那就應(yīng)該使出我們的殺手锏:Replace Method with Method Object(135)。
如何確定該提煉哪一段代碼呢?一個(gè)很好的技巧是:尋找注解。它們通常是指出[代碼用途和實(shí)現(xiàn)手法間的語義距離]的信號。如果代碼前方有一行注解,就是在提
醒你:可以將這段代碼替換成一個(gè)函數(shù),而且可以在注解的基礎(chǔ)上給這個(gè)函數(shù)命名。就算只有一行代碼,如果它需要以注解來說明,那也值得將它提煉到獨(dú)立函數(shù)
去。
條件式和循環(huán)常常也是提煉的信號。你可以使用Decompose Conditional(238)處理?xiàng)l件式。至于循環(huán),你應(yīng)該將循環(huán)和其內(nèi)的代碼提煉到一個(gè)獨(dú)立函數(shù)中。
在$HOME/.fcitx下修改config文件的:
LumaQQ支持=1
上面改為1,重啟X就好了。
最單純的Duplicated Code就是[同一個(gè)class內(nèi)的兩個(gè)函數(shù)含有相同表達(dá)式(express)]。這時(shí)候你需要做的就是采用Extract Method(110)提煉出重復(fù)的代碼,然后讓這兩個(gè)地點(diǎn)都調(diào)用被提煉出來的那一段代碼。
另一種常見情況就是[兩個(gè)互為兄弟(sibling)的subclass內(nèi)含相同表達(dá)式]。要避免這種情況,只需對兩個(gè)classes都使用Extract Method(110),然后再對被提煉出來的代碼使用Pull Up Method(332),將它推入superclass內(nèi)。如果代碼之間是類似,并非完全相同,那么就得運(yùn)用Extract Method(110)將相似部分和差異部分割開,構(gòu)成單獨(dú)一個(gè)函數(shù)。然后你可能發(fā)現(xiàn)或許可以運(yùn)用Form Template Method(345)獲得一個(gè)Template Method設(shè)計(jì)模式。如果有些函數(shù)以不同的算法做相同的事,你可以擇定其中較清晰的一個(gè),并使用Substitute Algorithm(139)將其他函數(shù)的算法替換掉。
如果兩個(gè)毫不相關(guān)的classes內(nèi)出現(xiàn)Duplicated Code,你應(yīng)該考慮對其中一個(gè)使用Extract Class(149),
將重復(fù)代碼提煉到一個(gè)獨(dú)立class中,然后在另一個(gè)class內(nèi)使用這個(gè)新class。但是,重復(fù)代碼所在的函數(shù)也可能的確只應(yīng)該屬于某個(gè)class,
另一個(gè)class只能調(diào)用它,抑或這個(gè)函數(shù)可能屬于第三個(gè)class,而另兩個(gè)classes應(yīng)該引用這第三個(gè)class。你必須決定這個(gè)函數(shù)放在哪個(gè)最
合適,并確保它被安置后就不會再在其他任何地方出現(xiàn)。
|