本文用eclipse的自動重構功能對一個程序實例進行重構,目的是探索Eclipse自動重構可以在多大程度上輔助重構這個過程。程序實例使用《Refactoring:Improving the Design of Existing Code》一書中的例子。
Eclipse的自動重構功能能夠很好地支持各種程序元素的重命名,並自動更新相關的引用。Eclipse能夠支持方法、字段在類之間移動,並自動更新引用。Eclipse較好地支持內聯字段、函數的更新替換。Eclipse較好地支持抽取方法、變量等程序元素。
重構的過程是一個不斷嘗試和探索的過程。Eclipse的重構支持撤銷和重做,並且能夠預覽重構結果,這些是很實用的功能。
Eclipse的重命名、抽取方法、移動、內聯功能、更改方法特征符等代碼結構級別的重構方法,是比較成熟同時也值得使用的功能。至於設計結構上的重構,eclipse還不能很好地支持。但是作者相信,自動重構的理念應該是"工具輔助下的重構工作",人仍然承擔大部分重構工作。
一、預備工作 本文使用《Refactoring:Improving the Design of Existing Code》一書第一章的例子。重構前的代碼及每一步重構後的代碼見附件。讀者最好配合《Refactoring:Improving the Design of Existing Code》一書閱讀本文。
Eclipse使用如下版本:
同時安裝了中文語言包。
二、重構第一步:分解並重組statement() 目的:
1、 把statement()函數中的swich語句提煉到獨立的函數amountFor()中。
2、 修改amountFor()參數命名
重構方法:
Extract Method
Rename Method
方法:
1、選中swich語句的代碼塊,在右鍵菜單中選擇"重構/抽取方法",出現參數對話框。Eclipse自動分析代碼塊中的局部變量,找到了兩個局部變量:each和thisAmount。其中,each只是在代碼塊中被讀取,但thisAmount會在代碼塊中被修改。按照重構Extract Method總結出來的規則,應該把each當作抽取函數的參數、thisAmount當作抽取函數的返回值。然而Eclipse並不做區分,直接把這兩個變量當作抽取新方法的參數,如圖。
我們的目的是把在抽取函數中不會被修改的each作為參數;會被修改的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;
}
選中這段代碼,在右鍵菜單中選擇"重構/抽取方法",eclipse這次變得聰明點了,如圖。
選擇"預覽"按鈕預先查看重構後的結果,符合我們最初的目的。
選擇"確定"按鈕,重構後的代碼片斷如下:
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()的參數each,在右鍵菜單中選擇"重構/重命名",在對話框中輸入新的名稱:aRental,選擇確定,amountFor()中所有each的引用全部被替換成新的名稱。用同樣的辦法修改amountFor()中的局部變量thisAmount為result。重構後的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;
}