<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ### 分解并重組statement() 第一個明顯引起我注意的就是長得離譜的statement() 。每當看到這樣長長的函數,我就想把它大卸八塊。要知道,代碼區塊愈小,代碼的功能就愈容易管理,代碼的處理和搬移也都愈輕松。 本章重構過程的第一階段中,我將說明如何把長長的函數切開,并把較小塊的代碼移至更合適的class 內。我希望降低代碼重復量,從而使新的(打印HTML 報表用的)函數更容易撰寫。 第一個步驟是找出代碼的邏輯泥團(logical clump)并運用 Extract Method。本例一個明顯的邏輯泥團就是switch 語句,把它提煉(extract)到獨立函數中似乎比較好。 和任何重構準則一樣,當我提煉一個函數時,我必須知道可能出什么錯。如果我提煉得不好,就可能給程序引入臭蟲。所以重構之前我需要先想出安全作法。由于先前我己經進行過數次這類重構,所以我已經把安全步驟記錄于書后的重構名錄(refactoring catalog)中了。 首先我得在這段代碼里頭找出函數內的局部變量(local variables)和參數(parameters)。我找到了兩個:each 和thisAmount,前者并未被修改,后者會被修改。任何不會被修改的變量都可以被我當成參數傳入新的函數,至于會被修改的變量就需格外小心。如果只有一個變量會被修改,我可以把它當作返回值。thisAmount 是個臨時變量,其值在每次循環起始處被設為0,并且在switch 語句之前不會改變,所以我可以直接把新函數的返回值賦予它。 下面兩頁展示重構前后的代碼。重構前的代碼在左頁,重構后的代碼在右頁。凡是從函數提煉出來的代碼,以及新代碼所做的任何修改,只要我覺得不是明顯到可以一眼看出,就以粗體字標示出來特別提醒你。本章剩余部分將延續這種左右比對形式。 ~~~ public String statement() { double totalAmount = 0; //總消費金。 int frequentRenterPoints = 0; //常客積點 Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); //取得一筆租借記。 //determine amounts for each line switch (each.getMovie().getPriceCode()) { //取得影片出租價格 case Movie.REGULAR: //普通片 thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: //新片 thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: //兒童。 thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } // add frequent renter points (累計常客積點。 frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental(顯示此筆租借記錄) result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines(結尾打印) result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ ~~~ public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = amountFor(each); //計算一筆租片費。 // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } } private int amountFor(Rental each) { //計算一筆租片費。 int thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: //普通片 thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: //新片 thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: //兒童。 thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } return thisAmount; } ~~~ 每次做完這樣的修改之后,我都要編譯并測試。這一次起頭不算太好——測試失敗了,有兩筆測試數據告訴我發生錯誤。一陣迷惑之后我明白了自己犯的錯誤。我愚蠢地將amountFor() 的返回值型別聲明為int,而不是double 。 ~~~ private double amountFor(Rental each) { //計算一筆租片費。 double thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: //普通片 thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: //新片 thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: //兒童。 thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } return thisAmount; } ~~~ 我經常犯這種愚蠢可笑的錯誤,而這種錯誤往往很難發現。在這里,Java 無怨無尤地把double 型別轉換為int 型別,而且還愉快地做了取整動作[Java Spec]。還好此處這個問題很容易發現,因為我做的修改很小,而且我有很好的測試。借著這個意外疏忽,我要闡述重構步驟的本質:由于每次修改的幅度都很小,所以任何錯誤都很容易發現。你不必耗費大把時間調試,哪怕你和我一樣粗心。 TIP:重構技術系以微小的步伐修改程序。如果你犯下錯誤,很容易便可發現它。 由于我用的是Java ,所以我需要對代碼做一些分析,決定如何處理局部變量。如果擁有相應的工具,這個工作就超級簡單了。Smalltalk 也的確擁有這樣的工具——Refactoring Browser。運用這個工具,重構過程非常輕松,我只需標示出需要重構的代碼,在選單中點選Extract Method,輸入新的函數名稱,一切就自動搞定。而且工具決不會像我那樣犯下愚蠢可笑的錯誤。我非常盼望早日出現Java 版本的重構工具! 現在,我已經把原本的函數分為兩塊,可以分別處理它們。我不喜歡amountFor() 內的某些變量名稱,現在是修改它們的時候。 下面是原本的代碼。 ~~~ private int amountFor(Rental each) { //計算一筆租片費。 int thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: //普通片 thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: //新片 thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: //兒童。 thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } return thisAmount; } ~~~ 下面是易名后的代碼: ~~~ private double amountFor(Rental aRental) { //計算一筆租片費。 double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: //普通片 result += 2; if (aRental.getDaysRented() > 2) result += (aRental.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: //新片 result += aRental.getDaysRented() * 3; break; case Movie.CHILDRENS: //兒童。 result += 1.5; if (aRental.getDaysRented() > 3) result += (aRental.getDaysRented() - 3) * 1.5; break; } return result; } ~~~ 易名之后我需要重新編譯并測試,確保沒有破壞任何東西。 更改變量名稱是值得的行為嗎?絕對值得。好的代碼應該清楚表達出自己的功能,變量名稱是代碼清晰的關鍵。如果為了提高代碼的清晰度,需要修改某些東西的名字,大膽去做吧。只要有良好的查找丨替換工具,更改名稱并不困難。語言所提供的強型別檢驗(strong typing)以及你自己的測試機制會指出任何你遺漏的東西。記住: TIP:任何一個傻瓜都能寫出計算機可以理解的代碼。惟有寫出人類容易理解的代碼,才是優秀的程序員。 代碼應該表現自己的目的,這一點非常重要。閱讀代碼的時候,我經常進行重構。這樣,隨著對程序的理解逐漸加深,我也就不斷地把這些理解嵌入代碼中,這么一來才不會遺忘我曾經理解的東西。 **搬移「金額計算」代碼** 觀察amountFor() 時,我發現這個函數使用了來自Rental class 的信息,卻沒有使 用來自Customer class 的信息。 ~~~ class Customer... private double amountFor(Rental aRental) { double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (aRental.getDaysRented() > 2) result += (aRental.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += aRental.getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (aRental.getDaysRented() > 3) result += (aRental.getDaysRented() - 3) * 1.5; break; } return result; } ~~~ 這立刻使我懷疑它是否被放錯了位置。絕大多數情況下,函數應該放在它所使用的數據的所屬object(或說class)內,所以amountFor() 應該移到Rental class 去。為了這么做,我要運用Move Method。首先把代碼拷貝到Rental class 內, 調整代碼使之適應新家,然后重新編譯。像下面這樣。 ~~~ class Rental... double getCharge() { double result = 0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (getDaysRented() > 2) result += (getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (getDaysRented() > 3) result += (getDaysRented() - 3) * 1.5; break; } return result; } ~~~ 在這個例子里,「適應新家」意味去掉參數。此外,我還要在搬移的同時變更函數名稱。 現在我可以測試新函數是否正常工作。只要改變Customer.amountFor() 函數內容,使它委托(delegate)新函數即可。 ~~~ class Customer... private double amountFor(Rental aRental) { return aRental.getCharge(); } ~~~ 現在我可以編譯并測試,看看有沒有破壞了什么東西。 下一個步驟是找出程序中對于舊函數的所有引用(reference)點,并修改它們,讓它們改用新函數。 下面是原本的程序。 ~~~ class Customer... public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = amountFor(each); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ 本例之中,這個步驟很簡單,因為我才剛剛產生新函數,只有一個地方使用了它。一般情況下你得在可能運用該函數的所有classes 中查找一遍。 ~~~ class Customer public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ 做完這些修改之后(圖1.3),下一件事就是去掉舊函數。編譯器會告訴我是否我漏掉了什么。然后我進行測試,看看有沒有破壞什么東西。 ![](https://box.kancloud.cn/2016-08-15_57b1b4f1a15d7.gif) 圖1.3 搬移「金額計算」函數后,所有classes 的狀態(state) 有時候我會保留舊函數,讓它調用新函數。如果舊函數是一個public 函數,而我又不想修改其他class 的接口,這便是一種有用的手法。 當然我還想對Rental.getCharge() 做些修改,不過暫時到此為止,讓我們回。 ~~~ public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ 下一件引我注意的事是:thisAmount 如今變成多余了。它接受each.charge 的執行結果,然后就不再有任何改變。所以我可以運用 Replace Temp with Query 除去。 ~~~ public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf (each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } } ~~~ 做完這份修改,我立刻編譯并測試,保證自己沒有破壞任何東西。 我喜歡盡量除去這一類臨時變量。臨時變量往往形成問題,它們會導致大量參數被傳來傳去,而其實完全沒有這種必要。你很容易失去它們的蹤跡,尤其在長長的函數之中更是如此。當然我這么做也需付出性能上的代價,例如本例的費用就被計算了兩次。但是這很容易在Rental class 中被優化。而且如果代碼有合理的組織和管理,優化會有很好的效果。我將在p.69的「重構與性能」一節詳談這個問題。 **提煉「常客積點計算」代碼** 下一步要對「常客積點計算」做類似處理。點數的計算視影片種類而有不同,不過不像收費規則有那么多變化。看來似乎有理由把積點計算責任放在Rental class 身上。首先我們需要針對「常客積點計算」這部分代碼(以下粗體部分)運用 Extract Method 重構準則。 再一次我又要尋找局部變量。這里再一次用到了each ,而它可以被當作參數傳入新函數中。另一個臨時變量是frequentRenterPoints。本例中的它在被使用之前已經先有初值,但提煉出來的函數并沒有讀取該值,所以我們不需要將它當作參數傳進去,只需對它執行「附添賦值動作」(appending assignment,operator+=)就行了。 我完成了函數的提煉,重新編譯并測試;然后做一次搬移,再編譯、再測試。重構時最好小步前進,如此一來犯錯的幾率最小。 ~~~ public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } } ~~~ ~~~ class Customer... public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } class Rental... int getFrequentRenterPoints() { if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1) return 2; else return 1; } ~~~ 我利用重構前后的UML(Unified Modeling Language ,統一建模語言)圖形(圖1.4 至圖1.7〕總結剛才所做的修改。和先前一樣,左頁是修改前的圖,右頁是修改后的圖。 ![](https://box.kancloud.cn/2016-08-15_57b1b4f1b7914.gif) 圖1.4 [常客積點計算」函數被提煉及搬移之前的class diagrams ![](https://box.kancloud.cn/2016-08-15_57b1b4f1cda78.gif) 圖1.5 「常客積點計算」函數被提煉及搬移之前的sequence diagrams ![](https://box.kancloud.cn/2016-08-15_57b1b4f1dfbdc.gif) 圖1.6 「常客積點計算」函數被提煉及搬移之后的class diagrams ![](https://box.kancloud.cn/2016-08-15_57b1b4f1f34e5.gif) 圖1.7 「常客積點計算」函數被提煉及搬移之后的sequence diagrams **去除臨時變量** 正如我在前面提過的,臨時變量可能是個問題。它們只在自己所屬的函數中有效,所以它們會助長「冗長而復雜」的函數。這里我們有兩個臨時變量,兩者都是用來從Customer 對象相關的Rental 對象中獲得某個總量。不論ASCII 版或HTML 版都需要這些總量。我打算運用 Replace Temp with Query,并利用所謂的query method 來取代totalAmount 和frequentRentalPoints 這兩個臨時變量。由于class 內的任何函數都可以取用(調用)上述所謂query methods ,所以它能夠促進較干凈的設計,而非冗長復雜的函數: ~~~ class Customer... public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ 首先我以Customer class 的getTotalCharge() 取代totalAmount 。 ~~~ class Customer... public String statement() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; } //add footer lines result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } //譯注:此即所謂query method private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } return result; } ~~~ 這并不是 Replace Temp with Query的最簡單情況。由于totalAmount 在循環內部內賦值,我不得不把循環復制到query method 中。 重構之后,重新編譯并測試,然后以同樣手法處理frequentRenterPoints。 ~~~ class Customer... public String statement() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; } //add footer lines result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } ~~~ ~~~ public String statement() { Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; } //add footer lines result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points"; return result; } //譯注:此即所謂query method private int getTotalFrequentRenterPoints(){ int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; } ~~~ 圖1.8至圖1.11分別以UML class diagram(類圖)和interaction diagram (交互作用圖)展示statement() 重構前后的變化。 ![](https://box.kancloud.cn/2016-08-15_57b1b4f2164ab.gif) 圖1.8 「總量計算」函數被提煉前的class diagram ![](https://box.kancloud.cn/2016-08-15_57b1b4f22b143.gif) 圖1.9 「總量計算」函數被提煉前的sequence diagram ![](https://box.kancloud.cn/2016-08-15_57b1b4f2403ab.gif) 圖1.10 「總量計算」函數被提煉后的class diagram ![](https://box.kancloud.cn/2016-08-15_57b1b4f253a53.gif) 圖1.11 「總量計算」函數被提煉后的sequence diagram 做完這次重構,有必要停下來思考一下。大多數重構都會減少代碼總量,但這次卻增加了代碼總量,那是因為Java 1.1需要大量語句(statements)來設置一個總和(summing)循環。哪怕只是一個簡單的總和循環,每個元素只需一行代碼,外圍的支持代碼也需要六行之多。這其實是任何程序員都熟悉的習慣寫法,但代碼數量還是太多了。 這次重構存在另一個問題,那就是性能。原本代碼只執行while 循環一次,新版本要執行三次。如果循環耗時很多,就可能大大降低程序的性能。單單為了這個原因,許多程序員就不愿進行這個重構動作。但是請注意我的用詞:如果和可能。除非我進行評測(profile),否則我無法確定循環的執行時間,也無法知道這個循環是否被經常使用以至于影響系統的整體性能。重構時你不必擔心這些,優化時你才需要擔心它們,但那時候你已處于一個比較有利的位置,有更多選擇可以完成有效優化(見p.69的討論)。 現在,Customer class 內的任何代碼都可以取用這些query methods 了,如果系統他處需要這些信息,也可以輕松地將query methods 加入Customer class 接口。如果沒有這些query methods ,其他函數就必須了解Rental class,并自行建立循環。在一個復雜系統中,這將使程序的編寫難度和維護難度大大增加。 你可以很明顯看出來,htmlStatement() 和statement() 是不同的。現在,我應該脫下「重構」的帽子,戴上「添加功能」的帽子。我可以像下面這樣編寫htmlStatement() ,并添加相關測試。 ~~~ public String htmlStatement() { Enumeration rentals = _rentals.elements(); String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); //show figures for each rental result += each.getMovie().getTitle()+ ": " + String.valueOf(each.getCharge()) + "<BR>\n"; } //add footer lines result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n"; result += "On this rental you earned <EM>" + String.valueOf(getTotalFrequentRenterPoints()) + "</EM> frequent renter points<P>"; return result; } ~~~ 通過計算邏輯的提煉,我可以完成一個htmlStatement() ,并復用(reuse)原本statements() 內的所有計算。我不必剪剪貼貼,所以如果計算規則發生改變,我只需在程序中做一處修改。完成其他任何類型的報表也都很快而且很容易。這次重構并不花很多時間,其中大半時間我用來弄清楚代碼所做的事,而這是我無論如何都得做的。 前述有些重構碼系從ASCII 版本里頭拷貝過來——主要是循環設置部分。更深入的重構動作可以清除這些重復代碼。我可以把處理表頭(header)、表尾(footer)和報表細目的代碼都分別提煉目出來。在 Form Template Method 實例中,.你可以看到如何做這些動作。但是,現在用戶又開始嘀咕了,他們準備修改影片分類規則。我們尚未清楚他們想怎么做,但似乎新分類法很快就要引入,現有的分類法馬上就要變更。與之相應的費用計算方式和常客積點計算方式都還待決定,現在就對程序做修改,肯定是愚蠢的。我必須進入費用計算和常客積點計算中,把「因條件 而異的代碼」(譯注:指的是switch 語句內的case 子句)替換掉,這樣才能為 將來的改變鍍上一層保護膜。現在,請重新戴回「重構」這頂帽子。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看