🎯 什麼情境該想到我

當你「開發者和領域專家在同一件事上用不同名詞、需求討論和程式碼裡的詞兜不起來」的時候。若某個講法在對話中很順、寫進模型卻卡卡的,那通常代表模型本身有問題,而不是措辭問題。

⚙️ 怎麼用(步驟 / 公式)

意圖:讓團隊(開發者與領域專家)圍繞領域模型,建立並使用「同一套」語彙,貫穿日常對話、圖、文件與程式碼。語言不一致,就是模型不清的訊號。

做法要點:

  1. 以領域模型為骨幹,挑出核心概念與其關係,替每個概念定一個大家都同意的名字。
  2. 讓這套詞同時出現在口頭討論、需求、圖示、類別/方法命名裡——同一件事只用同一個詞。
  3. 說話卡住或詞不達意時,別急著繞過,把它當成模型有缺陷的線索,回頭修模型與語言。
  4. 語言會演化:當領域理解變深,同步更新語彙與程式碼中的命名,保持一致。

★★ 它不是「寫一份詞彙表」,而是「讓程式碼、對話、文件用同一組詞」

★ 失敗的樣貌(極常見):
  會議上說「訂單被取消」
  程式碼裡是 order.status = 2
  資料庫欄位叫 ord_st
  API 回傳 {"state": "CANCELLED"}
  客服系統叫「作廢」
→ ★★ 每次溝通都要做一次翻譯,而★ 每次翻譯都是一次出錯機會

★★ 判準:你能不能直接把領域專家說的話,當成型別與方法的名字?
可以 → 通用語言成立。
需要「翻譯一下」→ 那個翻譯就是 bug 的溫床。

★★ Go 的落地:讓語言出現在型別系統裡

// ❌ 用技術詞彙包裝業務概念
type Order struct {
    Status int              // ★★ 2 是什麼?
    Flag1  bool
}
func (o *Order) Update(m map[string]any) error   // ★ "Update" 不是業務動作
 
// ✅ ★ 領域專家講得出來的每個名詞與動詞,都有對應的型別與方法
type Status int
const (
    StatusDraft Status = iota
    StatusPlaced
    StatusCancelled          // ★ 專家說「取消」→ 程式碼就叫 Cancelled
)
func (o *Order) Place() error   { … }    // ★ 「下單」
func (o *Order) Cancel(reason CancelReason) error { … }   // ★ 「取消」,而且取消要有理由

★★ 最有效的一個具體技巧:把專家講的「不可以」變成一個具名錯誤。

var (
    ErrAlreadyShipped   = errors.New("已出貨的訂單不可取消")
    ErrInsufficientQty  = errors.New("庫存不足")
)

★ 這讓「業務規則」變成可以被 grep、被測試、被討論的東西——
而且客服看到的錯誤訊息和工程師看到的是同一句話。

★★ 三個最容易漏掉的邊界

① ★★ 資料庫欄位名
   ★ 領域裡叫 cancelled,DB 叫 ord_st=2 → ★ 每個 SQL 都要翻譯一次
   → ★ 新專案一律讓 DB 欄位名等於領域詞彙;★★ 舊系統用 [[防腐層]] 隔開

② ★★ API 的對外名稱
   ★ 它是「另一個上下文」的語言(見 [[發布語言]])—— ★ 不一定要等於內部詞彙
   → ★★ 但必須有一份明確的對映,而且那份對映住在 [[資料傳輸物件]] 裡

③ ★★ 錯誤訊息與日誌
   ★ 「validation failed on field 3」→ 客服、PM、工程師都看不懂
   → ★ 用領域語言寫錯誤:「已出貨的訂單不可取消(訂單 #42)」

★★ 怎麼「建立」它:三個實際可做的動作

① ★★ 事件風暴(Event Storming)—— ★ 最有效的一個
   ★ 把領域專家和工程師放在同一個房間,用便利貼寫「發生了什麼事」(過去式)
   → ★ 一兩小時就能產出:事件清單、名詞、以及★★ 意見不一致的地方
   → ★★ 那些「不一致的地方」就是 [[限界上下文]] 的邊界候選

② ★ 把詞彙表放在程式碼裡,不是 wiki
   ★ 一個 domain/glossary.go 或型別上的 doc comment
   → ★★ wiki 上的詞彙表三個月後一定過時;程式碼裡的會被重構工具帶著走

③ ★★ 在 code review 裡糾正用詞
   ★ 這是唯一能讓它持續的機制 —— 而且成本極低
   → ★ 「這裡叫 user,但我們的語言是 customer」是一個合理的 review 意見

★★ 它的邊界:通用語言只在「一個限界上下文內」通用

★★ 「Ubiquitous」不是「全公司統一」。
  ★ 在訂單上下文,「客戶」是「下單的人 + 收件資訊」
  ★ 在行銷上下文,「客戶」是「一組人口屬性與行為標籤」
→ ★★ 強迫兩者統一 = 一個誰都不滿意的上帝模型(見 [[標準資料模型]] 的同一陷阱)
→ ★ 正確做法:★★ 各自的上下文內部嚴格一致,跨上下文用翻譯([[防腐層]]/[[上下文對應]])

★ 這是 DDD 最常被誤讀的一句:「統一語言」聽起來像全公司統一,
而 Evans 的意思正好相反——它是有邊界的。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 通用語言只在一個限界上下文內保證一致;跨上下文同一個詞可能有不同意義,別強求全公司統一一套詞。

  • 它要靠領域專家與開發者持續對話維護,不是寫一份詞彙表就一勞永逸。

  • 若領域專家不參與、只有工程單方面命名,很容易退化成技術黑話,失去「共用」的意義。

  • ★★ 「Ubiquitous」不是「全公司統一」,而是「一個限界上下文內統一」。 強迫跨上下文統一會得到一個誰都不滿意的上帝模型。

  • ★★ 判準:能不能直接把領域專家說的話當成型別與方法的名字? 需要「翻譯一下」→ 那個翻譯就是 bug 溫床。

  • ★ 詞彙表要放在程式碼裡(doc comment/glossary.go),不是 wiki。 wiki 版三個月後一定過時。

  • ★★ 三個最容易漏掉的邊界:資料庫欄位名、API 對外名稱、錯誤訊息與日誌。

  • ★ 把「業務上的不可以」變成具名錯誤(ErrAlreadyShipped),讓規則可 grep、可測試、可討論。

  • ★★ code review 糾正用詞是唯一能讓它持續的機制,而且成本極低。

  • ★ 事件風暴一兩小時就能找出「大家意見不一致的地方」,而那些正是上下文邊界的候選。

  • 不要為了通用語言而把技術詞彙全部改成業務詞彙:repository、handler、mutex 是技術層的詞,它們不在領域語言的範圍內。

🔗 相關工具