# 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)