第 7 / 10 部 待錄

測試與併發的真相

《Functional Design》Podcast 準備稿:測試與併發的真相

書名: Functional Design(Principles, Patterns, and Practices) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Functional Design.FP 真能免疫競態條件嗎 (7/10) 涵蓋範圍: 第四部導言 + 第13章 測試 + 第15章 併發

背景速覽

前三部講完了觀念、比較、設計原則。第四部把焦點拉到「真實工作中會遇到的問題」。這集挑了其中份量最重、也最有反轉的兩章:測試和併發。Uncle Bob 在這部開宗明義說,他的目的不是說服你 FP 優於 OO,而是清楚展示——FP 的工具、紀律、工程實務並沒有那麼神祕,也沒有想像中那麼「自動安全」。尤其併發那章,他要親手推翻自己第一集埋下的承諾(「FP 沒有競態條件」),這是全書最誠實、最反直覺的一集。

一句話重點

TDD 的紀律和語言無關(Uncle Bob 用過十種語言、結論一致),而 FP 還額外送你 property-based testing 這個強力工具;但更重要的真相是——即使你完全用函式式風格、完全不更新任何資料,只要系統裡有多個交互的狀態機,競態條件依然可能發生。FP 不是並行的銀彈。

值得討論的重點

1. TDD 與語言無關,連 mock 和 REPL 都不是藉口

Uncle Bob 從 20 多年前開始實踐 TDD,用過 Java、C#、C、C++、Ruby、Python、Lua、Clojure——結論很簡單:紀律和語言無關。FP 不改變測試策略,TDD 流程一樣是「測試先行幾秒、再寫程式」。他順手破了兩個都市傳說:(1)「函式式程式設計師都在 REPL 試、不需要 TDD」——他自己也常在 REPL 實驗,但會把學到的沉澱成測試,因為「測試像鑽石、永遠都在;REPL 的實驗到隔天就消失了」。(2)「函式式語言不支援 mock」——既然 LSP 在 FP 一樣成立、多型介面也容易做,FP 寫 mock 一點都不難。

2. Property-Based Testing:讓電腦幫你找反例,還自動「縮小」到最小案例

這是這集最值得安利的工具。傳統 TDD 你手寫一個個例子;property-based testing(源自 Haskell 的 QuickCheck)改成描述「該成立的性質」、讓電腦隨機產生上千個輸入去轟。Uncle Bob 用質因數分解示範:與其測「12 = [2,2,3]」,不如測兩條性質——「所有因數乘回去等於原數」和「每個因數都是質數」。這兩條同時成立,幾乎就是質因數分解的定義。真正的魔法在 shrinking(縮小):當它找到一個失敗案例,會持續尋找「仍能重現失敗的最小輸入」。他故意在影片出租店範例埋了個 bug,讓 quick-check 抓出來——:shrunk 直接把問題縮到「3 部電影 + 買二送一」這個最小重現,原因一目了然(一部免費,所以逐項加總不等於應付總額)。這讓 property-based testing 不只是測試,更是診斷工具

3. 併發的真相:FP 不免疫競態條件

全書最大的反轉。第一集說「FP 不更新資料、所以沒有並行更新問題、所以沒有競態條件」——這集 Uncle Bob 親自說:這不完全正確。他用 1960 年代電話系統的真實 race condition 重現:Bob 要打給 Alice、Alice 同時要打給 Bob,telco 還沒送出響鈴音、Alice 就拿起聽筒準備撥號——兩個狀態機錯失同步,結果誰也講不到話。即使用 Clojure 的 agent(把所有更新序列化、徹底消除並行更新),這個 race 還是會發生。因為問題不在「同時更新一塊記憶體」,而在「多個交互的有限狀態機可能彼此失去同步」。沒有更新,不代表沒有競態。

注意事項

⚠️ 這集要小心別讓「FP 不免疫競態條件」聽起來像在否定 FP。Uncle Bob 的論點是精確而非否定:可變變數的程式裡 race condition 也許更頻繁、更容易發生,FP 確實消除了「並行更新同一變數」這一大類;但它消除不了「狀態機之間失去同步」這另一類。要傳達的是「FP 是強力工具、但不是萬靈丹」的成熟態度,而非「FP 騙人」。

⚠️ property-based testing 的程式碼(generator、spec、quick-check)很多,別逐行讀。抓三個關鍵詞講白話:「描述性質而非例子」「隨機轟上千次」「自動縮小到最小反例」。shrinking 是最有記憶點的賣點,重點講它。

⚠️ 電話系統的 race condition 場景要講得夠生動、夠慢,這是全集高潮。但別陷進 agent/transition 函式的 Clojure 細節——聽眾要懂的是「兩個狀態機怎麼錯過彼此」這個故事,不是程式怎麼寫的。

