請先閱讀上一篇文章
重構
即使最有思想性且最熟練的程序員也不能預見一個軟件項目中的任何細微之處。問題總是出乎意外的出現,需求也可能在變化,結果是代碼被優化,共享然後代替。
重構是一個慣用的方法:檢查你所有的代碼,找出其中能統一化和簡單化的共同或者類似之處,使得你的代碼更加容易維護和擴展。重構也包括探索一個設計模式是否能夠應用到這個具體的問題上——這也能使解決方案簡單化。
重構,簡單點說是重命名一個屬性或者方法,復雜點說是壓縮一個已有的類。改變你的代碼使得它符合一個或者更多的設計模式是另外一種重構——讀完這本書後,你可能會去實現的。
沒有什麼能比例子來更好的解釋重構了!
讓我們考慮兩個簡單的類:CartLine和Cart。CartLine記錄了購物車裡面每個項目的單件價格和數量。比如CartLine可能記錄著“四見紅色的polo襯衣,每件19.99$”。Cart 是一個容器,用來裝載一個或者更多的CartLine對象並執行一些相關的計算工作,比如購物車裡面的所有商品的總花費。
下面是CartLine和Cart的簡單實現:
// 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;
}
}
重構的第一步必須有足夠的測試來覆蓋你所有的代碼。這樣才能保證你修改的代碼不能產生和你原來代碼不同的結果。順便提一下,除非你改變了需求(你代碼期望的結果)或者在測試實例中發現了錯誤,你的測試代碼是是不能改變的。
下面是一個測試CartLine和Cart的例子,它在重構的過程中是不會改變的。
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());
}
看著上面的代碼,你可能會發現它們有一些“code smells”(代碼臭味)——有著古怪的樣子而且看起來好像是有問題的代碼——它們就像重構的候選項。(更多關於code smells的資料請看http://c2.com/cgi/wiki?codesmell)。兩個最直接的重構候選者是注釋和計算(與銷售稅等相關的計算)。重構的一種形式:析取函數(Extract Method)將把這些難看的代碼從cart::calcTotal()中提取出來,然後用一個合適的方法來替代它,從而使得代碼更加簡潔。
比如,你可以增加兩個計算方法:lineTotal()和calcSalesTax():
protected function lineTotal($line) {
return $line->price * $line->qty;
}
protected function calcSalesTax($amount) {
return $amount * 0.07;
}
現在你可以重寫calcTotal()函數:
public function calcTotal() {
$total = 0;
foreach($this->lines as $line) {
$total += $this->lineTotal($line);
}
$total += $this->calcSalesTax($total);
return $total;
}
到目前為止的改動都是有意義的(至少在這個例子的上下文中),它對於再次暫停和運行這些代碼來驗證結果依然正確是很有幫助的。記得,一個綠色的成功條的顯示出來了!(譯者注:本章開始時,作者提及到:綠色的條意味著測試都通過了。)
然而,目前的代碼依然有一些可以挑剔的地方。其中一個就是在新方法lineTotal()中存取公共屬性。很明顯計算每行的之和的責任不應該屬於Cart類,而應該在類CartLine裡面實現。
再次重構,在CartLine中增加一個新的方法total()用來計算訂單裡面的每個項目的長期價錢。
public function total() {
return $this->price * $this->qty;
}
然後從類Cart中移除方法lineTotal(),並改變calcTotal()方法來使用新的cartLine::Total()方法。重新運行這個測試,你依然會發現結果是綠色條。
全新重構後的代碼就是這樣:
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;
}
}
現在這代碼不再需要每行注釋了,因為代碼本身更好的說明了每行的功能。這些新的方法,更好的封裝了計算這個功能,也更加容易適應將來的變化。(比如說,考慮不同大的銷售稅率)。另外,這些類也更加平衡,更容易維護。
這個例子顯然是微不足道的,但是希望你能從中推斷並預想出如何重構你自己的代碼。
在編碼的時候,你應該有出於兩種模式中的一種:增加新的特征或者重構代碼。當在增加特征的時候,你要寫測試和增加代碼。在重構的時候,你要改變你原有的代碼,並確保所有相關的測試依然能正確運行。
關於重構的主要參考資料有Martin Fowler著作的《重構:改進原有代碼的設計》(Refactoring:Improving the Design of Existing Code)。用一些精簡點來總結Fowler的書,重構的步驟如下所示:
定義需要重構的代碼
有覆蓋所有代碼的測試
小步驟的工作
每步之後都運行你的測試。編碼和測試都是相當重復的——和編譯型語言相比,解釋型語言,比如PHP是容易很多的。
使用重構來使你的代碼有更好的可讀性和可修改性。