refactor

refactor

熱門

外科手術式的程式碼重構,在不改變行為的前提下提升可維護性。涵蓋提取函式、重新命名變數、拆解上帝函式、改善型別安全、消除程式碼壞味道,以及套用設計模式。比 repo-rebuilder 較不激進,適用於漸進式改善。

3.6萬星標
4464分支
更新於 2026/6/23
SKILL.md
唯讀
名稱
refactor
描述

外科手術式的程式碼重構,在不改變行為的前提下提升可維護性。涵蓋提取函式、重新命名變數、拆解上帝函式、改善型別安全、消除程式碼壞味道,以及套用設計模式。比 repo-rebuilder 較不激進,適用於漸進式改善。

Refactor

概述

在不改變外部行為的前提下,改善程式碼結構與可讀性。重構是漸進式的演化,而非革命。用於改善既有程式碼,而非從頭改寫。

使用時機

在以下情況使用此技能:

  • 程式碼難以理解或維護
  • 函式/類別過於龐大
  • 需要處理程式碼壞味道
  • 因程式碼結構導致新增功能困難
  • 使用者要求「清理這段程式碼」、「重構這個」、「改善這個」

重構原則

黃金法則

  1. 行為保持不變 - 重構不改變程式碼的功能,只改變實作方式
  2. 小步驟 - 每次只做微小變更,變更後立即測試
  3. 版本控制是你的好朋友 - 在每個安全狀態前後進行提交
  4. 測試至關重要 - 沒有測試,你只是在編輯,而不是重構
  5. 一次只做一件事 - 不要將重構與功能變更混在一起

何時不該重構

- 程式碼能正常運作且未來不會再修改(如果沒壞,就別修)
- 沒有測試的關鍵生產環境程式碼(先補測試)
- 在時間壓力很大的情況下
- 「只是因為想重構」——需要有明確的目的

常見程式碼壞味道與修正

1. 過長的方法/函式

# 不好:200 行的函式包辦所有事情
- async function processOrder(orderId) {
-   // 50 行:取得訂單
-   // 30 行:驗證訂單
-   // 40 行:計算價格
-   // 30 行:更新庫存
-   // 20 行:建立出貨
-   // 30 行:發送通知
- }

# 好:拆解成專注的函式
+ async function processOrder(orderId) {
+   const order = await fetchOrder(orderId);
+   validateOrder(order);
+   const pricing = calculatePricing(order);
+   await updateInventory(order);
+   const shipment = await createShipment(order);
+   await sendNotifications(order, pricing, shipment);
+   return { order, pricing, shipment };
+ }

2. 重複的程式碼

# 不好:相同邏輯出現在多處
- function calculateUserDiscount(user) {
-   if (user.membership === 'gold') return user.total * 0.2;
-   if (user.membership === 'silver') return user.total * 0.1;
-   return 0;
- }
-
- function calculateOrderDiscount(order) {
-   if (order.user.membership === 'gold') return order.total * 0.2;
-   if (order.user.membership === 'silver') return order.total * 0.1;
-   return 0;
- }

# 好:提取共用邏輯
+ function getMembershipDiscountRate(membership) {
+   const rates = { gold: 0.2, silver: 0.1 };
+   return rates[membership] || 0;
+ }
+
+ function calculateUserDiscount(user) {
+   return user.total * getMembershipDiscountRate(user.membership);
+ }
+
+ function calculateOrderDiscount(order) {
+   return order.total * getMembershipDiscountRate(order.user.membership);
+ }

3. 過大的類別/模組

# 不好:知道太多的上帝物件
- class UserManager {
-   createUser() { /* ... */ }
-   updateUser() { /* ... */ }
-   deleteUser() { /* ... */ }
-   sendEmail() { /* ... */ }
-   generateReport() { /* ... */ }
-   handlePayment() { /* ... */ }
-   validateAddress() { /* ... */ }
-   // 還有 50 個方法...
- }

# 好:每個類別單一職責
+ class UserService {
+   create(data) { /* ... */ }
+   update(id, data) { /* ... */ }
+   delete(id) { /* ... */ }
+ }
+
+ class EmailService {
+   send(to, subject, body) { /* ... */ }
+ }
+
+ class ReportService {
+   generate(type, params) { /* ... */ }
+ }
+
+ class PaymentService {
+   process(amount, method) { /* ... */ }
+ }

