一個功能有三個入口,每個入口都自行決定重試、錯誤分類與去重。Agent 可以很快補上第四個入口,卻也可能帶來第四種處理方式。這時候,把共同決策收進模組,有明確的維護價值。
2026-10-03,Matt Pocock 在與 Lauren Tan(@poteto)交談後發表這則貼文,主張 AI 時代可以更積極設計抽象,並以嚴格 lint 限制使用方式。他也認為抽象能減少 token,做錯的抽象比較容易撤回。貼文沒有附對照實驗或成本數據;後兩點應視為待驗證的判斷。
本文延續原篇的深模組、接縫與領域語言主軸,補上這次觀點的驗收方法。我的立場是:**新增抽象的理由,是讓呼叫端少做一項容易做錯的決策。**類別、介面與目錄變多,並不自動達成這件事。
深模組要收走決策,薄包裝可能只多一次跳檔
John Ousterhout 在《A Philosophy of Software Design》討論以簡單介面封裝複雜實作的模組設計;他的作者網站也說明第二版加強了通用模組與深度的討論。這是軟體設計方法,並非針對 LLM 成本的量測。
套到 Agent 工作流,可以問一個很具體的問題:完成一次訂單操作,需要知道多少內部規則?
假設呼叫端必須自行選擇支付 SDK 方法、填入冪等鍵、解讀供應商錯誤,再決定是否重試。把相同參數轉交給 PaymentService,仍要求每個呼叫端理解這些細節,封裝就沒有完成。反過來,若模組接受已確認的訂單 ID,在內部統一處理重複請求與結果分類,呼叫端的責任就縮小了。
以下是設計草圖,沒有實作支付流程,也不是可直接上線的 SDK:
type CheckoutResult =
| { kind: "confirmed"; orderId: string }
| { kind: "rejected"; reason: "invalid-order" | "declined" }
| { kind: "pending"; operationId: string };
interface Checkout {
submit(orderId: string): Promise<CheckoutResult>;
}
這裡刻意保留 pending:外部請求逾時,不足以證明扣款失敗。模組需要有查詢與對帳策略,呼叫端則根據結果決定顯示什麼。這是本文的工程建議,不是 Matt 貼文中提供的支付設計。
這個介面值不值得存在,要看它承擔了哪些不變量。若每個呼叫端仍要重建冪等規則、辨識供應商例外,或偷偷讀取內部狀態,介面看似短小,實際決策面仍然很大。
嚴格 lint 必須同時指出合法入口
Matt 的建議有兩個部分:設計抽象,以及限制錯誤用法。只做前半,Agent 仍能跳過公開入口,直接引用內部 SDK。只做後半,錯誤訊息沒有替代路徑,工作可能停在反覆試錯。
下面用獨立的 JavaScript ESM 範例展示規則形狀。假設功能程式在 src/features/,支付實作在 src/payments/,公開入口是 @app/payments。這些名稱是本文示例,並非 CarlStack 的目錄或已安裝設定。
// eslint.config.mjs:示例設定,需依專案調整。
export default [
{
files: ["src/features/**/*.js"],
linterOptions: { noInlineConfig: true },
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "stripe",
message: "請經由 @app/payments 的公開入口操作支付。",
},
],
patterns: [
{
group: ["@app/payments/internal", "@app/payments/internal/**"],
message: "內部實作不提供給 feature 使用。",
},
],
},
],
},
},
];
ESLint 的 no-restricted-imports 支援指定模組名稱、模式與提示訊息;flat config 的 files 控制套用範圍,noInlineConfig 可禁止檔案內註解改動 lint 設定。此例只管 feature,支付模組本身仍可使用 SDK。
這條規則有明確上限:它管靜態 import 字串,沒有涵蓋動態 import()、CommonJS require(),也不會替你解析所有相對路徑與 alias。../../payments/internal/client.js 就沒有落在上述模式內。這份設定不能宣稱封死所有入口,更不是安全沙盒。
如果專案需要完整依賴邊界,應補上能解析實際模組路徑的檢查,或增加針對那些語法的規則。每加一道檢查,都要確認合法公開入口仍能使用;不能只看禁止清單愈來愈長。
規則也要有正反例,業務契約另行驗收
先建立一份小型 fixture,讓規則的保證範圍可被重跑。不要把違規樣本混進 production 的 feature 目錄;測試可以對樣本文字指定虛擬檔名。
- 合法靜態引用
@app/payments:應通過。 - feature 直接靜態引用
stripe:應得到no-restricted-imports錯誤。 - feature 引用
@app/payments/internal/client:應失敗。 - 支付實作引用
stripe:不應被這組 feature 規則擋下。 - feature 使用相對路徑、動態 import 或
require():上述單一規則不保證攔截;在導入完整邊界檢查前,將它們列為已知缺口。
這些是本文建議的驗收案例,不代表已在讀者的 repository 執行。專案採 TypeScript 時,還要配置相應 parser,不能把這份只匹配 .js 的設定直接當作 TypeScript 的完整防線。
**停止規則:禁止案例仍能通過,就不能宣稱該條邊界已受保護。**CI 也必須實際掃到目標檔案,並把錯誤當作合併阻擋;設定存在於 repository 不等於 gate 已啟用。
lint 綠燈只說明程式沒有命中已編碼的違規。它不會證明付款金額正確、冪等鍵沒有跨訂單碰撞,或逾時後能完成對帳。這些需要公開契約的行為測試。可以針對同一訂單重送、供應商逾時、明確拒付與後續確認結果,分別檢查狀態,而非只斷言某個 SDK 方法被呼叫。
型別則負責另一段工作。TypeScript 的 discriminated union 與 never 檢查能讓新增結果分支後,未補齊的處理路徑浮現。型別、lint 與行為測試各自提供有限的證據,不能互相代替。
接縫與領域語言,讓抽象有穩定的含義
原篇強調的 Seam 與 DDD,仍是設計公開契約時的兩個檢查角度。
接縫用來思考可替換的位置。例如同一支付契約由正式 adapter 與測試 fake 實作,呼叫端應該不必知道底層是哪一個。兩個實作能幫助暴露契約是否過度綁定供應商,但不應把「一定要有兩個 adapter」誤用為所有介面存在的必要條件。更完整的測試討論見〈Seam 與 In-Memory Fake〉。
領域語言則讓介面名稱對上業務動作。未付款的取消、已付款的退款、授權後的撤銷,可能有不同前提與狀態轉移;把它們全部命名為 cancel(),再以多個布林參數調整行為,會把決策推回呼叫端。
一份精簡的領域詞彙文件可以記錄「哪個狀態允許哪個操作」,再由型別與測試落實。文件本身不能強制 Agent 遵守;它的作用是讓設計者、使用者與測試作者說同一件事。語意工作可接著讀〈CONTEXT.md 與領域建模〉。
Token 與重構成本,要算完整任務
抽象可能讓 Agent 少讀內部檔案,也可能增加查找與理解成本。呼叫端只需讀公開契約時,前者較有機會成立;任務要求修改模組內部不變量時,Agent 仍然要讀實作與測試。不能用檔案變少、程式變短,直接推導 token 一定降低。
若團隊想驗證 Matt 的判斷,可以選擇可比較的任務,在相同模型、工具權限與驗收條件下,記錄總輸入輸出 token、失敗重試、人工審查時間與返工。不同架構的初次建立成本也要算進去;單次漂亮的結果,只能當下一次測試的線索。
「做錯再拆掉」也有邊界。尚未發布、沒有外部使用者的區域性 helper,通常比公開 API 容易替換。若抽象已寫入資料格式、跨服務協定或第三方整合,移除時還要處理相容性與遷移。Agent 能加速改碼,無法讓這些承諾消失。
下一次遇到重複實作,挑一項已經造成分歧的決策,寫成公開契約,再加一個合法案例與一個故意繞路的反例。兩者都按預期工作後,再評估是否值得擴大範圍。若需要安排整體 Agent 驗證與交付流程,可接著讀〈Agent 要能並行,先把工作變成可驗收結果〉。
若要往前檢查「需求本身是否定義正確」,可接著讀〈AI 越會寫程式,我們越需要學會抽象〉,從付款語意、可驗收契約與獨立商業案例檢查委派工作的前提。
來源與更新範圍
- Matt Pocock 原始貼文,2026-10-03;本文於台北時間 10/4 更新。抽象、lint、token 與撤回成本的主張來自此貼文,支付案例與驗收設計為本文推論。
- John Ousterhout:《A Philosophy of Software Design》作者頁,說明第二版的模組設計方向。
- ESLint:no-restricted-imports與設定檔,查核日期 2026-10-04(台北時間)。
- TypeScript:Narrowing/Exhaustiveness checking,查核日期 2026-10-04(台北時間)。
本次保留原篇 URL 與系列位置,補上可檢查的 lint 範圍與成本判準;原篇沒有實證支持的普遍效能與必然性敘述,改以條件與驗收案例表達。
