refactor 重構指引
重構指引(Refactoring Guide) 目錄 前言與目標 什麼是重構? 重構的核心目標 重構原則 基本原則 SOLID 原則在重構中的應用 重構時機 何時應該進行重構? 重構的紅綠燈系統 常見重構手法 提煉函數(Extract Function) 提煉類別(Extract Class) 簡化條件表達式(Simplify Conditional Expressions) 提煉常數(Extract Constants) 移除死程式碼(Remove Dead Code) 重構流程 標準重構流程 重構檢核清單 Java 重構最佳實務 IDE 重構工具使用 Maven 設定重構支援 重構中的測試策略 安全性考量 重構過程中的安全原則 重構中的資安檢核清單 效能考量 重構對效能的影響 效能測試與監控 重構工具與技術 靜態分析工具 自動化重構工具 持續整合中的重構 常見重構陷阱與解決方案 常見錯誤 最佳實務建議 重構案例研究 遺留系統重構 微服務重構 重構檢核清單 重構前檢核 重構中檢核 重構後檢核 團隊協作與重構 Code Review 中的重構 重構溝通策略 重構效果追蹤 短期追蹤 中期追蹤 長期追蹤 結論 前言與目標 什麼是重構? 重構(Refactoring)是指在不改變程式碼外部行為的前提下,對程式碼內部結構進行改善的過程。這是一種持續性的改進活動,旨在提升程式碼品質、可讀性和可維護性。 重構的核心目標 1. 提高可讀性 目標:讓程式碼更容易理解,降低未來維護的難度 效益: 新團隊成員能快速上手 減少程式碼理解時間 降低錯誤修改的風險 評估指標: 程式碼複雜度(Cyclomatic Complexity) 方法長度 類別職責單一性 2. 改善結構與設計 目標:優化架構,使程式更具彈性、可擴充性 效益: 更容易添加新功能 更好的模組化設計 符合 SOLID 設計原則 評估指標: 耦合度(Coupling) 內聚性(Cohesion) 設計模式使用適當性 3. 減少重複(DRY 原則) 目標:把重複的邏輯抽出來,讓程式碼更簡潔 效益: 減少程式碼維護成本 降低一致性問題 提高程式碼重用性 評估指標: 重複程式碼比例 共用元件使用率 4. 提升可測試性 目標:更清晰的結構有助於單元測試與整合測試 效益: 更容易編寫單元測試 提高測試覆蓋率 更好的依賴注入設計 評估指標: 測試覆蓋率 測試案例數量 模擬物件使用便利性 5. 降低技術債(Technical Debt) 目標:清理過時或混亂的程式碼,避免未來出現更多問題 效益: 提升開發效率 減少維護成本 降低系統風險 評估指標: SonarQube 品質評分 程式碼異味數量 安全漏洞數量 6. 促進團隊協作 目標:統一風格與結構,讓不同開發者更容易接手 效益: 提升團隊開發效率 降低知識傳承成本 統一開發標準 評估指標: 程式碼風格一致性 Code Review 效率 團隊生產力 重構原則 基本原則 1. 保持外部行為不變 重構過程中,程式的功能和對外介面不應改變 所有現有的測試案例應該繼續通過 使用者感受不到任何功能上的差異 2. 小步快跑 每次重構應該是小幅度的改動 頻繁進行測試驗證 避免大範圍的同時修改 3. 測試先行 重構前確保有足夠的測試覆蓋 重構過程中持續執行測試 新增測試案例以驗證重構結果 4. 循序漸進 按照優先順序進行重構 先解決最嚴重的程式碼異味 避免過度重構 SOLID 原則在重構中的應用 1. 單一職責原則(Single Responsibility Principle) // 重構前:一個類別負責多個職責 public class UserManager { public void saveUser(User user) { // 驗證使用者資料 if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new IllegalArgumentException("Invalid email"); } // 儲存到資料庫 DatabaseConnection conn = new DatabaseConnection(); conn.save(user); // 發送通知郵件 EmailService emailService = new EmailService(); emailService.sendWelcomeEmail(user.getEmail()); } } // 重構後:職責分離 public class UserValidator { public void validate(User user) { if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new IllegalArgumentException("Invalid email"); } } } public class UserRepository { public void save(User user) { DatabaseConnection conn = new DatabaseConnection(); conn.save(user); } } public class UserNotificationService { public void sendWelcomeNotification(String email) { EmailService emailService = new EmailService(); emailService.sendWelcomeEmail(email); } } public class UserService { private final UserValidator validator; private final UserRepository repository; private final UserNotificationService notificationService; public UserService(UserValidator validator, UserRepository repository, UserNotificationService notificationService) { this.validator = validator; this.repository = repository; this.notificationService = notificationService; } public void createUser(User user) { validator.validate(user); repository.save(user); notificationService.sendWelcomeNotification(user.getEmail()); } } 2. 開放封閉原則(Open/Closed Principle) // 重構前:修改現有程式碼來新增功能 public class DiscountCalculator { public double calculateDiscount(String customerType, double amount) { if ("REGULAR".equals(customerType)) { return amount * 0.05; } else if ("VIP".equals(customerType)) { return amount * 0.10; } else if ("PREMIUM".equals(customerType)) { return amount * 0.15; } return 0; } } // 重構後:使用策略模式,對擴展開放,對修改封閉 public interface DiscountStrategy { double calculateDiscount(double amount); } public class RegularCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.05; } } public class VipCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.10; } } public class PremiumCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.15; } } public class DiscountCalculator { private final Map<String, DiscountStrategy> strategies; public DiscountCalculator() { strategies = Map.of( "REGULAR", new RegularCustomerDiscount(), "VIP", new VipCustomerDiscount(), "PREMIUM", new PremiumCustomerDiscount() ); } public double calculateDiscount(String customerType, double amount) { DiscountStrategy strategy = strategies.get(customerType); return strategy != null ? strategy.calculateDiscount(amount) : 0; } } 重構時機 何時應該進行重構? 1. 程式碼異味(Code Smells)出現時 長方法(Long Method):方法超過 20-30 行 大類別(Large Class):類別職責過多,超過 200-300 行 重複程式碼(Duplicated Code):相同或相似的程式碼片段重複出現 長參數列表(Long Parameter List):方法參數超過 3-4 個 2. 新增功能前 為新功能建立適當的架構基礎 清理相關的程式碼區域 確保新功能不會增加技術債 3. 修復 Bug 時 分析 Bug 產生的根本原因 改善可能導致類似問題的程式結構 增加相關的測試覆蓋 4. Code Review 過程中 發現程式碼可讀性問題 識別潛在的設計問題 統一團隊的程式碼風格 重構的紅綠燈系統 🟢 綠燈:適合重構 有充足的測試覆蓋(>80%) 沒有緊急的產品發布壓力 團隊對重構區域有充分了解 有足夠的時間進行測試驗證 🟡 黃燈:謹慎重構 測試覆蓋率中等(60-80%) 有適度的時間壓力 重構範圍較大 需要多人協作 🔴 紅燈:暫停重構 測試覆蓋率不足(<60%) 有緊急的產品發布 程式碼變動頻繁 缺乏領域知識 常見重構手法 1. 提煉函數(Extract Function) 目的 將重複的程式碼片段提煉成獨立的函數,提高重用性和可讀性。 ...