識別代碼中的壞味道(三)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前兩篇文章 "},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/141435233","title":null},"content":[{"type":"text","text":"《識別代碼中的壞味道(一)》"}]},{"type":"text","text":" 和 "},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/141803738","title":null},"content":[{"type":"text","text":"《識別代碼中的壞味道(二)》"}]},{"type":"text","text":" 中已經介紹了 18 個代碼壞味道。《重構》中還涉及到另外 4 個代碼壞味道,本文將將詳細介紹剩餘的 4 個代碼壞味道。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這四個代碼壞味道是:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"中間人(Middle Man)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"狎暱關係"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"不完美的庫類"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"被拒絕的遺贈"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"01 中間人(Middle Man)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章中 "},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/141803738","title":null},"content":[{"type":"text","text":"《識別代碼中的壞味道(二)》"}]},{"type":"text","text":" 中在“過度耦合的消息鏈”這種代碼壞味道曾經提及過中間人(Middle Man)這種代碼壞味道,那麼中間人到底是一類什麼代碼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"中間人指的是一種過度使用委託的代碼,《重構》中給了一個參考值,如果一個類中有一半的方法都委託給其他對象進行,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼中間人是一種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"過度使用委託。這意味着當需求發生某些的變化的時候,這個中間人的類總是被牽連進來一併修改。這種中間人代碼越多,浪費掉的時間也就越多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何解決中間人這種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"中間人的代碼在於過度使用和委託兩點。因此解決中間人這種代碼壞味道就應該從減少委託下手:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刪除中間人的方法,可以使用 Remove Middle Man(移除中間人)這種重構技巧。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然如果原有代碼的代理類中並不怎麼變化,也可以選擇延遲重構,依照“事不過三,三則重構”的原則可以選擇當發生變化的時候進行重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"02 狎暱關係(Inappropriate Intimacy)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"指的是類之間花費太多的時間去探究彼此的私有的屬性或者方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"造成狎暱關係的原因可能是:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲繼承導致了狎暱關係;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"..."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼狎暱關係是一種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"狎暱關係會導致強耦合的表現;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"而且類和類之間的職責將會變得模糊;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"會因爲訪問對方的私有信息而導致過多的操作出現,或者產生封裝上的妥協,讓兩個類糾纏不清。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何解決狎妮關係這種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"通過 Move Field (搬移屬性),Move Method(搬移方法)來移動屬性和方法的位置,讓屬性和方法移動到它們本應該出現的位置。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果直接移動屬性和方法並不合適,可以嘗試使用 Extract Class(提煉類)看是否能夠找到公共類。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果是因爲相互調用導致的問題,可以嘗試 Change Bidirectional Association to Unidirectional(將雙向關聯改爲單向關聯)嘗試將關聯關係劃清。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"如果是因爲繼承導致狎暱關係,可以嘗試移除繼承關係,改用代理類來實現。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"03 不完美的庫類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當直接使用第三方庫的時候,導致代碼可讀性變差、意圖不明確的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼不完美的庫類是一種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三方類庫提供的功能能夠在很場景下被複用。但是放在業務場景下,卻總是要從業務視角切換到單純的技術視角來來使用某些第三方類庫。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Date newStart = new Date(\n previousEnd.getYear(), \n previousEnd.getMonth(), \n previousEnd.getDate() + 1);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一眼看上去這是在表達什麼意思其實並不容易看到。不完美的類庫就在於造成代碼中語意化變差。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何解決不完美庫類這種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多開發者會採用註釋的方式期望讓代碼可讀,但是這類註釋本身也是一種代碼的壞味道。不過可以藉助函數名來揭示意圖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以遇到上面例子的情況,可以使用 Extract Method 來提煉一個函數,生成如下代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Date newStart = nextDay(previousEnd);\n\n\n...\n\n\nprivate Date nextDay(Date previousEnd) {\n return new Date(\n previousEnd.getYear(), \n previousEnd.getMonth(), \n previousEnd.getDate() + 1); \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣調用 nextDay() 的地方,就可以輕鬆的知道獲取到 previousEnd 日期的下一天日期。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個類中存在多種這種調用,或者多個類中都有類似的函數的時候,提煉一個單獨一個類,並通過這個類對外提供這些方法無疑是一種消除重複提高複用的辦法。實現這個類的方式可以使用代理的方式,也可以使用繼承的方式。如果一個類只是提供代理方法,具體實現都要委託給類庫,這樣情況下,不如使用繼承來生成的子類,並在子類中添加那些可以複用的方法。重構的過程可以參考 Introduce Local Extension(引入本地擴展)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很顯然第三方類庫被設計的出發點往往是好的,但是實際調用的時候除了享受這種快速實現的方式,還需要關注第三方類庫給當前項目帶來的一些壞味道,並着手解決這些問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"04 拒絕的遺贈"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個壞味道指的是當子類繼承基類的時候,父類的一些方法即使子類並不需要也被迫被繼承的情況。出現這種壞味道的一般有兩種原因:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"繼承體系設計的不好,還需要調整;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"基類實現了某個接口,導致子類不需要的時候也會實現那個接口對應的方法。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"詳細的例子可以參考:"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/98756702","title":null},"content":[{"type":"text","text":"《重構分析21: 被拒絕的遺贈(Refused Bequest)》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼拒絕的遺贈是一種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個壞味道主要原因就是繼承帶來的壞味道,子類被迫實現某些方法或者從父類繼承的方法對自身不但沒有幫助甚至造成誤導,比如:代碼中通過繼承實現 正方形 繼承 長方形並求面積的例子,感興趣可以參考《敏捷軟件開發原則、模式、實踐》中的里氏替換原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何解決拒絕的遺贈的這種代碼壞味道?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有兩種思路:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此 22 種常見的代碼壞味道已經介紹完成。關注工具、框架的同時花一部分精力關注代碼質量,能夠讓項目隨着時間不斷演進。當然實際工作中遇到的壞味道往往比這 22 種還要多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目中我們可以使用 "},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/105583516","title":null},"content":[{"type":"text","text":"Check Style"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/105585075","title":null},"content":[{"type":"text","text":"PMD"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/107151861","title":null},"content":[{"type":"text","text":"Arch Unit"}]},{"type":"text","text":" 幫助我們及時發現項目中的問題,但是更”軟“的部分需要我們花精力來理解清楚是什麼、爲什麼、怎麼解決。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或許你也已經發現了,很多情況下壞味道的原因在於變化時,無法快速應對變化,有的是代碼設計的問題,有的是可讀性的問題。即使代碼壞味道也分爲強烈的壞味道和淡淡的壞味道,所以重構的原則也是”事不過三、三則重構“,因此面對代碼壞味道的時候如果代碼壞味道很淡我們可以延遲消除壞味道。如果壞味道已經很強烈,或者淡淡的壞味道因爲頻繁的變化而導致效率下降時,那就不如先解決這種壞味道。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"擴展"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2a/2a309baf7aad5b7d4137ec1ae7853668.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重構不是發生在項目結束的時候,而是融入在天天工作中進行的。採用TDD(測試驅動開發的方式)是一種很好的選擇,熟悉TDD以及測試工具的情況下,TDD 不但不會降低速度反而讓思路更加嚴謹、實現的代碼實現質量更高。感興趣的話可以參考:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/112536194","title":null},"content":[{"type":"text","text":"《TDD 實戰(1):體驗》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/122234942","title":null},"content":[{"type":"text","text":"《TDD 實戰(2):Tasking To Action》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/125691321","title":null},"content":[{"type":"text","text":"《TDD 實戰(3):Simple Design》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0eef946c4d47dee8fd309244ad0d2517.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"參考"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《重構》"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章