「老闆,網站上的年份顯示怪怪的...」
週一早上,你剛泡好咖啡,專案經理就衝進來。原來是客戶的歷史文物展覽網站,要求用羅馬數字顯示年代,結果顯示出來的東西讓人看不懂。
「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"(千)的第一個字母 |
羅馬數字的組合遵循三個基本規則:
加法規則:相同符號連續出現,數值相加
左加右加規則:小數字在大數字右邊,數值相加
減法規則:特定的小數字在大數字左邊,數值相減
建立 app/Services/RomanNumerals.php
:
<?php
namespace App\Services;
// 空白檔案,準備開始 TDD
建立 tests/Unit/Day11/RomanNumeralsTest.php
:
<?php
use App\Services\RomanNumerals;
describe('Roman Numerals Converter', function () {
it('converts 1 to I', function () {
// 第一個測試,還沒有實作
expect(toRoman(1))->toBe('I');
});
});
執行測試:
./vendor/bin/pest tests/Unit/Day11
錯誤訊息:
Error: Call to undefined function toRoman()
完美!這就是我們要的紅燈。
更新 app/Services/RomanNumerals.php
:
<?php
namespace App\Services;
class RomanNumerals
{
public static function toRoman(int $number): string
{
return 'I';
}
}
更新測試檔案:
<?php
use App\Services\RomanNumerals;
describe('Roman Numerals Converter', function () {
it('converts 1 to I', function () {
expect(RomanNumerals::toRoman(1))->toBe('I');
});
});
測試通過了!
加入第二個測試:
it('converts two to II', function () {
expect(RomanNumerals::toRoman(2))->toBe('II');
});
測試失敗(紅燈 🔴)。
更新實作:
<?php
namespace App\Services;
class RomanNumerals
{
public static function toRoman(int $number): string
{
if ($number === 1) {
return 'I';
}
if ($number === 2) {
return 'II';
}
return '';
}
}
測試通過(綠燈 🟢)!
加入第三個測試:
it('converts three to III', function () {
expect(RomanNumerals::toRoman(3))->toBe('III');
});
這時候我們發現模式了。重構時間!
<?php
namespace App\Services;
class RomanNumerals
{
public static function toRoman(int $number): string
{
$result = '';
for ($i = 0; $i < $number; $i++) {
$result .= 'I';
}
return $result;
}
}
所有測試都通過!這就是 TDD 的節奏:
app/Services/RomanNumerals.php
:
<?php
namespace App\Services;
class RomanNumerals
{
public static function toRoman(int $number): string
{
$result = '';
for ($i = 0; $i < $number; $i++) {
$result .= 'I';
}
return $result;
}
}
tests/Unit/Day11/RomanNumeralsTest.php
:
<?php
use App\Services\RomanNumerals;
describe('Roman Numerals Converter', function () {
it('converts 1 to I', function () {
expect(RomanNumerals::toRoman(1))->toBe('I');
});
it('converts 2 to II', function () {
expect(RomanNumerals::toRoman(2))->toBe('II');
});
it('converts 3 to III', function () {
expect(RomanNumerals::toRoman(3))->toBe('III');
});
});
Kata 練習是體驗 TDD 精髓的最佳方式,特別是在 Laravel 環境中使用 Pest 時:
小步前進:每次只處理一個簡單的案例
保持節奏:嚴格遵守紅-綠-重構的循環
相信過程:看似簡單的開始會帶來複雜的解決方案
享受旅程:重點不是目的地,而是練習的過程
初學者在練習 Kata 時經常犯這些錯誤:
❌ 過度設計:一開始就想設計完美的架構
✅ 簡單開始:從最簡單的實作開始,讓測試引導設計
❌ 跳步驟:想要一次處理多個測試案例
✅ 單步進行:一次只專注一個測試
❌ 忽略重構:只關心讓測試通過,不整理程式碼
✅ 持續重構:在每個綠燈後檢視是否需要重構
❌ 完美主義:追求一次到位的完美解法
✅ 漸進改善:接受不完美,透過循環逐步改善
在繼續之前,想想這些問題,這些思考會幫助你更深入理解即將面臨的挑戰:
思考一下,面對即將到來的複雜性,你會選擇:
這些問題沒有標準答案,重要的是讓測試引導你找到最適合的解決方案。
今天我們學到了:
在結束今天的學習前,試試這個小挑戰:
// 挑戰:不看上面的程式碼,自己寫出 toRoman 函數
// 提示:只需要處理 1, 2, 3 即可
function toRoman($number) {
// 你的程式碼
}
動手寫寫看,體驗 TDD 的第一步!
今天是我們 Kata 練習的起點,我們:
記住:TDD 不是一蹴可幾的,而是透過不斷練習培養的技能。就像學習樂器一樣,每天的練習都會讓你進步一點點。
明天,我們將進入更有趣的挑戰!