第 5 / 10 部 待錄

型別與資料流

《Functional Design》Podcast 準備稿:型別與資料流

書名: Functional Design(Principles, Patterns, and Practices) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Functional Design.靜態 vs 動態,是個偽戰爭 (5/10) 涵蓋範圍: 第10章 型別 + 第三部導言 + 第11章 資料流

背景速覽

上一集薪資系統留下一個不安:那些被叫做「物件」的東西其實只是沒有型別的 hash map,誰都能往裡塞任何東西,編譯器不檢查——地獄之門不就開了嗎?這集前半(第 10 章)正面回答這個千古爭論:靜態型別 vs 動態型別。後半(第 11 章)開啟全書第三部「函式式設計」,把前面零散觀察到的「資料流」傾向,正式升級成設計的核心偏好。這集是承上啟下的樞紐——從「比較兩種風格」過渡到「FP 怎麼做設計」。

一句話重點

靜態 vs 動態型別的吵架是個偽戰爭——兩邊都對(動態更好寫、靜態更安全更易懂),而 clojure.spec 這類工具找到了甜蜜點:你想要多少型別檢查就有多少、自由決定在哪檢查、還能描述靜態系統根本檢查不到的動態約束;而 FP 設計的靈魂,就是讓資料像水流過一連串純函式的管線,而不是一個個元素地迭代。

值得討論的重點

1. 靜態 vs 動態:別再吵了,兩邊都對

Uncle Bob 對這場「常常情緒化、雙方在自己擂台上吼、誰也聽不見誰」的爭論給了極成熟的裁決:動態型別讓程式更易寫;靜態型別讓程式更安全、更易理解、更內部一致——兩個論點都成立。重點不是選邊,是找平衡。他用 clojure.spec 示範:你可以對 payroll 的入口檢查 ::db、出口檢查結果,想嚴就嚴、想鬆就鬆。這比「全有或全無的靜態型別」更務實。

2. spec 是「漸進式型別」:你決定何時、何處、多嚴

這是這集最實用的觀念。傳統靜態語言(Java、Haskell)的型別是「全域強制」——每個變數都得有型別、編譯器全程盯著。Clojure 的 spec 反過來:預設不檢查,但你想檢查的地方可以檢查得比靜態語言還細。例如 spec 能規定「這個數字必須大於 0」「這個 tuple 第一位必須是 :salaried」——這些是型別系統表達不了的「值的約束」。Uncle Bob 說,spec 給的是「兩個世界各自最好的部分加總」。而且實踐 TDD 的人本來就靠測試確保物件被正確構造,型別地獄沒那麼可怕。

3. 資料流:FP 設計的核心偏好——行為流經物件,還是物件流經行為

第 11 章用 Advent of Code 的 CRT 顯示器題目把「資料流」說透。Java 版是「逐 cycle 迭代、累積像素」的迴圈,所有狀態(x、pixels、cycle、ic……)互相耦合——這是可變物件最常見的耦合形態,還得多一個 extraCycles 標記處理特例。Clojure 版是三段管線:execute-file → render-cycles → print-screen,state 流經每一站、每站用舊狀態產新狀態。Uncle Bob 把它類比成 Unix 的 |:資料從 ls 流出、經 pipe 進 cut,各做各的、互不打擾。連「addx 吃兩個 cycle」這種特例,FP 版用一行「把 x 連加兩次」就解決,根本不需要特殊邏輯。

注意事項

⚠️ spec 的程式碼看起來很嚇人(一大串 s/def),要先給聽眾打預防針:Uncle Bob 自己說「靜態型別語言要表達同樣多的約束時也是這個體量」——不是 Clojure 囉嗦,是「精確描述資料」這件事本來就有成本。重點是 spec 把這個成本變成可選的、漸進的

⚠️ 別把這集講成「動態型別贏了」。Uncle Bob 的立場是調和派,不是動態派。他甚至說 spec 的價值在於「同時擁有兩個世界的好處」。如果你站隊,就背叛了這章的精神。對靜態型別愛好者(TypeScript、Rust、Haskell 用戶)要尊重——他們的「安全、易懂、內部一致」是真的好處。

