From 2d7bed1e9d79f4721a75d9809b6e06bcab5c48e6 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:03:43 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 11 + README.md | 3 + commands/kb-green.md | 415 ++++++++++++++++++++++++++++++++ commands/kb-red.md | 274 +++++++++++++++++++++ commands/kb-refactor.md | 473 +++++++++++++++++++++++++++++++++++++ commands/kb-review.md | 359 ++++++++++++++++++++++++++++ commands/kb-start.md | 129 ++++++++++ plugin.lock.json | 61 +++++ 8 files changed, 1725 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/kb-green.md create mode 100644 commands/kb-red.md create mode 100644 commands/kb-refactor.md create mode 100644 commands/kb-review.md create mode 100644 commands/kb-start.md create mode 100644 plugin.lock.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..3c78dae --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "name": "kent-beck-tdd", + "description": "Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles", + "version": "1.0.0", + "author": { + "name": "cash" + }, + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..96a71d5 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# kent-beck-tdd + +Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles diff --git a/commands/kb-green.md b/commands/kb-green.md new file mode 100644 index 0000000..7c48e5d --- /dev/null +++ b/commands/kb-green.md @@ -0,0 +1,415 @@ +--- +description: 讓測試通過(Green 階段)。用最簡單、甚至是"錯誤"的方式快速讓測試變綠。 +--- + +# TDD Green - 讓測試通過(Green) + +用最簡單的方式讓測試通過。可以作弊、可以硬編碼、可以"錯誤"。 + +**【功能名】**:{{feature_name}} + +## Kent Beck 的 Green 階段理念 + +> "快速讓測試通過,用任何手段。" +> "乾淨的程式碼是目標,但先讓它能動。" + +### 三種讓測試通過的策略 + +Kent Beck 在書中提出的三種策略: + +#### 策略 1:Fake It(假實作) + +**最快速的方式:回傳常數** + +```javascript +// 測試 +test('5 * 2 = 10', () => { + expect(multiply(5, 2)).toBe(10); +}); + +// 假實作:直接回傳 10! +function multiply(a, b) { + return 10; +} +``` + +**為什麼這樣做?** +- 快速得到綠燈 +- 心理上的安全感 +- 用下一個測試來逼出真正的實作 + +#### 策略 2:Obvious Implementation(明顯實作) + +**當實作很明顯時,直接寫出來** + +```javascript +// 測試 +test('加法運算', () => { + expect(add(2, 3)).toBe(5); +}); + +// 明顯實作:加法很簡單 +function add(a, b) { + return a + b; +} +``` + +**何時使用?** +- 實作非常簡單 +- 你很有信心 +- 不需要三角測量 + +#### 策略 3:Triangulation(三角測量) + +**用多個測試來推導正確實作** + +```javascript +// 第 1 個測試 +test('1 + 1 = 2', () => { + expect(add(1, 1)).toBe(2); +}); + +// 假實作 +function add(a, b) { + return 2; +} + +// 第 2 個測試(三角測量) +test('2 + 3 = 5', () => { + expect(add(2, 3)).toBe(5); +}); + +// 被逼出真正的實作 +function add(a, b) { + return a + b; +} +``` + +**何時使用?** +- 不確定正確的實作方式 +- 想從多個角度驗證 +- 設計還不清楚 + +## 實作步驟 + +### 1. 選擇策略 + +根據你的信心程度: + +``` +信心很低 → Fake It(假實作) +信心中等 → Triangulation(三角測量) +信心很高 → Obvious Implementation(明顯實作) +``` + +### 2. 寫最少的程式碼 + +**重點:最少** + +```javascript +// 測試需要一個類別 +test('Dollar 乘法', () => { + const five = new Dollar(5); + const product = five.times(2); + expect(product.amount).toBe(10); +}); + +// 最少的實作 +class Dollar { + constructor(amount) { + this.amount = amount; + } + + times(multiplier) { + return new Dollar(10); // 假實作! + } +} +``` + +### 3. 執行測試 + +```bash +npm test +``` + +**看到綠燈!** 🟢 + +### 4. 更新 journey.md + +```markdown +#### 🟢 Green - 讓測試通過 + +策略:{Fake It / Obvious / Triangulation} + +實作說明: +{簡述你做了什麼} + +程式碼位置:{檔案路徑} + +測試結果:✅ 通過 + +#### 🤔 反思 +- 這個實作明顯是假的/硬編碼的嗎? +- 需要下一個測試來逼出真正的實作嗎? +- 有沒有重複的程式碼? + +#### 📝 下一步 +執行 /kb-refactor 重構 +``` + +## Fake It 的威力 + +### 範例:Money 的演進 + +**第 1 輪:完全假實作** + +```javascript +test('5 * 2 = 10', () => { + const five = new Dollar(5); + expect(five.times(2).amount).toBe(10); +}); + +class Dollar { + constructor(amount) { + this.amount = amount; + } + times(multiplier) { + return new Dollar(10); // 硬編碼! + } +} +``` + +**第 2 輪:三角測量逼出真實作** + +```javascript +test('5 * 3 = 15', () => { + const five = new Dollar(5); + expect(five.times(3).amount).toBe(15); +}); + +class Dollar { + times(multiplier) { + return new Dollar(this.amount * multiplier); // 真實作! + } +} +``` + +## 常見的"作弊"技巧 + +### 技巧 1:回傳常數 + +```javascript +function getWelcomeMessage() { + return "Hello, World!"; // 先硬編碼 +} +``` + +### 技巧 2:複製測試資料 + +```javascript +// 測試 +expect(processData(input)).toEqual({ + status: 'success', + count: 5 +}); + +// 實作:直接回傳期待值 +function processData(input) { + return { status: 'success', count: 5 }; +} +``` + +### 技巧 3:最簡單的 if + +```javascript +// 測試 1 +test('even number', () => { + expect(classify(2)).toBe('even'); +}); + +// 假實作 +function classify(n) { + if (n === 2) return 'even'; +} + +// 測試 2 會逼出真實作 +test('another even number', () => { + expect(classify(4)).toBe('even'); +}); + +function classify(n) { + return n % 2 === 0 ? 'even' : 'odd'; +} +``` + +## 實作的禁忌 + +Kent Beck 說明在 Green 階段應該避免的: + +### ❌ 不要過度設計 + +```javascript +// ❌ 不要這樣(太複雜) +class Dollar { + constructor(amount, currency = 'USD') { + this.amount = amount; + this.currency = currency; + } + + times(multiplier) { + // 處理多幣別、匯率轉換... + } + + // 一堆還不需要的方法 + add() {} + subtract() {} + convert() {} +} + +// ✅ 要這樣(夠用就好) +class Dollar { + constructor(amount) { + this.amount = amount; + } + + times(multiplier) { + return new Dollar(this.amount * multiplier); + } +} +``` + +### ❌ 不要一次實作多個測試 + +```javascript +// 目前只有一個測試需要 times() +// 不要順便實作 add(), subtract() +// 等有測試需要時再加 +``` + +### ❌ 不要重構 + +```javascript +// ❌ Green 階段不要重構 +// 先讓測試通過 +// 重構留給下一步 + +// ✅ 先這樣 +function calculate(x) { + return x * 2; +} + +// 不要在這裡就改成 +const MULTIPLIER = 2; +function calculate(x) { + return x * MULTIPLIER; +} +// 重構留給 /kb-refactor +``` + +## 心態:接受"醜陋"的程式碼 + +Kent Beck 強調的心態轉變: + +``` +階段 1:讓它能動 (Make it work) ← 現在在這裡 + ↓ +階段 2:讓它正確 (Make it right) + ↓ +階段 3:讓它快速 (Make it fast) +``` + +**Green 階段只追求"能動"** +- 硬編碼?沒關係 +- 重複?沒關係 +- 醜陋?沒關係 + +**下一步會改善** + +## 範例完整流程 + +### 測試(失敗) + +```javascript +test('購物車初始總價為 0', () => { + const cart = new ShoppingCart(); + expect(cart.getTotal()).toBe(0); +}); +``` + +### 實作策略選擇 + +**信心評估**:這個很簡單,用 Obvious Implementation + +### 實作程式碼 + +```javascript +// src/shopping-cart.js + +class ShoppingCart { + getTotal() { + return 0; // 最簡單的實作 + } +} + +module.exports = ShoppingCart; +``` + +### 執行測試 + +```bash +npm test +# ✅ 通過! +``` + +### 記錄到 journey.md + +```markdown +#### 🟢 Green - 讓測試通過 + +策略:Obvious Implementation + +實作說明: +建立 ShoppingCart 類別,getTotal() 回傳 0。 +雖然是硬編碼,但足以通過目前的測試。 + +程式碼位置:src/shopping-cart.js + +測試結果:✅ 通過 + +#### 🤔 反思 +- 實作很簡單,是硬編碼 +- 下一個測試:加入商品後總價應該改變 +- 目前沒有明顯重複 + +#### 📝 下一步 +執行 /kb-refactor 檢查是否需要重構 +``` + +## 速度的重要性 + +Kent Beck:**Green 階段要快** + +**目標**: +- 幾秒鐘到幾分鐘 +- 不要花太久時間 +- 快速得到綠燈的心理回饋 + +**如果卡住**: +- 寫更小的測試 +- 用更假的實作 +- 回退重來 + +## 下一步 + +測試通過了?執行: +``` +/kb-refactor +``` + +檢查是否有重複,進行重構! + +## 記住 Kent Beck 的話 + +> "讓測試通過的技巧是暫時降低對程式碼品質的標準。" +> "骯髒的程式碼是通往乾淨程式碼的墊腳石。" +> "當你看到綠燈時,那是重構的信號。" diff --git a/commands/kb-red.md b/commands/kb-red.md new file mode 100644 index 0000000..08f5d2f --- /dev/null +++ b/commands/kb-red.md @@ -0,0 +1,274 @@ +--- +description: 寫下一個測試(Red 階段)。一次只寫一個最小的測試,讓它失敗。 +--- + +# TDD Red - 寫下一個測試(Red) + +寫下一個小測試,看它失敗。 + +**【功能名】**:{{feature_name}} + +## Kent Beck 的 Red 階段理念 + +> "寫一個小測試。讓它失敗。" + +### 一次一個測試的原則 + +**為什麼一次只寫一個?** +- 保持專注 +- 快速反饋 +- 小步前進 +- 容易回退 + +### 如何選擇下一個測試? + +Kent Beck 的建議: + +1. **從 To-Do List 選**(心理 To-Do,不是文件) + - 腦中想到什麼測試,記下來 + - 選最簡單的開始 + +2. **從上一個測試的經驗** + - 上一個測試通過了,下一步自然浮現 + - "如果這樣,那會怎樣?" + +3. **三角測量** + - 如果不確定實作,寫第二個類似的測試 + - 從多個角度逼近正確實作 + +## 寫測試的步驟 + +### 1. 更新 journey.md + +```markdown +### 第 N 輪 - {日期時間} + +#### 🤔 想法 +{為什麼要寫這個測試?從上一輪學到什麼?} + +#### 🔴 Red - 寫測試 +測試名稱:{測試名稱} +``` + +### 2. 寫測試程式碼 + +**測試結構:Given-When-Then** + +```javascript +test('簡短描述測試意圖', () => { + // Given - 準備 + // 【準備】:{為什麼需要這些資料?} + const input = testData; + + // When - 執行 + // 【執行】:{測試什麼行為?} + const result = functionUnderTest(input); + + // Then - 驗證 + // 【驗證】:{為什麼期待這個結果?} + expect(result).toBe(expected); +}); +``` + +### 3. 執行測試,確認失敗 + +```bash +npm test +``` + +**必須看到失敗!** +- 如果沒失敗,測試可能有問題 +- 失敗訊息要清楚 + +### 4. 記錄失敗訊息 + +在 journey.md 中: +```markdown +#### 失敗訊息 +``` +{實際的錯誤訊息} +``` + +#### 📝 下一步 +執行 /kb-green 讓測試通過 +``` + +## 測試大小的藝術 + +### 測試要多小? + +Kent Beck 的答案:**取決於你的信心** + +**信心低(不確定)**: +- 寫更小的測試 +- 更頻繁的反饋 + +**信心高(很確定)**: +- 可以寫大一點的測試 +- 跨越明顯的步驟 + +### 範例:小測試 vs 大測試 + +**小測試(信心低時)**: +```javascript +// 第 1 個測試:最基本的 +test('5 元乘以 2 等於 10 元', () => { + const five = new Dollar(5); + const product = five.times(2); + expect(product.amount).toBe(10); +}); + +// 第 2 個測試:檢查副作用 +test('乘法不改變原物件', () => { + const five = new Dollar(5); + five.times(2); + expect(five.amount).toBe(5); // 原值不變 +}); +``` + +**大測試(信心高時)**: +```javascript +// 直接測試完整行為 +test('Dollar 乘法運算', () => { + const five = new Dollar(5); + const ten = five.times(2); + expect(ten.amount).toBe(10); + expect(five.amount).toBe(5); // 也檢查副作用 +}); +``` + +## 三角測量的時機 + +當你不確定實作時,用三角測量: + +```javascript +// 第 1 個測試 +test('1 + 1 = 2', () => { + expect(add(1, 1)).toBe(2); +}); + +// 最簡單的假實作 +function add(a, b) { + return 2; // 硬編碼 +} + +// 第 2 個測試(三角測量) +test('2 + 3 = 5', () => { + expect(add(2, 3)).toBe(5); +}); + +// 現在必須寫真正的實作 +function add(a, b) { + return a + b; +} +``` + +## To-Do List 技巧 + +Kent Beck 建議在測試程式碼中寫註解: + +```javascript +describe('Dollar', () => { + // TODO: 測試加法 + // TODO: 測試負數 + // TODO: 測試相等性 + + test('乘法', () => { + // 目前的測試 + }); +}); +``` + +或在 journey.md 中: + +```markdown +## 心理 To-Do List +- [x] 基本乘法 +- [ ] 乘法副作用 +- [ ] 加法運算 +- [ ] 負數處理 +``` + +## 常見問題 + +### Q: 要不要測試所有邊界情況? + +Kent Beck:**不用一開始就全測** +- 先從明顯的案例開始 +- 邊界情況在需要時再加入 +- 讓測試自然演進 + +### Q: 測試名稱要多詳細? + +Kent Beck:**描述意圖,不是實作** +```javascript +// ✅ 好的測試名稱 +test('購物車加入商品後,總價增加') + +// ❌ 不好的測試名稱 +test('testAddItem') +``` + +### Q: 要寫測試到什麼程度? + +Kent Beck:**寫到你有信心為止** +- 如果還不確定,再寫一個測試 +- 如果已經確定,可以跳到實作 + +## 範例:Money 第一個測試 + +```javascript +// __tests__/money.test.js + +describe('Dollar', () => { + test('乘法運算', () => { + // 【準備】:建立 5 元的 Dollar 物件 + const five = new Dollar(5); + + // 【執行】:乘以 2 + const product = five.times(2); + + // 【驗證】:結果應該是 10 元 + expect(product.amount).toBe(10); + }); +}); +``` + +執行測試: +```bash +npm test + +# 結果:失敗! +# Error: Dollar is not defined +``` + +在 journey.md 記錄: +```markdown +### 第 1 輪 + +#### 🔴 Red - 寫測試 +測試名稱:Dollar 乘法運算 + +#### 失敗訊息 +``` +Error: Dollar is not defined +``` + +#### 📝 下一步 +執行 /kb-green 建立 Dollar 類別 +``` + +## 下一步 + +測試寫好且失敗了?執行: +``` +/kb-green +``` + +用最簡單的方式讓測試通過! + +## 記住 + +> "測試不是目的,而是思考的工具。" +> "失敗的測試是進步的開始。" +> "一次一小步,但要持續前進。" diff --git a/commands/kb-refactor.md b/commands/kb-refactor.md new file mode 100644 index 0000000..f1ca68d --- /dev/null +++ b/commands/kb-refactor.md @@ -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 的話 + +> "重構的時機是在測試通過之後。" +> "重複是設計的敵人。" +> "小步重構,頻繁測試,永遠保持綠燈。" +> "測試讓你有勇氣重構,重構讓程式碼保持清潔。" diff --git a/commands/kb-review.md b/commands/kb-review.md new file mode 100644 index 0000000..90e64ad --- /dev/null +++ b/commands/kb-review.md @@ -0,0 +1,359 @@ +--- +description: 回顧這一輪 TDD,決定下一步。遵循 Kent Beck 的持續演進理念。 +--- + +# TDD Review - 回顧與規劃下一步 + +完成一輪 Red-Green-Refactor 後,停下來思考。 + +**【功能名】**:{{feature_name}} + +## Kent Beck 的回顧理念 + +> "每完成一個測試,都要問自己:我學到了什麼?" +> "下一個測試不是計劃出來的,而是從經驗中浮現的。" + +## 回顧步驟 + +### 1. 回顧這一輪 + +在 journey.md 中更新: + +```markdown +### 第 N 輪完成 - {日期時間} + +#### 💭 回顧與學習 + +**這一輪做了什麼** +{簡述這輪的測試和實作} + +**學到了什麼** +- {發現 1} +- {發現 2} +- {驚喜或意外} + +**設計如何演進** +{設計有什麼改變?} + +**目前的進度感覺** +{覺得進度如何?順利還是卡住?} +``` + +### 2. 回顧問題清單 + +Kent Beck 建議問自己: + +#### 關於測試 +- ✅ 這個測試真的需要嗎? +- ✅ 測試太大了嗎? +- ✅ 測試太小了嗎? +- ✅ 測試名稱清楚嗎? + +#### 關於實作 +- ✅ 實作還是假的嗎? +- ✅ 需要三角測量嗎? +- ✅ 有沒有過度設計? +- ✅ 程式碼清楚嗎? + +#### 關於重構 +- ✅ 還有重複嗎? +- ✅ 命名恰當嗎? +- ✅ 結構清晰嗎? +- ✅ 需要更多重構嗎? + +#### 關於節奏 +- ✅ 步伐太大還是太小? +- ✅ 速度合適嗎? +- ✅ 感覺順暢嗎? + +### 3. 決定下一步 + +Kent Beck:有三個選擇 + +#### 選項 1:繼續測試(最常見) + +**何時選擇**: +- 還有明顯的功能未實作 +- 想到下一個測試案例 +- 設計還在演進中 + +**下一步**: +``` +/kb-red +``` + +**在 journey.md 記錄**: +```markdown +#### 🎯 下一步計劃 + +**下一個測試想法** +{為什麼要寫這個測試?} + +**預期會發生什麼** +{這個測試會逼出什麼實作?} + +執行 /kb-red +``` + +#### 選項 2:暫停休息 + +**何時選擇**: +- 累了 +- 卡住了 +- 需要思考 + +**暫停建議**: +- 寫下目前的想法 +- 記錄卡住的地方 +- 休息一下再回來 + +**在 journey.md 記錄**: +```markdown +#### ⏸️ 暫停點 + +**目前狀態** +{進行到哪裡了} + +**下次回來要做什麼** +{下一步的提示} + +**卡住的問題**(如果有) +{什麼地方不確定} +``` + +#### 選項 3:結束這個會話 + +**何時選擇**: +- 目前的目標達成了 +- 找到自然的停止點 +- 功能暫時夠用了 + +**在 journey.md 記錄**: +```markdown +#### 🎉 會話結束 + +**達成的成果** +{列出完成的功能} + +**目前的狀態** +- 測試數量:{數量} +- 測試通過率:100% +- 程式碼品質:{評估} + +**未來可能的方向** +- {可能的擴展 1} +- {可能的擴展 2} +``` + +## 心理 To-Do List + +Kent Beck 建議維護一個簡單的 To-Do List: + +### 在 journey.md 中 + +```markdown +## 心理 To-Do List + +### 已完成 +- [x] 基本乘法運算 +- [x] 處理負數 + +### 下一步可能的測試 +- [ ] 加法運算 +- [ ] 相等性比較 +- [ ] 不同幣別 + +### 設計想法 +- [ ] 考慮提取 Money 父類別 +- [ ] 可能需要 Currency 概念 +``` + +**特點**: +- 非正式的 +- 隨時可以改 +- 不是承諾 +- 只是提醒 + +## 調整步伐 + +### 如果進度太慢 + +Kent Beck:**加大步伐** +- 寫更大的測試 +- 用 Obvious Implementation +- 跳過明顯的步驟 + +### 如果感到不安 + +Kent Beck:**縮小步伐** +- 寫更小的測試 +- 用 Fake It +- 三角測量 +- 更頻繁測試 + +## 範例反思記錄 + +### 順利的情況 + +```markdown +### 第 3 輪完成 - 2025-10-15 22:00 + +#### 💭 回顧與學習 + +**這一輪做了什麼** +測試了負數乘法,確認 Dollar(-5).times(2) 得到 -10。 + +**學到了什麼** +- 現有實作自動支援負數 +- 不需要特殊處理 +- 乘法的數學性質自然延伸 + +**設計如何演進** +沒有改變,驗證了設計的穩健性。 + +**目前的進度感覺** +很順利!基本的乘法功能已經穩定。 + +#### 🎯 下一步計劃 + +**下一個測試想法** +測試加法:Dollar(5).plus(Dollar(3)) 應該等於 Dollar(8) + +**預期會發生什麼** +需要新增 plus 方法,可能會很直接。 + +執行 /kb-red +``` + +### 卡住的情況 + +```markdown +### 第 5 輪完成 - 2025-10-15 22:30 + +#### 💭 回顧與學習 + +**這一輪做了什麼** +嘗試實作不同幣別的比較。 + +**學到了什麼** +- 設計變複雜了 +- 不確定該怎麼處理幣別 +- 可能需要重新思考 + +**設計如何演進** +引入了 Currency 概念,但感覺有點勉強。 + +**目前的進度感覺** +有點卡住。可能步伐太大了。 + +#### ⏸️ 暫停點 + +**目前狀態** +Dollar 和 Franc 可以獨立運作,但比較功能還不清楚。 + +**下次回來要做什麼** +考慮回退到更簡單的設計,或者寫更小的測試。 + +**卡住的問題** +- 幣別應該是字串還是物件? +- 需要匯率轉換嗎? +- 設計是否過度複雜? + +可能需要回到更小的步伐,寫一個更簡單的測試。 +``` + +## 長期視角 + +### Kent Beck 的"完成"定義 + +TDD **沒有**真正的完成: +- 軟體是演進的 +- 永遠可以加新測試 +- 永遠可以改善設計 +- 永遠可以學到新東西 + +### 知道何時停止 + +**可以暫時停止的信號**: +- 目前的需求滿足了 +- 設計達到一個穩定點 +- 沒有明顯的下一步 +- 想要轉向其他功能 + +**不是因為**: +- "測試寫夠了" +- "覆蓋率達標了" +- "文件說的都做完了" + +## 節奏意識 + +Kent Beck 強調 TDD 的節奏: + +``` +🔴 Red → 緊張(寫測試) +🟢 Green → 放鬆(通過了!) +🔵 Refactor → 改善(重構) +💭 Review → 回顧(現在) +``` + +**好的節奏**: +- 快速循環(幾分鐘) +- 頻繁的綠燈 +- 定期的反思 + +**不好的節奏**: +- 卡在紅燈太久 +- 測試寫太大 +- 實作太複雜 +- 忘記重構 +- 沒有回顧 + +## 下一步選擇 + +### 繼續前進 + +```bash +/kb-red +``` + +### 查看旅程記錄 + +```bash +cat docs/tdd/{feature_name}/journey.md +``` + +### 執行所有測試 + +```bash +npm test +``` + +## 記住 Kent Beck 的話 + +> "TDD 不是一個目的地,而是一個旅程。" +> "每個測試都教會你一些東西。" +> "下一步不是計劃出來的,而是從經驗中浮現的。" +> "當你不知道下一步時,寫一個你知道答案的測試。" +> "信任這個過程,設計會自然演進。" + +--- + +## 完整循環 + +你已經完成一輪完整的 TDD 循環! + +``` +/kb-start ← 從這裡開始 + ↓ +/kb-red ← 寫測試 (Red) + ↓ +/kb-green ← 實作 (Green) + ↓ +/kb-refactor ← 重構 (Refactor) + ↓ +/kb-review ← 回顧(你在這裡) + ↓ +循環回 /kb-red 或結束 +``` + +準備好下一輪了嗎? 🚀 diff --git a/commands/kb-start.md b/commands/kb-start.md new file mode 100644 index 0000000..975a1e6 --- /dev/null +++ b/commands/kb-start.md @@ -0,0 +1,129 @@ +--- +description: 啟動新的 TDD 會話。遵循 Kent Beck 的純粹 TDD 理念,從第一個測試開始。 +--- + +# TDD Start - 啟動 TDD 會話 + +開始一個新的 TDD 開發會話。遵循 Kent Beck 的理念:從測試開始,讓設計演進。 + +**【功能想法】**:{{brief_idea}} + +## Kent Beck 的起點 + +> "開始時不要想太多,從最簡單的測試開始。" + +**不要做:** +- ❌ 寫完整的需求文件 +- ❌ 規劃所有測試案例 +- ❌ 事先設計類別結構 +- ❌ 想太多細節 + +**要做:** +- ✅ 想一個最簡單的情境 +- ✅ 寫出第一個測試 +- ✅ 讓測試驅動設計 + +## 開始步驟 + +### 1. 建立開發記錄 + +在 `docs/tdd/{feature_name}/` 建立: +``` +journey.md - TDD 開發旅程記錄(不是計劃) +``` + +### 2. 想第一個測試 + +問自己: +- 什麼是最簡單的使用情境? +- 什麼是最明顯的行為? +- 從哪裡開始最容易? + +**範例思考過程:** +``` +功能想法:「計算購物車總價」 + +❌ 不要想: +- 需要支援優惠券 +- 需要處理多幣別 +- 需要計算稅金 +- ... + +✅ 只想: +「一個商品的購物車,總價應該等於商品價格」 + +這就是第一個測試! +``` + +### 3. journey.md 初始格式 + +```markdown +# {feature_name} TDD Journey + +## 開始想法 +{簡單描述要做什麼,1-2 句話} + +## TDD 循環記錄 + +### 第 1 輪 - {日期時間} + +#### 🤔 想法 +{為什麼要寫這個測試?} + +#### 📝 下一步 +執行 /kb-red 寫第一個測試 +``` + +## 心態準備 + +Kent Beck 強調的 TDD 心態: + +**好奇心** +- "如果我這樣寫測試會怎樣?" +- 用測試來探索問題 + +**勇氣** +- 敢於寫會失敗的測試 +- 敢於用最簡單的方式實作 + +**簡單** +- 從最簡單的開始 +- 不要過度設計 + +**反饋** +- 頻繁執行測試 +- 快速得到反饋 + +## 範例:開始一個 Money 類別 + +```markdown +# Money TDD Journey + +## 開始想法 +建立一個 Money 類別,可以處理金錢運算。 + +## TDD 循環記錄 + +### 第 1 輪 - 2025-10-15 21:30 + +#### 🤔 想法 +最簡單的情境:5 元乘以 2 應該等於 10 元。 + +#### 📝 下一步 +執行 /kb-red 寫第一個測試 +``` + +## 下一步 + +準備好了嗎?執行: +``` +/kb-red +``` + +開始寫你的第一個測試! + +## 記住 Kent Beck 的話 + +> "TDD 的目的不是測試,而是思考。" +> "從最簡單的測試開始,讓複雜度自然演進。" +> "如果不知道該寫什麼測試,那就寫一個你知道答案的測試。" diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..268e217 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,61 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:cashwu/claude-code-tdd-marketplace:plugins/kent-beck-tdd", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "d87d435feabff2801b6588c5c30da50c827463e5", + "treeHash": "7465967f0db91be17641bb12a329d7b3c5de8d78a821220c2871175c1466945c", + "generatedAt": "2025-11-28T10:14:30.209144Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "kent-beck-tdd", + "description": "Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "b2a7d3905094705b6e1c8a4f4522caa81c6fde713a443bc1e70e1cf6aff859fd" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "846c3bcb35ac3800de4df92e658001e7aa4f7c542f26ec83eb5e0ed254367b4b" + }, + { + "path": "commands/kb-review.md", + "sha256": "939f2259438c215ffb981075309ee216b850fc4c2324afb87fbbe29bc18c18be" + }, + { + "path": "commands/kb-red.md", + "sha256": "64ad38a10362a984b6c71ea433a99a782e07f95889b4bebc30655585b85d5988" + }, + { + "path": "commands/kb-start.md", + "sha256": "8f277395e542800a7c5ae4abceefc3e3474e64c592fda5b564b485eb467692cf" + }, + { + "path": "commands/kb-refactor.md", + "sha256": "d952ee0fb05993f76e8d8bd192e8edb41e9587778b7b6e306c45e82edd1d683d" + }, + { + "path": "commands/kb-green.md", + "sha256": "4f6f72649d03f65b97dfa7db1b264106f7fb6bd13187430c18d0792cfd85f4a1" + } + ], + "dirSha256": "7465967f0db91be17641bb12a329d7b3c5de8d78a821220c2871175c1466945c" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file