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

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章中,介紹了通過名字就能理解的 8 個壞味道,感興趣可以查看"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/141435233","title":null},"content":[{"type":"text","text":"《識別代碼中的壞味道(一)》"}]},{"type":"text","text":"。本篇文章將識別代碼中的另外 10 個代碼壞味道:10個晦澀但是通過簡單的即可識別的壞味道。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/69/69e5856550643d0276da2029112f2628.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":"如上圖,這 10 個代碼壞味道是:"}]},{"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":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"數據泥球"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"基本類型偏執"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"平行繼承體系"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"冗贅類"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"過度耦合信息鏈"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":9,"align":null,"origin":null},"content":[{"type":"text","text":"異曲同工的類"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":10,"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":2},"content":[{"type":"text","text":"01 發散式變化"}]},{"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":"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","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":"單一職責原則可以用來解決發散式變化、過大的類的壞味道的指導原則:一個類只有一個引起其變化的原因。既然由於一個類存在過類行爲,可以通過 Extract Class 來將不同的方法提煉到不同職責的類中。"}]},{"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":2},"content":[{"type":"text","text":"02 霰彈式修改"}]},{"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":"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","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":"既然霰彈式修改是一種耦合性的表現,我們可以將相關的代碼通過 Move Field (移動屬性)和 Move Method (移動方法)兩種重構手段將代碼移動到一個類中。這樣做的好處是讓變化的內容聚集到了,有助於簡化後續的修改。"}]},{"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":"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":2},"content":[{"type":"text","text":"03 平行繼承體系(Parallel Inheritance Hierarchies)"}]},{"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":"平行繼承體系指:當一個類增加 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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/21/21ecacaba5a3e8360219d38615e657e9.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":"當添加 XXXVIPTaskService 的時候就會需要新增出新的 XXXVIPScoreService 。"}]},{"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣的實現容易導致在 GradeService 中 Switch 語句的產生,switch 語句本身就是一種重複的體現。關於Switch 語句的問題可以參考:"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/141435233","title":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":"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":"建立直接引用。即 SVIPTaskService 直接引用 SVIPScoreService。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"參考"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/97442825","title":null},"content":[{"type":"text","text":"《Java有限狀態機的4種實現對比》"}]},{"type":"text","text":" 消除繼承體系,這裏過程可以使用Move Field 和 Move Method 等重構手法。"}]}]}]},{"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":"通過上面的重構,隱形的關聯變成直接引用。另外避免了 Switch 語句的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class OrderService {\n\n public List findAllOrders() {\n ...\n }\n\n public Order findLatestOrder(List orders) {\n ...\n }\n\n public Order addProduct(Order order, Product product) {\n ...\n }\n\n}\n\npublic class CartService {\n ...\n\n public void addProduct(Product product) {\n ...\n List orders = orderService.findAllOrders();\n Order order = orderService.findLatestOrder(orders);\n order = orderService.addProduct(product);\n ...\n }\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":"再是不用考慮上面這段的代碼業務上的合理性。代碼中 CartService 中多次調用 OrderService 的方法,其目的就是執行最後的 addProduct() 方法,這就是一種依戀情結的代碼。"}]},{"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":"仔細觀察 CartService.addProduct() 方法不難發現那三行的代碼的意圖就是將 product 添加到最新的 order 中,如何實現將 product 添加到 product 這個目的,上面帶代碼顯然展示了一種策略的具體實現。顯然這種實現使得方法的職責不再單一。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"另外一個問題是,當 OrderService中的 findAllOrders()、findLatestOrder()、addProduct() 方法因爲需求發生變動的時候,都有可能會牽連到 CartService 中的代碼發生變化。因此上面中代碼通過強耦合性雖然實現了功能,但是應對變化的能力也隨之降低。代碼是不斷演進的,忽略了這種壞味道,會導致後續變化付出相應的代價。"}]}]}]},{"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":"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":"將多次產生調用的幾行代碼使用 Extract Method(提煉函數)提煉爲一個新的函數,並通過名稱來解釋這幾個行代碼所要表達的意思。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"接下來可以使用 Move Method (搬移函數)將剛剛提煉的函數放置到一個更合適的類中,可以是剛剛被調用的類中,也可以創建新的類。"}]}]}]},{"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":"通過上面簡單兩步,我們可以將後續變化影響的範圍變小,OrderService 內的變化將不再容易牽連到 CartService。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"05 數據泥球"}]},{"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":"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":"如果類中的字段出現了數據泥球,對於這些重複的字段可以使用 Extract Class( 提煉類) 將關聯幾個屬性提煉到一個類中,賦予它一個業務的概念。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果是多個方法參數中出現了多個重複的多個參數,可以通過 Introduce Parameter Object(引入參數對象)將多個參數使用對象來代替,從而有效的減少重複和參數個數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"其中 2 的另外一種情況,如何調用者先通過一些邏輯生成幾個變量,再將這幾個變量通過參數傳遞給調用的方法,那麼可以使用 Presere Whole Object(保持對象完整),將變量生成提煉到一個函數中,並並取消參數的傳遞,而是在被調用的方法中直接調用原本要傳遞的參數。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"06 基本類型偏執"}]},{"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":"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":"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":"這種代碼也是存在可讀性的問題,而且非常容易導致 switch 語句的壞味道。"}]},{"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":"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":"通過 Extract Method (提煉函數)將幾個基本數據類型拼接的邏輯提煉爲一個方法,比通過方法名來解釋意圖。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果按照1做了,發現類中出現不應該出現的職責,那麼就可以將幾個相關的基本數據類型通過 Extract Class(提煉類)將幾個基本數據類型提煉爲一個類來表達一個概念,然後通過 Move Method 來講相關的操作挪動到該類中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果使用基本數據類型來表示狀態,可以選擇使用 Replace Type Code with Class(以類取代類型碼),並將相關的操作移動到類中,避免 Switch 語句。場景可以參考"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/97442825","title":null},"content":[{"type":"text","text":"《Java有限狀態機的4種實現對比》"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"07 冗贅類(Lacy Class)"}]},{"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":"代碼中一旦遇到職責過度拆分的情況就可以通過 Inline Class 或者 Collapse Hierarchy 來刪除一些類,將概念合併到一個類中。"}]},{"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":2},"content":[{"type":"text","text":"08 過度耦合的消息鏈"}]},{"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":"public class User {\n ...\n private Address address;\n ...\n}\n\npublic class Address {\n ...\n private City city;\n ...\n}\n\npublic class City {\n ...\n private PostCode postCode;\n ...\n}\n\npublic class PostCode {\n ...\n private String code;\n ...\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":"多度耦合的消息鏈代碼如下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"String postCode = user.getAddress()\n .getCity()\n .getPostCode()\n .getCode();"}]},{"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":"上面的實現雖然能夠正常運行,但是會導致類之間的耦合,即 User 類的調用者需要在自己的內部來獲得沒有直接練習的 postCode 的實現;"}]}]},{"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","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":"可以通過 Extract Method 來提煉函數,然後 通過 Move Method 來將提煉的方法移動到合適的位置。"}]},{"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":"如果讀過《重構》還會提到 Hide Delegate(隱藏代理關係)的重構手法。不過不推薦使用,因爲它引入多個 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":"因此可以有限照顧可讀性,通過 Extract Method 和 Move Method 來進行重構,從而獲取實現和維護性上的平衡。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"09 異曲同工的類"}]},{"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":"即兩個類做的同一件事或者同一類事。這種代碼很常見,比如兩個開發者同時執行自己的開發工作,創建了功能類似但是方法不同的類,Code Review 的時候很容易發現這種代碼。"}]},{"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":"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":"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}},{"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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"10 純數據類"}]},{"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":"純數據類指的是:一個類中只有屬性和這些屬性所涉及到的 getter、setter。"}]},{"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":"純數據類有其使用場景,比如 DTO 經常這種貧血模型。但是如果結合業務到的純數據類頻繁出現,那可不是什麼好的事情,因爲操作這個類中屬性的方法將會散落在各個類中,即存在者多處強耦合。"}]},{"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":"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":"Extract Method 將部分調用邏輯進行提煉,提煉成一定的方法;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"再使用 Move Method 將方法移動到類中,"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"最後 Hide Method 刪除純出局類中的 getter 和 setter。"}]}]}]},{"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":2},"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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章