[30天快速上手TDD][Day 9]Refactoring legacy code 簡介

[30天快速上手TDD][Day 9] Refactoring legacy code 簡介

前言

到上一篇文章為止, TDD 中所需具備的基本測試知識,已經告一段落。

接下來要練習的,是重構的手法。

接下來幾篇文章,會跟各位讀者朋友介紹:

  1. 要怎麼找到需要重構的部分
  2. 要怎麼讓程式碼會說話
  3. 要怎麼與測試結合

這一篇文章則會先介紹,要如何找到程式碼中需要重構的地方。

接下來幾篇重構的手法,濃縮版的請見之前的文章:[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

 

重構的時機與目標

基本上最適合重構的時機有三類:

  1. Debug完成後
    debug
    (圖片來源:http://awards.gettyimages.com/awards.cfm?selCategory=all&display=photographer&workID=67&photographerID=7&photoID=70&sp_sortID=1)
  2. 需求異動
    需求異動
    (圖片來源:http://www.lykasal.com/2012/10/cats-that-pester-for-food-could-be.html
  3. 系統有 Bad Smell 的地方
    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 。如下圖所示:

source monitor v0

再點開詳細資訊後,可以看到  btnCalculate_Click 這個方法,就是造成最大複雜度與最大深度的原因。如下圖所示:

source monitor button 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)上也相當薄弱。

呈現的畫面與功能,如下圖所示:

UI

 

小結

要重構之前,得先瞭解重構的目的、意義,以及如何找到需要重構的程式。

期望重構之後,能對原本可以正常執行的結果完全沒有影響,但程式碼因此具備了更高的可讀性、擴充性、健壯性等等...

重構的基本原則是:

  1. 建立測試,確保安全
  2. 由小到大,絕不貪心
  3. 適可而止,絕不偏執

由於重構在 TDD 中,也佔了很重要的一個角色,所以希望接下來幾篇,可以幫助讀者手把手的跟著練習一遍,這樣看似簡單、又像複雜、又沒啥彈性的程式碼,如何從亂七八糟,變成最後一應俱全的健壯程式。

 

補充

有讀者朋友問到,什麼樣的程式碼算的上是 Bad smell ?

這邊列出筆者工作環境中的門檻值:

  1. 循環複雜度 > 10
  2. 繼承深度 > 3
  3. 區塊深度 > 4
  4. 相似程式碼 > 15行
  5. 綜合可維護性指數 < 75

以上,不代表超過標準就一定不好,但就像健康檢查報告的指數一樣,這些的確是需要被 highlight 出來說明的。

其他靜態程式碼分析的工具與投影片簡介,請參考這篇文章:[.NET][Tool]靜態程式碼分析工具簡介

對敏捷開發有興趣的朋友,可以參考我的粉絲專頁:91敏捷開發之路

對 TDD 課程有興趣的朋友,課程內容、大綱與學員心得,可以參考 skilltree 的公開課程:自動測試與 TDD 實務開發

若需要聯絡我,可以透過粉絲專頁私訊或是側欄的關於我。