這篇文章的用途

這篇筆記明確標記每一個關於 design pattern 的主張,區分為「可佐證(empirically or engineering-fact supported)」與「合理的工程意見(widely held but lacking rigorous comparative data)」。所謂「可佐證」,指的是:可以從技術規格或資料庫 ACID 語義直接推導(邏輯必然性),或者有可重複量測的 benchmark 數字,或者有可查閱的工程事後記錄(post-mortem、A/B 對照)。

這個區分有實際的工程意義。Pattern 的話語圈充斥著 cargo culting——工程師援引某個 pattern 的名稱,但對它解決的根本問題、適用前提、以及帶來的代價缺乏認識。誤用 pattern 不只是浪費工時,而是引入真實的系統複雜度:過度設計的 Event Sourcing、沒有壓測過的 Circuit Breaker 閾值、解決了部署耦合但無法解決邏輯混亂的 Micro-frontends。這份筆記的目的是把「有根據的」與「合理但未被嚴格驗證的」清楚分開,讓引用者知道自己站在哪個認識論地位上。


可佐證的主張

主張可佐證的理由重要附帶說明
Outbox pattern 消除雙寫(dual-write)問題邏輯上可形式化:事務性語意 + 單一 DB + relay 這個組合消除了 DB 和 broker 之間的一致性窗口。結論來自 ACID 語義本身的推論,不依賴「業界共識」。Relay(CDC 或 polling)本身需要正確實作,at-least-once delivery 仍需下游冪等。Pattern 消除的是「雙寫不一致」,不是「任何一致性問題」。
Idempotency key + 唯一約束可防重複側效DB 唯一約束的語意保證,是資料庫規格的一部分,不是意見。Stripe 的 idempotency-key 文件記錄了已知有效的工業實作,可以驗證行為。保護的是持久化層的重複;若側效涉及外部系統(如發 email),需要額外的冪等保障。
Circuit Breaker 防止 cascading failure 的機制可追蹤Netflix Hystrix 的 2012–2016 blog posts 記錄了 open-source 前後的故障隔離效果;resilience4j 的 GitHub 有可量測的 behavior tests。Pattern 的效果可在壓測中直接驗證。效果取決於閾值設定;拍腦袋的參數可能讓 breaker 太遲開路或誤觸發(見下文)。
Blue-Green 讓版本切換對 router 而言是原子性的Load balancer / router 切換流量目標是技術事實,不是主張。舊版本和新版本同時存在,切換是 router 的單一設定變更。資料庫 schema 的向前/向後相容性仍是 Blue-Green 成功的前提,pattern 本身不解決這個問題。
GitOps 提升部署 auditabilityGit commit history + PR review 是可查的 audit trail,這是 Git 的核心設計,不是 GitOps 的主張。部署記錄在 SCM 裡這件事本身就是可驗證的。Auditability 提升不等於 auditability 足夠(例:commit message 品質、review 深度由人決定)。
Signals 的細粒度更新避免 unnecessary re-render可在 benchmark 中直接量測。js-framework-benchmark(https://github.com/krausest/js-framework-benchmark)有 Solid.js vs React 的數字差距,方法論公開透明,可重複執行。Benchmark 情境(synthetic DOM table)和真實應用的工作負載分布不同;實際改善幅度依應用形狀而異。
Islands Architecture 減少 JS 傳輸量可量測:靜態 HTML + 小 island vs 全量 SPA hydration 的 bundle size 是客觀數字。Astro 的官方文件有 benchmark 數字,可以用相同應用複現。減少傳輸量不等於減少 TTI(Time to Interactive);如果 island 的 hydration 在關鍵路徑上,TTI 可能不變。

合理的工程意見(廣泛接受但缺乏嚴格對照數據)

1. 「Hexagonal Architecture 提升可維護性」

這是被廣泛接受的觀點,但「可維護性」的量化定義本身就有爭議——bug 密度?修改一個功能平均花費的時間?需要新人 onboard 的時間?沒有嚴格的對照研究(有 hexagonal vs 沒有 hexagonal 的相似系統,在相似規模的團隊下,維護成本的比較)。背後的機制是合理的:介面隔離讓替換容易、business logic 可獨立測試。但「提升可維護性」這個主張在工程實踐中的量化幅度是工程意見,不是可測量的事實。

2. 「Microservices 比單體更易擴展」

有條件正確:網路 boundary 讓個別服務獨立 scale 這件事是技術事實;但「整體擴展成本更低」是意見。Microservices 帶來的分散式系統複雜度(網路故障處理、最終一致性設計、分散式 tracing、跨服務 schema 協調)在某些系統中讓整體維護成本比設計良好的單體更高。Amazon、Netflix 的成功使用案例有大量的前置工程投資(統一的 deployment platform、service discovery、可觀測性基礎設施),這些前提成本在援引案例時經常被省略。

3. 「Saga 比 2PC 更適合微服務」

技術上,Saga 確實避免了 2PC 的長鎖定問題(跨服務的持有鎖等待網路往返),這是有根據的。但「更適合」隱含著一個前提:你接受最終一致性,且業務場景允許補償邏輯(compensating transaction)。對於需要強一致性的金融交易,Saga 的補償邏輯複雜性——以及補償失敗時的人工介入處理——有時讓整體方案的複雜度超過 2PC 在受控環境下的問題。「更適合」是視場景而定的工程判斷,不是一般性事實。

4. 「Service Mesh 降低跨語言重複工作」

機制上合理:把 mTLS、retry、circuit breaking 從應用程式碼移到 sidecar proxy,確實可以避免各語言各自實作。但實際的工程成本(Istio 的學習曲線、control plane 的運維複雜度、debug 困難度)是否低於各語言各自實作這些功能?沒有公開的系統性比較研究。大多數主張 Service Mesh「降低複雜度」的說法,來自廠商文件或大型平台工程部落格,缺乏獨立的成本比較。

5. 「Micro-frontends 讓多團隊獨立部署」

部署解耦的技術機制是真的,這部分可佐證。但完整的主張——「採用 MFE 後多團隊的開發效率提升」——缺乏嚴格量化。實際的 DX 代價(Module Federation 設定複雜、跨 micro-app 樣式衝突、共享 bundle 版本衝突、跨 app 的 E2E 測試困難)在決策討論中很少被量化。多數 MFE 的成功案例來自大型工程部落格(DAZN、Ikea),這些公司有規模讓前置投資攤薄;失敗案例因為公開動機較少,在話語圈中系統性地被低估。


常被誤用的 Pattern(情境錯誤導致反效果)

1. Singleton 被濫用

GoF 的 Singleton 解決的是「語言層面沒有模組系統」的問題——在那個時代,「確保一個 class 只有一個 instance」需要在語言層面設計。在現代語言裡,Singleton 通常是全局可變狀態的包裝,讓測試變得困難:每個測試都需要 reset global state,測試間有隱性依賴。正確做法是用 DI container 或模組層級 export,讓 scope 由呼叫者控制,而不是由物件本身強制。

2. Event Sourcing 被過度採用

Event Sourcing 適合的場景是「需要查詢歷史狀態」和「需要重播事件重建 Read Model」。對於簡單的 CRUD 服務,Event Sourcing 引入的複雜度——event schema 的版本演進(schema evolution)、snapshot 管理、事件版本升級的 upcasting 邏輯——遠超收益。值得注意的是,Greg Young(Event Sourcing 的主要推廣者之一)本人在多個演講中明確警告,絕大多數系統不應該使用 Event Sourcing;這個警告來自 pattern 的創始推廣者,但在 pattern 的使用者社群中傳播程度遠低於 pattern 本身。

3. Circuit Breaker 參數拍腦袋

Circuit Breaker 的 failure threshold、half-open 的 probe 數量、reset timeout 如果不經過針對該服務的壓測直接使用預設值,可能在高流量下太晚觸發(threshold 設太高,cascading failure 已經發生才開路),或太敏感(正常偶發的逾時就讓整個 breaker 開路,造成假陽性)。Pattern 的存在不等於正確使用了 pattern。Circuit Breaker 需要配合 SLA 分析和壓測,才能設定有根據的參數。

4. Micro-frontends 作為技術債解決方案

有團隊試圖用 MFE 解決「大型單一前端難以維護」的問題,但根本原因往往是架構邊界不清晰、業務邏輯散落在 UI 層、缺乏明確的 feature ownership——這些都不是部署耦合造成的。MFE 讓部署解耦,但不讓混亂的業務邏輯邊界清晰。套用 MFE 可能讓這些混亂分散到多個 repo,同時增加跨 app 協作的摩擦,讓問題更難看見、更難修復。

5. Feature Flags 無限增長

沒有清理機制的 feature flag 是技術債的累積:flag 的評估邏輯在 codebase 裡擴散,條件分支增加認知負擔,最終沒有人確定哪些 flag 仍在使用、當前生產環境的值是什麼、哪些已可安全刪除。Feature flag 系統需要嚴格的 lifecycle 管理:每個 flag 需要 owner、預期的 expiry date、以及刪除流程。Flags 是工具,不是免費的——每個存活的 flag 都是對未來工程師的認知負擔。


需要更多研究的開放問題

  • AI 生成程式碼是否更容易產生 pattern 的「形式正確但語意錯誤」實作?例如:Outbox pattern 的實作看起來結構正確,但 relay 邏輯有 race condition;或者 idempotency key 有正確的 schema 但缺少正確的事務邊界。目前沒有系統性研究針對 LLM 生成的 pattern 實作做語意正確性的評估。

  • Pattern 的適用性與團隊規模的相關性尚未被嚴格研究。Micro-frontends 在 3 人團隊和 50 人團隊的 ROI 肯定不同,但業界討論這個 pattern 時很少控制「團隊規模」這個變數;成功案例的分享者(DAZN、Ikea、Zalando)都是大型工程組織,這個選樣偏差(survivorship bias + large-org bias)很少被承認。

  • 現代語言特性是否讓某些並發 pattern 的必要性降低?具體而言:Rust 的 ownership 模型讓共享可變狀態在編譯期就不合法,這是否讓 Actor Model 的主要動機(避免 shared mutable state 的競態條件)在 Rust 中變得部分多餘?Kotlin 的 coroutines 是否讓 Reactive Streams 的 backpressure pattern 的必要性降低?這些問題沒有嚴格的答案,但值得比較語言特性與 pattern 的解決層次。


與既有專欄的關係

Engineering AI Coding 專欄有類似的「主張 vs 可佐證」分析,針對 AI coding 方法論,見 AI Coding Methodology:主張 vs 可佐證


更新紀錄

  • 2026-07-03 — 建立。