SKILL.md
readonlyread-only
name
refactor
description
Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.
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 | 組合優於繼承 |






