第 5 / 10 部 待錄

元件耦合與架構

《Clean Architecture》Podcast 準備稿:元件耦合與「什麼是架構」——ADP、SDP、SAP 與延遲決策

書名: 整潔的軟體架構 Clean Architecture(A Craftsman’s Guide to Software Structure and Design) 作者: Robert C. Martin(Uncle Bob) 系列: Reading Uncle Bob(恩普拉氏) 公開標題建議: Reading Uncle Bob|Clean Architecture.元件耦合+架構:依賴指向穩定的方向,以及「盡量晚做決定」的勇氣 (5/10) 涵蓋範圍: 第四部分第 14 章「元件耦合性」(重章) + 第五部分第 15 章「什麼是架構」

背景速覽

上一集我們講了元件「內部」怎麼內聚——哪些類別該打包在一起,那個沒有正確答案的張力三角。這一集處理另一半:元件「之間」怎麼耦合——依賴箭頭該怎麼畫,以及更宏觀的問題:到底什麼是架構?架構師整天在做什麼?

第 14 章是全書數學味最濃的一章,Uncle Bob 罕見地給了量化公式(不穩定性 I、抽象性 A、距離 D)。三個耦合原則 ADP/SDP/SAP 用一句白話串起來就是:「不要循環依賴、依賴要指向更穩定的方向、越穩定的東西越該抽象。」 這三句話加上那張「主序列」象限圖,構成了整本書「依賴規則」的最終形態——後面所有架構章節的同心圓,本質都是這三條規則的具象化。

然後第 15 章是一個漂亮的收束與轉場:它第一次正面回答「什麼是架構」,並丟出全書最有哲學意味、也最值得辯論的一句話——架構師的工作不是做決定,而是讓決定可以盡量晚做。 這集就停在這個張力上:延遲決策的智慧,對上「先把東西做出來」的現實。

一句話重點

元件耦合三原則(ADP 無環、SDP 依賴指向更穩定、SAP 越穩定越抽象)把「依賴規則」量化成可檢測的數學;而「什麼是架構」則把架構師重新定義為——維持軟體「軟性」、盡量延遲不可逆決策的人。

值得討論的重點

1. ADP——不要循環依賴,以及「宿醉症候群」

ADP(無環依賴原則):元件依賴圖裡不該有環。 Uncle Bob 用一個工程師都懂的痛來開場——「宿醉症候群(Morning After Syndrome)」:昨天程式跑得好好的,今天回來編譯失敗,因為隔壁團隊改了他們的元件。當系統出現 A → B → C → A 的循環時,這三個元件其實已經黏成一個無法獨立發佈的「超級元件」,牽一髮動全身。

解法兩種:(1) 用 DIP 反轉依賴——加一個介面,把其中一個箭頭調頭;(2) 抽出新元件——把造成循環的共同依賴類別抽到一個新元件去。

關鍵心法(書裡特別用 NOTE 強調):元件結構不是專案第一天 top-down 設計好的,而是隨系統發展演進的。 環會隨功能增加不斷冒出來,架構師的工作是持續監控、隨時消除。這點很反直覺——很多人以為架構是一次性畫好的藍圖。

2. SDP——依賴要指向更穩定的方向(以及「穩定 = 難改」不是「不變」)

SDP(穩定依賴原則):依賴箭頭必須指向更穩定的方向。 這裡「穩定」有個反直覺的定義:穩定不是「不會變」,而是「難以改變」。 一個被很多人依賴的元件,你想改它就得驚動一堆下游,所以它「難改」——它穩定。

Uncle Bob 給了量化公式,這是本集可以秀一下的硬貨:

$$I = \frac{FanOut}{FanIn + FanOut}$$

  • Fan-in(輸入依賴): 多少外部類別依賴我(越多越穩定)。
  • Fan-out(輸出依賴): 我依賴多少外部類別。
  • I = 0: 最穩定(只有人依賴我、我不依賴人)——負責者。
  • I = 1: 最不穩定(我不被依賴、卻依賴一堆人)——不負責者。

