第 8 / 10 部 待錄

整潔架構本尊

《Clean Architecture》Podcast 準備稿:整潔架構本尊

書名: 整潔的軟體架構 Clean Architecture(A Craftsman’s Guide to Software Structure and Design) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Clean Architecture.第 22-28 章:那張同心圓圖,到底在講什麼 (8/10) 涵蓋範圍: 第 22 章「整潔的架構」、第 23 章「Presenter 與 Humble Object」、第 26 章「主元件」、第 28 章「測試邊界」

背景速覽

前面七集,我們花了很多時間在「磚塊」跟「牆」——SOLID 原則、元件內聚、元件耦合、邊界劃分、政策與層級。這一集,Uncle Bob 終於把所有東西收攏成一張圖:那張你大概在無數投影片、無數部落格文章裡看過的「同心圓」。

這張圖是全書的心臟。但我想先打一劑預防針:很多人「看過這張圖」,卻沒真的「讀懂這張圖」。它不是一張分層架構圖(那種上面 UI、中間 Service、下面 DB 的疊疊樂),它的重點根本不在「分幾層」,而在箭頭的方向

這一集我會把同心圓講到位,然後帶你看三個讓這張圖能真正落地的配套零件:Presenter / Humble Object(怎麼把難測的東西推到邊緣)、Main 元件(那個最髒、負責組裝一切的傢伙),以及測試邊界(測試也是架構的一部分)。最後,我們會誠實面對一件事——這張圖其實不是 Uncle Bob 發明的,而且它有它的適用邊界。

一句話重點

整潔架構的全部精髓只有一句話——「原始碼的依賴關係,只能指向內層、指向更高階的政策」;同心圓、Presenter、Main、測試邊界,全都是為了守住這一條規則而存在的配套。

值得討論的重點

重點一:同心圓的四層,與唯一的鐵律「依賴規則」

由內而外四層:實體(Entities,企業級業務規則)、使用案例(Use Cases,應用層業務規則)、介面轉接(Interface Adapters,Controllers / Presenters / Gateways)、框架與驅動(Frameworks & Drivers,DB / Web / UI)。但層數不是重點,你要四層五層六層都行。重點是那條鐵律:原始碼依賴只能向內指。內層對外層一無所知——Use Case 不知道有沒有資料庫、Entity 不知道畫面長怎樣。這帶來書一開頭就承諾的五個「獨立於」:獨立於框架、可測試、獨立於 UI、獨立於資料庫、獨立於外部代理。

重點二:控制流向外,依賴卻向內——靠 DIP 反轉

這是整張圖最容易卡住的地方,也是最精妙的地方。實務上 Use Case 常常需要「呼叫」外層,例如算完結果要叫 Presenter 去顯示。控制流明明要向外走,但依賴規則說原始碼依賴只能向內。怎麼辦?答案是依賴反轉:在內層定義介面,讓外層去實作它。於是控制流向外、但原始碼依賴向內(Presenter 反過來依賴 Use Case 定義的 OutputBoundary 介面)。順帶一條紀律:跨邊界傳遞的,永遠是簡單的 DTO,絕不把 Entity 或一行 DB Row 直接丟給外層

重點三:Humble Object——把「測不動的東西」榨乾到不需要測

總有些東西天生難測:GUI 畫素、DOM、SQL 連線。Humble Object 模式的招數是把一個難測的行為「劈成兩半」。一半是謙卑物件(View),笨到只會「把資料貼到螢幕上」、笨到幾乎不可能寫錯、笨到不需要測;另一半是邏輯模組(Presenter),負責所有判斷——日期格式化、金額格式化、「餘額負就 isRedText=true」——它只操作單純資料結構,超好測。中間用一個只有字串/布林/列舉的 View Model 連起來。同一招也用在 DB 邊界:SQL 是謙卑的 Gateway,複雜邏輯留在可測的 Interactor。

重點四:Main 是最髒的元件,也是測試邊界的延伸

我們整本書都在講依賴抽象,但總得有人去 new 出具體物件、總得有人把 Spring、Oracle driver、真實 UI 兜起來。這個髒活就是 Main。Main 是層級最低、最髒、卻沒有任何東西依賴它的元件——它在依賴鏈的最邊緣。神來一筆是:Main 其實是應用程式的一個「外掛」,所以你可以有 MainDev、MainTest、MainProd,各自注入 Mock、Stub、真實設施。而測試呢?測試在架構圖裡比 UI 還外層,它也是系統的一個元件,要靠 Testing API 把「測試結構」跟「應用結構」解耦,否則你會掉進「脆弱測試」地獄——改一個欄位名、幾百個測試一起紅。

注意事項

⚠️ 這張圖不是 Uncle Bob 原創,而是「同一個想法的不同畫法」。 整潔架構跟 Alistair Cockburn 的 Hexagonal(Ports & Adapters)、Jeffrey Palermo 的 Onion Architecture,本質上是同一家族——核心都是「業務邏輯在內、技術細節在外、依賴只能向內」。Uncle Bob 自己在書裡也承認是「整合」前人智慧。所以聽眾如果之前學過六邊形,不必覺得這是全新東西;反過來說,別把「整潔架構」當成某種獨門祕方來膜拜。三張圖,一個靈魂。

⚠️ 層層 indirection 對小專案 / CRUD 是過度設計,這是真實且嚴肅的反對意見。 一個增刪改查為主的後台,你硬要 Entity / Use Case / Input Boundary / Output Boundary / Presenter / View Model / Gateway 全套上,常常是寫了三倍的樣板程式碼,只為了搬資料。DHH(Rails 之父)公開批評過這種「抽象潔癖」——他認為對很多 Web 產品而言,貼著框架走、讓 Active Record 直接是你的領域物件,反而更快更清楚。我的立場:依賴規則是一個滑桿,不是開關。你可以只反轉「最值得保護的那條邊界」,而不是每一條邊界都鍍金。

