Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:03:43 +08:00
commit 2d7bed1e9d
8 changed files with 1725 additions and 0 deletions

473
commands/kb-refactor.md Normal file
View File

@@ -0,0 +1,473 @@
---
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 的話
> "重構的時機是在測試通過之後。"
> "重複是設計的敵人。"
> "小步重構,頻繁測試,永遠保持綠燈。"
> "測試讓你有勇氣重構,重構讓程式碼保持清潔。"