本文用eclipse的自動重構(gòu)功能對一個程序?qū)嵗M行重構(gòu),目的是探索Eclipse自動重構(gòu)可以在多大程度上輔助重構(gòu)這個過程。程序?qū)嵗褂谩禦efactoring:Improving the Design of Existing Code》一書中的例子。 ![]()
![]() 我們的目的是把在抽取函數(shù)中不會被修改的each作為參數(shù);會被修改的thisAmount作為返回值。解決的辦法是,把 double thisAmount = 0; 這行代碼移到switch語句的上面,變成這樣: 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; } 選中這段代碼,在右鍵菜單中選擇"重構(gòu)/抽取方法",eclipse這次變得聰明點了,如圖。 ![]() 選擇"預覽"按鈕預先查看重構(gòu)后的結(jié)果,符合我們最初的目的。 ![]() 選擇"確定"按鈕,重構(gòu)后的代碼片斷如下: public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + " "; while(rentals.hasMoreElements()){ Rental each = (Rental)rentals.nextElement(); double thisAmount = amountFor(each); frequentRenterPoints ++; if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE &&each.getDaysRented()>1) frequentRenterPoints ++; result += " " + each.getMovie().getTitle() + " " +String.valueOf(thisAmount) + " "; totalAmount += thisAmount; } result += "Amount owed is " + String.valueOf(totalAmount) + " "; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } /** * @param each * @return */ 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; } 2、選中amountFor()的參數(shù)each,在右鍵菜單中選擇"重構(gòu)/重命名",在對話框中輸入新的名稱:aRental,選擇確定,amountFor()中所有each的引用全部被替換成新的名稱。用同樣的辦法修改amountFor()中的局部變量thisAmount為result。重構(gòu)后的amountFor()代碼如下: /** * @param aRental * @return */ 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; } 三、重構(gòu)第二步:搬移"金額計算"代碼 目的: 1、 將函數(shù)amountFor()轉(zhuǎn)移到Rental類中,并更名為getCharge()。 2、 更新并替換所有對amountFor()的引用。 重構(gòu)方法: Move Method Change Method signatrue Inline Method Inline Temp 方法: 1、選中函數(shù)amountFor()的定義,在右鍵菜單中選擇"重構(gòu)/移動",顯示參數(shù)設置對話框。把新方法名改成getCharge。按下"確定"按鈕,Customer Class中的amountFor()函數(shù)被移動到Rental Class中,并更名為:getCharge()。 ![]() 同時eclipse自動在Customer的amountFor()函數(shù)中添加一行對新函數(shù)的"委托"代碼: private double amountFor(Rental aRental) { return aRental.getCharge(); } 這行代碼會產(chǎn)生編譯錯誤,原因是amountFor()的private型被傳遞到了新的方法中: /** * @param this * @return */ private double getCharge() { …… } 2、繼續(xù)重構(gòu)!選中getCharge()方法,在右鍵菜單中選擇"重構(gòu)/更改方法特征符",彈出參數(shù)選擇對話框,把訪問修飾符從private改成public。Eclipse的編譯錯誤提示自動消失。 ![]() 3、回到Customer類,把所有對amountFor()引用的地方替換成直接對getCharge()的引用。選中Customer類的函數(shù)amountFor(Rental aRental),在右鍵菜單中選擇"重構(gòu)/內(nèi)聯(lián)",出現(xiàn)參數(shù)選擇對話框。 ![]() 選擇"確認"按鈕,引用amountFor()的地方被替換成對getCharge()的引用。 public String statement() { …… double thisAmount = each.getCharge(); …… } 4、除去臨時變量thisAmount。 選中變量thisAmount,在右鍵菜單中選擇"重構(gòu)/內(nèi)聯(lián)",重構(gòu)預覽窗口如下,可見達到了重構(gòu)的目的。按下"確認"按鈕重構(gòu)代碼。 ![]() statement()代碼: public String statement() { double totalAmount = 0; // 總消費金額 int frequentRenterPoints = 0; // 常客積點 Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + " "; while(rentals.hasMoreElements()){ Rental each = (Rental)rentals.nextElement(); //取得一筆租借記錄 // add frequent renter points(累加 常客積點) frequentRenterPoints ++; // add bouns for a two day new release rental if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1) frequentRenterPoints ++; // show figures for this rental(顯示此筆租借數(shù)據(jù)) result += " " + each.getMovie().getTitle() + " " + String.valueOf(each.getCharge()) + " "; totalAmount += each.getCharge(); } // add footer lines(結(jié)尾打印) result += "Amount owed is " + String.valueOf(totalAmount) + " "; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } 四、重構(gòu)第三步:提煉"常客積點計算"代碼 目的:提取"常客積點計算"代碼并放在Rental類中,"常客積點計算"代碼如下。 public String statement() { …… // add frequent renter points frequentRenterPoints ++; // add bouns for a two day new release rental if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1) frequentRenterPoints ++; …… } 重構(gòu)后的代碼如下: frequentRenterPoints += each.getFrequentRenterPoints(); 重構(gòu)方法: Extract Method Move Method Change Method signatrue Inline Method 方法: 1、 首先,抽取代碼到獨立的函數(shù)中。 用"抽取方法"重構(gòu)代碼,函數(shù)名:getFrequentRenterPoints。很遺憾,eclipse的不能生成諸如:frequentRenterPoints += getFrequentRenterPoints(Rental aRental); 的代碼。原因是執(zhí)行自增操作的局部變量frequentRenterPoints要出現(xiàn)在等式右邊,因此抽取函數(shù)getFrequentRenterPoints()一定要把frequentRenterPoints作為參數(shù)。手工修改函數(shù)和對函數(shù)的引用,重構(gòu)后的代碼如下: public String statement() { …… while(rentals.hasMoreElements()){ …… frequentRenterPoints += getFrequentRenterPoints(each); …… } …… } /** * @param each * @return */ private int getFrequentRenterPoints(Rental each) { if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1) return 2; else return 1; } 2、 把getFrequentRenterPoints()移動到Rental類中。 3、 對getFrequentRenterPoints()"更改方法特征符"為public。 4、 對Customer的函數(shù)getFrequentRenterPoints()執(zhí)行內(nèi)聯(lián)操作,重構(gòu)目標完成。 五、重構(gòu)第四步:去除臨時變量(totalAmount和frequentRenterPoints) 目的:去除臨時變量(totalAmount和frequentRenterPoints) 方法: 1、 分析totalAmount和frequentRenterPoints的定義和引用結(jié)構(gòu)如下: // 聲明和定義 double totalAmount = 0; int frequentRenterPoints = 0; …… // 在循環(huán)中修改 while(rentals.hasMoreElements()){ …… frequentRenterPoints += each.getFrequentRenterPoints(); …… totalAmount += each.getCharge(); …… } …… // 在循環(huán)外使用 result += "Amount owed is " + String.valueOf(totalAmount) + " "; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; …… 上述兩個變量在循環(huán)體外面定義和使用,在循環(huán)中被修改,運用Replace Temp with Query方法去除這兩個臨時變量是一項稍微復雜的重構(gòu)。很遺憾,eclipse目前不支持這樣的重構(gòu)。 2、手工修改代碼。 六、重構(gòu)第五步:運用多態(tài)取代與價格相關的條件邏輯 目的: 1、 把Rental類中的函數(shù)getCharge()移動到Movie類中。 2、 把Rental類中的函數(shù)getFrequentRenterPoints()移動到Movie類中。 重構(gòu)方法: Move Method Inline Method 方法: 1、 選中Rental類中的函數(shù)getCharge(),右鍵菜單選中"重構(gòu)/移動",eclipse提示找不到接收者,不能移動。原因在于這行語句: switch(getMovie().getPriceCode()){//取得影片出租價格 選中getMovie(),右鍵菜單選中"重構(gòu)/內(nèi)聯(lián)",確定后代碼成為: switch(_movie.getPriceCode()){ //取得影片出租價格 選中getCharge(),執(zhí)行"重構(gòu)/移動"后,函數(shù)被移動到Movie類中。然而這只是部分達成了重構(gòu)目的,我們發(fā)現(xiàn),移動后的代碼把Rental作為參數(shù)傳給了getCharge(),手工修改一下,代碼變成: class Movie …… /** * @param this * @return */ public double getCharge(int _daysRented) { double result = 0; switch(getPriceCode()){ //取得影片出租價格 case Movie.REGULAR: // 普通片 result += 2; if(_daysRented>2) result += (_daysRented-2)*1.5; break; case Movie.NEW_RELEASE: // 新片 result += _daysRented*3; break; case Movie.CHILDRENS: // 兒童片 result += 1.5; if(_daysRented>3) result += (_daysRented-3)*1.5; break; } return result; } class Rental…… /** * @param this * @return */ public double getCharge() { return _movie.getCharge(_daysRented); } 2、用同樣的步驟處理getFrequentRenterPoints(),重構(gòu)后的代碼: class Movie …… /** * @param frequentRenterPoints * @param this * @return */ public int getFrequentRenterPoints(int daysRented) { if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1) return 2; else return 1; } class Rental…… /** * @param frequentRenterPoints * @param this * @return */ public int getFrequentRenterPoints(int daysRented) { if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1) return 2; else return 1; } 七、重構(gòu)第六步:終于……我們來到繼承 目的:對switch語句引入state模式。 方法: 很遺憾,不得不在這里提前結(jié)束eclipse的自動重構(gòu)之旅。Eclipse幾乎不能做結(jié)構(gòu)上的重構(gòu)。也許Martin Fowler在書中呼喚的自動重構(gòu)工具止于"工具輔助下的重構(gòu)工作"這一理念。藝術是人類的專利,編程藝術的夢想將持續(xù)下去。 感興趣的讀者可以查看手工重構(gòu)的最后一步代碼。將重構(gòu)進行到底! 附錄:eclipse支持的重構(gòu)方法(摘自eclipse中文幫助) 名稱功能 撤銷執(zhí)行上一次重構(gòu)的"撤銷"。只要除了重構(gòu)之外尚未執(zhí)行任何其它源更改,重構(gòu)撤銷緩沖區(qū)就有效。 重做執(zhí)行上一次撤銷重構(gòu)的"重做"。只要除了重構(gòu)之外尚未執(zhí)行任何其它源更改,重構(gòu)撤銷/重做緩沖區(qū)就有效。 重命名 啟動"重命名"重構(gòu)對話框:重命名所選擇的元素,并更正對元素的所有引用(如果啟用了的話)(還在其它文件中)。可用于:方法、字段、局部變量、方法參數(shù)、類型、編譯單元、包、源文件夾和項目,以及解析為這些元素類型中的其中一種的文本選擇部分。 移動 啟動"移動"重構(gòu)對話框:移動所選擇的元素,并更正對元素的所有引用(如果啟用了的話)(還在其它文件中)。適用于:一個實例方法(可以將它移至某個組件)、一個或多個靜態(tài)方法、靜態(tài)字段、類型、編譯單元、包、源文件夾和項目,以及解析為這些元素類型中的其中一種的文本選擇部分。 更改方法特征符啟動"更改方法特征符"重構(gòu)對話框。更改參數(shù)名稱、參數(shù)類型和參數(shù)順序,并更新對相應方法的所有引用。此外,可以除去或添加參數(shù),并且可以更改方法返回類型和它的可視性。可以將此重構(gòu)應用于方法或解析為方法的文本選擇。 將匿名類轉(zhuǎn)換為嵌套類啟動"將匿名類轉(zhuǎn)換為嵌套類"重構(gòu)對話框。幫助您將匿名內(nèi)部類轉(zhuǎn)換為成員類。可以將此重構(gòu)應用于匿名內(nèi)部類。 將嵌套類型轉(zhuǎn)換成頂層啟動"將嵌套類型轉(zhuǎn)換為頂層類型"重構(gòu)對話框。為所選成員類型創(chuàng)建新的 Java 編譯單元,并根據(jù)需要更新所有引用。對于非靜態(tài)成員類型,將添加字段以允許訪問先前的外圍實例。可以將此重構(gòu)應用于成員類型或解析為成員類型的文本。 下推啟動"下推"重構(gòu)對話框。將一組方法和字段從一個類移至它的子類。可以將此重構(gòu)應用于在同一個類型中聲明的一個或多個方法和字段或者字段或方法內(nèi)的文本選擇。 上拉啟動"上拉"重構(gòu)型中聲明的一個或多個方法、字段和成員類型,也可以應用于字段、方法或成員類型內(nèi)的文本選擇。向?qū)А⒆侄位蚍椒ㄒ浦疗渎暶黝惖某惢蛘撸▽τ诜椒ǎ⿲⒎椒暶鳛槌愔械某橄箢悺?梢詫⒋酥貥?gòu)應用于在同一個類 抽取接口啟動"抽取接口"重構(gòu)對話框。使用一組方法創(chuàng)建新接口并使選擇的類實現(xiàn)該接口,并盡可能地將對該類的引用更改為對新接口的引用(可選)。可以將此重構(gòu)應用于類型。 盡可能使用超類型啟動"盡可能使用超類型"對話框。將某個類型的出現(xiàn)替換為它的其中一個超類型,在執(zhí)行此替換之前,需要標識所有有可能進行此替換的位置。此重構(gòu)可用于類型。 內(nèi)聯(lián)啟動"內(nèi)聯(lián)"重構(gòu)對話框。內(nèi)聯(lián)局部變量、方法或常量。此重構(gòu)可用于方法、靜態(tài)終態(tài)字段和解析為方法、靜態(tài)終態(tài)字段或局部變量的文本選擇。 抽取方法啟動"抽取方法"重構(gòu)對話框。創(chuàng)建一個包含當前所選擇的語句或表達式的新方法,并將選擇替換為對新方法的引用。可以使用編輯菜單中的擴大選擇至以獲取有效的選擇范圍。此功能對于清理冗長、雜亂或過于復雜的方法是很有用的。 抽取局部變量啟動"抽取變量"重構(gòu)對話框。創(chuàng)建為當前所選擇的表達式指定的新變量,并將選擇替換為對新變量的引用。此重構(gòu)可用于解析為局部變量的文本選擇。可以使用編輯菜單中的擴大選擇至以獲取有效的選擇范圍。 抽取常量啟動"抽取常量"重構(gòu)對話框。從所選表達式創(chuàng)建靜態(tài)終態(tài)字段并替換字段引用,并且可以選擇重寫同一表達式的其它出現(xiàn)位置。此重構(gòu)可用于靜態(tài)終態(tài)字段和解析為靜態(tài)終態(tài)字段的文本選擇。 將局部變量轉(zhuǎn)換為字段啟動"將局部變量轉(zhuǎn)換為字段"重構(gòu)對話框。將局部變量轉(zhuǎn)換為字段。如果該變量是在創(chuàng)建時初始化的,則此操作將把初始化移至新字段的聲明或類的構(gòu)造函數(shù)。此重構(gòu)可用于解析為局部變量的文本選擇。 封裝字段啟動"自封裝字段"重構(gòu)對話框。將對字段的所有引用替換為 getting 和 setting 方法。它適用于所選擇的字段或解析為字段的文本選擇。 |
溫馨提示:喜歡本站的話,請收藏一下本站!