⚠️ Humble Object 與全套 DTO 轉換有真實成本。 為了讓 Presenter 可測,你要多定義 View Model、多寫一層轉換;很多團隊的 Presenter 最後只是把欄位原封不動搬過去,測試價值極低、維護成本卻是真的。判斷標準應該是「這個邊界的邏輯複雜到值得隔離嗎」,而不是「規範說要有 Presenter 所以加一個」。

⚠️ 「Testing API + 繞過 UI / 繞過安全限制」要小心被濫用。 解耦測試與結構是對的,但「讓測試直接存取狀態、繞過安全」如果設計不當,會養出一組「只測得過自己」的測試——它驗證的是 Testing API,而不是使用者真正會走的路徑。端對端的真實路徑仍然需要少量測試守著。

專家補充

💡 Spring 的「組態類別」就是 Main 元件的現實化身。 當你看到 @Configuration + @Bean,或一個 main() 裡的 DI 容器啟動,那就是 Uncle Bob 講的 Main——組裝 + 移交。理解這點後,「為什麼 DI 設定要集中、為什麼測試要能換掉這層」就突然合理了:因為它本來就該是可抽換的外掛。

💡 Humble Object 是「為了可測性而做的依賴反轉」,跟 MVP/MVVM 同源。 Android 的 MVVM、前端的 Presentational/Container 元件拆分、Redux 把邏輯抽到 reducer——全都是同一招:把笨的渲染層榨乾,把聰明的邏輯留在好測的地方。聽眾若做前端,這一段其實他們天天在用,只是沒叫它 Humble Object。

💡 ArchUnit / Spotless 之類的工具,能把「依賴規則」從口號變成 CI 守門員。 書裡靠紀律維持依賴方向,但紀律在交付壓力下會失守。現代做法是寫一條 ArchUnit 測試:「domain package 不准 import infrastructure」,讓編譯/CI 直接擋下違規。這個觀念在最後一集(遺漏的章節)會被 Simon Brown 大力強調,這集先埋個伏筆。

討論問題

🎙️ 你手上最近一個專案,如果要畫成同心圓,「Use Case 層」實際存在嗎?還是你的業務邏輯其實散在 Controller 和 Service 裡?誠實回答這題,比畫對那張圖更有價值。

🎙️ 「依賴規則是滑桿不是開關」——如果只能保護一條邊界,你會選哪一條?是 DB 那條,還是 Web 那條?為什麼?

🎙️ 你寫過「為了符合規範而存在、其實沒測到什麼」的 Presenter 或 DTO 轉換嗎?回頭看,那個邊界值得隔離嗎?

🎙️ 你的測試是「重構的朋友」還是「重構的敵人」?上次大改一個欄位名,紅了幾個測試?這個數字其實在量你的「結構耦合」。

更大範圍關聯

  • 向上承接前七集: 同心圓不是憑空冒出來的——它是 SRP(水平切角色)、DIP(反轉依賴方向)、邊界劃分、政策與層級這些前面零件的「總成圖」。這集是把磚塊砌成建築的一刻。

  • 向後鋪陳 EP9-EP10: 同心圓最外圈那些「藍色的細節」——資料庫、Web、框架——下兩集會被一一點名「你只是細節」。這集講的是「規則」,下兩集講的是「把規則套到具體技術上會吵起來的地方」。

  • 與 DDD 的交會: 內層用「領域通用語言」命名、Entity 是企業級規則——這跟 Eric Evans 的 Domain-Driven Design 高度共鳴。整潔架構可以視為「DDD 的戰術擺位圖」。

  • 與現代框架的張力: Rails、Spring Boot、Next.js 這類「全家桶」框架,商業模式就是讓你貼著它快速產出。整潔架構要你「跟框架保持距離」,本質上是在跟框架的設計哲學唱反調——這個張力會在 EP9、EP10 整個爆發。

錄製建議

目標約 20 分鐘,單人主講。建議節奏:

  1. 開場 2 分鐘——破除迷思先行: 直接說「今天這集講那張你看過一百次的同心圓圖,但我打賭你沒真的讀懂它」。先講「它不是分層疊疊樂,重點是箭頭方向」,把聽眾的注意力從「層」拉到「依賴方向」。

  2. 核心 8 分鐘——吸收: 依「重點一→重點二」講透依賴規則與 DIP 反轉(這是全集最硬、也最值錢的部分,控制流向外 vs 依賴向內這個反差一定要講清楚,可以用「Use Case 算完結果,怎麼通知畫面」當具體例子)。再用 4 分鐘帶 Presenter/Humble Object 與 Main,這兩個用「劈成兩半」「最髒的組裝工」當記憶鉤子。

  3. 辯證 6 分鐘——這集的靈魂: 進「注意事項」。先丟「這圖不是他發明的、跟六邊形/洋蔥同源」降低神祕感,再進 DHH 那段「CRUD 小專案這是過度設計」。這裡語氣要誠實、不要替任何一方護航,落在「依賴規則是滑桿不是開關」這個我自己的折衷立場。

  4. 收尾 3-4 分鐘: 用一句話總結——「整本書濃縮成一句,就是依賴規則」,並預告 EP9「下集 Uncle Bob 要對資料庫和 Web 開砲,這是全書最有名也最容易吵架的兩章」。

語速策略:DIP 反轉那段刻意放慢、可以重複講一次;辯證段落語氣轉成「跟工程師朋友聊真心話」,這是恩普拉氏的招牌調性。