程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 《重構》C#版實現(二)抽取AmountFor方法

《重構》C#版實現(二)抽取AmountFor方法

編輯:C#入門知識

在開始前的最後一刻,還需要提醒的是,也許你帶著滿腔熱情,想把代碼重構得優雅極致,但就像平時的工作一樣,重構的過程更需要的是理性思考,而不是沖動。每一次重構實踐,都應該包含了對設計、實現、可維護、可擴展性,以及成本的估算和權衡。
所以,首先看看對於Statement方法來說,從哪裡入手比較合適?
[csharp] 
public string Statement() 

    double totalAmount = 0; 
    int frequentRenterPoints = 0; 
    string result = "Rental Record for " + Name + "\n"; 
    foreach (Rental rental in Rentals) 
    { 
        double thisAmount = 0; 
         
        // determine amounts for each line 
        switch (rental.Movie.PriceCode) 
        { 
            case Movie.REGULAR: 
                thisAmount += 2; 
                if (rental.DaysRented > 2) 
                    thisAmount += (rental.DaysRented - 2) * 1.5; 
                break; 
            case Movie.NEW_RELEASE: 
                thisAmount += rental.DaysRented * 3; 
                break; 
            case Movie.CHILDRENS: 
                thisAmount += 1.5; 
                if (rental.DaysRented > 3) 
                    thisAmount += (rental.DaysRented - 3) * 1.5; 
                break; 
        } 
 
        // add frequent renter points 
        frequentRenterPoints++; 
        // add bonus for a two day new release rental 
        if (rental.Movie.PriceCode == Movie.NEW_RELEASE && 
            rental.DaysRented > 1) frequentRenterPoints++; 
         
        // show figures for this rental 
        result += "\t" + rental.Movie.Title + "\t" + thisAmount.ToString() + "\n"; 
        totalAmount += thisAmount; 
    } 
    // add footer lines 
    result += "Amount owed is " + totalAmount.ToString() + "\n"; 
    result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points"; 
    return result; 

實際上,應該換一個問題:對現在的程序來說,哪裡最容易產生變化?畢竟,如果代碼不會變化,重構就是多余的行為。優雅的代碼只能滿足程序員的審美需求,而不是客戶對於功能的需求。
《重構》中指出:該程序最可能產生的變化有三點:
1.報告輸出的類型可能變化,例如由普通字符串變成HTML格式的文本
2.計費方式可能發生變化
3.影片類型可能發生變化
無論哪一種變化,上面的Statement方法都不能很好地應對——它太胖了,涉及的邏輯、細節太多。所以,第一步,《重構》的作者選擇將該方法中最長、並且同時涉及上述所有變化的,計算每一部影片花費的那個switch給抽取出去。
具體步驟是:
1.在Customer中新建一個計算花費的新方法AmountFor:
[csharp] 
public int AmountFor(Rental rental) 

    return 0; 

2.把switch的代碼copy(不是剪切)到AmountFor裡:
[csharp] 
public int AmountFor(Rental rental) 

    switch (rental.Movie.PriceCode) 
    { 
        case Movie.REGULAR: 
            thisAmount += 2; 
            if (rental.DaysRented > 2) 
                thisAmount += (rental.DaysRented - 2) * 1.5; 
            break; 
        case Movie.NEW_RELEASE: 
            thisAmount += rental.DaysRented * 3; 
            break; 
        case Movie.CHILDRENS: 
            thisAmount += 1.5; 
            if (rental.DaysRented > 3) 
                thisAmount += (rental.DaysRented - 3) * 1.5; 
            break; 
    } 
    return 0; 

3.但此時的代碼是編譯不了得,因為AmountFor中,thisAmount不存在。所以,在switch前面添加一個thisAmount的聲明:
[csharp] 
public int AmountFor(Rental rental) 

    int thisAmount = 0; 
    switch (rental.Movie.PriceCode) 
    { 
        case Movie.REGULAR: 
            thisAmount += 2; 
            if (rental.DaysRented > 2) 
                thisAmount += (rental.DaysRented - 2) * 1.5; 
            break; 
        case Movie.NEW_RELEASE: 
            thisAmount += rental.DaysRented * 3; 
            break; 
        case Movie.CHILDRENS: 
            thisAmount += 1.5; 
            if (rental.DaysRented > 3) 
                thisAmount += (rental.DaysRented - 3) * 1.5; 
            break; 
    } 
    return thisAmount; 

