Martin Fowler 經典名著《重構:改善既有程式的設計》第一章的「影片出租計費程式」是學習重構技法的最佳教材。本文實際操作 Eclipse(含中文化語言包)的自動重構功能,逐步重構該範例,並深度分析 Eclipse 在程式碼層級與設計層級重構的支援程度與限制。
Customer(客戶)、Rental(租借)、Movie(影片)statement()statement() 中的 switch 邏輯提煉為獨立方法 amountFor()each → aRental,區域變數 thisAmount → result步驟 1:Extract Method
初次選取 switch 區塊執行「重構/抽取方法」,Eclipse 將 each(唯讀)與 thisAmount(寫入)同視為參數,不符合重構規則(應為參數與回傳值)。
解決方案:先將 double thisAmount = 0; 移至 switch 之前,再選取整個 switch 區塊重新抽取,Eclipse 即能正確識別 each 為參數、thisAmount 為回傳值。
步驟 2:Rename
對參數 each、區域變數 thisAmount 分別執行「重構/重命名」,Eclipse 同步更新所有引用,展現優秀的語意感知能力。
amountFor()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;
}
amountFor() 移至 Rental 並更名 getCharge()Customer 中的委託代碼與暫時變數 thisAmountamountFor() → 「重構/移動」 → 目標 Rental → 新名稱 getCharge。Eclipse 在 Customer 保留委託 stub:return aRental.getCharge();Rental.getCharge() 由 private 改為 public,消除編譯錯誤。Customer.amountFor() 執行「內聯」,所有呼叫點直接改呼叫 each.getCharge()。thisAmount 執行「內聯」,徹底移除。statement() 片段while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
frequentRenterPoints++;
if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE
&& each.getDaysRented() > 1)
frequentRenterPoints++;
result += "\t" + each.getMovie().getTitle() + "\t"
+ String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
原始碼中積點邏輯包含 frequentRenterPoints++ 兩次,且第二次在 if 區塊內。Eclipse Extract Method 無法直接產生 frequentRenterPoints += getFrequentRenterPoints(each) 形式,因為區域變數在等式左邊會被修改。
int 的方法:private int getFrequentRenterPoints(Rental each) { ... return 2; } else return 1; }Rental,Change Signature 改 public。Customer 的舊方法執行 Inline Method。totalAmount、frequentRenterPointsReplace Temp with Query 需將累加邏輯提煉為查詢方法,但 Eclipse 目前 不支援此重構。需手動完成:
getTotalCharge()、getTotalFrequentRenterPoints() 於 Customer,內部迭代計算。將 Rental.getCharge()、getFrequentRenterPoints() 移至 Movie,並引入 State/Strategy 模式消除 switch。
getMovie().getPriceCode(),Eclipse 找不到合適接收者。getMovie() 內聯為直接存取 _movie 欄位,再嘗試移動,僅部分成功。getCharge(Rental),需手動改為 getCharge(int daysRented) 並移除對 Rental 依賴。建立 Price 抽象類別與 RegularPrice、NewReleasePrice、ChildrensPrice 具體類別,Movie 持有 Price 參考,徹底消除 switch。此層級的設計重構超出 Eclipse 自動化能力範圍。
| 重構類別 | 支援程度 | 備註 |
|---|---|---|
| 重命名 | ⭐⭐⭐⭐⭐ | 完美支援,跨檔案更新引用 |
| 抽取方法 | ⭐⭐⭐⭐ | 參數/回傳值判斷偶需輔助調整 |
| 移動方法/欄位 | ⭐⭐⭐⭐ | 產生委託 stub,需配合 Change Signature |
| 內聯方法/暫時變數 | ⭐⭐⭐⭐⭐ | 極度實用,安全可預覽 |
| 變更方法特徵 | ⭐⭐⭐⭐ | 支援參數增減、順序、可視性、回傳型別 |
| Replace Temp with Query | ❌ | 不支援,需手動完成 |
| 設計層級(引入多態/模式) | ❌ | 屬於架構決策,工具無法自動化 |
「自動重構工具的定位應為『工具輔助下的重構工作』,人仍承擔大部分設計決策與複雜轉換。」