1. 程式人生 > 實用技巧 >第3章 程式碼的壞味道

第3章 程式碼的壞味道

重構的判斷基礎:

能知道什麼時候需要重構,什麼地方需要重構,是需要一定的判斷力,下面列出的22條“壞味道”條款,可能會成為判斷的基礎。

一、重複程式碼

如果在一個以上地方看到相同的程式結構,那麼可以肯定,將它們合二為一,程式會更好。

1.能舉一個簡單的例子說明什麼是重複程式碼麼?

答:一個最常見的情況,就是在同一個類裡,有兩個方法中都有相同的表示式,那麼這個表示式就可以被抽出來,封裝起來供那兩個方法呼叫。

2.能舉一個你自己的例子麼?

答:在之前設計模式讀書筆記的設計原則總結中我有提到過,在以前的專案中,我設計一個複雜型別資料的更新和插入,都是寫在用了同一個方法裡,不管更新還是插入都呼叫這個方法,但是導致了耦合度過高,違反了單一職責原則,不利於後續的擴充套件,於是當時只是想著這樣處理是錯誤的,應該把他們拆開,雖然會導致很多重複程式碼,但是拆開是有必要的。現在在瞭解了重構之後,我覺得雖然有一些細節上是不同的,但是拆開插入和更新方法的同時,他們資料的構建大部分是一致的,所以可以把資料構建的方法提出來,這樣既可以滿足單一職責原則,又可以避免重複程式碼。

考慮到更細和插入的資料大部分相同,就將組裝相同的資料提到方法dataBuild()裡面,然後些許不同的地方就分別構建,新增資料的構造就放在addDataBuild()裡,更新資料的構造就放在updateDataBuild()裡,然後在新增資料就直接呼叫方法add(),更新資料就直接呼叫update()

新增資料 →  add()   →  addDataBuild()   → dataBuild()
更新資料 → update() → updateDataBuild() → dataBuild()

二、方法過長

擁有短函式的物件會活的比較久,比較長。

1.怎麼判斷是不是方法過長,有什麼原則麼?

答:你應該積極分解函式,我們遵循這樣一條原則:每當感覺需要用註釋來說明點什麼的時候,我們就需要把說明的東西寫進單獨的函式裡,並以其用途(而非實現手法)命名。

且原文中寫道(目前還不是能特別理解這句,會不會太絕對了,這樣不會產生很多細碎的方法呼叫麼?):我們可以對一組甚至短短一行程式碼做這件事,哪怕替換後的函式呼叫動作比函式自身還長,只要函式名稱能夠解釋其用途,我們也該毫不猶豫地那麼做。關鍵不在於函式的長度,而在於函式“做什麼”和“如何做”之間的語義距離。

2.如何確定該提煉哪一段程式碼呢?

答:一個很好地技巧,即尋找註釋

。它們通常能指出程式碼用途和實現手法之間的語義距離,用註釋不如提出來封裝到函式中,用函式命名說明。另外條件表示式迴圈也是提煉的訊號。

三、過大的類

類中如果有太多的程式碼,也是程式碼重複,混亂,並最終走向死亡的源頭。

1.當類過大時,怎麼提取程式碼?

答:有個技巧,即先確定客戶端如何使用它們,然後為每一種使用方式提煉出一個介面,這或許可以幫你看清楚如何分解這個類。

四、過長的引數列

如果傳入一個方法的引數過多過長,請傳入一個物件代替。

1.那入參我都封裝成一個物件可以麼,有例外麼?

答:一兩個引數還是沒有必要,或者有時候你明顯不希望造成“被呼叫物件”與“較大物件”間的某種依賴關係,這時將資料從物件中單獨拆解出來作為引數也是合情合理的。

五、發散式變化

1.什麼叫發散式變化?

答:發散式變化是指:某個類經常因不同的原因在不同的方向上發生變化。出現這種情況時,那麼就應該考慮重構了。

2.能舉個例子麼?

