SKILL.md
唯讀
名稱
refactor
描述
外科手術式的程式碼重構,在不改變行為的前提下提升可維護性。涵蓋提取函式、重新命名變數、拆解上帝函式、改善型別安全、消除程式碼壞味道,以及套用設計模式。比 repo-rebuilder 較不激進,適用於漸進式改善。
Refactor
概述
在不改變外部行為的前提下,改善程式碼結構與可讀性。重構是漸進式的演化,而非革命。用於改善既有程式碼,而非從頭改寫。
使用時機
在以下情況使用此技能:
- 程式碼難以理解或維護
- 函式/類別過於龐大
- 需要處理程式碼壞味道
- 因程式碼結構導致新增功能困難
- 使用者要求「清理這段程式碼」、「重構這個」、「改善這個」
重構原則
黃金法則
- 行為保持不變 - 重構不改變程式碼的功能,只改變實作方式
- 小步驟 - 每次只做微小變更,變更後立即測試
- 版本控制是你的好朋友 - 在每個安全狀態前後進行提交
- 測試至關重要 - 沒有測試,你只是在編輯,而不是重構
- 一次只做一件事 - 不要將重構與功能變更混在一起
何時不該重構
- 程式碼能正常運作且未來不會再修改(如果沒壞,就別修)
- 沒有測試的關鍵生產環境程式碼(先補測試)
- 在時間壓力很大的情況下
- 「只是因為想重構」——需要有明確的目的
常見程式碼壞味道與修正
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 | 組合優於繼承 |






