在當代軟體工程中,重構已成為不可或缺的核心實踐。它不僅能顯著減輕開發人員的維護負擔,更能大幅提升開發產能。正如 developerWorks 上 David Carew 所言:現今開發者的大部分工作聚焦於修改既有代碼而非撰寫新代碼。隨著修改與擴展的累積,軟體內部結構漸趨惡化,而重構正是在不改變外部行為的前提下,優化內部結構,使代碼更易理解、修改成本更低。
在 Java 開發環境中,Eclipse 內建的重構工具(如 Rename、Extract Method 等)已帶來實質效益:
然而,現有重構功能難免無法滿足特定專案或團隊的獨特需求。此時,擴展 Eclipse 平台、開發客製化重構功能便成為最佳解決方案。若該功能具備通用價值,更可貢獻回 Eclipse 社區,造福廣大開發者。
本文將以一個具體案例示範如何開發新重構功能:將 JUnit 3 風格的測試方法(以 test 開頭的方法名)自動遷移為 JUnit 4 風格(添加 @Test 註解)。進階需求包含支援 @Test(timeout=...) 屬性,透過嚮導頁面讓使用者決定是否加入 timeout 參數及其數值。
啟動重構後,將彈出三頁式嚮導:
確認無誤按下 Finish 後,源代碼即被修改。例如:
package main;
public class TestSomething {
@Test(timeout=500)
public void testSomething() {
}
}
Eclipse 重構操作由三大核心類別協作完成:
標準重構流程:
透過 File → New → Plug-in Project 建立專案 manage.annotation,選用 "Hello, World" 範本以便在 Refactor 選單加入新項目。Action 類別命名為 AnnotationManageAction。
於 MANIFEST.MF 的 Require-Bundle 加入必要依賴:
org.eclipse.jface.text
org.eclipse.ltk.core.refactoring
org.eclipse.ltk.ui.refactoring
org.eclipse.jdt
org.eclipse.jdt.core
修改 plugin.xml,擴展 org.eclipse.ui.actionSets,於 Refactor 選單的 reorgGroup 分隔符下新增 "Annotation Manage" 選單項與工具列按鈕,事件由 AnnotationManageAction 處理。
在 AnnotationManageAction 的 selectionChanged 中驗證使用者選取:僅當單一選取項為 IJavaElement 時啟用 Action,否則置灰並提示不可用。
建立 AnnotationRefactoring 繼承 org.eclipse.ltk.core.refactoring.Refactoring,需實作三大核心方法:
IJavaElement,向上追溯至 COMPILATION_UNIT 層級,收集所有可寫入的編譯單元。RefactoringStatus.createInfoStatus("Initial Condition is OK!")。collectChanges() 掃描所有以 "test" 開頭的方法,若無符合者回傳 FATAL 錯誤,否則回傳 INFO。Change 物件(詳見下節)。代碼修改有兩條路徑:IScanner+IBuffer 直接操作字串,或遍歷 AST 進行結構化編輯。本文採後者,優勢在於語義層面操作,避免字串處理的脆弱性。
建立 AST:
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(cu); // ICompilationUnit
ASTNode root = parser.createAST(null);
訪問節點收集目標方法:
private void getMethods(ASTNode cuu, final List methods) {
cuu.accept(new ASTVisitor() {
public boolean visit(MethodDeclaration node) {
methods.add(node);
return false;
}
});
}
插入 @Test 註解:
private boolean collectChanges(CompilationUnit root, MethodDeclaration method) {
if (method.getName().getFullyQualifiedName().startsWith("test")) {
AST ast = method.getAST();
if (needTimeout) {
NormalAnnotation na = ast.newNormalAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
MemberValuePair pair = ast.newMemberValuePair();
pair.setName(ast.newSimpleName("timeout"));
pair.setValue(ast.newNumberLiteral("500"));
na.values().add(pair);
method.modifiers().add(0, na);
} else {
MarkerAnnotation na = ast.newMarkerAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
method.modifiers().add(0, na);
}
return true;
}
return false;
}
記錄修改並產生 Change:
root.recordModifications();
// ... 修改 AST ...
TextEdit edits = root.rewrite(document, cu.getJavaProject().getOptions(true));
TextFileChange change = new TextFileChange("", (IFile) cu.getResource());
change.setEdit(edits);
若需修改多個檔案,使用 CompositeChange 封裝多個 Change 為單一物件回傳。
Wizard 架構遵循 OCP 原則:界面(WizardPage)、容器、對話框分離,業務邏輯封裝於 Refactoring,支援動態組合頁面。
AnnotationRefactoringWizardPage 繼承 UserInputWizardPage,於 createControl 構建 UI(Checkbox、Text 欄位),並透過 setControl(composite) 納入框架管理。使用 notifyStatus 設定頁面完成狀態,setRefactoring 將參數傳遞給 Refactoring 實例(利用 Observer 模式)。AnnotationRefactoringWizard 繼承 RefactoringWizard,建構子載入 Refactoring,覆寫 addUserInputPages 加入自訂頁面。AnnotationManageAction.run() 中建立 Refactoring 與 Wizard 實例,透過 RefactoringWizardOpenOperation 開啟對話框(會自動執行初始檢查)。善用 Eclipse 重構框架開發客製化功能,能大幅提升開發效率與代碼品質。雖然目前 JDT 的重構支援最完善,但透過本文架構,開發者可為任何基於 Eclipse 的語言環境(C++、Python 等)補強重構能力,推動工具生態的完善。