Files
gh-cashwu-claude-code-tdd-m…/commands/kb-refactor.md
2025-11-29 18:03:43 +08:00

474 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: 重構消除重複Refactor 階段)。在綠燈的保護下,改善程式碼品質。
---
# TDD Refactor - 重構消除重複Refactor
在測試通過後,消除重複、改善設計。測試是你的安全網。
**【功能名】**{{feature_name}}
## Kent Beck 的 Refactor 理念
> "重構的核心是消除重複。"
> "測試通過後,就是重構的時機。"
### 什麼是重複?
Kent Beck 定義的重複包括:
#### 1. 明顯的程式碼重複
```javascript
// ❌ 重複
function processOrderA(order) {
if (!order) throw new Error('Invalid order');
return order.total * 1.1;
}
function processOrderB(order) {
if (!order) throw new Error('Invalid order');
return order.total * 0.9;
}
// ✅ 消除重複
function validateOrder(order) {
if (!order) throw new Error('Invalid order');
}
function processOrderA(order) {
validateOrder(order);
return order.total * 1.1;
}
```
#### 2. 測試和實作之間的重複
```javascript
// 測試
test('5 * 2 = 10', () => {
expect(multiply(5, 2)).toBe(10);
});
// ❌ 實作中的重複(硬編碼)
function multiply(a, b) {
return 10; // 和測試中的 10 重複!
}
// ✅ 消除重複(真正的實作)
function multiply(a, b) {
return a * b;
}
```
#### 3. 概念上的重複
```javascript
// ❌ 概念重複(魔術數字)
function applyDiscount(price) {
return price * 0.9;
}
function calculateTax(price) {
return price * 0.1;
}
// ✅ 消除重複(提取常數)
const DISCOUNT_RATE = 0.9;
const TAX_RATE = 0.1;
function applyDiscount(price) {
return price * DISCOUNT_RATE;
}
function calculateTax(price) {
return price * TAX_RATE;
}
```
## 重構步驟
### 1. 確認測試通過
```bash
npm test
# 必須是綠燈!🟢
```
**如果是紅燈**
- 不要重構
- 先讓測試通過
### 2. 識別重複或異味
問自己:
- 有重複的程式碼嗎?
- 有硬編碼的值嗎?
- 有不清楚的命名嗎?
- 有太長的函式嗎?
- 有不必要的複雜度嗎?
### 3. 小步重構
**一次只改一個地方**
```
改一小步 → 執行測試 → 通過
改下一步 → 執行測試 → 通過
...
```
### 4. 每次都執行測試
**頻率**:每改一個地方就測試一次
```bash
# 改善命名
npm test
# 提取函式
npm test
# 提取常數
npm test
```
### 5. 如果測試失敗,立即回退
```bash
# 紅燈!
git checkout . # 回退
# 或手動 Undo
```
## 常見重構技巧
### 技巧 1消除硬編碼
**Before**
```javascript
class Dollar {
times(multiplier) {
return new Dollar(10); // 硬編碼
}
}
```
**After**
```javascript
class Dollar {
times(multiplier) {
return new Dollar(this.amount * multiplier);
}
}
```
### 技巧 2提取函式
**Before**
```javascript
function processOrder(order) {
// 驗證
if (!order.items || order.items.length === 0) {
throw new Error('No items');
}
// 計算
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
return total;
}
```
**After**
```javascript
function processOrder(order) {
validateOrder(order);
return calculateTotal(order.items);
}
function validateOrder(order) {
if (!order.items || order.items.length === 0) {
throw new Error('No items');
}
}
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
```
### 技巧 3提取常數
**Before**
```javascript
function calculateShipping(weight) {
if (weight > 1000) return 100;
return 50;
}
```
**After**
```javascript
const HEAVY_WEIGHT_THRESHOLD = 1000;
const SHIPPING_HEAVY = 100;
const SHIPPING_STANDARD = 50;
function calculateShipping(weight) {
if (weight > HEAVY_WEIGHT_THRESHOLD) {
return SHIPPING_HEAVY;
}
return SHIPPING_STANDARD;
}
```
### 技巧 4改善命名
**Before**
```javascript
function calc(x) {
return x * 0.9;
}
```
**After**
```javascript
function applyDiscount(price) {
const DISCOUNT_RATE = 0.9;
return price * DISCOUNT_RATE;
}
```
### 技巧 5簡化條件
**Before**
```javascript
function canPurchase(user, product) {
if (user.age >= 18) {
if (user.balance >= product.price) {
if (product.stock > 0) {
return true;
}
}
}
return false;
}
```
**After**
```javascript
function canPurchase(user, product) {
return user.age >= 18
&& user.balance >= product.price
&& product.stock > 0;
}
```
## 何時停止重構?
Kent Beck 的判斷標準:
### 停止的信號
**可以停止了**
- 沒有明顯的重複
- 程式碼清楚易懂
- 命名恰當
- 函式簡短
**也可以停止**
- 想不到明顯的改善
- 繼續重構的收益不大
- 想寫下一個測試了
### 不要過度重構
```javascript
// ✅ 夠好了
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
// ❌ 過度了(除非真的需要)
class TotalCalculator {
constructor(strategy) {
this.strategy = strategy;
}
calculate(items) {
return this.strategy.compute(items);
}
}
class StandardCalculationStrategy {
compute(items) {
return items.reduce((sum, item) =>
sum + this.calculateItemTotal(item), 0);
}
calculateItemTotal(item) {
return new Money(item.price)
.multiply(item.quantity)
.getAmount();
}
}
// ... 太複雜了!
```
## 重構的節奏
Kent Beck 建議的節奏:
```
快速寫測試(幾分鐘)
快速讓測試通過(幾分鐘)
快速重構(幾分鐘)← 現在在這裡
回到寫測試
```
**每個階段都要快**
- 不要花太久在重構
- 幾分鐘就好
- 保持節奏
## 更新 journey.md
```markdown
#### 🔵 Refactor - 重構
重構內容:
1. {改善項目 1}
- 重構前:{簡述}
- 重構後:{簡述}
- 原因:{為什麼}
2. {改善項目 2}
...
測試狀態:✅ 持續通過
#### 📝 下一步
執行 /kb-review 回顧下一步
```
## 範例Money 的重構
### 重構前
```javascript
class Dollar {
constructor(amount) {
this.amount = amount;
}
times(multiplier) {
return new Dollar(this.amount * multiplier);
}
}
class Franc {
constructor(amount) {
this.amount = amount;
}
times(multiplier) {
return new Franc(this.amount * multiplier);
}
}
```
**發現重複**Dollar 和 Franc 幾乎一樣!
### 重構步驟
**Step 1提取父類別**
```javascript
class Money {
constructor(amount) {
this.amount = amount;
}
}
class Dollar extends Money {
times(multiplier) {
return new Dollar(this.amount * multiplier);
}
}
class Franc extends Money {
times(multiplier) {
return new Franc(this.amount * multiplier);
}
}
```
測試:✅ 通過
**Step 2將 times 提升到父類別**
```javascript
class Money {
constructor(amount) {
this.amount = amount;
}
times(multiplier) {
return new Money(this.amount * multiplier);
}
}
class Dollar extends Money {}
class Franc extends Money {}
```
測試:✅ 通過
**消除了重複!**
## 重構的勇氣來自哪裡?
Kent Beck**測試給你重構的勇氣**
```
沒有測試:
😰 不敢改程式碼
😰 怕改壞東西
😰 程式碼越來越爛
有測試:
😎 隨時可以重構
😎 測試會告訴你有沒有改壞
😎 程式碼越來越好
```
## 下一步
重構完成?執行:
```
/kb-review
```
回顧這一輪,計劃下一步!
## 記住 Kent Beck 的話
> "重構的時機是在測試通過之後。"
> "重複是設計的敵人。"
> "小步重構,頻繁測試,永遠保持綠燈。"
> "測試讓你有勇氣重構,重構讓程式碼保持清潔。"