為什麼要談「距離」#
Chapter 5–7 圍繞著一個核心觀念:耦合越強,元件越容易需要一起改。但 Chapter 7 結尾留下伏筆:「知識共享不是引發連動修改的唯一原因」。
本章把焦點轉到耦合的另一個維度——空間(space)。元件在程式碼庫中的物理位置如何影響耦合?又如何影響連動修改與系統複雜度?
距離與封裝邊界#
軟體並非生來就有結構。早期程式可以是一大段 statements 加上滿地的 goto。隨著典範演進,我們累積了不同層級的封裝邊界:routine、function、object、namespace/package、library、service……這些邊界其實全都是「軟體模組」(Chapter 4)。
同樣的知識可以被共享在不同物理距離上。把「什麼是 preferred customer」分散在同一物件的兩個方法(A),和分散在兩個微服務(B),共享的是同樣的知識,但維護成本天差地別。

Figure 8.1: 同樣的知識共享於不同的封裝邊界(同物件方法 vs 微服務)
距離的成本#
跨方法 → 跨物件 → 跨 namespace → 跨 library → 跨 service → 跨系統。距離越遠,協同實作變更所需的成本越高。

Figure 8.2: 協調成本隨物理距離變化的趨勢
成本來源包括:
- 在不同 codebase 中協調修改
- 跨團隊的溝通與協作
- 維護者的認知負擔——距離越遠,越容易忘了同步某個元件,造成系統行為錯誤
回到 preferred customer 的例子:
- A:邏輯重複在同一物件裡:改一處、部署一次
- B:邏輯散在兩個微服務:兩個服務都要改,且必須同步部署,否則出現不一致
連動修改的成本與距離成正比。
距離 = 生命週期耦合的反向#

Figure 8.3: 生命週期耦合隨物理距離變化的趨勢
軟體模組的生命週期:需求 → 設計 → 實作 → 測試 → 部署 → 維護。生命週期耦合(lifecycle coupling) 指模組必須一起經歷這些階段。
距離與生命週期耦合成反比:距離越近,生命週期耦合越強——即使元件之間根本沒有關聯。
例子:違反 SRP 的 SupportCase#
class SupportCase {
fun createCase(...) { ... }
fun assignAgent(...) { ... }
fun resolveCase(...) { ... }
fun logActivity(...) { ... }
fun scheduleFollowUp(...) { ... }
fun sendEmailNotification(...) { ... }
fun sendSMSNotification(...) { ... }
fun processPayment(...) { ... }
fun convertMilesToKilometers(...): Double { ... }
}把不相關的功能塞進同一物件,等於把它們的生命週期綁在一起。
想部署支援案件的修改?必須把通知、付款、單位轉換的修改一起 compile/test/deploy,或回滾未完成的部分。或在不同分支開發——但同檔案多分支幾乎注定 merge conflict。
把這個物件拆成 SupportCase、Notification、Payment、UnitConversion 四個物件,即使在同一 namespace 中,至少不必修改同一個檔案。再進一步拆到四個微服務,生命週期耦合會降到最低。
評估距離#
距離可以用「兩個模組最近的共同祖先」來表達。例如以下三個 C# 型別:
1. WolfDesk.Routing.Agents.Competencies.Evaluation
2. WolfDesk.CaseManagement.SupportCase.Message
3. WolfDesk.CaseManagement.SupportCase.Attachment- 1 與 2 的共同祖先是
WolfDesk(最頂層)→ 距離大 - 2 與 3 的共同祖先是
SupportCase→ 距離小
影響距離的其他因素#

Figure 8.4: 耦合元件之間距離造成的雙重效應
社會技術設計(Socio-Technical Design)#
距離不只跟程式碼物理位置有關,也跟「擁有權」有關。模組可能由:
- 同一個人實作
- 同一團隊
- 同部門不同團隊
- 不同部門
- 甚至不同組織
擁有權距離越遠,協調成本越高。 即使物理上很近,但分屬不同團隊就等於有效距離拉遠。
回到先前微服務的例子:「一起部署」對同團隊只是內部協調;對跨團隊就是大型協作。
擁有權距離也會反向降低生命週期耦合——分屬不同團隊的微服務比較不可能有同一份開發與部署排程。
Conway’s Law#
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.(Melvin Conway, 1968)
軟體系統的結構終將反映其組織的溝通結構。即使初始設計刻意違反組織結構,時間久了也會被「拉回」與組織結構對齊。
擁有權距離會在時間中逐步影響系統的封裝距離。設計時應認真考慮 Conway’s Law,否則組織結構會幫你重新設計系統。
Runtime Coupling(執行期耦合)#

Figure 8.5: 服務之間的同步與非同步整合
執行期耦合指「一個模組的可用性受另一個模組可用性影響的程度」。
- 同步整合(如 RPC):消費者期待立即回應,提供者掛了 → 消費者直接受影響 → runtime coupling 高
- 非同步整合(如 message bus):只要消費者能從 message bus 取到訊息,提供者掛了它仍能繼續運作 → runtime coupling 低
高 runtime coupling 會讓故障在元件間傳播,等於把生命週期綁在一起,降低了距離。
非同步通訊與變更成本#
「非同步整合通常是更彈性的設計」聽起來很合理。但若兩服務透過 message 整合卻是 model-coupled(訊息直接暴露 producer 內部模型),則任何模型變動都需要 producer 改格式、consumer 改解析——甚至需要中介版本支援新舊雙格式。這時非同步反而比同步更難改。
Distance vs Proximity#
「proximity」是「distance」的反面。兩者是同一現象的不同視角。本書統一用 distance,理由:
- 「low/high distance」比「low/high proximity」更直觀
- 距離直接對應變更成本,視覺化容易
- proximity 傳統上只描述封裝邊界距離,但本書要納入 socio-technical 與 runtime coupling 的影響
Distance vs Integration Strength#
兩者是不同維度:
- Integration strength:跨邊界共享的知識——決定是否會一起變動
- Distance:物理 / 組織 / 執行期距離——決定一起變動的成本有多高
常見的設計陷阱:
- 把 monolith 拆成微服務時只考慮「距離」(封裝邊界),忽略「強度」(知識流向)→ 結果是 distributed Big Ball of Mud
- 以為導入 EDA 與 message bus 就自動解耦——若知識共享沒被優化,非同步元件仍有共享的變更原因,仍可能引發複雜互動
重點整理#
設計時除了問「共享了哪些知識」,也要問:
- 這些知識會穿越多遠的距離?(封裝邊界、團隊邊界、執行期邊界)
- 同處一地的元件即使不共享知識,也會因生命週期耦合而牽動彼此
- 拆解成本與整合成本要一起評估
下一章將迎來 Part II 最後一個維度:易變性(volatility)——時間維度的耦合。