重構(gòu)
即使最有思想性且最熟練的程序員也不能預(yù)見一個軟件項目中的任何細(xì)微之處。問題總是出乎意外的出現(xiàn),需求也可能在變化,結(jié)果是代碼被優(yōu)化,共享然后代替。
重構(gòu)是一個慣用的方法:檢查你所有的代碼,找出其中能統(tǒng)一化和簡單化的共同或者類似之處,使得你的代碼更加容易維護(hù)和擴(kuò)展。重構(gòu)也包括探索一個設(shè)計模式是否能夠應(yīng)用到這個具體的問題上——這也能使解決方案簡單化。
重構(gòu),簡單點說是重命名一個屬性或者方法,復(fù)雜點說是壓縮一個已有的類。改變你的代碼使得它符合一個或者更多的設(shè)計模式是另外一種重構(gòu)——讀完這本書后,你可能會去實現(xiàn)的。
沒有什么能比例子來更好的解釋重構(gòu)了!
讓我們考慮兩個簡單的類:CartLine和Cart。CartLine記錄了購物車?yán)锩婷總項目的單件價格和數(shù)量。比如CartLine可能記錄著“四見紅色的polo襯衣,每件19.99$”。Cart 是一個容器,用來裝載一個或者更多的CartLine對象并執(zhí)行一些相關(guān)的計算工作,比如購物車?yán)锩娴乃猩唐返目偦ㄙM。
下面是CartLine和Cart的簡單實現(xiàn):
// PHP5 class CartLine { public $price = 0; public $qty = 0; } class Cart { protected $lines = array(); public function addLine($line) { $this->lines[] = $line; } public function calcTotal() { $total = 0; // add totals for each line foreach($this->lines as $line) { $total += $line->price * $line->qty; } // add sales tax $total *= 1.07; return $total; } }
重構(gòu)的第一步必須有足夠的測試來覆蓋你所有的代碼。這樣才能保證你修改的代碼不能產(chǎn)生和你原來代碼不同的結(jié)果。順便提一下,除非你改變了需求(你代碼期望的結(jié)果)或者在測試實例中發(fā)現(xiàn)了錯誤,你的測試代碼是是不能改變的。
下面是一個測試CartLine和Cart的例子,它在重構(gòu)的過程中是不會改變的。
function TestCart() { $line1 = new CartLine; $line1->price = 12; $line1->qty = 2; $line2 = new CartLine; $line2->price = 7.5; $line2->qty = 3; $line3 = new CartLine; $line3->price = 8.25; $line3->qty = 1; $cart = new Cart; $cart->addLine($line1); $cart->addLine($line2); $cart->addLine($line3); $this->assertEqual( (12*2 + 7.5*3 + 8.25) * 1.07, $cart->calcTotal()); }
看著上面的代碼,你可能會發(fā)現(xiàn)它們有一些“code smells”(代碼臭味)——有著古怪的樣子而且看起來好像是有問題的代碼——它們就像重構(gòu)的候選項。(更多關(guān)于code smells的資料請看http://c2.com/cgi/wiki?codesmell)。兩個最直接的重構(gòu)候選者是注釋和計算(與銷售稅等相關(guān)的計算)。重構(gòu)的一種形式:析取函數(shù)(Extract Method)將把這些難看的代碼從cart::calcTotal()中提取出來,然后用一個合適的方法來替代它,從而使得代碼更加簡潔。
比如,你可以增加兩個計算方法:lineTotal()和calcSalesTax():
protected function lineTotal($line) { return $line->price * $line->qty; } protected function calcSalesTax($amount) { return $amount * 0.07; }
現(xiàn)在你可以重寫calcTotal()函數(shù):
public function calcTotal() { $total = 0; foreach($this->lines as $line) { $total += $this->lineTotal($line); } $total += $this->calcSalesTax($total); return $total; }
到目前為止的改動都是有意義的(至少在這個例子的上下文中),它對于再次暫停和運行這些代碼來驗證結(jié)果依然正確是很有幫助的。記得,一個綠色的成功條的顯示出來了。ㄗg者注:本章開始時,作者提及到:綠色的條意味著測試都通過了。)
然而,目前的代碼依然有一些可以挑剔的地方。其中一個就是在新方法lineTotal()中存取公共屬性。很明顯計算每行的之和的責(zé)任不應(yīng)該屬于Cart類,而應(yīng)該在類CartLine里面實現(xiàn)。
再次重構(gòu),在CartLine中增加一個新的方法total()用來計算訂單里面的每個項目的長期價錢。
public function total() { return $this->price * $this->qty; }
然后從類Cart中移除方法lineTotal(),并改變calcTotal()方法來使用新的cartLine::Total()方法。重新運行這個測試,你依然會發(fā)現(xiàn)結(jié)果是綠色條。
全新重構(gòu)后的代碼就是這樣:
class CartLine { public $price = 0; public $qty = 0; public function total() { return $this->price * $this->qty; } } class Cart { protected $lines = array(); public function addLine($line) { $this->lines[] = $line; } public function calcTotal() { $total = 0; foreach($this->lines as $line) { $total += $line->total(); } $total += $this->calcSalesTax($total); return $total; } protected function calcSalesTax($amount) { return $amount * 0.07; } }
現(xiàn)在這代碼不再需要每行注釋了,因為代碼本身更好的說明了每行的功能。這些新的方法,更好的封裝了計算這個功能,也更加容易適應(yīng)將來的變化。(比如說,考慮不同大的銷售稅率)。另外,這些類也更加平衡,更容易維護(hù)。
這個例子顯然是微不足道的,但是希望你能從中推斷并預(yù)想出如何重構(gòu)你自己的代碼。
在編碼的時候,你應(yīng)該有出于兩種模式中的一種:增加新的特征或者重構(gòu)代碼。當(dāng)在增加特征的時候,你要寫測試和增加代碼。在重構(gòu)的時候,你要改變你原有的代碼,并確保所有相關(guān)的測試依然能正確運行。
關(guān)于重構(gòu)的主要參考資料有Martin Fowler著作的《重構(gòu):改進(jìn)原有代碼的設(shè)計》(Refactoring:Improving the Design of Existing Code)。用一些精簡點來總結(jié)Fowler的書,重構(gòu)的步驟如下所示:
定義需要重構(gòu)的代碼 有覆蓋所有代碼的測試 小步驟的工作 每步之后都運行你的測試。編碼和測試都是相當(dāng)重復(fù)的——和編譯型語言相比,解釋型語言,比如PHP是容易很多的。 使用重構(gòu)來使你的代碼有更好的可讀性和可修改性。
出處:phpchina
責(zé)任編輯:bluehearts
上一頁 php設(shè)計模式介紹之編程慣用法 [1] 下一頁 php設(shè)計模式介紹之編程慣用法 [3]
◎進(jìn)入論壇網(wǎng)絡(luò)編程版塊參加討論
|