4.此時,代碼還是編譯不了,因為花費計算時用到了小數,而thisAmount是整數。加上強制轉換後:
[csharp] 
public int AmountFor(Rental rental) 

    int thisAmount = 0; 
    switch (rental.Movie.PriceCode) 
    { 
        case Movie.REGULAR: 
            thisAmount += 2; 
            if (rental.DaysRented > 2) 
                thisAmount += (int)((rental.DaysRented - 2) * 1.5); 
            break; 
        case Movie.NEW_RELEASE: 
            thisAmount += rental.DaysRented * 3; 
            break; 
        case Movie.CHILDRENS: 
            thisAmount += (int)1.5; 
            if (rental.DaysRented > 3) 
                thisAmount += (int)((rental.DaysRented - 3) * 1.5); 
            break; 
    } 
    return thisAmount; 

5.在認為新的方法完成後,去掉Statement中的switch代碼塊,變成:
[csharp] 
public string Statement() 

    double totalAmount = 0; 
    int frequentRenterPoints = 0; 
    string result = "Rental Record for " + Name + "\n"; 
    foreach (Rental rental in Rentals) 
    { 
        double thisAmount = AmountFor(rental); 
 
        // add frequent renter points 
        frequentRenterPoints++; 
        // add bonus for a two day new release rental 
        if (rental.Movie.PriceCode == Movie.NEW_RELEASE && 
            rental.DaysRented > 1) frequentRenterPoints++; 
         
        // show figures for this rental 
        result += "\t" + rental.Movie.Title + "\t" + thisAmount.ToString() + "\n"; 
        totalAmount += thisAmount; 
    } 
    // add footer lines 
    result += "Amount owed is " + totalAmount.ToString() + "\n"; 
    result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points"; 
    return result; 

6.接著是非常重要的一步,運行上一篇中的單元測試項目Tests

7.意外發現,所有測試都失敗了,查看StatementForCharles的錯誤消息如下:
[plain] 
Assert.AreEqual 失敗。應為: < 
Rental Record for Charles 
    BraveHeart  12 
    GodFather   6.5 
Amount owed is 18.5 
You earned 3 frequent renter points 
>,實際為: < 
Rental Record for Charles 
    BraveHeart  12 
    GodFather   6 
Amount owed is 18 
You earned 3 frequent renter points>。 
8.結果中發現,所有數值都是整數,而期待結果中包含小數。想起剛才我們為了讓編譯通過而添加的強制轉型動作,可以據此合理推出問題就出在AmountFor的返回值類型上。遂更改AmountFor如下:
[csharp] 
public double AmountFor(Rental rental) 

    double thisAmount = 0; 
    switch (rental.Movie.PriceCode) 
    { 
        case Movie.REGULAR: 
            thisAmount += 2; 
            if (rental.DaysRented > 2) 
                thisAmount += (rental.DaysRented - 2) * 1.5; 
            break; 
        case Movie.NEW_RELEASE: 
            thisAmount += rental.DaysRented * 3; 
            break; 
        case Movie.CHILDRENS: 
            thisAmount += 1.5; 
            if (rental.DaysRented > 3) 
                thisAmount += (rental.DaysRented - 3) * 1.5; 
            break; 
    } 
    return thisAmount; 

9.運行單元測試項目,結果如下:


至此,一個簡單的方法抽取(Extract Method)重構實踐方算是完成了,總結的步驟如下:
1.思考現有代碼的缺陷、確定重構的價值 www.2cto.com
2.從重構價值入手,確定重構入手點
3.在修改原方法前,先建立一個方法,該方法名稱必須能表達被抽取內容的語義(概念)
4.將待抽取內容復制到新方法中,並根據需要添加局部變量的定義
5.去除原方法中的相關內容,替換成新方法的調用
6.運行單元測試
7.如果測試不通過,檢查測試結果,或調試源代碼,直到測試通過為止
8.重構完成
這個例子包含了作者刻意添加的一個小意外,主要是為了演示測試的重要性。但即使不考慮這種意外,上面的步驟還是十分繁復的,對於重構新手而言,一步一步跟著做可能收獲會更大。除非對重構技術十分熟悉,否則不要輕易跳過上面的任何一步。一來,良好的基礎和習慣是十分重要的。二來,每一步都有它的考慮,跳過任何一步意味著少了一份思考。
作者:virtualxmars

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved