14 领域语言

用贴近问题域的语言表达规则,让术语、语法、代码和错误信息共同承担领域约束。

学习目标

  • 能把“14 领域语言”解释为领域词汇、语义边界、表达、执行和反馈共同组成的规则接口
  • 能用“提示22:靠近问题域编程”把业务规则写成可读、可验证且能拒绝非法状态的内部或外部 DSL
  • 能在真实代码中改变一条领域规则,定位错误语义和首个边界偏离,并让领域人员与独立复核者重放验证

为什么 14 领域语言不是给代码换名字

本页依据 David Thomas、Andrew Hunt《程序员修炼之道:通向务实的最高境界(第2版)》,云风译,电子工业出版社,2020年4月,ISBN 9787121384356 的公开完整中文目录,独立重构 14 领域语言。本页不复制原书正文、插图、练习答案或代码,而把目录命题转写成领域语言管线、错误定位、代码样例和复核证据。

领域语言的价值在于让规则靠近提出问题的人:领域专家能读出语义并指出不成立的表达,程序员能把规则编译或解释成明确行为,错误信息能告诉操作者如何修复。单纯把 process() 改名为 settle() 没有改变语义;如果输入、状态、边界和错误仍然隐藏,命名只是装饰。

本单元把问题拆成 领域词汇 → 语义边界 → 表达 → 执行 → 反馈。每一步都写出谁负责、什么输入合法、何时拒绝、错误如何回到领域概念,以及代码和领域表述是否仍然一致。

14 领域语言:规则接口

节点要问的问题可观察产物失败动作
领域词汇领域人如何称呼对象、动作和状态词汇表、例子和禁用词与领域专家核对同义词和歧义
语义边界哪些状态、转换和约束合法不变量、边界样例和拒绝条件明确非法状态,不让调用者猜
表达规则以什么 DSL 或 API 被写出可读的命令、配置或代码缩小语法并保留领域词汇
执行表达如何产生动作、数据和错误解释器、编译器、规则引擎或函数保留原始领域位置和输入
反馈使用者怎样知道哪里不符合规则错误定位、修复建议和回归在最早语义边界拒绝

Domain Vocabulary 不只是类名;它还包括用户界面、错误信息、配置和测试中的同一语义。

Semantic Boundary 让非法状态在最接近来源处失败,而不是等到数据库或用户结果才暴露。

Internal DSL 可以利用宿主语言的类型和测试,但不能让宿主语言细节掩盖领域约束。

External DSL 要为语法错误、语义错误、版本和迁移提供明确反馈,不是把复杂文本交给一个通用解析器就结束。

Error Localization 让人知道是哪个领域规则不满足,以及下一步应改值、改状态还是请求领域决策。

提示22:靠近问题域编程

提示22:靠近问题域编程。先从 Domain Vocabulary 和 Semantic Boundary 开始,再选择 Internal DSL、External DSL 或普通函数表达规则。目标不是让业务人员直接维护所有代码,而是减少从业务问题到实现行为之间的翻译损耗,让领域反馈能在代码和测试中留下位置。

一个好的表达让“冻结账户”“释放预授权”“部分退款”“超过结算窗口”等动作各自有边界和错误,而不是统一落到一个含糊的 updateStatus。领域语言也必须接受反例:一个词在不同团队有不同含义时,先命名冲突和适用范围,再决定是否合并。

从提示到可失败的因果链

本页主链是 Domain Vocabulary → Semantic Boundary → DSL 表达 → 执行 → Error Localization。每条边要写明传递的是规则、状态、数据、命令还是反馈;每个节点都要有输入、输出、owner、版本和拒绝动作。语法能解析但语义错误,仍然是失败。

先写预测,再执行正常样本、边界样本和一次故障样本。三类样本共享规则版本和输入,只改变一个词汇、约束或表达条件;结果出现差异时记录首个语义偏离,不能靠通用异常消息掩盖领域位置。

14 领域语言:规则从词汇走到行为Domain Vocabulary对象 / 动作 / 状态领域 ownerSemantic Boundary合法状态 / 转换非法即拒绝DSL 表达Internal / External代码 / 配置 / 版本执行与反馈行为 / 数据Error Localization提示22:语义比语法更接近问题域领域人员核对语义,代码落实边界,反馈回到词汇
领域语言让业务规则在词汇、代码和错误反馈中保持同一含义。

用代码表达领域边界

下面的 Internal DSL 让领域词汇出现在代码中,同时用类型和函数结果表达非法状态。它不是完整支付系统,而是把“部分退款必须小于已捕获金额”这条规则放在靠近领域的位置:

type Money = { cents: number; currency: "CNY" | "USD" };
type CapturedPayment = { captured: Money; refundedCents: number };
 
type RefundDecision =
  | { kind: "approved"; remainingCents: number }
  | {
      kind: "rejected";
      reason: "amount-exceeds-captured" | "currency-mismatch";
    };
 
function requestPartialRefund(
  payment: CapturedPayment,
  amount: Money,
): RefundDecision {
  if (amount.currency !== payment.captured.currency)
    return { kind: "rejected", reason: "currency-mismatch" };
 
  const available = payment.captured.cents - payment.refundedCents;
  if (amount.cents <= 0 || amount.cents > available)
    return { kind: "rejected", reason: "amount-exceeds-captured" };
 
  return { kind: "approved", remainingCents: available - amount.cents };
}

这里仍需领域人员确认“可退款金额”“已捕获”“部分退款”的语义和错误措辞。类型和测试可以阻止一部分非法输入,但不能独自决定退款窗口、权限、审计或用户通知;这些约束要进入 Semantic Boundary 的合同。

外部 DSL 与反馈

当规则需要被运营、分析或领域专家维护时,External DSL 可以让表达脱离编程语言细节。例如:

refund_policy:
  name: partial_refund
  when: captured_amount >= requested_amount
  deny:
    - currency_mismatch
    - outside_refund_window
  message: requested amount exceeds captured amount

解析器应分别报告词法、语法和语义问题,并指出字段、规则和修复建议。requested amount exceeds captured amount 比“invalid policy”更接近 Domain Vocabulary,但仍要由项目 owner 核对金额单位、舍入、权限和窗口时区。

14 领域语言:错误要回到规则位置领域表达词汇 / DSL / 输入版本 / 位置解析 + 语义校验语法 / 类型Semantic BoundaryError Localization字段 / 规则 / 位置修复建议执行合法反馈非法即回到领域词汇与规则,不吞成通用异常错误定位把技术失败翻译成可行动的领域反馈
领域错误应告诉人哪里不符合哪条规则,以及如何继续。

三步完成一次领域语言重构

分步1 / 3

先对齐词汇与语义边界

与领域人员选一条规则,写对象、动作、状态、合法转换、非法样例和同义词。把 Domain Vocabulary 与 Semantic Boundary 放在同一张规则卡片上,标记仍需决策的术语。

14 领域语言:规则从词汇走到行为Domain Vocabulary对象 / 动作 / 状态领域 ownerSemantic Boundary合法状态 / 转换非法即拒绝DSL 表达Internal / External代码 / 配置 / 版本执行与反馈行为 / 数据Error Localization提示22:语义比语法更接近问题域领域人员核对语义,代码落实边界,反馈回到词汇
领域语言让业务规则在词汇、代码和错误反馈中保持同一含义。

可重放的领域语言记录

domain_language_record:
  unit: tpp20-topic-14-domain-languages
  rule: 部分退款不得超过已捕获且仍可退的金额
  domain_vocabulary: 捕获、可退款、部分退款、退款窗口
  semantic_boundary: 币种一致、金额大于零、金额不超过余额、窗口有效
  internal_dsl: requestPartialRefund(payment, amount)
  external_dsl: refund_policy YAML
  error_localization: 字段 -> 规则 -> 修复建议
  injected_change: 只把可退款余额边界改为等于上限
  first_mismatch: 解析通过但时区窗口错误
  recovery: 补时区语义测试并让领域 owner 复核错误信息

这份记录把“靠近问题域”变成代码、配置、测试和反馈的共同合同。若领域人员只能确认词汇却无法确认状态转换,或程序员只能确认语法却无法定位业务错误,语言仍然没有靠近问题域。

选择、拒绝与迁移矩阵

判断接受证据应拒绝的信号
词汇领域人员能定义、举例和指出禁用同义词同一个词在不同流程含义漂移
语义合法状态、边界和非法样例清楚只写“有效/无效”没有规则
内部 DSL类型、函数和测试保留领域词汇宿主语言细节掩盖领域动作
外部 DSL解析、版本、迁移和错误定位可验证语法能读但错误只说 invalid
执行领域表达与运行行为一致代码命名正确、行为仍绕过规则
反馈错误指向规则、位置和修复动作领域专家只能看最终堆栈

迁移到云服务、数据系统或 AI 辅助开发时,分别记录领域 owner、规则版本、数据敏感性、解析器版本、自动化反馈和审计要求。工具可以生成类型、解析器和测试,却不能替领域人员决定语义边界。

常见误区

本章回顾

掌握 14 领域语言,不是增加一层术语,而是按 提示22:靠近问题域编程 让 Domain Vocabulary、Semantic Boundary、Internal DSL 或 External DSL、执行行为和 Error Localization 共同表达规则。领域人员能核对语义,代码能拒绝非法状态,错误能回到规则位置,独立复核者才能重放结论。

可验证练习

练习

本组练习覆盖 14 领域语言提示22:靠近问题域编程,要求提交 Domain Vocabulary、Semantic Boundary、Internal DSL、External DSL 和 Error Localization 的代码与复核证据。

问题 1: 如何把“部分退款不得超过可退款余额”从散落条件提升为领域表达?

问题 2: 如何实践“提示22:靠近问题域编程”,而不是只把函数重命名?

问题 3: 外部规则文件语法正确,但生产行为接受了错误时区。怎样定位和恢复?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

Domain Vocabulary

问题域中稳定、可被领域专家确认的对象、动作、状态和约束词汇。

Semantic Boundary

定义合法状态、允许转换和非法输入拒绝位置的领域边界。

Internal DSL

通过宿主语言组织成可读、可组合和可验证领域规则的内部语言。

External DSL

由业务或外部系统书写并由程序解析、校验和执行的领域表达。

Error Localization

把错误映射回领域词汇、位置、规则和修复动作的反馈方式。

前后导航

来源与改写范围

讨论

评论区加载中…