首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Eclipse 自動重構實戰:以《重構》經典範例深度解析

     編輯:Java 相關知識

前言

Martin Fowler 經典名著《重構:改善既有程式的設計》第一章的「影片出租計費程式」是學習重構技法的最佳教材。本文實際操作 Eclipse(含中文化語言包)的自動重構功能,逐步重構該範例,並深度分析 Eclipse 在程式碼層級與設計層級重構的支援程度與限制。

一、重構前準備

  • 範例來源:《重構》第一章程式碼(附件提供各步驟前後對照)
  • 工具版本:Eclipse 3.x + 中文語言包
  • 核心類別:Customer(客戶)、Rental(租借)、Movie(影片)

二、重構第一步:分解並重組 statement()

目標

  1. 將 statement() 中的 switch 邏輯提煉為獨立方法 amountFor()
  2. 修正參數命名:each → aRental,區域變數 thisAmount → result

實作過程與 Eclipse 行為分析

步驟 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;
}

三、重構第二步:搬移「金額計算」至 Rental 類別

目標

  1. 將 amountFor() 移至 Rental 並更名 getCharge()
  2. 移除 Customer 中的委託代碼與暫時變數 thisAmount

關鍵步驟與 Eclipse 互動

  1. Move Method:選取 amountFor() → 「重構/移動」 → 目標 Rental → 新名稱 getCharge。Eclipse 在 Customer 保留委託 stub:return aRental.getCharge();
  2. Change Method Signature:將 Rental.getCharge() 由 private 改為 public,消除編譯錯誤。
  3. Inline Method:對 Customer.amountFor() 執行「內聯」,所有呼叫點直接改呼叫 each.getCharge()。
  4. Inline Temp:對暫時變數 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) 形式,因為區域變數在等式左邊會被修改。

解決策略:手動微調 + Eclipse 輔助

  1. 先手動改寫為回傳 int 的方法:
    private int getFrequentRenterPoints(Rental each) { ... return 2; } else return 1; }
  2. 再用 Move Method 移至 Rental,Change Signature 改 public。
  3. 最後對 Customer 的舊方法執行 Inline Method。

五、重構第四步:移除暫時變數 totalAmount、frequentRenterPoints

Replace Temp with Query 需將累加邏輯提煉為查詢方法,但 Eclipse 目前 不支援此重構。需手動完成:

  • 新增 getTotalCharge()、getTotalFrequentRenterPoints() 於 Customer,內部迭代計算。
  • 以方法呼叫取代變數引用。

六、重構第五步:以多態取代價格條件邏輯

目標

將 Rental.getCharge()、getFrequentRenterPoints() 移至 Movie,並引入 State/Strategy 模式消除 switch。

Eclipse 限制實測

  1. Move Method 失敗:因方法內呼叫 getMovie().getPriceCode(),Eclipse 找不到合適接收者。
  2. Inline getMovie():先將 getMovie() 內聯為直接存取 _movie 欄位,再嘗試移動,僅部分成功。
  3. 參數調整需手動:移動後方法簽名變為 getCharge(Rental),需手動改為 getCharge(int daysRented) 並移除對 Rental 依賴。

手動完成多態重構

建立 Price 抽象類別與 RegularPrice、NewReleasePrice、ChildrensPrice 具體類別,Movie 持有 Price 參考,徹底消除 switch。此層級的設計重構超出 Eclipse 自動化能力範圍。

七、Eclipse 重構功能總結與心得

重構類別支援程度備註
重命名⭐⭐⭐⭐⭐完美支援,跨檔案更新引用
抽取方法⭐⭐⭐⭐參數/回傳值判斷偶需輔助調整
移動方法/欄位⭐⭐⭐⭐產生委託 stub,需配合 Change Signature
內聯方法/暫時變數⭐⭐⭐⭐⭐極度實用,安全可預覽
變更方法特徵⭐⭐⭐⭐支援參數增減、順序、可視性、回傳型別
Replace Temp with Query❌不支援,需手動完成
設計層級(引入多態/模式)❌屬於架構決策,工具無法自動化

核心結論

「自動重構工具的定位應為『工具輔助下的重構工作』,人仍承擔大部分設計決策與複雜轉換。」

  • Eclipse 在程式碼結構層級(重命名、抽取、移動、內聯、變更簽名)極為成熟,配合 Undo/Redo、Preview 可安心實驗。
  • 設計層級重構(引入繼承、多態、設計模式)仍需開發者具備領域知識與重構素養手動主導。
  • 善用 Eclipse 重構功能可將「機械性、易出錯」的程式碼搬移自動化,讓開發者專注於「語意性、創造性」的設計改善。

附錄:Eclipse 內建重構清單(中文說明)

  • Undo/Redo:重構專用緩衝區,非重構操作會清空
  • Rename:方法、欄位、區域變數、參數、類型、套件、專案等
  • Move:實例方法、靜態方法/欄位、類型、編譯單元、套件
  • Change Method Signature:參數名稱/型別/順序、增減參數、回傳型別、可視性
  • Extract Method/Variable/Constant:從陳述式/運算式建立新方法/變數/常數
  • Inline:方法、靜態常數、區域變數內聯
  • Encapsulate Field:產生 getter/setter 並取代直接存取
  • Pull Up / Push Down:方法/欄位在繼承體系上下移動
  • Extract Interface / Superclass:從現有類別萃取介面/超類別
  • Convert Anonymous to Nested / Nested to Top-level:匿名/巢狀類別轉換
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved