[30天快速上手TDD][Day 9] Refactoring legacy code 簡介
前言
到上一篇文章為止, TDD 中所需具備的基本測試知識,已經告一段落。
接下來要練習的,是重構的手法。
接下來幾篇文章,會跟各位讀者朋友介紹:
- 要怎麼找到需要重構的部分
- 要怎麼讓程式碼會說話
- 要怎麼與測試結合
這一篇文章則會先介紹,要如何找到程式碼中需要重構的地方。
接下來幾篇重構的手法,濃縮版的請見之前的文章:[ASP.NET]重構之路番外篇 –Refactoring to Patterns,在這幾篇文章,會針對裡面的每個手法做更詳細的說明,以及每一個步驟的目的,以及可以帶來的幫助。
現況
我們所面臨的系統狀況,通常也就是 Legacy Code (提到 Legacy Code ,就要順便介紹一本好書:Working Effectively with Legacy Code),就像下圖一樣:
(圖片來源:圖片來源:http://www.chancedia.com/?p=41470)
就像廣告說的一樣:「每個 Dev 都喜歡乾淨的 code ,但是又喜歡把 code 弄髒。」
重構的目的
我們希望可以把雜亂無章的 code ,乾淨整齊的放在它們所屬的位置上。
(圖片來源:http://jung9572002.pixnet.net/blog/post/1733351-%E6%94%B6%E7%B4%8D%E9%81%94%E4%BA%BA)
重構的時機與目標
基本上最適合重構的時機有三類:
-
Debug完成後
(圖片來源:http://awards.gettyimages.com/awards.cfm?selCategory=all&display=photographer&workID=67&photographerID=7&photoID=70&sp_sortID=1) -
需求異動
(圖片來源:http://www.lykasal.com/2012/10/cats-that-pester-for-food-could-be.html) -
系統有 Bad Smell 的地方
(圖片來源:http://www.thetorontopost.net/2012/09/smell-test-total-fail-for-rob-ford-in.html)
簡單的說,就是要修改程式的時候,或是程式很髒的時候,適合重構。
但請記住:「一次只做一件事」
如何找出 Bad Smell
這邊的範例,我建立了一個不同物流商會計算出不同運費的網站。
先以 SourceMonitor 為例,來找出系統中複雜度太高的 function ,並將它當做我們重構的目標。( SourceMonitor 的介紹,有興趣的朋友可以看之前這篇文章:[Tool]SourceMonitor - 程式碼掃瞄)
掃描後,按照 Max Complexity 排序,可以看到 Prodcut_v0.aspx.cs ,最大複雜度 14 ,最大深度 5 。如下圖所示:
再點開詳細資訊後,可以看到 btnCalculate_Click 這個方法,就是造成最大複雜度與最大深度的原因。如下圖所示:
也可以使用 VS2012/VS2010 的程式碼度量,來找到複雜度過高的程式,請參考:[Tool]Visual Studio 2010 - 程式碼度量
接著,來看一下這個 function 的程式碼,如下所示:
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);
}
}
}
上面就是一陀攤在角落的 code ,一眼望過去,每個字都認識,但卻要動腦袋猜測,甚至動手測試才能了解這一段 code 是什麼意思。除了難以理解以外,這樣巢狀 if 的設計方式,健壯性(robustness)上也相當薄弱。
呈現的畫面與功能,如下圖所示:
小結
要重構之前,得先瞭解重構的目的、意義,以及如何找到需要重構的程式。
期望重構之後,能對原本可以正常執行的結果完全沒有影響,但程式碼因此具備了更高的可讀性、擴充性、健壯性等等...
重構的基本原則是:
- 建立測試,確保安全
- 由小到大,絕不貪心
- 適可而止,絕不偏執
由於重構在 TDD 中,也佔了很重要的一個角色,所以希望接下來幾篇,可以幫助讀者手把手的跟著練習一遍,這樣看似簡單、又像複雜、又沒啥彈性的程式碼,如何從亂七八糟,變成最後一應俱全的健壯程式。
補充
有讀者朋友問到,什麼樣的程式碼算的上是 Bad smell ?
這邊列出筆者工作環境中的門檻值:
- 循環複雜度 > 10
- 繼承深度 > 3
- 區塊深度 > 4
- 相似程式碼 > 15行
- 綜合可維護性指數 < 75
以上,不代表超過標準就一定不好,但就像健康檢查報告的指數一樣,這些的確是需要被 highlight 出來說明的。
其他靜態程式碼分析的工具與投影片簡介,請參考這篇文章:[.NET][Tool]靜態程式碼分析工具簡介
blog 與課程更新內容,請前往新站位置:http://tdd.best/