《Domain-Driven Design 領域驅動設計》Podcast 準備稿:柔性設計 Supple Design
書名: 領域驅動設計 Domain-Driven Design(Tackling Complexity in the Heart of Software) 作者: Eric Evans 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Domain-Driven Design.讓設計柔軟到歡迎變更 (8/13) 涵蓋範圍: Part III 第10章 柔性設計(Supple Design)
背景速覽
這集是 Part III 篇幅最長、也最「工程味」的一章。前一集講怎麼讓模型變深,這章講怎麼讓設計變柔軟(supple)——一個歡迎變更、用起來愉快的設計。Evans 整理出一組互相強化的模式:揭示意圖的介面、無副作用函式、斷言、概念輪廓、獨立類別、運算封閉性、宣告式設計。這章和函數式程式設計、和 Clean Code 的某些主張高度共鳴,是 DDD 戰術設計裡最接近「純軟體工藝」的一章。
一句話重點
深層模型給你原料,柔性設計把原料錘鍊成「歡迎變更」的形狀——透過揭示意圖的介面、無副作用函式、明確的斷言,讓封裝和抽象變得安全,讓開發者敢於不斷重新組合而不怕踩雷。
值得討論的重點
1. 軟體先服務開發者
Evans 開宗明義:軟體最終服務使用者,但在那之前,它得先服務開發者——尤其在強調重構的流程裡,開發者一直在重排、改寫每個部分。設計差,就難重構、難組合、重複叢生,開發者甚至害怕去看既有的混亂程式碼。柔性設計同時服務兩種角色:用模型寫應用的人(要能靈活表達場景)、和維護變更的人(要容易理解、變更在彈性點上彎曲)。Evans 誠實地說:沒有柔性設計的公式,只有一組「適用時往往奏效」的模式。
2. 三個讓抽象變安全的模式
這三個是核心,互相強化:
- Intention-Revealing Interfaces(揭示意圖的介面):命名描述「效果和目的」、不洩漏實作手段,符合統一語言。如果開發者得讀實作才會用一個元件,封裝的價值就丟了。先寫測試(站在 client 角度)能逼出好介面。
- Side-Effect-Free Functions(無副作用函式):把操作分成 query(取資訊、無副作用)和 command(改狀態)。盡量把邏輯放進不改狀態、回傳結果的 function。複雜計算移進不可變的 Value Object——VO 天生 immutable,所有操作都是 function,安全、好測試。
- Assertions(斷言):來自 Meyer 的 Design by Contract——用 post-condition、precondition、class invariant 描述狀態而非程序。如果你信任 post-condition,就不必管方法內部怎麼運作。程式語言編不了的,就寫自動化測試。
三者合起來:揭示意圖的介面(溝通性)+ 無副作用函式(可預測性)+ 斷言(明確性)= 讓封裝和抽象變得安全。
3. Conceptual Contours(概念輪廓)
別機械化地問「該切細還是切粗」。大多數領域深處有邏輯一致性,反覆重構最終會讓概念輪廓浮現——觀察「變化與穩定的軸線」,把模型對齊領域中一致的面向。判準:每個切分決定,問自己「這是當前模型的權宜之計,還是呼應了底層領域的某個輪廓?」當連續的重構趨於局部化、不再撼動多個廣泛概念時,就是模型契合度的指標。
4. 走向宣告式:Specification 組合與 Shares Math
把概念解耦、降低依賴(Standalone Classes)、讓操作回傳同型別(Closure of Operations,像實數相乘還是實數),會把你推向 Declarative Design(宣告式設計)——用「可執行的規格」描述軟體該有的性質。Evans 延伸第9章的 Specification:用 AND/OR/NOT 組合成 Composite,化學品「又揮發又易爆」就 ventilated.and(armored),表達力大增。
壓軸是把所有模式整合的 Shares Math(接第7集的聯貸 Share Pie):把 Share Pie 做成不可變 Value Object,定義 plus、minus、prorated 三個無副作用、運算封閉的數學操作,Loan 的方法瞬間變得極簡、自我說明。Evans 點出四個讓它如此柔軟的特性:複雜邏輯封裝在 Value Object 的無副作用函式裡、狀態修改簡單且有斷言、概念解耦、借用了人人熟悉的「算術」形式化。
注意事項
⚠️ 這章很容易被讀成「一堆好聽的軟體原則清單」。但 Evans 的核心訊息是這些模式互相強化、且都服務同一個目的:讓抽象變安全、讓設計歡迎變更。別把它們當成獨立的 checklist 背,要理解它們為什麼湊在一起。
⚠️ Declarative Design 有陷阱,Evans 講得很平衡。宣告式語言常表達力不足、框架讓「超出自動化部分」的擴展很難、code generation 會破壞迭代循環。很多嘗試的結局是「模型和應用被框架弄笨(dumbing-down)」。所以 Evans 的務實建議是:你不一定要全套 DSL,只要有了揭示意圖的介面、無副作用函式、斷言,你就已經踏進宣告式的領域、享受到大部分好處了。
⚠️ Closure of Operations、Standalone Classes 主要適用 Value Object,不適用 Entity——Entity 的生命週期在領域中有意義,你不能憑空造一個來回答問題。別硬把這些模式套到 Entity 上。
⚠️ Subsumption(涵蓋判定,判斷一個 Specification 是否比另一個更嚴格)很強但複雜。只有 AND 時證明簡單,加上 OR/NOT 就複雜許多。Evans 建議:大多數情況透過放棄部分操作來避免這種複雜度。又一次提醒:別為了模式的完整而過度設計。
專家補充
💡 這一章其實是 DDD 和**函數式程式設計(FP)**的會合點。無副作用函式、不可變值、運算封閉性——這些就是 FP 的核心信條。Evans 在 2003 用 OO 的語言講出了 FP 的精神。今天如果你用 Kotlin、Scala、F#、甚至現代 Java/C# 的 record,會發現「不可變 Value Object + 純函式」幾乎是免費的。所以這章在 FP 復興的今天讀,特別有共鳴。可以跟聽眾聊:DDD 的柔性設計和 FP 其實是同一座山的兩條路。
💡 「軟體先服務開發者」這句,和 Clean Code 的「程式碼是寫給人看的、讀寫比 10:1」完全同調。但 Evans 多了一層:好讀還不夠,要好改——設計要在「最常變的地方」保持柔軟。這比 Clean Code 更進一步,因為它把「哪裡該柔軟」交給領域知識決定(概念輪廓),而不是一視同仁地追求小函式。
💡 命令查詢分離(Command-Query Separation, CQS)這個原則——「改狀態的方法別回傳資料、回傳資料的方法別改狀態」——也是 Meyer 提出的,Evans 在這章直接採用。它後來放大成架構層級的 CQRS。所以這章其實藏著從「方法設計原則」到「系統架構模式」的種子。CQS 是好上手、立刻能用的個人習慣,值得推給聽眾。
討論問題
🎙️ 開場鉤子:「我們常說要寫『好懂』的程式,但 Evans 要的更多——他要『好改』。一個歡迎變更、改起來愉快的設計,他叫它 supple,柔性的。這集講怎麼讓你的設計,柔軟到你不再害怕打開它。」
🎙️ 自問自答:「無副作用函式為什麼這麼重要?」——因為當呼叫一個方法可能引發二級、三級的連鎖效果時,你根本無法預測後果、不敢組合。把邏輯收進純函式和不可變值,組合就變安全,你才敢蓋出豐富的行為。
🎙️ 帶走的一題:「挑一個你 code 裡『又改狀態又回傳值』的方法,試著把它拆成一個純查詢和一個簡單命令——感受一下,它是不是突然好測試、好理解多了?」
更大範圍關聯
- 柔性設計 = 深層模型的工程互補:第7集挖出深層概念是原料,這集把原料錘成歡迎變更的形狀。兩者合起來才是「重構邁向深層理解」的完整圖像。
- 連到函數式程式設計:無副作用函式、不可變值、運算封閉——DDD 用 OO 講出了 FP 的核心。在 Kotlin/Scala/F# 與現代 record 的時代,這章的理想幾乎成了免費午餐。
- 連到 Clean Code 與 Refactoring:「軟體先服務開發者」呼應 Clean Code「程式碼是寫給人看的」;但 Evans 把目標從「好讀」推到「好改、在對的地方柔軟」。重構手法(Fowler)是達成柔性設計的工具——程式碼設計三本在這裡咬合。
- 連到 CQS / CQRS:命令查詢分離從這章的方法設計原則,後來放大成系統架構模式。
- 形式化的力量:Shares Math 示範「找出領域裡對應數學的部分、套用形式化」能帶來極致的柔性。這條會在第 12 集(大尺度結構的 Cohesive Mechanism)再出現。
錄製建議
- 建議時長:約 26-28 分鐘(這章最長、最密)。配比:軟體先服務開發者+三個安全模式約 12 分(核心,慢慢講透「為什麼這三個讓抽象變安全」)、概念輪廓約 4 分、宣告式設計+Specification 組合約 6 分、Shares Math 整合範例約 5 分。
- 討論策略:這章模式多,最大風險是變成念清單。對策:抓住「讓抽象變安全」這條主線,把三個核心模式(揭示意圖介面、無副作用函式、斷言)當成一組來講,反覆強調它們為什麼湊在一起。Shares Math 放最後當高潮——它把這集所有模式 + 第7集的突破故事整合,是最好的收尾。
- 大力連到函數式程式設計,會讓現代聽眾眼睛一亮:「原來 2003 的 DDD 早就在講 FP 的東西。」這是這集最容易出彩的切入點。