核心觀點#
在現代軟體開發中,我們很少從零開始打造所有東西。
我們經常需要使用第三方套件(Third-party packages)、開源函式庫或外部 API。
這產生了系統的「邊界(Boundaries)」。
作者 Robert C. Martin 指出,如何優雅地處理這些邊界,決定了系統的整潔度與穩定性。
如果不加以控制,外部程式碼的變動將輕易摧毀我們的系統。
- 目標衝突: 第三方提供者追求「泛用性」(適應各種環境);
使用者追求「特定性」(解決特定問題)。
這導致直接使用第三方介面時,往往暴露了過多不必要的功能與風險。- 控制權: 我們無法控制第三方程式碼,但我們可以控制「如何使用」它。
學習式測試 (Learning Tests)#
開始用一個第三方函式庫時,通常會花大量時間閱讀文件。
作者建議採用一種高效方法:與其閱讀文件,不如撰寫測試來學習。 這被稱為「學習式測試」。
學習式測試的好處#
| 好處 | 說明 |
|---|---|
| 低成本的實驗 | 透過撰寫測試來驗證我們對 API 的理解, 確保它如我們預期般運作 |
| 明確的成果 | 這些測試只關注「我們想使用的功能」, 過濾掉不相關的雜訊 |
| 升級的保障 | 當第三方軟體發布新版本時,只需執行這些測試, 就能立刻知道新版本是否破壞了我們的系統(Regression Test)。 如果沒有這些測試,每次升級都是一場賭博 |
邊界管理的策略#
為了保持邊界整潔,我們應盡量減少在系統中直接引用第三方軟體的地方。
1. 使用封裝 (Encapsulation)#
不要將第三方物件(如 java.util.Map 或其他複雜 API 物件)直接傳遞到系統各個角落。
這會造成系統與該第三方實作高度耦合。
將第三方物件包裝在自己的類別中,只暴露業務邏輯需要的幾個方法。
範例:封裝 Map 邊界
不好做法 (邊界洩漏):
將 Map 傳遞給所有模組,每人都可隨意 Clear 或修改,但 Map 介面一旦改變,全系統都要改。
// 系統各處都在用這個原始物件
Map<String, Sensor> sensors = new HashMap<>();
Sensor s = sensors.get(sensorId);好做法 (邊界封裝): 建立一個 Sensors 類別。
外部使用者只知道 getById,不知道底層是用 Map 還是資料庫實作。
public class Sensors {
private Map<String, Sensor> sensors = new HashMap<>();
public Sensor getById(String id) {
return sensors.get(id);
}
// 只開放需要的介面
}2. 使用轉接器 (Adapter Pattern)#
當系統需與外部 API 溝通,但對方介面與我們設計的不相容時,可以使用轉接器模式。
- 做法: 定義一個自己系統「最舒服、理想」的介面,
然後撰寫一個 Adapter 來將這介面轉換為第三方 API 的呼叫 - 優點: 核心邏輯只需依賴自己的介面,未來若更換第三方供應商,
只需重寫 Adapter ,核心邏輯完全不用動

Figure 8.2: Predicting the transmitter
結論#
邊界是系統中最容易發生變化的地帶。
好的軟體設計應具備適應變化的能力,而不是讓變動蔓延到整個系統。
- 依靠你能控制的: 依賴自己的程式碼,多於依賴外部的程式碼
- 保護投資: 透過封裝與 Adapter,避免第三方程式碼的細節滲透進核心邏輯
“Good software designs accommodate change without huge investments and rework.”
好的軟體設計能輕鬆適應變化,無需巨大的投資與重工。