iT邦幫忙

2025 iThome 鐵人賽

DAY 10
1
Software Development

從 0 到 1:與 AI 協作的 Golang TDD 實戰系列 第 10

Day 10 - Kata 演練:FizzBuzz (二) - 最簡單的實作與重構 (綠燈 -> 重構)

  • 分享至 

  • xImage
  •  

昨日回顧與今日目標

在 Day 9,我們成功地點亮了第一盞紅燈,我們為 FizzBuzz 問題定義了最簡單的一個案例: Generate(1) 應該回傳 "1",並編寫了一個會失敗的測試來驗證它。

我們現在有了一個清晰的、由測試定義的目標,這盞紅燈就像導航系統上的目的地,指引著我們前進。

今天的目標:完成我們的第一個 TDD 循環!

  • 綠燈 (Green): 編寫「最少量」的產品程式碼,讓昨天的失敗測試轉為通過。
  • 重構 (Refactor): 在測試的保護下,審視並改善剛剛寫下的程式碼。

綠燈階段:做「最簡單能奏效」的事

面對昨天的失敗報告(預期 "1",實際 ""),我們的任務非常明確:修改 Generate 函式,讓它在接收到 1 時,能回傳 "1"。

此刻,TDD 的紀律再次提醒我們:

只做最簡單能奏效的事 (The Simplest Thing That Could Possibly Work)。

一個急於求成的開發者可能會想:「哦,我知道了,我需要把整數轉換成字串。」然後馬上去查 Go 語言的 strconv 套件,並把所有轉換邏輯都寫好。
但一個嚴格的 TDD 實踐者會問一個更「笨」的問題:「什麼是最簡單的程式碼,能讓 Generate(1) 回傳 "1"?」,答案可能讓你發笑:

// "最笨"但有效的寫法
func Generate(number int) string {
    return "1" // 直接寫死答案!
}

這個寫法能讓測試通過嗎?絕對能!但它是不是一個好的通用解法?
當然不是!這就是 TDD 的核心思想:

在「綠燈」階段,我們的唯一目標是讓測試通過,暫時不要考慮任何未來的需求。

這能讓我們保持專注,並確保我們的每一步都是由測試驅動的。當然,對於這個特定問題,將整數轉換為字串是一個同樣簡單且更具通用性的解法,所以我們選擇它。

動手實踐:讓燈變綠

引入 strconv 套件: 為了將整數 int 轉換為字串 string,我們需要使用 Go 的標準函式庫 strconv 中的 Itoa,而 Itoa是 Integer to ASCII 的縮寫。

修改production code: 打開 fizzbuzz/fizzbuzz.go,將其修改如下:

// fizzbuzz/fizzbuzz.go
package fizzbuzz

import "strconv" // 導入函式庫

// Generate 透過 TDD 來實現
func Generate(number int) string {
    // 寫下剛好能讓測試通過的程式碼 
    return strconv.Itoa(number)
}

見證「綠燈」的時刻

程式碼修改完畢。回到終端機,再次執行測試指令:

go test -v ./...

見證奇蹟的時刻到了!你會看到一片令人心曠神怡的綠色:

=== RUN   TestFizzBuzzGenerator
=== RUN   TestFizzBuzzGenerator/should_return_1_for_number_1
--- PASS: TestFizzBuzzGenerator (0.00s)
    --- PASS: TestFizzBuzzGenerator/should_return_1_for_number_1 (0.00s)
PASS
ok      go-tdd-kata/fizzbuzz    2.241s

恭喜!你已經成功地將紅燈轉為綠燈! 這證明我們剛剛編寫的產品程式碼,滿足了我們在測試中定義的需求。

重構階段:有東西需要清理嗎?

現在,我們站在了「綠燈」的安全地帶,這不是我們自己認為的,是測試告訴我們: 程式碼的功能是正確的。此刻,我們可以安心地審視我們的作品,思考:

在不改變外部行為(保持綠燈)的前提下,有沒有辦法讓程式碼的內部結構變得更好?

我們通常會尋找一些 「壞味道」(Code Smells),例如:

  • 重複的程式碼
  • 過於複雜的邏輯
  • 模糊不清的變數命名
  • 被「寫死」的魔法數字或字串

讓我們來審視一下我們剛剛的傑作:

func Generate(number int) string {
    return strconv.Itoa(number)
}

這段程式碼……坦白說,它已經非常乾淨了。它只有一行,意圖清晰,沒有重複,沒有複雜的邏輯,因此,在這個循環中,我們的重構結論是:無需重構

這是一個非常重要的認知:「重構」是一個有意識的步驟,而「決定不重構」也是一個有效的、有意識的決策,我們不是為了重構而重構,而是為了讓程式碼變得更好。如果它已經足夠好,那就自信地進入下一個循環。

第一次完整的 TDD 循環

讓我們回顧一下我們剛剛完成的旅程:

  • 紅燈 (Day 9): 我們為「Generate(1) 應回傳 "1"」寫了一個失敗的測試。
  • 綠燈 (Day 10): 我們寫了最簡單的程式碼 return strconv.Itoa(number) 讓測試通過。
  • 重構 (Day 10): 我們審視了程式碼,並自信地決定它目前無需改善。

我們完成了一個完整、微小但意義重大的 TDD 循環。我們透過這個過程,為 Generate 函式建立了一個堅實的、可驗證的基礎行為。

今日總結

今天,我們體驗了從「紅」到「綠」的喜悅,並完成了第一個完整的 TDD 循環。
我們學會了:

  • 在綠燈階段,要用最簡單的方式解決問題。
  • 在重構階段,要帶著批判性的眼光審視程式碼,並做出有意識的決策。

這個小小的循環給了我們巨大的信心,因為我們知道,程式碼的行為是被自動化測試所保護的。
預告:Day 11 - Kata 演練:FizzBuzz (三) - 透過 TDD 逐步疊加功能
既然我們已經處理了最普通的情況,下一步是什麼?當然是引入新的規則!明天,我們將再次從「紅燈」開始,新增一個測試案例來驅動出 "Fizz" 的邏輯,將見證 TDD 是如何幫助我們安全、逐步地為系統疊加新功能的。


上一篇
Day 9 - Kata 演練:FizzBuzz (一) - 寫下第一個失敗的測試 (紅燈)
下一篇
Day 11 - Kata 演練:TDD 如何優雅地完成 FizzBuzz
系列文
從 0 到 1:與 AI 協作的 Golang TDD 實戰30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言