重構-改善程式碼的既有設計-程式碼的壞味道(1)
3.1.重複程式碼(Duplicated Code)
Extract Method, Form Template Method,Substitute Algorithm,Extract Class.
同一個類的兩個函式含有相同的表示式,這時只需要從用Extract Method提煉出重複的程式碼,然後讓這兩個地點都呼叫被提煉出來的那一段程式碼。
兩個互為兄弟的子類內含有相同表示式。要避免這種情況,只需對兩個類都使用Extract Method,然後再對被提煉出的程式碼使用Pull Up Method,將它推入超類內。如果程式碼之間只是類似,並非完全相同,那麼就得使用Extract Method將相似的部分和差異部分割開,構成單獨一個函式。然後你可能發現可以運用Form Template Method獲得一個Template Method設計模式。如果有些函式以不同的演算法做相同的事,你可以選擇其中比較清晰的一個,並使用Substitute Algorithm將其他函式的演算法替換掉。
如果兩個毫不相關的類出現重複程式碼,應該考慮對其中一個使用Extract Class將程式碼提煉到一個獨立類中,然後在另一個類內使用這個新類。
3.2.過長函式(Long Method)Extract Method,Replace Temp with Query,Introduce Parameter Object,Preserver Whole Object.Replace Method with Method Object.
3.3.過大的類(Large Class)Extract Class(提煉至新類)。Extract Subclass(提煉至子類)。一個類如果有太多程式碼有個技巧:先確定客戶端如何使用它們,然後運用Extract Interface為每一種使用方式提煉出一個介面。這可以幫助你看清楚如何分解這個類。
3.4.過長引數列(Long Parameter List)Replace Parameter with Method。Preserver Whole Object(一堆資料來自同一物件),Introduce Parameter Object(製造一個“引數物件”)。
3.5.發散式變化(Divergent Change)一個類受多種變化的影響。如果某個類總因為不同的原因在不同的方向上發生變化,就會纏身Divergent Change。針對某一外界變化的所有響應修改,都只應該發生在單一類中,而這個類中所有內容都應該反映此變化,然後運用Extract Class將它們提煉至另一個類中。
3.6.霰彈式修改(Shotgun Surgery) 一個變化需要引發多個類響應修改。如果需要修改的程式碼散佈四處,你不但很難找到它們,也很容易忘記某個重要的修改。此時應使用Move Method,Move Field把所有需要修改的程式碼放進同一個類中。如果眼下沒有合適的類安置這些程式碼,就創造一個。可以運用Inline Class把一系列相關行為放進同一個類。這可能造成Divergent Change,但你可以輕易處理它。
3.7.依戀情節(Feature Envy) 對其他類的興趣高過自己所處類的興趣。這種孺慕之情最通常的焦點便是資料。此時應該使用Move Method把這個函式移至另一個地點。
有時候函式中只有一部分受這種依戀之苦,此時應使用 Extract Method將這一部分提煉到獨立函式中,再使用MoveMethod帶它去夢想家園.
一個函式用到幾個類的功能,那麼Extract Method的Method究竟該置於何處呢?原則是:判斷哪個類擁有最多被此函式使用的資料,然後就把這個函式和那些資料擺在一起。如果先以Extract Method將這個函式分解為數個較小函式並分別置放於不同地點,上述步驟也就比較容易完成了。
幾個複雜精巧的模式破壞了這個規則。Strategy和Visitor。用這些模式對抗Divergent Change。最根本的原則是:將總是一起變化的東西放在一塊。資料和引用這些資料的行為總是一起變化的,但也有例外。如果例外出現,我們就搬移哪些行為,保持變化只在一地發生。Stragegy和Visitor使你得以輕鬆修改函式行為,因為它們將少量被覆寫的行為隔離開來,當然也付出了多一層間接層的代價。
3.8.資料泥團(Data Clumps)
資料項像小孩子,喜歡成群結隊的待在一塊。可以在很多地方常常看到相同的三四項資料:兩個類中相同的欄位,許多函式中相同的引數,這些總綁在一起出現的資料真應該擁有屬於它們自己的物件。
首先找出這些資料項以欄位形式出現的地方,運用Extract Class將它們提煉到一個獨立物件中。
然後將注意力轉移到函式簽名上,運用Introduce Parameter Object或Preserve Whole Object為它減肥。這樣可以是引數列縮短,簡化函式呼叫。不必在意Data Clumps只用上新物件的一部分欄位,只要以新物件取代兩個(或更多)欄位,你就值回票價了。
一個好的評判方法是:刪掉眾多資料項中的一項。這麼做,其他資料有沒有因而失去意義?如果它們不再有意義,這就是個明確訊號:你應該為它們產生一個物件。
3.9.基本型別偏執(Primitive Obsession)
大多數程式設計環境的兩種資料:
結構型別允許你將資料組織成有意義的形式,
基本型別則是構成結構型別的積木
結構總是帶來額外的開銷。它們可能代表著資料庫中的表,如果只為做一兩件事而建立結構型別可能顯得太麻煩。
物件的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本資料和體積較大的類之間的界限。你可以輕鬆編寫出一些與語言內建(基本)型別無異的小型類。
3.10.switch驚悚現身(Switch Statements)
面向物件程式的一個最明顯特徵就是:少用switch(或case)語句。從本質上說,switch語句的問題在於重複。你常會發現同樣的switch語句散佈於不同地點。如果要為它新增一個新的case語句,要查詢所有並一一修改。面向物件的多型概念可以為此帶來更優雅的解決辦法。
大多數時候,一看到switch語句,你就應該考慮以多型來替換它。問題是多型該出現在哪??????
switch語句常常根據型別碼進行選擇,你要的是與該型別碼相關的函式和類,所以應該使用Extract Method將Switch語句提煉到一個獨立函式中,再以MoveMethod將它搬至需要多型性的那個類裡。此時你必須決定是否使用Replace Type Code with Subclasses或者Replace Type Code with State/Strategy。一旦完成這樣的繼承結構之後,就可以使用Replace Conditonal with Polymorphism.
如果只是在單一函式中有些選擇事例,且並不想改動它們,那麼多型就有點殺雞用牛刀了。這種情況下 Replace Parameter with Explicit Methods是個不錯的選擇.
如果你的選擇條件之一是nulll, 可以試試Introduce Null Object.