⚠️ 這集跳過了第 14 章 GUI(留給下一集單獨講 turtle graphics),錄製時不要提到 GUI 內容,避免和 EP8 重疊。

專家補充

💡 「測試像鑽石、REPL 像沙畫」這個對比,其實點到 FP 社群一個長年分裂——REPL-driven development(在互動環境裡探索)vs TDD(測試先行)。Uncle Bob 的調和很高明:兩者不衝突,REPL 負責探索、測試負責固化。這對任何用 Jupyter、用 ghci、用 Clojure REPL 的人都是好提醒——你的探索成果,有沉澱成回歸測試嗎?

💡 property-based testing 是「FP 送給整個業界的禮物」最好的例子之一。QuickCheck(Haskell,2000 年)發明後,幾乎每個語言都移植了:Clojure 的 test.check、Scala 的 ScalaCheck、Python 的 Hypothesis、JS 的 fast-check、Rust 的 proptest。Uncle Bob 順帶問了個好問題:為什麼這工具在 OO 界沒那麼流行?他的答案是——它搭配純函式最有效。要在可變系統裡設定 generator 和性質,理論可行但複雜得多。這是純度買到的又一個能力。

💡 併發那章還藏了一段技術史的反思:世紀之交時 Moore 定律的「時脈」面向死了(撞到 3GHz),硬體開始塞多核,大家恐慌「128 核怎麼寫」,函式式語言因此走紅(以為 FP 能自動平行化)。但 Uncle Bob 指出兩件事:(1)Moore 定律的「密度」面向後來也死了,處理器停在 4 核左右,128 核恐慌消散,FP 的這個迫切感跟著減弱;(2)那個推論本身就有瑕疵——FP 消除的是更新衝突,不是狀態機失步。這段是難得的、對「FP 因多核而紅」這個敘事的冷靜祛魅。

討論問題

🎙️ 開場鉤子:「第一集我跟你說,函式式程式沒有競態條件,因為它根本不更新資料。聽起來太美好了對吧?今天 Uncle Bob 要親手推翻這句話——即使你一個變數都不改,race condition 還是會找上門。」

🎙️ 自問自答:「不用一個個寫測試例子,那要測什麼?」——測『性質』。比如質因數分解,與其測『12 等於 2 乘 2 乘 3』,不如測『所有因數乘回去等於原數、而且每個都是質數』,然後讓電腦隨機丟上千個數字來驗。它還會自動把失敗案例縮到最小,直接告訴你哪裡錯。

🎙️ 帶走的一題:「你的程式裡有沒有兩個以上『各自有狀態、又會互相溝通』的元件(兩個服務、兩個狀態機、兩個 actor)?就算它們內部都是不可變的——它們之間有沒有可能『錯過彼此、失去同步』?這就是 FP 也擋不住的那類競態。」

更大範圍關聯

  • 第四部實務核心:這集涵蓋第四部三章中最重的兩章(測試、併發),下一集補上第 14 章 GUI。
  • property-based testing 的譜系:QuickCheck(Haskell)→ test.check / ScalaCheck / Hypothesis / fast-check,是 FP 影響全業界測試實務的最佳案例。延伸讀物可推《Property-Based Testing with PropEr/Erlang/Elixir》。
  • 與前作《Clean Craftsmanship》互文:TDD 的完整紀律在 Reading Uncle Bob 第五本講透,這集是「TDD 與語言無關」的 FP 驗證。
  • 並行模型的版圖:agent(actor 模型的近親)接得上 Erlang/Elixir 的 actor、Go 的 channel——FP 處理並行的工具光譜很廣。用 [軟體工程領域切分藍圖] 看,這集橫跨「測試」與「並行/分散式」兩支。

錄製建議

  • 建議時長:約 24 分鐘。配比:TDD 與語言無關(含 mock/REPL 破除)約 6 分、property-based testing(含 shrinking 診斷)約 9 分、併發真相(電話 race condition + 多核祛魅)約 9 分。
  • 討論策略:開場直接拿「我要推翻第一集說過的話」當鉤子,誠實感本身就吸引人。property-based testing 用「描述性質、隨機轟、自動縮小」三拍講,shrinking 那個埋 bug 的示範是亮點。併發那段用電話系統故事當高潮,慢慢鋪 Bob/Alice 互打的喜劇場面,再點破「狀態機失步」這個本質。
  • 這集的精神是「成熟、誠實、不吹捧」。整集要傳達 Uncle Bob 難得的自我修正姿態——一個敢推翻自己承諾的作者,反而更可信。這也呼應整季「經典要讀、但要帶腦讀」的定調。