從[Day 9]開始,一直到[Day 18],我們從最初不知道從哪開始重構,到現在程式碼變得高內聚、低耦合、可擴充、可讀、可維護,而且有了相關的測試保護,不再需要擔心受怕,因為別人改了某一個地方,導致我們的程式壞了。
這一篇文章,是本重構系列的總結,將帶著各位讀者驗收一下這幾天重構的成果。
最後將帶出,重構的循環,其實可視為是TDD的循環之一。就像兩個齒輪一般,互相搭配運轉。
上一篇文章:[Day 18]Refactoring - Factory Pattern
本系列文章專區
@前言
讓我們先來回顧一下,一開始要重構的程式碼,是什麼樣子呢?重構前的程式碼如下:
protected void btnCalculate_Click(object sender, EventArgs e)
{
if (this.IsValid)
{
if (this.drpCompany.SelectedValue == "1")
{
this.lblCompany.Text = "黑貓";
var weight = Convert.ToDouble(this.txtProductWeight.Text);
if (weight > 20)
{
this.lblCharge.Text = "500";
}
else
{
var fee = 100 + weight * 10;
this.lblCharge.Text = fee.ToString();
}
}
else if (this.drpCompany.SelectedValue == "2")
{
this.lblCompany.Text = "新竹貨運";
var length = Convert.ToDouble(this.txtProductLength.Text);
var width = Convert.ToDouble(this.txtProductWidth.Text);
var height = Convert.ToDouble(this.txtProductHeight.Text);
var size = length * width * height;
//長 x 寬 x 高(公分)x 0.0000353
if (length > 100 || width > 100 || height > 100)
{
this.lblCharge.Text = (size * 0.0000353 * 1100 + 500).ToString();
}
else
{
this.lblCharge.Text = (size * 0.0000353 * 1200).ToString();
}
}
else if (this.drpCompany.SelectedValue == "3")
{
this.lblCompany.Text = "郵局";
var weight = Convert.ToDouble(this.txtProductWeight.Text);
var feeByWeight = 80 + weight * 10;
var length = Convert.ToDouble(this.txtProductLength.Text);
var width = Convert.ToDouble(this.txtProductWidth.Text);
var height = Convert.ToDouble(this.txtProductHeight.Text);
var size = length * width * height;
var feeBySize = size * 0.0000353 * 1100;
if (feeByWeight < feeBySize)
{
this.lblCharge.Text = feeByWeight.ToString();
}
else
{
this.lblCharge.Text = feeBySize.ToString();
}
}
else
{
var js = "alert('發生不預期錯誤,請洽系統管理者');location.href='http://tw.yahoo.com/';";
this.ClientScript.RegisterStartupScript(this.GetType(), "back", js, true);
}
}
}
@重構後的程式碼
經過一系列重構的步驟(每一個技巧幾乎只要3分鐘就可以學會)之後,最後也要請各位讀者記得,重構完成後(嚴格來說,應該是過程中),記得把不必要的程式碼清除(尤其是被註解掉的程式碼),並且把Class與Function相關的API Document補上。我們最終的程式碼如下(體會一下,現在的程式碼,是不是自己會說話)::
protected void btnCalculate_Click(object sender, EventArgs e)
{
//若頁面通過驗證
if (this.IsValid)
{
//取得畫面資料
var product = this.GetProduct();
ILogistics logistics = FactoryRepository.GetILogistics(this.drpCompany.SelectedValue, product);
if (logistics != null)
{
logistics.Calculate();
var companyName = logistics.GetsComapanyName();
var fee = logistics.GetsFee();
//呈現結果
this.SetResult(companyName, fee);
}
else
{
var js = "alert('發生不預期錯誤,請洽系統管理者');location.href='http://tw.yahoo.com/';";
this.ClientScript.RegisterStartupScript(this.GetType(), "back", js, true);
}
}
}
/// <summary>
/// 呈現結果
/// </summary>
/// <param name="companyName"></param>
/// <param name="fee"></param>
private void SetResult(string companyName, double fee)
{
this.lblCompany.Text = companyName;
this.lblCharge.Text = fee.ToString();
}
/// <summary>
/// 取得畫面資料
/// </summary>
/// <returns></returns>
private Product GetProduct()
{
var result = new Product
{
Name = this.txtProductName.Text.Trim(),
Weight = Convert.ToDouble(this.txtProductWeight.Text),
Size = new Size()
{
Length = Convert.ToDouble(this.txtProductLength.Text),
Width = Convert.ToDouble(this.txtProductWidth.Text),
Height = Convert.ToDouble(this.txtProductHeight.Text)
},
IsNeedCool = this.rdoNeedCool.SelectedValue == "1"
};
return result;
}
可以看到,在context端邏輯分明,程式碼就像會說故事一般,有節奏地將要執行的步驟與目的都說的一清二楚。
@檢視重構結果
從三個角度來檢視我們重構完的設計:
UML(套用strategy pattern後,未來增加物流商,符合開放封閉原則)
複雜度(從14降到4)
程式碼覆蓋率(從0%到92.86%)
Smell Good? Isn't It?
很簡單,也很美好,不是嗎?
@總結
幾點重要的總結:
@你還記得嗎?
最後提出一個問題:『在這整個重構過程後,有讀者記得三間物流商是怎麼計算運費的嗎?』
相信絕大部分的讀者答案都是不記得。
這樣就對了,這就是抽象。不需要把頭埋入細節中,把精神關注在物件的行為、職責以及互動上,才是重點。
@延伸概念
下圖是TDD的循環:
感覺很熟悉,對吧?
其實,我們的重構循環,就是TDD的Refactor的細部動作,請見下圖:
如果沒有重構的能力,那TDD出來的成品,只是一坨可以正確執行的垃圾。TDD的重點在可以正確執行出期望的結果,而在很多時候,我們也只需要可以執行出正確的結果即可。
The End is the Beginning,每一個重構的結束,都是為了下一個TDD循環的開始。當重構完通過測試後,給自己個獎勵,take a break~
享受並掌握一下,這樣的開發節奏、律動與循環,建議可以搭配蕃茄鐘工作法與Scrum的流程,將更能掌握屬於自己與團隊的心流(flow)狀態,更加事半功倍。
最後最後,引用一下Ruddy老師的一段話,用來作整系列重構文章的原則:『重構,應該針對需要的部份重構,且適可而止。』
Ruddy Lee:
常常有工程師把只會被執行個幾回就一輩子不會再被執行到的程式;寫得完美的一蹋糊塗,真是太愛乾淨了。真是愛做白工… 還不如早一點睡來得有價值,起碼會活的健康些,程式只要在他被需要的生命週期內活得好好的,就ok了! 這就是done了。
筆者個人的重構底限,就是SOLID原則,DRY原則,KISS原則,YAGNI原則。而目的就是為了滿足使用者需求。期望讓程式碼更好理解、更好擴充、更好維護。
下一篇文章開始,我們將開始談ATDD與BDD。接著用個簡單的例子,從使用者需求這源頭開始,示範如何從頭開始TDD。
@Sample Code
附上本系列文章的Sample Code(包括所有版本與測試) :sample code.zip