凡是寫過一些代碼的程序猿都能夠意識到應該避免重複的代碼和邏輯。我們通過提取方法,提取抽象類等等措施來達到這一目的。我們總能時不時的聽到類似這樣的話:”把這些公用的類放到shared項目去,別的項目還要使用。。。“,什麼算是公用(重複)的代碼?是不是公用(重複)的代碼就要放到一個叫shared的地方?
爲什麼說重複的代碼和邏輯會帶來問題呢?
你從一個類中複製了一段代碼到另一個類中,但是這段代碼足夠的穩定,百年不變,這樣的重複會帶來問題嗎?
也許不會。
如果這段代碼需要時不時的修改,那麼你就要花時間去修改所有包含這段邏輯的代碼,這樣無形中增加了維護成本和發生bug的機率。這時候就要着手消除和抽取重複的代碼。
對於消除重複的代碼有一個三次法則(rule of three):
1.第一次先寫了一段代碼。
2.第二次在另一個地方寫了一段相同的代碼,你已經有消除和提取重複代碼的衝動了。
3.再次在另一個地方寫了同樣的代碼,你已忍無可忍,現在可以考慮提取和消除重複代碼了。
這一法則也適應於對重構時機的把握,過早的重構可能會引入新的問題,三次法則給了你一個重構依據。當然,隨着你經驗的增長,你可能在第二階段已經能非常有信心的預料到問題所在。
什麼樣的代碼算是重複的?
DRY is about Knowledge一文中給了這樣一個實例:
<?php // example 1 final class Basket { private $products; public function addProduct($product) { if (3 == count($this->products)) { throw new Exception("Max 3 products allowed"); } $this->products[] = $product; } } final class Shipment { private $products; public function addProduct($product) { if (3 == count($this->products)) { throw new Exception("Max 3 products allowed"); } $this->products[] = $product; } }
這兩段代碼中都有一個相同的方法addProduct,這兩段代碼算是重複的嗎?他們違反DRY原則嗎?
作爲一個領域驅動的實踐者,我們可以從業務的角度來分析這一代碼,第一段代碼似乎是一個用戶在購物,但是我們最多允許用戶購買三件商品。在第二段代碼的場景中,我們想要給所有用戶提供相同的機會並限制用戶最大購買數量。
這一分析說明了這兩段代碼所描述的領域(Domain)、業務(Business)、知識(Knowledge)、邊界(Boundary)、職責(Resposibility)不同,這兩段代碼之所以相同完全屬於巧合。對這樣的代碼進行提取和消除重複是錯誤的。
我之前待的team維護了一個北美的項目,如果讓我指出這一項目最大的設計問題,就是將一些本不應該提取的代碼(各種Model、數據訪問)抽取到了一個叫做Shared的工程中,這一舉動導致開發人員在後期不敢再修改Shared工程中的任何代碼,以至於開發人員寧可重新添加一個方法也不敢修改之前的代碼。
DRY看似初級人員都要掌握的能力,如果使用不當會造成非常嚴重的後果,正是因爲我維護了這樣的項目纔有感而發,希望大家引以爲戒,當然大家若是有不同的看法可以一起討論。