写 SQL 之前,先说清“已完成订单”指什么
WHERE 子句会悄悄选定一种含义,选错了,驻场的前线部署工程师(FDE)要用客户信任买单。
要点速览
- “Customer”“Product”这类多义词是软件项目里反复出现的混乱来源,不同含义之间的边界通常由部门文化划定。
- 不要强迫全公司只用一种含义:给每种含义起一个独立的名字,指定负责部门和一条可度量的规则。
- 术语表只有落到列名、指标名和语义层里才有价值,躺在没人看的 wiki 页面上毫无用处。
设想你在一家零售公司驻场的第二周。你演示一个名为“本月已完成订单”的看板。销售负责人点头,财务主管却皱起眉头,因为这个数字比她账上的高。数据管道没有 bug,两人只是用同一个名字在数不同的东西。
Martin Fowler 认为这种情况在一个又一个项目里反复出现:“Customer”“Product”这类多义词会带来无休止的混乱。对开发内部产品的工程师来说,代价是多开几次会。
对驻场客户现场的前线部署工程师(FDE)来说,代价是信任:人们一旦见过一个错误的数字,之后的每个数字都会带着怀疑去看。
之所以要在写代码之前解决这个问题,原因很简单。Fowler 写道,软件不擅长应对歧义。人可以说“已完成订单”,并在上下文中彼此理解。而 WHERE status = 'completed' 子句必须只选一种含义,而且是悄悄选的,不会告诉任何人。
为什么一个词会有三种含义?
事件风暴(Event Storming)的创始人 Alberto Brandolini 直言,术语天生就有歧义:每个人对同一个词都有自己的理解,因为每个部门的视角和需求不同。
Codurance 博客的一篇文章举了同一家公司内部的例子:销售专家和财务专家可能给同一个词赋予两个不同的概念。
Fowler 还有一个更值得记住的观点:业务上下文之间的边界通常不是由技术决定的,而主要由人的文化决定,比如哪些团队一起工作、哪个部门负责什么。每个上下文对同一个概念可能有完全不同的模型。
所以你的目标不是在“正确”含义的争论中胜出。Three Dots Labs 说得很清楚:同一个词在不同上下文中含义不同是正常的,只要上下文之间的边界划分清楚。
FDE 的工作是把每种含义摆到台面上,给它起名,并记录它适用的范围。
一个“已完成”,三个数字
回到那家假设的零售公司。销售认为客户付款成功,订单就算完成。仓库认为货物扫码出库、交给承运商,订单才算完成。财务则要求客户已收货、退货期已过且没有退款,订单才算完成。
用数字说得更具体些:假设本月有 1,000 笔订单付款成功,920 笔已从仓库发出,850 笔已过退货期且没有退款。你的看板按付款状态列统计,所以显示 1,000。财务记的是 850。这 150 笔订单的差距不是数据错误,而是定义错误。
再加上一个回答“我的订单完成了吗?”的客服 Agent。如果 Agent 按销售的含义理解这个词,那么对一笔还在仓库里的订单,它也会回答“是”。同样的定义错误,这次直接到了终端客户那里。
这个例子确定后的术语表可能是这样的:
| 规范名称 | 负责部门 | 可度量规则 | 数据字段 | 已确定的边界情况 |
|---|---|---|---|---|
| 已付款订单 | 销售 | 支付交易成功 | payment_status = 'captured' |
货到付款订单何时算数? |
| 已发货订单 | 仓库 | 有出库扫码记录 | shipped_at IS NOT NULL |
部分发货的订单 |
| 已结算收入订单 | 财务 | 已送达、已过退货期、无退款 | settled_at IS NOT NULL |
换成其他商品的订单 |
注意,这张表刻意不再出现“已完成”一词。从现在起,单独的“已完成”不得出现在代码、指标名称或 Agent 的提示词中。
五个步骤确定术语表
第一步是收集术语。阅读工单、表名、列名、客户发来的 Excel 文件和会议纪要,标出出现最频繁的名词。正如 Fowler 所警告的,“客户”“订单”“商品”这些最熟悉的词往往最危险。
第二步:按照 Codurance 的建议,分别访谈每位领域专家。不要问“已完成是什么意思?”,因为每个人都会泛泛而谈。要用边界情况来问:“退了一半款的订单算不算?”“客户换了尺码,原订单算什么?”正是这些情况,才能暴露人们实际使用的定义。
第三步:把各部门请到同一场会议里。Three Dots Labs 认为,事件风暴是发现并统一开发者与利益相关方词汇的最佳方法之一。
当“客户付款”“货物出库”“退货期结束”这些事件按时间顺序贴出来,每个人都能看到自己的“已完成”落在这条线的哪个位置。
第四步:为每种含义命名并指定负责人。每种含义有自己的名字,有一个在定义需要变更时负责的部门,还有一条机器可以执行的规则。“订单没问题”不是定义,“有 settled_at 且没有退款记录”才是。
第五步:把术语表写进系统。Eric Evans 把这个终点称为通用语言(ubiquitous language):开发者和业务人员之间共享的严谨语言,在会议里和代码里用法完全一致。变量名、列名、指标名和 Agent 的工具描述都必须使用规范名称。
这一步可以借助工具。dbt 的语义层让你在建模层只定义一次指标,无论各业务部门使用什么工具,都共享同一个定义。
定义变更时,改动会同步到所有调用它的地方。在 Palantir,Ontology 用对象、属性和关联来描述客户组织。但工具只负责存储定义,选择哪个定义,仍然是人与人之间的事。
让术语表早早夭折的错误
最常见的错误,是采用嗓门最大的人或者为项目付钱的部门的含义。另一个部门不会在会上争论,而是默默继续用自己的 Excel 文件,你的系统就成了第二个没人信任的系统。
第二个错误,是术语表只放在 wiki 上,从未进入代码。如果列名仍叫 is_completed,半年后新来的工程师会按自己猜测的含义去用它。
第三个错误,是把术语表冻结。Fowler 强调,语言必须随团队对领域的理解一起演进,所以要记录每个术语确定的日期和批准人,并在客户流程变化时重新讨论。
把这项技能变成职业优势
阅读 FDE 岗位描述时,留意“domain modeling”“ontology”“semantic layer”或“work with business stakeholders”这类表述。它们说明公司需要的正是这项技能。
简历上不要写“善于与客户沟通”。写一行有具体结果的经历,比如与三个部门确定了一套指标定义,消除了看板与财务账目之间的差距。
面试时,准备一个关于某个术语让两个部门互相误解的故事:你是怎么发现的,问了哪些边界情况,最终的定义落在系统的哪个位置。这样的故事能说明你既能处理数据,也能与人打交道,而这正是这个岗位的要求。
练习:一个状态列,一个下午
打开你正在处理的订单表或客户表,对状态列执行 SELECT DISTINCT,列出其中的所有取值。为每个取值写一个边界情况问题,比如“部分退款的订单取哪个值?”。
拿着这份清单去问两个不同部门的人,然后把结果填进像上面示例那样的五列表格。两方回答不一致的地方,就是你术语表的第一行。
写错的代码一个下午就能改好。一个被误解的词一旦扩散到每张表、每份报表和每个 Agent 回答里,就还得在客户的脑子里纠正过来。所以,在敲下第一个 WHERE 之前,先把它的含义确定下来。
这篇文章对你有帮助吗?
感谢你的反馈!
7 个信源
- Bounded Context (Martin Fowler) · 2014-01-15
- Ubiquitous Language (Martin Fowler) · 2006-10-31
- Alberto Brandolini on Domain-Driven Design and EventStorming (codecentric) · 2019-10-14
- Talk to the business (Codurance) · 2015-10-26
- Ubiquitous Language (Three Dots Labs Academy)
- dbt Semantic Layer (dbt Docs)
- Ontology overview (Palantir Foundry docs)