4. 過長的參數列表

# 不好:太多參數
- function createUser(email, password, name, age, address, city, country, phone) {
-   /* ... */
- }

# 好:將相關參數分組
+ interface UserData {
+   email: string;
+   password: string;
+   name: string;
+   age?: number;
+   address?: Address;
+   phone?: string;
+ }
+
+ function createUser(data: UserData) {
+   /* ... */
+ }

# 更好:使用建造者模式處理複雜建構
+ const user = UserBuilder
+   .email('test@example.com')
+   .password('secure123')
+   .name('Test User')
+   .address(address)
+   .build();

5. 依戀情結

# 不好:方法使用另一個物件的資料多於自己的
- class Order {
-   calculateDiscount(user) {
-     if (user.membershipLevel === 'gold') {
+       return this.total * 0.2;
+     }
+     if (user.accountAge > 365) {
+       return this.total * 0.1;
+     }
+     return 0;
+   }
+ }

# 好:將邏輯移到擁有資料的物件
+ class User {
+   getDiscountRate(orderTotal) {
+     if (this.membershipLevel === 'gold') return 0.2;
+     if (this.accountAge > 365) return 0.1;
+     return 0;
+   }
+ }
+
+ class Order {
+   calculateDiscount(user) {
+     return this.total * user.getDiscountRate(this.total);
+   }
+ }

6. 基本型別偏執

# 不好:使用基本型別表示領域概念
- function sendEmail(to, subject, body) { /* ... */ }
- sendEmail('user@example.com', 'Hello', '...');

- function createPhone(country, number) {
-   return `${country}-${number}`;
- }

# 好:使用領域型別
+ class Email {
+   private constructor(public readonly value: string) {
+     if (!Email.isValid(value)) throw new Error('Invalid email');
+   }
+   static create(value: string) { return new Email(value); }
+   static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }
+ }
+
+ class PhoneNumber {
+   constructor(
+     public readonly country: string,
+     public readonly number: string
+   ) {
+     if (!PhoneNumber.isValid(country, number)) throw new Error('Invalid phone');
+   }
+   toString() { return `${this.country}-${this.number}`; }
+   static isValid(country: string, number: string) { /* ... */ }
+ }
+
+ // 使用方式
+ const email = Email.create('user@example.com');
+ const phone = new PhoneNumber('1', '555-1234');

7. 魔術數字/字串

# 不好:未經說明的數值
- if (user.status === 2) { /* ... */ }
- const discount = total * 0.15;
- setTimeout(callback, 86400000);

# 好:具名常數
+ const UserStatus = {
+   ACTIVE: 1,
+   INACTIVE: 2,
+   SUSPENDED: 3
+ } as const;
+
+ const DISCOUNT_RATES = {
+   STANDARD: 0.1,
+   PREMIUM: 0.15,
+   VIP: 0.2
+ } as const;
+
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
+
+ if (user.status === UserStatus.INACTIVE) { /* ... */ }
+ const discount = total * DISCOUNT_RATES.PREMIUM;
+ setTimeout(callback, ONE_DAY_MS);

8. 巢狀條件式

# 不好:箭頭型程式碼
- function process(order) {
-   if (order) {
-     if (order.user) {
-       if (order.user.isActive) {
-         if (order.total > 0) {
-           return processOrder(order);
+         } else {
+           return { error: 'Invalid total' };
+         }
+       } else {
+         return { error: 'User inactive' };
+       }
+     } else {
+       return { error: 'No user' };
+     }
+   } else {
+     return { error: 'No order' };
+   }
+ }

# 好:防衛子句 / 提前回傳
+ function process(order) {
+   if (!order) return { error: 'No order' };
+   if (!order.user) return { error: 'No user' };
+   if (!order.user.isActive) return { error: 'User inactive' };
+   if (order.total <= 0) return { error: 'Invalid total' };
+   return processOrder(order);
+ }

