「老闆,網站上的年份顯示怪怪的...」
週一早上,你剛泡好咖啡,專案經理就衝進來。原來是客戶的歷史文物展覽網站,要求用羅馬數字顯示年代,結果顯示出來的東西讓人看不懂。
「MMXXIII 應該是 2023 年吧?」你心想,「但網站顯示的是 MMXVIII...」
這就是我們接下來要解決的問題:如何用 TDD 的方式,一步步建立一個可靠的羅馬數字轉換器。
就像武術的「型」或音樂的「練習曲」,Code Kata 是程式設計師用來磨練技巧的練習題。Roman Numeral Kata 是最經典的 TDD 練習之一。
「Kata」源自日文,原意是武術中重複練習的基本動作組合。在程式設計領域,Code Kata 指的是短小且重複練習的程式設計問題,目的是透過反覆練習來培養直覺和肌肉記憶。
就像鋼琴家每天練習音階、武術家每天練習基本動作一樣,程式設計師也需要透過 Kata 來保持和提升自己的技能。每次練習同一個 Kata,你都會發現新的細節和改進空間。
這個 Kata 有許多優點,讓它成為 TDD 學習的完美選擇:
第一階段:打好基礎(Day 1-10)✅
第二階段:挑戰練習(Day 11-17)
├── Day 11:入門與環境準備 ⬅️ 我們在這裡
├── Day 12:基礎符號轉換
├── Day 13:擴展到百位數
├── Day 14:完整範圍
├── Day 15:反向轉換
├── Day 16:效能優化
└── Day 17:程式碼整理與回顧
第三階段:後續章節...
羅馬數字系統是古羅馬人發展的數字表示法,至今仍在許多場合使用,如鐘錶、建築物年份、書籍章節編號等。
羅馬數字的七個基本符號:
符號 | 數值 | 記憶方法 |
---|---|---|
I | 1 | 一根手指 |
V | 5 | 五根手指張開的形狀 |
X | 10 | 兩個 V 交叉 |
L | 50 | 拉丁文 "Quinquaginta" 的第一個字母 |
C | 100 | 拉丁文 "Centum"(百)的第一個字母 |
D | 500 | 拉丁文 "Quingenti" 的簡化 |
M | 1000 | 拉丁文 "Mille"(千)的第一個字母 |
羅馬數字的組合遵循三個基本規則:
加法規則:相同符號連續出現,數值相加
左加右加規則:小數字在大數字右邊,數值相加
減法規則:特定的小數字在大數字左邊,數值相減
建立 src/roman/romanNumerals.ts
:
// 空白檔案,準備開始 TDD
建立 tests/day11/roman-numerals.test.ts
:
import { describe, it, expect } from 'vitest'
describe('Roman Numerals Converter', () => {
it('convertsOneToI', () => {
// 第一個測試,還沒有實作
expect(toRoman(1)).toBe('I')
})
})
執行測試:
npm test tests/day11
錯誤訊息:
ReferenceError: toRoman is not defined
完美!這就是我們要的紅燈。
更新 src/roman/romanNumerals.ts
:
export function toRoman(num: number): string {
return 'I'
}
更新測試檔案:
import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'
describe('Roman Numerals Converter', () => {
it('convertsOneToI', () => {
expect(toRoman(1)).toBe('I')
})
})
測試通過了!
加入第二個測試:
it('convertsTwoToII', () => {
expect(toRoman(2)).toBe('II')
})
測試失敗(紅燈 🔴)。
更新實作:
export function toRoman(num: number): string {
if (num === 1) return 'I'
if (num === 2) return 'II'
return ''
}
測試通過(綠燈 🟢)!
加入第三個測試:
it('convertsThreeToIII', () => {
expect(toRoman(3)).toBe('III')
})
這時候我們發現模式了。重構時間!
export function toRoman(num: number): string {
let result = ''
for (let i = 0; i < num; i++) {
result += 'I'
}
return result
}
所有測試都通過!這就是 TDD 的節奏:
src/roman/romanNumerals.ts
:
export function toRoman(num: number): string {
let result = ''
for (let i = 0; i < num; i++) {
result += 'I'
}
return result
}
tests/day11/roman-numerals.test.ts
:
import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'
describe('Roman Numerals Converter', () => {
it('convertsOneToI', () => {
expect(toRoman(1)).toBe('I')
})
it('convertsTwoToII', () => {
expect(toRoman(2)).toBe('II')
})
it('convertsThreeToIII', () => {
expect(toRoman(3)).toBe('III')
})
})
Kata 練習是體驗 TDD 精髓的最佳方式。在這個過程中,你會深刻感受到測試驅動開發的核心理念:
小步前進:每次只處理一個簡單的案例
保持節奏:嚴格遵守紅-綠-重構的循環
相信過程:看似簡單的開始會帶來複雜的解決方案
享受旅程:重點不是目的地,而是練習的過程
初學者在練習 Kata 時經常犯這些錯誤:
❌ 過度設計:一開始就想設計完美的架構
✅ 簡單開始:從最簡單的實作開始,讓測試引導設計
❌ 跳步驟:想要一次處理多個測試案例
✅ 單步進行:一次只專注一個測試
❌ 忽略重構:只關心讓測試通過,不整理程式碼
✅ 持續重構:在每個綠燈後檢視是否需要重構
❌ 完美主義:追求一次到位的完美解法
✅ 漸進改善:接受不完美,透過循環逐步改善
在繼續之前,想想這些問題,這些思考會幫助你更深入理解即將面臨的挑戰:
思考一下,面對即將到來的複雜性,你會選擇:
這些問題沒有標準答案,重要的是讓測試引導你找到最適合的解決方案。
今天我們學到了:
在結束今天的學習前,試試這個小挑戰:
// 挑戰:不看上面的程式碼,自己寫出 toRoman 函數
// 提示:只需要處理 1, 2, 3 即可
function toRoman(num: number): string {
// 你的程式碼
}
動手寫寫看,體驗 TDD 的第一步!
今天是我們 Kata 練習的起點,我們:
記住:TDD 不是一蹴可幾的,而是透過不斷練習培養的技能。就像學習樂器一樣,每天的練習都會讓你進步一點點。
明天,我們將進入更有趣的挑戰!