第 1 / 10 部 待錄

導論與不可變性

《Functional Design》Podcast 準備稿:導論與不可變性

書名: Functional Design(Principles, Patterns, and Practices) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Functional Design.沒有變數的程式長什麼樣 (1/10) 涵蓋範圍: 前言 + 第1章 不可變性 + 第2章 持久性資料

背景速覽

這是 Reading Uncle Bob 系列的最後一本,也是 Uncle Bob 思想的晚期集大成。寫《Clean Code》的那個 Java 老兵,晚年一頭栽進了 Clojure 與函式式編程——這本書就是他把一輩子累積的設計直覺,放到「沒有賦值述句」這個新世界裡重新檢驗一遍的成果。這集是起手式:先講清楚這本書的立場(實用而非理論),再從函式式最反直覺、也最核心的一條——不可變性——切入。

一句話重點

函式式編程的本質可以濃縮成一句驚人的話:「不使用賦值述句的程式設計」——一旦沒有了「變數會變」這件事,時序耦合、競態條件這一整類 bug 就從源頭消失了;而看似昂貴的「不修改、只複製」,靠「結構共享」這個資料結構魔法,代價低到可以忽略。

值得討論的重點

1. 一個讓人坐立難安的定義:函式式就是「沒有變數」

Uncle Bob 不跟你談 Monad、Functor 這些理論(他開宗明義說那些對戰壕裡的工程師「已經被烤進蛋糕裡」了)。他給的定義粗暴又好記:函式式編程=不用賦值述句的編程。沒有 x = 1 這種「x 早就存在、現在被改掉」的動作,只有 int x = 0 這種「x 此刻誕生」的初始化。既然沒有東西會變,所有被命名的值都是常數——這就是不可變性。怎麼可能不賦值還寫得出程式?答案是遞迴:用「把新值當參數傳給下一次呼叫」取代「覆寫舊變數」。

2. 賦值才是萬惡之源:時序耦合與競態條件

這是這集最有縱深的一刀。為什麼避免賦值更好?因為只要有 x = 1,前後兩段程式碼就必須照順序跑,否則就錯——這叫時序耦合。Uncle Bob 用了個漂亮的比喻:open/closemalloc/freenew/delete 這些成對操作,「就像星際大戰的西斯,永遠是兩個」。我們有多少疑難雜症,最後是靠「把兩行呼叫對調」解決的?而垃圾回收,他說穿了是「人類承認自己不擅長處理時序耦合而拄的拐杖」。沒有賦值,就沒有時序耦合,也就沒有競態條件——這是整本書的地基。

3. 結構共享:消滅「複製太貴」這個藉口

第 2 章直面最大的質疑:不修改、每次都複製一整個陣列,記憶體和時間不是爆炸嗎?答案是不會。秘密在用來表示陣列——葉子是元素、分支只是走訪順序。要在尾端加一個值並保留舊版本,只需新增一個根節點,舊樹的絕大部分節點被新舊兩版共用。實務上用 32 元樹,一百萬元素的陣列深度才 4 到 5 層,改一個值只要複製 5 個小節點。這個性質叫「持久性」——可變動、但記得每一個過往版本。Clojure 原生就是這樣的資料結構。

注意事項

⚠️ 這本書用 Clojure 舉例,對大多數聽眾是陌生語言。整季的定調要在這集立好:我們聽這本書,是取它的思維,不是學語法。括號海、recurconj 這些不必糾結;聽眾只要抓住「狀態怎麼演進而不被修改」這個觀念就夠了。每集我會把 Clojure 名詞翻成白話,不要求聽眾看懂程式。

⚠️ Uncle Bob 對函式式的詮釋帶個人色彩。他自己說書裡的 functional 是「寬鬆使用」的——接受使用者輸入的「函式」其實不純(每次回傳都不同)。他關心的是函式式風格,不是學院派的「純函式式」。這點要老實講,免得懂 Haskell 的聽眾覺得我們在偷換概念。