# 更好:使用 Result 型別
+ function process(order): Result<ProcessedOrder, Error> {
+   return Result.combine([
+     validateOrderExists(order),
+     validateUserExists(order),
+     validateUserActive(order.user),
+     validateOrderTotal(order)
+   ]).flatMap(() => processOrder(order));
+ }

9. 死程式碼

# 不好:未使用的程式碼殘留
- function oldImplementation() { /* ... */ }
- const DEPRECATED_VALUE = 5;
- import { unusedThing } from './somewhere';
- // 被註解掉的程式碼
- // function oldCode() { /* ... */ }

# 好:移除它
+ // 刪除未使用的函式、匯入和註解掉的程式碼
+ // 如果之後需要,git 歷史記錄中還有

10. 不當的親密關係

# 不好:一個類別深入另一個類別的內部
- class OrderProcessor {
-   process(order) {
-     order.user.profile.address.street;  // 太過親密
-     order.repository.connection.config;  // 破壞封裝
+   }
+ }

# 好:詢問,不要指揮
+ class OrderProcessor {
+   process(order) {
+     order.getShippingAddress();  // Order 知道如何取得
+     order.save();  // Order 知道如何儲存自己
+   }
+ }

提取方法重構

重構前後對比

# 重構前:一個長函式
- function printReport(users) {
-   console.log('USER REPORT');
-   console.log('============');
-   console.log('');
-   console.log(`Total users: ${users.length}`);
-   console.log('');
-   console.log('ACTIVE USERS');
-   console.log('------------');
-   const active = users.filter(u => u.isActive);
-   active.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Active: ${active.length}`);
-   console.log('');
-   console.log('INACTIVE USERS');
-   console.log('--------------');
-   const inactive = users.filter(u => !u.isActive);
-   inactive.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Inactive: ${inactive.length}`);
- }

# 重構後:提取方法
+ function printReport(users) {
+   printHeader('USER REPORT');
+   console.log(`Total users: ${users.length}\n`);
+   printUserSection('ACTIVE USERS', users.filter(u => u.isActive));
+   printUserSection('INACTIVE USERS', users.filter(u => !u.isActive));
+ }
+
+ function printHeader(title) {
+   const line = '='.repeat(title.length);
+   console.log(title);
+   console.log(line);
+   console.log('');
+ }
+
+ function printUserSection(title, users) {
+   console.log(title);
+   console.log('-'.repeat(title.length));
+   users.forEach(u => console.log(`- ${u.name} (${u.email})`));
+   console.log('');
+   console.log(`${title.split(' ')[0]}: ${users.length}`);
+   console.log('');
+ }

引入型別安全

從無型別到有型別

# 重構前:無型別
- function calculateDiscount(user, total, membership, date) {
-   if (membership === 'gold' && date.getDay() === 5) {
-     return total * 0.25;
-   }
-   if (membership === 'gold') return total * 0.2;
-   return total * 0.1;
- }

# 重構後:完整的型別安全
+ type Membership = 'bronze' | 'silver' | 'gold';
+
+ interface User {
+   id: string;
+   name: string;
+   membership: Membership;
+ }
+
+ interface DiscountResult {
+   original: number;
+   discount: number;
+   final: number;
+   rate: number;
+ }
+
+ function calculateDiscount(
+   user: User,
+   total: number,
+   date: Date = new Date()
+ ): DiscountResult {
+   if (total < 0) throw new Error('Total cannot be negative');
+
+   let rate = 0.1; // 預設 bronze
+
+   if (user.membership === 'gold' && date.getDay() === 5) {
+     rate = 0.25; // 金卡會員星期五 bonus
+   } else if (user.membership === 'gold') {
+     rate = 0.2;
+   } else if (user.membership === 'silver') {
+     rate = 0.15;
+   }
+
+   const discount = total * rate;
+
+   return {
+     original: total,
+     discount,
+     final: total - discount,
+     rate
+   };
+ }

重構用的設計模式

策略模式

# 重構前:條件邏輯
- function calculateShipping(order, method) {
-   if (method === 'standard') {
-     return order.total > 50 ? 0 : 5.99;
-   } else if (method === 'express') {
-     return order.total > 100 ? 9.99 : 14.99;
+   } else if (method === 'overnight') {
+     return 29.99;
+   }
+ }

