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