⚠️ 「TCO(尾呼叫優化)」這個詞會反覆出現。一句話帶過即可:它讓遞迴不會把堆疊吃爆,效果等同迴圈。JVM 對它支援不佳,所以 Clojure 要用 recur 明示——這是技術細節,點到為止。

專家補充

💡 這本書最值得講的故事,是 Uncle Bob 本人的晚期轉向。一個寫了一輩子 C++/Java、以 OO 和 SOLID 聞名世界的人,晚年公開擁抱 Lisp 家族的 Clojure,還寫書論證「FP 與 OO 不但不對立,還互利」。這在工程界是很有份量的姿態——等於宗師親自拆掉「陣營對立」這道牆。整季都可以扣住這條主線。

💡 「不可變性消滅一整類 bug」不是文青口號,是有體感的。寫過多執行緒的人都知道,最難 debug 的災難就是競態條件——兩條執行緒搶著改同一塊記憶體,99.99% 對、偶爾錯一次,還重現不出來。FP 的承諾是:沒有更新,就沒有「同時更新」的問題。這條 hook 對任何踩過並行雷的工程師都極有共鳴(不過第 15 集會回來打臉這個承諾的「過度樂觀」,先埋伏筆)。

💡 「程式沒有變數還能寫」這件事的數學根基很美:1936 年圖靈(圖靈機)和邱奇(λ 演算)各自獨立證明了同一件事,後來又證明兩者完全等價。程序式風格=圖靈視角,函式式風格=邱奇視角。任何程式都能用兩種風格寫。這條接得上計算機科學的源頭,講起來很有縱深。

討論問題

🎙️ 開場鉤子:「如果我跟你說,有一種寫程式的方式,整個語言裡沒有變數——你第一反應大概是『那還寫得出東西嗎』。今天這集,就是要回答這個問題。」

🎙️ 自問自答:「賦值到底錯在哪?它不就是把值存起來嗎?」——用時序耦合回答:問題不在「存」,在「改」。一旦能改,前後順序就變成隱形的依賴,而那正是最難抓的 bug 的溫床。

🎙️ 帶走的一題:「回想你最近一次最難 debug 的 bug——是不是跟『某個東西在你沒預期的時候被改掉了』有關?FP 的整套設計,就是為了讓那種 bug 從根本上無法發生。」

更大範圍關聯

  • Reading Uncle Bob 系列收官:本系列共 6 本(Clean Code → Clean Architecture → Clean Coder → Clean Agile → Clean Craftsmanship → Functional Design)。這是最後、也最「叛逆」的一本——前五本立基於 OO,這本卻轉向 FP,是 Uncle Bob 思想的晚期延伸。
  • FP 思想源頭:「不可變、用遞迴推進狀態」直通 Abelson & Sussman 的《SICP》(MIT 經典)——那本書教的就是用 λ 演算的眼光看程式。可把這集當成「給 SICP 的一張現代實用門票」。
  • 持久性資料結構:結構共享背後是 Phil Bagwell 與 Rich Hickey(Clojure 之父)的 HAMT(Hash Array Mapped Trie)工作,是 Clojure 最硬的技術賣點,也影響了 Scala、Immutable.js 等。
  • 領域座標:用 [軟體工程領域切分藍圖] 看,這本書落在「coding-practice / 函式式設計」這一格,與《Grokking Simplicity》《Domain Modeling Made Functional》同屬「把 FP 講給實務工程師聽」的這一脈。

錄製建議

  • 建議時長:約 22 分鐘。配比:前言+系列定位約 5 分、不可變性(賦值的問題、時序耦合)約 10 分、持久性資料(結構共享)約 7 分。
  • 討論策略:開場一定要花 1 分鐘介紹「Reading Uncle Bob 是誰、這是收官第六本、為什麼一個 OO 宗師晚年寫 FP」——這個反差本身就是最好的鉤子。中段「賦值=萬惡之源」用 open/close、垃圾回收當拐杖這些生動比喻打,聽眾很買單。結構共享別陷進樹的細節,講清楚「不修改也不貴」這個結論即可。
  • 這集是整季地基,「取思維不取語法」「FP 與 OO 不對立」這兩條定調務必講清楚,後面九集都靠它撐。