SDP 法則:依賴箭頭指向的元件,I 要小於發起依賴的那個。也就是 I 值要順著依賴方向遞減。如果一個 I=1(易變)的元件被一個 I=0(難改)的元件依賴,那就是架構錯誤——你把穩定的東西綁死在易變的東西上了。

3. SAP 與主序列——越穩定越該抽象,以及那兩個要逃離的地帶

SAP(穩定抽象原則):一個元件的抽象度,應與它的穩定度一致。 邏輯是這樣的:如果一個元件很穩定(I=0、很難改),我們又希望它能擴展,怎麼辦?答案是讓它抽象(OCP:用介面/抽象類別)。穩定 + 抽象 = 難改但靈活。

抽象度也有公式:$A = N_a / N_c$(抽象類別數 / 總類別數),A=0 全具體,A=1 全抽象。

把 I 當 X 軸、A 當 Y 軸,就得到那張著名的象限圖,有兩個要逃離的死亡地帶:

  • 痛苦地帶(Zone of Pain),(0,0): 高度穩定 + 高度具體。又難改(大家依賴)又無法擴展(不是介面)。典型例子:資料庫 schema、具體 utility 函式庫——這個例子工程師一聽就懂。
  • 無用地帶(Zone of Uselessness),(1,1): 高度不穩定 + 高度抽象。沒人依賴你,你卻搞了一堆抽象介面。這就是過度設計。
  • 主序列(Main Sequence): 連接 (1,0) 和 (0,1) 的對角線。好元件該落在這條線附近——越穩定越抽象、越不穩定越具體。架構師的目標是讓每個元件到這條線的距離 $D = |A + I - 1|$ 趨近 0。

這給了一個罕見的東西:量化架構品質的數學依據。 你真的可以畫散點圖、看哪些元件落在死亡地帶。

4. 什麼是架構——維持「軟」,以及「延遲決策」

第 15 章把鏡頭拉到最高。Uncle Bob 先打破一個迷思:架構師也寫程式,不是坐象牙塔畫圖的。 架構是「建置者賦予系統的樣貌(Shape)」,而這個樣貌的目的不是讓系統「能跑」(那是基本),而是支撐系統的整個生命週期——終極目標是「最小化建置與維護成本、最大化工程師生產力」。

架構影響生命週期的四個面向:開發(讓多團隊並行不互相踩腳)、部署(理想是「單一動作」就跑起來;反例是要設定十幾個微服務 config——這往往是切太細的失敗架構)、運行(影響其實最小,效能常能靠加硬體解決;但好架構能「尖叫」出系統在做什麼業務)、維護(成本之王,主要花在「探礦 Spelunking」——為改一行得花數小時挖懂現有邏輯)。

然後是全章靈魂——保持選項開放(Keeping Options Open):

  • 軟體之所以叫「軟」體,就是因為它本該易於改變。架構師的任務是維持這份「軟」。
  • 區分策略(Policy,業務規則,系統真正價值)細節(Details,DB、Web 框架、硬體——都只是讓人/系統與策略互動的機制)
  • 延遲決策: 「架構師的職責不是做決定,而是讓決定盡量晚做。」好架構讓你開發一年後還沒決定要用 MySQL 還是 Oracle、要 Web 還是 Console。為什麼?因為決定推得越晚,你掌握的資訊越多,決定越正確。 好架構讓你能對老闆說:「資料庫的事先不急,我可以先把核心業務邏輯寫好測完。」

注意事項

⚠️ 元件耦合三原則 + I/A/D 公式在大型 monorepo 或多團隊微服務體系裡非常實用(真的有工具能掃依賴圖、抓循環、畫主序列散點),但對小專案明顯偏理論。一個三人團隊的單體應用,花力氣去算每個元件的 I 值,投報率很低。誠實的講法是:這套量化工具的價值與系統規模、團隊數量正相關;規模不到,它就是好看的數學而非可行動的指引。別讓聽眾以為「不算 I 值就是不專業」。

