前兩篇文章 《識(shí)別代碼中的壞味道(一)》 和 《識(shí)別代碼中的壞味道(二)》 中已經(jīng)介紹了 18 個(gè)代碼壞味道逢净「绶牛《重構(gòu)》中還涉及到另外 4 個(gè)代碼壞味道,本文將將詳細(xì)介紹剩余的 4 個(gè)代碼壞味道爹土。
這四個(gè)代碼壞味道是:
- 中間人(Middle Man)
- 狎昵關(guān)系
- 不完美的庫(kù)類
- 被拒絕的遺贈(zèng)
01 中間人(Middle Man)
在上一篇文章中 《識(shí)別代碼中的壞味道(二)》 中在“過(guò)度耦合的消息鏈”這種代碼壞味道曾經(jīng)提及過(guò)中間人(Middle Man)這種代碼壞味道,那么中間人到底是一類什么代碼呢社露?
中間人指的是一種過(guò)度使用委托的代碼峭弟,《重構(gòu)》中給了一個(gè)參考值脱拼,如果一個(gè)類中有一半的方法都委托給其他對(duì)象進(jìn)行挪拟,
為什么中間人是一種代碼壞味道玉组?
過(guò)度使用委托惯雳。這意味著當(dāng)需求發(fā)生某些的變化的時(shí)候鸿摇,這個(gè)中間人的類總是被牽連進(jìn)來(lái)一并修改。這種中間人代碼越多揪荣,浪費(fèi)掉的時(shí)間也就越多往史。
如何解決中間人這種代碼壞味道椎例?
中間人的代碼在于過(guò)度使用和委托兩點(diǎn)。因此解決中間人這種代碼壞味道就應(yīng)該從減少委托下手:
刪除中間人的方法订歪,可以使用 Remove Middle Man(移除中間人)這種重構(gòu)技巧刷晋。
當(dāng)然如果原有代碼的代理類中并不怎么變化,也可以選擇延遲重構(gòu)或舞,依照“事不過(guò)三蒙幻,三則重構(gòu)”的原則可以選擇當(dāng)發(fā)生變化的時(shí)候進(jìn)行重構(gòu)邮破。
02 狎昵關(guān)系(Inappropriate Intimacy)
指的是類之間花費(fèi)太多的時(shí)間去探究彼此的私有的屬性或者方法。
造成狎昵關(guān)系的原因可能是:
兩個(gè)類本來(lái)就不應(yīng)該拆分開矫渔;
兩個(gè)類之間存在雙向關(guān)聯(lián)摧莽;
-
因?yàn)槔^承導(dǎo)致了狎昵關(guān)系镊辕;
...
為什么狎昵關(guān)系是一種代碼壞味道?
- 狎昵關(guān)系會(huì)導(dǎo)致強(qiáng)耦合的表現(xiàn)石咬;
- 而且類和類之間的職責(zé)將會(huì)變得模糊卖哎;
- 會(huì)因?yàn)樵L問(wèn)對(duì)方的私有信息而導(dǎo)致過(guò)多的操作出現(xiàn),或者產(chǎn)生封裝上的妥協(xié)焕窝,讓兩個(gè)類糾纏不清袜啃。
如何解決狎妮關(guān)系這種代碼壞味道?
- 通過(guò) Move Field (搬移屬性)晰韵,Move Method(搬移方法)來(lái)移動(dòng)屬性和方法的位置雪猪,讓屬性和方法移動(dòng)到它們本應(yīng)該出現(xiàn)的位置起愈。
- 如果直接移動(dòng)屬性和方法并不合適抬虽,可以嘗試使用 Extract Class(提煉類)看是否能夠找到公共類。
- 如果是因?yàn)橄嗷フ{(diào)用導(dǎo)致的問(wèn)題休涤,可以嘗試 Change Bidirectional Association to Unidirectional(將雙向關(guān)聯(lián)改為單向關(guān)聯(lián))嘗試將關(guān)聯(lián)關(guān)系劃清功氨。
- 如果是因?yàn)槔^承導(dǎo)致狎昵關(guān)系手幢,可以嘗試移除繼承關(guān)系,改用代理類來(lái)實(shí)現(xiàn)跺涤。
03 不完美的庫(kù)類
當(dāng)直接使用第三方庫(kù)的時(shí)候桶错,導(dǎo)致代碼可讀性變差、意圖不明確的問(wèn)題佛点。
為什么不完美的庫(kù)類是一種代碼壞味道黎比?
第三方類庫(kù)提供的功能能夠在很場(chǎng)景下被復(fù)用鸳玩。但是放在業(yè)務(wù)場(chǎng)景下,卻總是要從業(yè)務(wù)視角切換到單純的技術(shù)視角來(lái)來(lái)使用某些第三方類庫(kù)颓帝。
例如
Date newStart = new Date(
previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate() + 1);
一眼看上去這是在表達(dá)什么意思其實(shí)并不容易看到购城。不完美的類庫(kù)就在于造成代碼中語(yǔ)意化變差瘪板。
如何解決不完美庫(kù)類這種代碼壞味道漆诽?
很多開發(fā)者會(huì)采用注釋的方式期望讓代碼可讀,但是這類注釋本身也是一種代碼的壞味道厢拭。不過(guò)可以借助函數(shù)名來(lái)揭示意圖供鸠。
所以遇到上面例子的情況,可以使用 Extract Method 來(lái)提煉一個(gè)函數(shù)家制,生成如下代碼
Date newStart = nextDay(previousEnd);
...
private Date nextDay(Date previousEnd) {
return new Date(
previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate() + 1);
}
這樣調(diào)用 nextDay() 的地方颤殴,就可以輕松的知道獲取到 previousEnd 日期的下一天日期涵但。
如果一個(gè)類中存在多種這種調(diào)用帖蔓,或者多個(gè)類中都有類似的函數(shù)的時(shí)候矮瘟,提煉一個(gè)單獨(dú)一個(gè)類澈侠,并通過(guò)這個(gè)類對(duì)外提供這些方法無(wú)疑是一種消除重復(fù)提高復(fù)用的辦法哨啃。實(shí)現(xiàn)這個(gè)類的方式可以使用代理的方式,也可以使用繼承的方式审姓。如果一個(gè)類只是提供代理方法魔吐,具體實(shí)現(xiàn)都要委托給類庫(kù)莱找,這樣情況下宋距,不如使用繼承來(lái)生成的子類谚赎,并在子類中添加那些可以復(fù)用的方法壶唤。重構(gòu)的過(guò)程可以參考 Introduce Local Extension(引入本地?cái)U(kuò)展)。
很顯然第三方類庫(kù)被設(shè)計(jì)的出發(fā)點(diǎn)往往是好的悯辙,但是實(shí)際調(diào)用的時(shí)候除了享受這種快速實(shí)現(xiàn)的方式躲撰,還需要關(guān)注第三方類庫(kù)給當(dāng)前項(xiàng)目帶來(lái)的一些壞味道击费,并著手解決這些問(wèn)題蔫巩。
04 拒絕的遺贈(zèng)
這個(gè)壞味道指的是當(dāng)子類繼承基類的時(shí)候,父類的一些方法即使子類并不需要也被迫被繼承的情況圆仔。出現(xiàn)這種壞味道的一般有兩種原因:
- 繼承體系設(shè)計(jì)的不好坪郭,還需要調(diào)整嗦锐;
- 基類實(shí)現(xiàn)了某個(gè)接口鸵隧,導(dǎo)致子類不需要的時(shí)候也會(huì)實(shí)現(xiàn)那個(gè)接口對(duì)應(yīng)的方法。
詳細(xì)的例子可以參考:《重構(gòu)分析21: 被拒絕的遺贈(zèng)(Refused Bequest)》
為什么拒絕的遺贈(zèng)是一種代碼壞味道菊值?
這個(gè)壞味道主要原因就是繼承帶來(lái)的壞味道腻窒,子類被迫實(shí)現(xiàn)某些方法或者從父類繼承的方法對(duì)自身不但沒(méi)有幫助甚至造成誤導(dǎo)磅崭,比如:代碼中通過(guò)繼承實(shí)現(xiàn) 正方形 繼承 長(zhǎng)方形并求面積的例子柔逼,感興趣可以參考《敏捷軟件開發(fā)原則割岛、模式癣漆、實(shí)踐》中的里氏替換原則维咸。
如何解決拒絕的遺贈(zèng)的這種代碼壞味道?
有兩種思路:
改善繼承體系惠爽。剔除子類不需要的方法癌蓖,并創(chuàng)建子類的兄弟,通過(guò) Move Method 將不需要的方法移動(dòng)到兄弟類中婚肆,通過(guò) Move Field 將涉及到非公共屬性也移動(dòng)到兄弟子類中租副。
使用代理來(lái)取代繼承。這種方式的修改只涉及到對(duì)子類的調(diào)整旬痹,影響范圍較小附井,并且也不會(huì)因此而像第一種重構(gòu)方法那樣因?yàn)橐S護(hù)繼承體系而導(dǎo)致一些新概念的產(chǎn)生。同時(shí)還能避免因?yàn)榛惱^承了某個(gè)接口两残,而導(dǎo)致的子類被迫實(shí)現(xiàn)某些方法的情況永毅。
總結(jié)
至此 22 種常見的代碼壞味道已經(jīng)介紹完成。關(guān)注工具人弓、框架的同時(shí)花一部分精力關(guān)注代碼質(zhì)量沼死,能夠讓項(xiàng)目隨著時(shí)間不斷演進(jìn)耸别。當(dāng)然實(shí)際工作中遇到的壞味道往往比這 22 種還要多。
項(xiàng)目中我們可以使用 Check Style、PMD蠢沿、Arch Unit 幫助我們及時(shí)發(fā)現(xiàn)項(xiàng)目中的問(wèn)題野宜,但是更”軟“的部分需要我們花精力來(lái)理解清楚是什么、為什么、怎么解決原茅。
或許你也已經(jīng)發(fā)現(xiàn)了通贞,很多情況下壞味道的原因在于變化時(shí),無(wú)法快速應(yīng)對(duì)變化,有的是代碼設(shè)計(jì)的問(wèn)題旭斥,有的是可讀性的問(wèn)題圆米。即使代碼壞味道也分為強(qiáng)烈的壞味道和淡淡的壞味道昙楚,所以重構(gòu)的原則也是”事不過(guò)三、三則重構(gòu)“,因此面對(duì)代碼壞味道的時(shí)候如果代碼壞味道很淡我們可以延遲消除壞味道爆袍。如果壞味道已經(jīng)很強(qiáng)烈夹攒,或者淡淡的壞味道因?yàn)轭l繁的變化而導(dǎo)致效率下降時(shí),那就不如先解決這種壞味道。
擴(kuò)展
重構(gòu)不是發(fā)生在項(xiàng)目結(jié)束的時(shí)候酣倾,而是融入在天天工作中進(jìn)行的映之。采用TDD(測(cè)試驅(qū)動(dòng)開發(fā)的方式)是一種很好的選擇蠢甲,熟悉TDD以及測(cè)試工具的情況下,TDD 不但不會(huì)降低速度反而讓思路更加嚴(yán)謹(jǐn)、實(shí)現(xiàn)的代碼實(shí)現(xiàn)質(zhì)量更高。感興趣的話可以參考:
《TDD 實(shí)戰(zhàn)(1):體驗(yàn)》
《TDD 實(shí)戰(zhàn)(2):Tasking To Action》
《TDD 實(shí)戰(zhàn)(3):Simple Design》
參考
《重構(gòu)》