⚠️ 資料流那段,Uncle Bob 誠實補了一句很重要的話:「這是設計品味的差異,不是語言限制」。他也能用 Clojure 寫成跟 Java 一樣的迭代演算法;新版 Java/C# 也有 Stream/LINQ 能寫管線。差別是「自然傾向」——FP 程式設計師預設鋪管線,命令式程式設計師預設迭代。別把它講成「FP 才能做資料流」。

專家補充

💡 「漸進式型別」(gradual typing)是過去十年程式語言界最重要的趨勢之一,clojure.spec 是其中一個流派。同一條路上的還有 TypeScript(給 JS 加可選型別)、Python 的 type hints + mypy。Uncle Bob 這章其實站在一個更大的時代潮流上:業界正在從「靜態 vs 動態的二元對立」走向「漸進、可調」的中間地帶。這個脈絡能讓聽眾覺得這不只是 Clojure 的小聰明。

💡 spec 能表達「值的約束」(>0、必須是某個 enum)這件事,其實指向型別系統的一個盲區。傳統型別只管「形狀」(這是個 int),管不了「範圍」(這個 int 必須在 1 到 100)。要管範圍,要嘛用 refinement types(學術界、F* 那種),要嘛用 runtime 檢查(spec、契約式設計)。Uncle Bob 選後者,跟他《Clean Craftsmanship》裡推崇的「契約式設計」(Design by Contract,源自 Bertrand Meyer 的 Eiffel)一脈相承。

💡 「資料流 vs 迭代」是品味差異這點,值得深挖一層。為什麼 FP 程式設計師「自然」偏向管線?因為純函式不能改變傳入的資料,所以你被迫「產出新資料、傳給下一個函式」——管線是純度的自然產物。命令式程式設計師有可變變數可用,迭代+累積是阻力最小的路。工具形塑習慣——這是個關於「語言如何塑造思維」的好討論點。

討論問題

🎙️ 開場鉤子:「『靜態型別還是動態型別比較好』——這大概是程式設計師吵得最兇、也最沒結論的戰爭之一。今天 Uncle Bob 要告訴你:這場仗從一開始就打錯了。」

🎙️ 自問自答:「沒有編譯器幫我檢查型別,我怎麼睡得著?」——靠兩樣東西:TDD 的測試(本來就在確保物件被正確構造),加上 spec(想在哪檢查、檢查多嚴,你自己決定)。你甚至能檢查靜態型別根本管不到的東西,比如『這個數字必須大於零』。

🎙️ 帶走的一題:「看看你手上的程式:你是習慣『寫個迴圈、逐個處理、累積結果』,還是『把資料丟進一條 map/filter/reduce 的管線』?這禮拜挑一段迴圈,試著改寫成管線——感受一下哪一種讀起來更像在描述『資料怎麼變』。」

更大範圍關聯

  • 承上啟下的樞紐:型別章收束第二部「比較分析」的不安,資料流章開啟第三部「函式式設計」。下一集是全書最硬的 SOLID。
  • 漸進式型別運動:clojure.spec 與 TypeScript、Python type hints、Racket 的 contracts 同屬「可選型別」家族——這是當代語言設計的主流方向。
  • 契約式設計血脈:spec 的 :pre/:post 條件直接繼承 Bertrand Meyer 的 Design by Contract(Eiffel 語言),也呼應 Uncle Bob 在《Clean Craftsmanship》對契約的推崇。
  • 資料流的譜系:「物件流經行為」接得上 Unix 哲學(小工具 + pipe)、APL/J、以及《Grokking Simplicity》的 calculations 鏈。用 [軟體工程領域切分藍圖] 看,資料流扣到「函式式 / 資料處理」這一支。

錄製建議

  • 建議時長:約 22 分鐘。配比:型別與 spec(含靜態動態之爭)約 12 分、資料流(CRT 範例 + Unix pipe 類比)約 10 分。
  • 討論策略:型別那段最容易引發聽眾共鳴(誰沒參與過型別之爭),用「兩邊都對、別再吵了」的成熟立場切入,再用 spec 的「漸進、可調」收尾,會讓聽眾覺得耳目一新。資料流那段用 Unix pipe 類比最好懂——大家都用過 ls | grep,那就是資料流。務必補上 Uncle Bob「這是品味不是限制」的誠實註腳,保持平衡。
  • 這集是樞紐集,結尾預告下一集:「講完了風格和品味,下一集要動真格——SOLID 五大設計原則,在沒有類別的函式式世界裡,還站得住嗎?」