許多系統的核心邏輯與第三方 API 呼叫混雜在一起——整個程式看起來就是一連串的 API 呼叫。這讓測試和重構變得極為困難,因為你無法輕易地將 API 依賴隔離出來。

核心問題#

當應用程式充滿 API 呼叫時:

  • 難以測試:API 呼叫通常需要真實的外部資源(資料庫、網路、檔案系統)
  • 邏輯被埋沒:業務邏輯散佈在 API 呼叫之間,難以獨立理解和測試
  • 高度耦合:如果 API 改變,需要修改大量程式碼

解決策略:分離邏輯與 API 呼叫#

核心策略是將業務邏輯API 呼叫分離。即使程式碼看起來全是 API 呼叫,通常仍有隱藏的邏輯可以提取出來。

Skin and Wrap the API#

這是本章最重要的技術。有兩種主要方式:

方式一:Wrap API 呼叫#

為 API 建立一層 wrapper,讓你的程式碼依賴 wrapper 而非直接依賴 API:

  • 建立一個 interface 來代表你需要的 API 功能
  • 實作一個真正呼叫 API 的 wrapper 類別
  • 在測試中使用 fake implementation

方式二:Skin the Application#

將業務邏輯提取到獨立的類別中,讓 API 呼叫留在一層薄薄的「皮膚」裡:

  • 找出 API 呼叫之間隱藏的決策邏輯
  • 將這些邏輯提取到可獨立測試的類別
  • 讓 API 呼叫層變成簡單的委派

範例#

假設有一段充滿 API 呼叫的程式碼:

public class MailForwarder {
    public void forwardMessage(Message message) {
        // 一堆 API 呼叫混著業務邏輯
        Session session = Session.getDefaultInstance(props, null);
        MimeMessage forward = new MimeMessage(session);
        forward.setFrom(getFrom(message));
        // ... 更多 API 呼叫 ...
        if (shouldForward(message)) {
            Transport.send(forward);
        }
    }
}

可以將其拆分為:

  • 邏輯層shouldForward(message) 及其他決策邏輯,可獨立測試
  • API 層:實際的 mail API 呼叫,透過 interface 隔離

Figure 15.1: A better mailing list server

當你面對全是 API 呼叫的程式碼時,問自己:「這些呼叫之間有沒有決策?有沒有計算?」如果有,那些就是可以提取出來獨立測試的邏輯。

選擇哪種方式#

情境建議方式
API 較小且穩定Wrap the API
API 很大但邏輯可獨立抽取Skin the Application
需要替換整個 APIWrap the API
邏輯與 API 高度交織先 Skin,再逐步 Wrap

關鍵不是選擇哪種方式,而是開始分離。即使一開始只能分離出一小部分邏輯,那也是有價值的第一步。隨著測試覆蓋率的提升,你可以逐步提取更多邏輯。

實務考量#

  • 不要試圖一次重寫所有 API 呼叫:逐步地在需要改變的地方引入 wrapper
  • Wrapper 可以從簡單開始:只包裝你目前需要測試的部分
  • 保持 wrapper 層薄:Wrapper 不應該包含業務邏輯,它只是轉接層
  • 善用 Lean on the Compiler:改變參數型別後,讓編譯器告訴你哪些地方需要更新