答:比如新的需求是加入一個數據庫,然後這個類有三個方法需要修改,然後另一個新需求是加入一個監控工具,這時這個類有其他四個方法需要修改,這時就應該考慮將這些方法分開,將這個類拆成兩個類。

六、散彈式修改

1.那什麼叫散彈式修改?

答:每次遇到某種變化,你都需要在許多不同的類做出許多小的修改,這就是散彈式修改。

2.遇到這種情況怎麼重構?

答:將所有需要修改的程式碼放進一個類,如果目前沒有合適的類來存放,那麼就新建一個類。將這些方法提到一個類之後,可能又會導致些許的發散式修改,但你可以輕易修改它。

七、依戀情節

物件技術的全部要點在於:這是一種“將資料和對資料的操作行為包裝在一起”的技術。

1.依戀?

答:也就是說,一個類中的方法,對另一個類的興趣高於對自己類的興趣,這種興趣的程度怎麼判斷呢,一般是通過資料來體現的。

2.舉個例子

答:Class A中的方法calculateAmount(),這個方法在進行計算總額是,用了許多Class B中的取值方法,顯而易見,這個方法應該移動到Class B中。

3.一個方法用到幾個類中取值呼叫,那又怎麼移動呢?

答:可以看哪個類擁有最多被這個方法使用的資料,就移動到哪個類中。

八、資料泥團

1.取名小天才請告訴我什麼又叫資料泥團?

答:舉個例子來理解,比如有兩個類中有著相同的欄位,定義著一些相同的引數,那麼這些資料應該擁有自己的類,或者說擁有自己的物件。

2.如果抽象出了一個物件有3個欄位,A類有2個相同欄位,B類有2個相同欄位,C類只有1個相同欄位,那麼這個物件還值得單獨提出麼?

答:只要用一個物件取代大於等於兩個物件,就是賺了。

九、基本型別偏執

可以將一些基本資料型別替換為物件,將數值和幣種結合成money類,初始值和結束值結合成range類等,走進物件的世界。

十、switch驚悚現身

1.為什麼要把switch statement翻譯成“switch驚悚現身”?

答:夢裡不知身是客,一晌貪歡。

2.為什麼少用switch?

答:switch的問題在於重複,每次新加一個switch中case的子類,就要找到相應的switch語句,找到相應點並新增,如果有很多switch語句,就要全部找到的並依次新增。

3.怎麼減少?

答:使用多型,但有時候使用多型又會顯得殺雞用牛刀,這時可以以明確的函式代替引數。

十一、平行繼承體系

1.什麼是平行繼承體系?

答:當增加一個類的子類時,還必須為另一個類增加一個子類,且這兩個繼承體系的類名稱字首是相同的時。

2.舉個例子

答:比如小誠喜歡打王者榮耀,他在買了蘋果手機後,安裝了遊戲,這時特朗普突然禁止和微信的交易,那不是不能買面板了?小誠又只好再買了一個手機,手機是華為手機,那麼華為手機就算是手機的子類,在華為手機繼承手機的時候,還需要新增一個王者榮耀遊戲子類繼承遊戲類。

3.怎麼消除這種壞味道?

答:讓一個繼承體系的例項,引用另一個繼承體系的例項,如:

參考橋接模式:https://www.cnblogs.com/wencheng9012/p/13445419.html

十二、冗贅類

無用即刪

十三、誇誇其談未來性

1.啥意思?

答:即:“哦,我想我們總有一天會做這件事”的這種程式碼

2.舉個例子

答:比如多餘的引數,本來只需要構造兩個引數,時間和地點,但你卻想到了以後可能會用到的引數,然後也一同加上了:人物,行為等,這些引數對目前沒用。

3.這樣有什麼壞處?

答:使程式碼難以理解和維護,也不值得。

十四、臨時欄位

1.什麼叫臨時欄位?

答:某個例項變數僅為程式碼中一小部分功能臨時所用而建立。

2.有什麼壞味道?

答:可讀性差。

3.解決方法?

答:將所有臨時欄位放在一起。