⚠️ 「延遲決策」是本書最迷人也最容易被誤用的觀念,值得花時間辯證。理想面:推遲不可逆決定、保留選項,確實是高手的智慧。現實面有兩個反對聲音:(1) YAGNI / 精實創業——有時候「先用最簡單的 SQLite 做出來、上線拿到回饋」比「為了將來能換 DB 而蓋一堆抽象層」更有價值,因為過早的「保持彈性」本身就是一種投機性過度設計。(2) DHH / Rails 哲學——「擁抱單體、擁抱具體選擇」,認為很多「保持選項開放」的抽象,代價是讓今天的開發變慢、讓程式變難讀。真正的張力是:延遲決策保護了「未來的可改性」,但代價可能是「現在的簡單性」。 沒有免費的選項——保持選項開放本身有持有成本。

⚠️ Uncle Bob 對微服務切太細的隱性批評(部署需要設定十幾個 config = 失敗架構)在 2025 年看很有先見之明,但也要平衡:微服務的價值不只在「單一動作部署」,還在組織擴展、獨立伸縮、技術異質性。用「部署是否單一動作」當唯一評判標準,對某些大規模場景並不公平。書裡的視角偏向單體/模組化單體,聽眾要懂得脈絡化。

⚠️ 「穩定 = 難改」這個定義雖然精準,但容易讓人把「穩定」當成褒義詞而盲目追求。其實 I=0 的元件「難改」是雙面刃:它穩,但一旦真的要改,代價極高。SAP 的整個重點就是補救這個——強迫穩定的東西同時抽象,讓「難改」的元件至少「易擴展」。在節目裡要把這層因果講清楚,否則聽眾會誤以為「越穩定越好」。

專家補充

💡 SDP/SAP 的 I/A/D 公式並非只是學術擺設——業界真有靜態分析工具實作它(早年的 JDepend、NDepend,到現代各種架構守護工具如 ArchUnit、Structure101)。如果想給聽眾一個可行動的 takeaway:在 CI 裡加一條「禁止新增循環依賴」的守護規則(ArchUnit 一行就能寫),是 ADP 最務實的落地——比手算 I 值實際得多。

💡 「延遲決策」和「策略 vs 細節」直接通向全書下半場的招牌圖:Clean Architecture 同心圓。內圈是 Entities/Use Cases(策略),外圈是 DB/UI/框架(細節),依賴規則規定箭頭只能由外指向內。這集講的 SDP「依賴指向更穩定」+ 第 15 章「策略比細節穩定」,合起來就推導出了那張同心圓——所以這集其實是後半本書的「公理層」。可以預告:你現在學的三條耦合規則,下集開始會看到它們被砌成一座具體的城堡。

💡 「架構是演進出來的、不是第一天畫好的」(ADP 那段 NOTE)呼應了 Martin Fowler 的「演進式架構(Evolutionary Architecture)」和「YAGNI」。這跟「延遲決策」是同一枚硬幣:正因為架構會演進、決策能延遲,我們才不需要、也不應該在第一天就把所有結構定死。這條線把 Uncle Bob 和敏捷社群的主流共識接上了。

討論問題

🎙️ 你的系統裡有沒有那種「改它就得驚動一票人、所以根本沒人敢動」的元件?用 I 值的視角看,它是不是穩定(I→0)卻又具體(A→0)、正卡在「痛苦地帶」?它該被抽象化嗎?

🎙️ 回想你經歷過的「宿醉症候群」——早上來編譯壞了因為別人改了上游。那個依賴圖裡是不是藏著一個循環?如果用 ArchUnit 之類的工具在 CI 守住「禁止循環」,你的團隊會少掉多少這種早晨?

🎙️ 在你最近的專案裡,有哪個技術決定是你「太早做了、後來後悔」的(選錯 DB、選錯框架)?如果當初能延遲那個決定,你會做得更好嗎?——還是說,早點定下來反而讓你跑得更快?

🎙️ 「保持選項開放」對你而言是智慧還是逃避?你怎麼分辨「健康的延遲決策」和「不敢承諾、用抽象層拖延」這兩件事?

