前兩篇文章 《識別代碼中的壞味道(一)》 和 《識別代碼中的壞味道(二)》 中已經介紹了 18 個代碼壞味道。《重構》中還涉及到另外 4 個代碼壞味道,本文將將詳細介紹剩余的 4 個代碼壞味道。
這四個代碼壞味道是:
- 中間人(Middle Man)
- 狎昵關系
- 不完美的庫類
- 被拒絕的遺贈
01 中間人(Middle Man)
在上一篇文章中 《識別代碼中的壞味道(二)》 中在“過度耦合的消息鏈”這種代碼壞味道曾經提及過中間人(Middle Man)這種代碼壞味道,那么中間人到底是一類什么代碼呢?
中間人指的是一種過度使用委托的代碼,《重構》中給了一個參考值,如果一個類中有一半的方法都委托給其他對象進行,
為什么中間人是一種代碼壞味道?
過度使用委托。這意味著當需求發生某些的變化的時候,這個中間人的類總是被牽連進來一并修改。這種中間人代碼越多,浪費掉的時間也就越多。
如何解決中間人這種代碼壞味道?
中間人的代碼在于過度使用和委托兩點。因此解決中間人這種代碼壞味道就應該從減少委托下手:
刪除中間人的方法,可以使用 Remove Middle Man(移除中間人)這種重構技巧。
當然如果原有代碼的代理類中并不怎么變化,也可以選擇延遲重構,依照“事不過三,三則重構”的原則可以選擇當發生變化的時候進行重構。
02 狎昵關系(Inappropriate Intimacy)
指的是類之間花費太多的時間去探究彼此的私有的屬性或者方法。
造成狎昵關系的原因可能是:
兩個類本來就不應該拆分開;
兩個類之間存在雙向關聯;
-
因為繼承導致了狎昵關系;
...
為什么狎昵關系是一種代碼壞味道?
- 狎昵關系會導致強耦合的表現;
- 而且類和類之間的職責將會變得模糊;
- 會因為訪問對方的私有信息而導致過多的操作出現,或者產生封裝上的妥協,讓兩個類糾纏不清。
如何解決狎妮關系這種代碼壞味道?
- 通過 Move Field (搬移屬性),Move Method(搬移方法)來移動屬性和方法的位置,讓屬性和方法移動到它們本應該出現的位置。
- 如果直接移動屬性和方法并不合適,可以嘗試使用 Extract Class(提煉類)看是否能夠找到公共類。
- 如果是因為相互調用導致的問題,可以嘗試 Change Bidirectional Association to Unidirectional(將雙向關聯改為單向關聯)嘗試將關聯關系劃清。
- 如果是因為繼承導致狎昵關系,可以嘗試移除繼承關系,改用代理類來實現。
03 不完美的庫類
當直接使用第三方庫的時候,導致代碼可讀性變差、意圖不明確的問題。
為什么不完美的庫類是一種代碼壞味道?
第三方類庫提供的功能能夠在很場景下被復用。但是放在業務場景下,卻總是要從業務視角切換到單純的技術視角來來使用某些第三方類庫。
例如
Date newStart = new Date(
previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate() + 1);
一眼看上去這是在表達什么意思其實并不容易看到。不完美的類庫就在于造成代碼中語意化變差。
如何解決不完美庫類這種代碼壞味道?
很多開發者會采用注釋的方式期望讓代碼可讀,但是這類注釋本身也是一種代碼的壞味道。不過可以借助函數名來揭示意圖。
所以遇到上面例子的情況,可以使用 Extract Method 來提煉一個函數,生成如下代碼
Date newStart = nextDay(previousEnd);
...
private Date nextDay(Date previousEnd) {
return new Date(
previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate() + 1);
}
這樣調用 nextDay() 的地方,就可以輕松的知道獲取到 previousEnd 日期的下一天日期。
如果一個類中存在多種這種調用,或者多個類中都有類似的函數的時候,提煉一個單獨一個類,并通過這個類對外提供這些方法無疑是一種消除重復提高復用的辦法。實現這個類的方式可以使用代理的方式,也可以使用繼承的方式。如果一個類只是提供代理方法,具體實現都要委托給類庫,這樣情況下,不如使用繼承來生成的子類,并在子類中添加那些可以復用的方法。重構的過程可以參考 Introduce Local Extension(引入本地擴展)。
很顯然第三方類庫被設計的出發點往往是好的,但是實際調用的時候除了享受這種快速實現的方式,還需要關注第三方類庫給當前項目帶來的一些壞味道,并著手解決這些問題。
04 拒絕的遺贈
這個壞味道指的是當子類繼承基類的時候,父類的一些方法即使子類并不需要也被迫被繼承的情況。出現這種壞味道的一般有兩種原因:
- 繼承體系設計的不好,還需要調整;
- 基類實現了某個接口,導致子類不需要的時候也會實現那個接口對應的方法。
詳細的例子可以參考:《重構分析21: 被拒絕的遺贈(Refused Bequest)》
為什么拒絕的遺贈是一種代碼壞味道?
這個壞味道主要原因就是繼承帶來的壞味道,子類被迫實現某些方法或者從父類繼承的方法對自身不但沒有幫助甚至造成誤導,比如:代碼中通過繼承實現 正方形 繼承 長方形并求面積的例子,感興趣可以參考《敏捷軟件開發原則、模式、實踐》中的里氏替換原則。
如何解決拒絕的遺贈的這種代碼壞味道?
有兩種思路:
改善繼承體系。剔除子類不需要的方法,并創建子類的兄弟,通過 Move Method 將不需要的方法移動到兄弟類中,通過 Move Field 將涉及到非公共屬性也移動到兄弟子類中。
使用代理來取代繼承。這種方式的修改只涉及到對子類的調整,影響范圍較小,并且也不會因此而像第一種重構方法那樣因為要維護繼承體系而導致一些新概念的產生。同時還能避免因為基類繼承了某個接口,而導致的子類被迫實現某些方法的情況。
總結
至此 22 種常見的代碼壞味道已經介紹完成。關注工具、框架的同時花一部分精力關注代碼質量,能夠讓項目隨著時間不斷演進。當然實際工作中遇到的壞味道往往比這 22 種還要多。
項目中我們可以使用 Check Style、PMD、Arch Unit 幫助我們及時發現項目中的問題,但是更”軟“的部分需要我們花精力來理解清楚是什么、為什么、怎么解決。
或許你也已經發現了,很多情況下壞味道的原因在于變化時,無法快速應對變化,有的是代碼設計的問題,有的是可讀性的問題。即使代碼壞味道也分為強烈的壞味道和淡淡的壞味道,所以重構的原則也是”事不過三、三則重構“,因此面對代碼壞味道的時候如果代碼壞味道很淡我們可以延遲消除壞味道。如果壞味道已經很強烈,或者淡淡的壞味道因為頻繁的變化而導致效率下降時,那就不如先解決這種壞味道。
擴展
重構不是發生在項目結束的時候,而是融入在天天工作中進行的。采用TDD(測試驅動開發的方式)是一種很好的選擇,熟悉TDD以及測試工具的情況下,TDD 不但不會降低速度反而讓思路更加嚴謹、實現的代碼實現質量更高。感興趣的話可以參考:
參考
《重構》