# Learn Go with tests 讀書會
:::spoiler 目錄
[TOC]
:::
### [共筆連結](https://hackmd.io/HV-WKXKASG-Mf4xQQ1O-2A)
- 重點資訊
- 學習 Golang 與 TDD
- 主要教材:[Learn Go with tests](https://quii.gitbook.io/learn-go-with-tests/)
- 每周五 21:00 - 22:30,待基礎章節完成能開始寫練習題後,可能改成隔周五一次
- 預計 5/16 (五) 開始
- 需要動手實作
- 第一次進行時,讓大家認領導讀各章節
- 導讀者可以提供自由小練習,讓大家更有目標
- 如果發現當次講不完,別硬講,留下次繼續。大家能聽懂才有意義
- 可參考許多程式碼範例:[Go by Example](https://gobyexample.com/)
- 強烈建議使用 Windows 的伙伴用 WSL,並在第一次讀書會前安裝好 WSL (因為裝 WSL 比較久)
- 開放旁聽,但鼓勵加入為成員做出貢獻,更有互動機會能學到更多
- 讀書會基本規則請見:[Lois 的公告](https://discord.com/channels/1174010592282034216/1181966173944938566)
- 細部運作方式,可隨著大家的共識動態調整
- 成為讀書會成員的條件
- 有高度意願讀完列出的章節,以及實作練習程式,並與成員分享
- 有高度意願主持讀書會及參與每次討論
- 有信心在讀書會期間請假不超過兩次
- 有信心不會臨時放棄退出
- 實作歷程紀錄:[workshop-learn-go-with-tests](https://github.com/Tech-Book-Community/workshop-learn-go-with-tests)
- 輔助文章
- Golang
- [軟體工程視角:Golang 優劣勢分析](https://jakrh.github.io/posts/20250404-golang-pros-cons/)
- [在 macOS、Linux 和 WSL 上高效安裝與管理 Go 版本](https://jakrh.github.io/posts/20250328-multi-version-go/)
- TDD
- [提升程式碼品質與開發信心的利器:測試驅動開發 (TDD)](https://jakrh.github.io/posts/20250402-tdd/)
- 記憶體指標概念
- [記憶體操作概念:以 C 語言為例](https://jakrh.github.io/posts/20250329-c-memory/)
# 讀書會章節分工
## TDD, Install Go, and Hello, World
- 日期:2025/05/16, 2025/05/23
- 導讀人:Jack
- 請假:nil
- 缺席:nil
- 簡報:
- [軟體工程視角:Golang 優劣勢分析](https://jakrh.github.io/posts/20250404-golang-pros-cons/)
- [提升程式碼品質與開發信心的利器:測試驅動開發 (TDD)](https://jakrh.github.io/posts/20250402-tdd/)
## Integers and Iteration
- 日期:2025/05/23, 2025/05/30
- 導讀人:Tiny_Murky
- 請假:nil
- 缺席:nil
- 簡報:[Integer](https://hackmd.io/@HWjmtqGJQRmj4pwClBcCKg/BkmCxz2-xg), [Iteration](https://hackmd.io/@HWjmtqGJQRmj4pwClBcCKg/HyKz-63Wxx), [github](https://github.com/TinyMurky/learn-go-with-test/tree/main)
## Arrays and slices
- 日期:2025/05/30
- 導讀人:gson
- 請假:nil
- 缺席:nil
- 簡報:[Arrays and Slices 概念](https://hackmd.io/@JgGTFI_BRjyUv6YuG1bmUQ/B16-xH8bex), [learn-golang-with-test](https://github.com/leetcode-golang-classroom/learn-golang-with-tests), [code-with-gitpod](https://gitpod.io/#https://github.com/leetcode-golang-classroom/learn-golang-with-tests)
## Structs, methods & interfaces
- 日期:2025/06/06
- 導讀人:Jack
- 請假:雷N, Tiny_Murky
- 缺席:nil
- 簡報:
- [教材](https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/structs-methods-and-interfaces)
- Interface pollution
- [Avoid Interface Pollution](https://www.ardanlabs.com/blog/2016/10/avoid-interface-pollution.html)
- [Interface pollution](https://100go.co/5-interface-pollution/)
- [7 Common Interface Mistakes in Go](https://medium.com/@andreiboar/1d3f8e58be60)
## Pointers & errors
- 日期:2025/06/13
- 導讀人:gson
- 請假:nil
- 缺席:nil
- 簡報:
- [Pointer & Errors](https://hackmd.io/@JgGTFI_BRjyUv6YuG1bmUQ/S1rEqxqGge)
- [gitpod Link](https://leetcodegol-learngolang-budwhkriitm.ws-us120.gitpod.io/)
- [learn-golang-with-test repo](https://github.com/leetcode-golang-classroom/learn-golang-with-tests/tree/master/pointers-and-errors)
- [dont just check errors handle them gracefully](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully)
- [go package for check uncheck errors](https://github.com/kisielk/errcheck)
- [vscode golang debug tool](https://github.com/derekparker/delve)
## Maps
- 日期:2025/06/20
- 導讀人:Tiny_Murky
- 請假:Steven
- 缺席:nil
- 簡報:[hackmd](https://hackmd.io/@HWjmtqGJQRmj4pwClBcCKg/r1yxxYbQll), [Github 範例](https://github.com/TinyMurky/learn-go-with-test/tree/main/map)
## Dependency Injection and Mocking (6/27)
- 日期:2025/06/27
- 導讀人:雷N
- 請假:nil
- 缺席:nil
- 簡報:[GitPod](https://gitpod.io/start/#tedmax100-learngowithte-mx1b2n9a0dt) ,[DI](https://github.com/tedmax100/learn-go-with-tests/tree/main/di) ,[Mocking](https://github.com/tedmax100/learn-go-with-tests/tree/main/mocking)
## Concurrency and Select
- 日期:2025/07/04
- 導讀人:Tiny_Murky
- 請假:nil
- 缺席:nil
- 簡報:[Hackmd](https://hackmd.io/@HWjmtqGJQRmj4pwClBcCKg/HkOfCAXVex)
## Reflection
- 日期:2025/07/11
- 導讀人:Steven
- 請假:nil
- 缺席:nil
- 簡報:[Hackmd](https://hackmd.io/@HWjmtqGJQRmj4pwClBcCKg/HkOfCAXVex)
## Sync
- 日期:2025/07/18
- 導讀人:雷N
- 請假:nil
- 缺席:nil
- 簡報:[Sync](https://github.com/tedmax100/learn-go-with-tests/tree/main/sync)
## Context
- 日期:2025/07/25
- 導讀人:Steven
- 請假:nil
- 缺席:nil
- 簡報:
## Intro to property based tests
- 日期:2025/08/01
- 導讀人:gson
- 請假:nil
- 缺席:nil
- 簡報: [Roman Numerals with property based test](https://hackmd.io/@JgGTFI_BRjyUv6YuG1bmUQ/By4LvYkLgg)
## Maths
- 日期:2025/08/08
- 導讀人:gson
- 請假:nil
- 缺席:nil
- 簡報: [SVG clock with acceptance test](https://hackmd.io/@JgGTFI_BRjyUv6YuG1bmUQ/SkoTaXWPle)
## Reading files
- 日期:2025/08/15
- 導讀人:Steven
- 請假:Jack
- 缺席:nil
- 簡報:
## Templating
- 日期:2025/08/22
- 導讀人:Jack
- 請假:nil
- 缺席:nil
- 簡報:[摘要](https://hackmd.io/@lhhungx/SyZM_OHtgl)
## Generics
- 日期:2025/08/29
- 導讀人:雷N
- 請假:
- 缺席:
- 簡報:
## Revisiting arrays and slices with generics
- 日期:
- 導讀人:雷N
- 請假:
- 缺席:
- 簡報:
<!--
- 讀書會章節切分,請各導讀者在章節前方填名字
- `Jack`: TDD, Install Go, and Hello, World
- `Tiny_Murky`:Integers and Iteration
- `gson`:Arrays and slices
- `Jack`: Structs, methods & interfaces
- `gson`:Pointers & errors
- `Tiny_Murky`:Maps
- `雷N`: Dependency Injection and Mocking (6/27)
- `Tiny_Murky`:Concurrency and Select
- `Steven`: Reflection
- `雷N`: Sync
- `Steven`: Context
- `gson`: Intro to property based tests
- `gson`: Maths
- `Steven`: Reading files
- `Jack`: Templating
- `雷N`: Generics
- `雷N`:Revisiting arrays and slices with generics
-->
<!-- - 想法
- 比起典型的讀書會,更像 workshop
- 整合 TDD
- 可能在正式開始前,先找 1~2 位伙伴協助測試流暢度 (已測完並收到回饋,謝謝 @ian_24 與 @stevenchang421 的協助)
- 測試收到的回饋整理
- 請使用 Winodws 的伙伴先安裝好 WSL,因為安裝時間較久。
- 第一場的時間規劃
- 介紹 Golang 的特性與優劣勢:15m
- https://jakrh.github.io/posts/20250404-golang-pros-cons/
- 可以快速展示 Go 的招牌功能,goroutine,讓大家感受一下優勢
- 現場環境建置
- 安裝Golang:10m
- https://jakrh.github.io/posts/20250328-multi-version-go/
- 設定 vscode "User Settings" 的 `go.testFlags`
```json
"go.testFlags": [
"-v"
]
```
- 介紹 TDD:20m
- https://jakrh.github.io/posts/20250402-tdd/
- 開始工作坊
- Hello, World:30m
- Integers:20m
- Recap
- 如果發現當次講不完,別硬講,下次繼續。大家能聽懂才有意義。
- 為避免遺忘,可能要一周一次
- 每次提供自由小練習
- 可以現場出題,演算法方面,可請 AI 輸出成學員自已熟悉的語言,再由學員寫成 Go
- 提醒可善用 https://gobyexample.com/
- 留意坑點:
- 字串迭代有區分 byte 和 rune 兩種方式
- slice 那章,slice 結構與 2d slice 可能有難度
- 可能要補充 https://go.dev/blog/slices-intro
-->
# 內容分類
* 基礎
* hello world, switch
* primitive types
* iteration
* arrays, slices
* structs, methods
* pointers
* errors
* maps
* 物件導向
* composition
* polymorphism, interfaces
* 並行
* go
* select
* defer
* sync
* context
* 進階測試
* mocking
* property based tests
* 進階
* reflection
* generics
* maths
* file processing
* templating
* 其他
* design patterns
* anti-patterns
* interface pollution (https://100go.co/5-interface-pollution/)
# 額外的練習題目
供導讀者與成員有一些具體目標可以練習
* 基礎
* **字串處理:**
* 設計一個函數,接受一個字串作為輸入,並返回該字串的反轉字串。
* 設計一個函數,接受一個字串作為輸入,並返回該字串中所有母音字母的數量。
* 設計一個函數,接受兩個字串作為輸入,判斷第二個字串是否為第一個字串的子字串。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **陣列/切片操作:**
* 設計一個函數,接受一個整數切片作為輸入,並返回該切片中所有元素的總和。
* 設計一個函數,接受一個整數切片作為輸入,並返回該切片中的最大值和最小值。
* 設計一個函數,接受兩個整數切片作為輸入,並返回兩個切片的交集。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **錯誤處理:**
* 設計一個函數,接受一個整數作為輸入,如果該整數為負數,則返回一個錯誤。
* 設計一個函數,讀取一個檔案,如果檔案不存在或讀取失敗,則返回一個錯誤。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **Hash Map:**
* 設計一個函數,接受一個字串切片作為輸入,並返回一個 hash map,其中 key 為字串,value 為該字串出現的次數。
* 設計一個函數,接受兩個 hash map 作為輸入,並返回兩個 hash map 的合併結果。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **簡易的 RESTful API:**
* 使用 `net/http` 套件,設計一個簡易的 RESTful API,提供新增、查詢、更新和刪除使用者資料的功能。
* 使用 hash map 儲存使用者資料。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **簡易的計算機:**
* 設計一個計算機,支援加、減、乘、除四種運算。
* 支援括號運算。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* **簡易的文字處理工具:**
* 設計一個文字處理工具,支援統計字數、行數、單字數等功能。
* 支援搜尋和取代功能。
* 要求:使用 TDD 方法,先編寫測試案例,再實現程式碼。
* 物件導向
* **不同形狀的面積周長計算器** <!-- TODO: 還有細節需修正 -->
* **目標:** 練習介面(interface)和多型(polymorphism)。
* **需求:**
1. 建立一個 `Shape` 介面,包含 `Area()` 和 `Perimeter()` 方法,用於計算形狀的面積與周長。
2. 建立 `Rectangle`(矩形)、 `Circle`(圓形)和 `Triangle` (三角形) 結構體,並實作 `Shape` 介面。
3. 建立一個 `AreaCalculator` 結構體,包含一個 `CalculateArea()` 方法,該方法接受一個 `Shape` 介面作為參數,並回傳形狀的面積。
4. 建立一個 `PerimeterCalculator` 結構體,包含一個 `CalculatePerimeter()` 方法,該方法接受一個 `Shape` 介面作為參數,並回傳形狀的周長。
5. 使用 TDD 撰寫測試案例,驗證 `Rectangle`、`Circle`、`Triangle`、`AreaCalculator` 和 `PerimeterCalculator` 的功能。
* **步驟:**
1. **定義介面 (Interface):**
* 建立一個名為 `Shape` 的介面,包含兩個方法:
* `Area() float64`:計算面積
* `Perimeter() float64`:計算周長
2. **建立結構體 (Struct):**
* 建立以下結構體,並實作 `Shape` 介面:
* `Rectangle` (長方形):包含 `width` 和 `height` 屬性。
* `Circle` (圓形):包含 `radius` 屬性。
* `Triangle` (三角形):包含 `base`,`height`,`sideA`,`sideB`,`sideC`屬性。
3. **使用 Composition:**
* 建立一個名為 `Square` (正方形) 的結構體,並使用 composition 嵌入 `Rectangle` 結構體。
4. **多型 (Polymorphism):**
* 建立一個 `Calculate` 函式,接受 `Shape` 介面作為參數,並輸出形狀的面積和周長。
* 在 `main` 函式中,建立不同形狀的實例,並呼叫 `Calculate` 函式。
5. **測試驅動開發 (TDD):**
* 在每個步驟中,先撰寫測試案例,再實作程式碼,確保程式碼的正確性。
* 測試案例應涵蓋各種邊界情況和錯誤情況。
* **不同類型的支付方式**
* **目標:** 練習介面(interface)和多型(polymorphism)。
* **需求:**
1. 建立一個 `PaymentMethod` 介面,包含一個 `Pay()` 方法,用於執行支付。
2. 建立 `CreditCard`(信用卡)、`PayPal`(貝寶)和 `BankTransfer`(銀行轉帳)結構體,並實作 `PaymentMethod` 介面。
3. 建立一個 `PaymentProcessor` 結構體,包含一個 `ProcessPayment()` 方法,該方法接受一個 `PaymentMethod` 介面作為參數,並執行支付。
4. 使用 TDD 撰寫測試案例,驗證 `CreditCard`、`PayPal`、`BankTransfer` 和 `PaymentProcessor` 的功能。
* 進階測試
* 並行
* **並行計算器 (Concurrent Counter)**
* **目標:** 理解競爭條件 (Race Condition) 並使用 `sync.Mutex` 解決,同時練習 `sync.WaitGroup`。
* **描述:**
1. 創建一個全域或共享的整數計數器變數,初始值為 0。
2. 啟動 N 個 goroutine (例如 N=1000)。
3. 每個 goroutine 將計數器增加 100 次。
4. 主 goroutine 需要等待所有 N 個 goroutine 完成。
5. **步驟 A (觀察問題):** 直接運行,觀察最終計數器的值是否為 `N * 100`。使用 `go run -race main.go` 來檢測競爭條件。
6. **步驟 B (解決問題):** 使用 `sync.Mutex` 來保護對計數器的訪問,確保每次只有一個 goroutine 可以修改它。
7. **步驟 C (等待完成):** 使用 `sync.WaitGroup` 來確保主 goroutine 會等待所有增加計數的 goroutine 都執行完畢後才打印最終結果。
* **重點:** `go`, `sync.Mutex`, `sync.WaitGroup`, Race Condition 檢測。
* **成就感來源:** 親手發現並解決一個常見的並行問題,看到 `-race` 檢測器的作用,最終得到正確的計算結果。
* **TDD 提示:** 編寫一個測試,驗證在步驟 B 和 C 完成後,最終計數器的值是否等於預期的 `N * 100`。
* **簡易工作池 (Simple Worker Pool)**
* **目標:** 使用 channel 在 goroutine 之間傳遞工作,練習 goroutine 的管理。
* **描述:**
1. 創建一個 `jobs` channel (e.g., `chan int`) 和一個 `results` channel (e.g., `chan int`)。
2. 啟動 M 個 worker goroutine (例如 M=3)。
3. 每個 worker goroutine 從 `jobs` channel 讀取一個數字,計算其平方,然後將結果發送到 `results` channel。模擬工作耗時,例如 `time.Sleep(50 * time.Millisecond)`。
4. 主 goroutine 向 `jobs` channel 發送一批數字 (例如 1 到 10)。
5. 發送完所有工作後,關閉 `jobs` channel。這是一個重要的信號,告知 worker 不再有新的工作。
6. 主 goroutine 從 `results` channel 讀取所有結果並打印。如何知道何時停止讀取? (提示:需要等待所有 worker 完成,或者讀取固定數量的結果)。可以結合 `sync.WaitGroup` 來等待所有 worker 處理完畢。
* **重點:** `go`, channels (buffered/unbuffered), `close()` channel, `for range` on channel, `sync.WaitGroup` (用於等待 worker)。
* **成就感來源:** 實現了一個基礎但常見的並行模式,看到多個 worker 同時處理任務,體會到 channel 作為 goroutine 間通信橋樑的便利性。
* **TDD 提示:** 測試可以驗證發送 N 個 job 後,是否能從 results channel 收到 N 個正確的結果。
* **並行網站狀態檢查器 (Concurrent Website Status Checker)**
* **目標:** 結合 `net/http` 進行實際 I/O 操作,使用 `context` 處理超時和取消,練習 `select` 的多路複用。
* **描述:**
1. 定義一個包含多個 URL 字串的 slice。
2. 為每個 URL 啟動一個 goroutine,使用 `net/http.Get(url)` 檢查網站狀態。
3. **要求 1 (檢查):** 所有 URL 的檢查應該並行進行。
4. **要求 2 (獨立超時):** 為 *每個* HTTP請求設置一個超時 (例如 2 秒)。可以使用 `http.Client` 的 `Timeout` 字段,或者結合 `context.WithTimeout`。
5. **要求 3 (整體超時/取消):** 為 *整個* 檢查過程設置一個總超時 (例如 5 秒)。如果總超時到達,應嘗試取消所有仍在進行的請求。使用 `context.WithTimeout` 或 `context.WithCancel`。
6. **要求 4 (結果收集):** 使用 channel 收集每個 URL 的檢查結果(成功狀態碼或錯誤信息)。主 goroutine 使用 `select` 來接收結果或處理超時/取消信號。
7. **要求 5 (資源清理):** 確保每個 HTTP 回應的 `Body` 都被關閉 (使用 `defer resp.Body.Close()`)。
* **重點:** `go`, `net/http`, `context` (WithTimeout, WithCancel), `select`, channels, `defer`, Error Handling。
* **成就感來源:** 解決了一個相對實際的問題,體驗了 Go 如何優雅地處理 I/O 密集型任務的並行和超時控制,這是 Go 的一大優勢。
* **TDD 提示:**
* 直接測試外部 HTTP 請求比較困難(需要 Mocking,但目前不教)。
* 可以將核心的並行邏輯(啟動 goroutine、使用 context、select 收集結果)抽離出來。
* 測試時,可以用模擬的 "worker" goroutine 取代實際的 HTTP 請求,這些模擬 worker 可以 `time.Sleep` 一段時間後向結果 channel 發送數據或模擬超時/錯誤,這樣就能測試 `select` 和 `context` 的處理邏輯是否正確。
* 進階
* reflection
* generics
* maths
* file processing
* templating
## 其他資源
- Jack 大的 [leetcode with Go](https://github.com/Jakrh/leetcode-go)