更大範圍關聯

  • 接續上集(元件內聚): 上集講元件「內部」怎麼打包(REP/CCP/CRP 張力三角),這集講元件「之間」怎麼連(ADP/SDP/SAP)。內聚決定邊界、耦合決定箭頭——兩集合起來才是完整的元件理論,也才湊齊了推導同心圓所需的全部公理。
  • DIP 的回歸: ADP 解循環的首選手法就是 DIP(第 11 章,上集講過)。你會看到同一個工具——「加介面、反轉依賴」——從類別尺度一路用到元件尺度。這再次印證 Uncle Bob 方法論的碎形性。
  • 通向全書後半(同心圓): 第 15 章的「策略 vs 細節 + 依賴指向穩定」就是 Clean Architecture 同心圓的公理。這集是承先(收束元件原則)啟後(開啟架構正篇)的樞紐。
  • 與 SOLID 的閉環: SAP 明說「穩定的元件要靠 OCP 來達成難改但靈活」——這把第四部分的元件原則和第三部分的 SOLID 正式扣回去。整個前半本書到此形成閉環:SOLID(類別)→ 內聚(打包)→ 耦合(連線)→ 架構(全局)。
  • 與 Ousterhout《A Philosophy of Software Design》及 DHH/敏捷: 「延遲決策 vs 先做出來」的張力,是 Uncle Bob 與精實/Rails 陣營最值得對照的分歧點。Ousterhout 也會提醒:抽象要藏住複雜度才值得,為了「保留選項」而加的空殼抽象是負債。這集的辯證核心就站在這條斷層線上。

錄製建議

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

  • 開場 2 分鐘: 用「宿醉症候群」開場——這是工程師最有共鳴的痛點,一句「昨天好好的、今天編譯壞了,因為隔壁組改了東西」立刻把人拉進來。點明這集要回答兩個問題:依賴箭頭該怎麼畫、以及架構師到底在幹嘛。
  • 耦合三原則 9 分鐘:
    • ADP(3 分鐘)——宿醉故事 + 循環變超級元件 + 兩種解法。強調「架構是演進的、環會一直冒、要持續清」。
    • SDP(3 分鐘)——重點打在「穩定 = 難改不是不變」這個反直覺定義。I 公式可以唸出來但別陷進去,用「負責者 vs 不負責者」的白話帶。
    • SAP + 主序列(3 分鐘)——「痛苦地帶 = 資料庫 schema」這個例子一定要講,最有畫面。主序列就講「越穩定越該抽象」一句話,D 值點到為止。
  • 什麼是架構 6 分鐘:
    • 架構的定義 + 四面向(3 分鐘)——快速帶過開發/部署/運行/維護,重點放「維護是成本之王、探礦最燒錢」和「部署該是單一動作」。
    • 保持選項開放 + 延遲決策(3 分鐘)——這是收尾的情感高潮。「軟體本該軟」「架構師的職責不是做決定、是讓決定晚做」這兩句要慢、要重。用「先寫核心邏輯、晚點再決定 DB」那個對老闆說話的畫面收。
  • 辯證 3 分鐘: 集中在「延遲決策 vs 先做出來」這條斷層線(注意事項第二點)。誠實擺出 YAGNI/精實/DHH 的反方:保持選項開放本身有持有成本,過早的彈性也是一種過度設計。語氣:「我深深認同延遲決策的智慧——但我也要誠實說,它很容易變成不敢承諾的藉口。」
  • 收尾 1 分鐘: 拋「延遲決策是智慧還是逃避」那題,預告下集進入全書後半——這三條耦合規則會被砌成那座著名的同心圓城堡。點明:你剛剛學的是公理,接下來看的是定理。

策略提醒:這集橫跨「最數學的一章」和「最哲學的一章」,情緒曲線要刻意設計成「先收緊(公式、象限、量化)、再放開(架構的本質、延遲決策的勇氣)」。前半用具體例子(資料庫 schema 在痛苦地帶)壓住數學的乾澀,後半讓「保持選項開放」這句話有呼吸空間。公式不要照本宣科推導,挑「I=難改程度」「D=離理想線多遠」兩個直覺講就夠,工程師聽眾要的是心智模型不是算式。