十五、過度耦合的訊息鏈

1.什麼是訊息鏈?

答:使用者向一個物件請求另一個物件,另一個物件又請求下一個物件,然後再請求另一個物件...這就是訊息鏈。

2.壞味道?

答:可能一旦物件間的關係出現任何變化,客戶端就不得不做出相應修改,耦合度過高,不利於修改和維護。

3.怎麼解決?

答:通常使用隱藏委託關係,在重構前先觀察訊息鏈最終得到的物件是用來幹什麼的,看看能否用抽象方法,把該物件的方法提煉到一個函式中,再用搬運函式把函式推入訊息鏈中。

4.什麼是隱藏委託關係?

即:萬一委託關係發生變化,客戶也得相應變化,如圖中圖中需要得到經理時:Client.getDepartment.getManager()。你可以在server端(如這裡的:Person)放置一個簡單的委託函式(delegating method),將委託關係隱藏起來,從而去除這種依存性。這麼一來即便將來發生關係上的變化,變化將被限制在server中,不會涉及客戶。

十六、中間人

1.概念

答:物件的基本特徵之一就是封裝,封裝往往伴隨著委託,但有時候會過度運用委託,當你看到某個類的一個介面一大半的方法都委託給了其他類,或者每當客戶要使用受託類的新特性時,你就必須在服務端新增一個簡單委託函式。隨著委託類的特性(功能)越來越多,這一過程讓你痛苦不已。服務類完全變成了“中間人”,這就是過度運用,此時你就應該讓客戶直接呼叫受託類。

2.重構

答:讓客戶端直接呼叫受託類。

十七、狎暱關係

兩個類如果過於親密,花太多時間去探究各自private成分,就如繼承往往會導致過度親密,則希望讓這個子類離開繼承體系,把兩個類分開,劃清界限。

十八、異曲同工的類

兩個功能相同的類,卻有著不同的簽名,要麼根據真正用途重新命名, 要麼去二存一,融為一體。

十九、不完美的庫類

封裝好的類庫中功能不能滿足實際需求,通過引入外加函式或者引入本地擴充套件解決。

二十、純稚的資料類

一個物件應該包括它的屬性及對其的操作方法,對特定資料的操作應集中在一個地方,而不是隨意存放。把相應的方法搬移到對應的類中後,對類中對應的在外部沒有被使用過的get/set方法設定成私有方法。

二十一、被拒絕的遺贈

1.概念

答:繼承某個類的子類,不需要父類的某些方法,或屬性,或實現父類的實現的介面。

2.解決

答:看收益,消除不必要的繼承,組合優於繼承,或使用代理取代繼承。

二十二、過多的註釋

一是有時候不是需要註釋,而是需要修改程式碼,程式碼可讀性提高了,那麼註釋也就不需要了,二是有些陳舊的註釋如果沒有隨著程式碼的改變而改變,又會影響可讀性。

1.建議:

答:當你感覺要寫註釋時,請先嚐試重構,試著讓所有註釋變得多餘。

疑問點小結:

1.原文如下,我感覺有點太絕對了,這樣會產生很多細碎的方法呼叫,或者可能在我現在的認知裡,有很多需要加的註釋是沒有必要的,那怎麼把握這個度也是我要學習和進步的一個點。

“我們可以對一組甚至短短一行程式碼做這件事,哪怕替換後的函式呼叫動作比函式自身還長,只要函式名稱能夠解釋其用途,我們也該毫不猶豫地那麼做。關鍵不在於函式的長度,而在於函式“做什麼”和“如何做”之間的語義距離。”

進度:未解決。

2.在解決散彈式修改時提到,如果幾次變化都是同一個型別,且這個變化都要引起部分類的修改,那麼就應該把這些方法單獨提到一個類中,但是可能又會造成發散式變化的壞味道,然後作者說:“會造成少量的發散式變化,但你可以輕易修改它。”這個輕易修改是怎麼修改,先提取到一個類再進行拆分麼?

進度:未解決。