# 重構後:策略模式
+ interface ShippingStrategy {
+   calculate(order: Order): number;
+ }
+
+ class StandardShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 50 ? 0 : 5.99;
+   }
+ }
+
+ class ExpressShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 100 ? 9.99 : 14.99;
+   }
+ }
+
+ class OvernightShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return 29.99;
+   }
+ }
+
+ function calculateShipping(order: Order, strategy: ShippingStrategy) {
+   return strategy.calculate(order);
+ }

責任鏈模式

# 重構前:巢狀驗證
- function validate(user) {
-   const errors = [];
-   if (!user.email) errors.push('Email required');
+   else if (!isValidEmail(user.email)) errors.push('Invalid email');
+   if (!user.name) errors.push('Name required');
+   if (user.age < 18) errors.push('Must be 18+');
+   if (user.country === 'blocked') errors.push('Country not supported');
+   return errors;
+ }

# 重構後:責任鏈模式
+ abstract class Validator {
+   abstract validate(user: User): string | null;
+   setNext(validator: Validator): Validator {
+     this.next = validator;
+     return validator;
+   }
+   validate(user: User): string | null {
+     const error = this.doValidate(user);
+     if (error) return error;
+     return this.next?.validate(user) ?? null;
+   }
+ }
+
+ class EmailRequiredValidator extends Validator {
+   doValidate(user: User) {
+     return !user.email ? 'Email required' : null;
+   }
+ }
+
+ class EmailFormatValidator extends Validator {
+   doValidate(user: User) {
+     return user.email && !isValidEmail(user.email) ? 'Invalid email' : null;
+   }
+ }
+
+ // 建立責任鏈
+ const validator = new EmailRequiredValidator()
+   .setNext(new EmailFormatValidator())
+   .setNext(new NameRequiredValidator())
+   .setNext(new AgeValidator())
+   .setNext(new CountryValidator());

重構步驟

安全的重構流程

1. 準備
   - 確保測試存在(如果缺少就補寫)
   - 提交當前狀態
   - 建立功能分支

2. 識別
   - 找出要處理的程式碼壞味道
   - 理解程式碼的功能
   - 規劃重構方式

3. 重構(小步驟)
   - 做一個小變更
   - 執行測試
   - 測試通過就提交
   - 重複以上步驟

4. 驗證
   - 所有測試通過
   - 必要時進行手動測試
   - 效能不變或改善

5. 清理
   - 更新註解
   - 更新文件
   - 最終提交

重構檢查清單

程式碼品質

  • [ ] 函式短小(少於 50 行)
  • [ ] 函式只做一件事
  • [ ] 沒有重複的程式碼
  • [ ] 名稱具描述性(變數、函式、類別)
  • [ ] 沒有魔術數字/字串
  • [ ] 死程式碼已移除

結構

  • [ ] 相關程式碼放在一起
  • [ ] 明確的模組邊界
  • [ ] 依賴關係朝單一方向流動
  • [ ] 沒有循環依賴

型別安全

  • [ ] 所有公開 API 都已定義型別
  • [ ] 沒有未經說明的 any 型別
  • [ ] 可為 null 的型別已明確標記

測試

  • [ ] 重構後的程式碼有測試
  • [ ] 測試涵蓋邊界情況
  • [ ] 所有測試通過

常見重構操作

操作 說明
Extract Method 將程式碼片段提取為方法
Extract Class 將行為移到新類別
Extract Interface 從實作建立介面
Inline Method 將方法主體移回呼叫端
Inline Class 將類別行為移到呼叫端
Pull Up Method 將方法移到父類別
Push Down Method 將方法移到子類別
Rename Method/Variable 改善清晰度
Introduce Parameter Object 將相關參數分組
Replace Conditional with Polymorphism 用多型取代 switch/if
Replace Magic Number with Constant 使用具名常數
Decompose Conditional 拆解複雜條件
Consolidate Conditional 合併重複條件
Replace Nested Conditional with Guard Clauses 使用提前回傳
Introduce Null Object 消除 null 檢查
Replace Type Code with Class/Enum 強型別化
Replace Inheritance with Delegation 組合優於繼承