写需求:和歧义的战争
这一章讲四件事: 需求错误随时间升值的汇率表;让每条需求「可以被点名」的编号法; 自然语言与形式化模型怎么配合着消灭歧义(本书最精彩的一段对照,走查给你看); 以及三类最容易漏写的「没说清」。读完你就明白:需求文档不是写给客户看的作文,是给下游的合同。
1. 先看现象:同一句中文,两个团队做出两个系统
「系统应及时响应用户请求」——写这句话的人觉得清楚极了。 设计团队读成「半秒内返回」,测试团队 按「两秒内都算过」写用例,客户上线后发现「等三秒才出结果」,投诉。
没有一个人做错了。每个人都「忠实实现」了那句中文。 歧义(同一句话有多种读法)不产生争论——它产生两个各自都说得通、但不一样的系统, 等发现时,两个都已经造完了。
这就是本章的全部动机:需求一旦写下,就会被下游当真。写得含糊,等于授权下游替你做决定。
2. 顶层全景
需求找对了(第 03 章)
│ 写下来的那一刻,错误开始「升值」
▼
汇率表:设计阶段 5× → 编码 10× → 单元测试 20× → 交付 200×
│ 所以每个字都要可追责
▼
编号(P52)+ 记录动机(P43)+ 排优先级(P50)
│ 文字本身还有歧义,两头夹攻
▼
自然语言(人人能读) + 形式化模型(无歧义探误器)
│ 三类「没说清」单独显式写
▼
可靠性口径(P57) · 环境越界行为(P58) · 待定项死线(P59)
图说:左列是动作,右列是它防住的事故。
这条链走完,需求文档才从「作文」变成「合同」。
3. 核心原理
3.1 需求错误的汇率表:为什么这里最值得花时间
原则 41 给出的数字是全书被引用最多的一张表1——需求规格说明里的一个错误, 每往下游多活一个阶段,修复代价乘一次:
| 错误活到了 | 修复代价 |
|---|---|
| 设计阶段 | 5 倍 |
| 编码阶段 | 10 倍 |
| 单元测试阶段 | 20 倍 |
| 交付之后 | 200 倍 |
这些倍数怎么来的?想想火星轨道器那个「磅当牛顿」的错误(第 01 章): 在纸上改一个单位是 1 分钟的事;错在真机上,连改的机会都没有。 错误越晚发现,它周围已经盖起来的东西越多——设计照着它做了,代码照着设计写了,测试照着代码过了。 200 倍不是精确汇率,它的量级来自「下游一切都要返工」这个结构。
作者称这是「要在需求阶段修错误」最令人信服的证据1。
3.2 让每条需求可以被点名:编号
要修错误,先得能指认它。「第 38 页那个响应时间的问题」不算指认。原则 52 给的最低要求: 每条需求单独编号2。书里给了三种做法,由重到轻:
- 每条需求打唯一标识,如
[需求R27]; - 段落编号到句子:「段落 3.2 第 k 句的需求」编号为「需求 3.2-sk」;
- 规定每条需求必含「应该」二字,用文本搜索程序自动提取编号、汇总成附录。
编号不是文书洁癖——它承重两次:设计要能追到需求(第 05 章 5.2 的对照表),测试要能追到需求(第 08 章 8.2 的表格)。 两张追溯表都以「每条需求有唯一编号」为地基。
3.3 编号之外,还要记「它为什么在」
原则 43:每条需求旁边,记下它从哪来——哪次访谈、谁说的、什么时候3。 书里的例子:决策「响应时间应该是两秒」旁边要记访谈的日期、时间、参与者,最好附上原话或录音。
这条防的是两种事故:客户后来要求改这条需求时,你查得到当初为什么定两秒,才能判断改了会不会伤到别的; 系统做不出两秒时,你拿得出依据,去和客户谈「改成四秒行不行」,而不是空对空地吵。
3.4 排优先级:不是所有需求都一样重要
原则 50 的例子选得好:载人飞船的需求单里,既有「速溶橙汁 」也有「全功能生命维持系统」。 缺橙汁不会中止发射,生命维持坏了会——它们显然不该在同一张「必须完成」的清单里4。
给出的两个工具,任选:每条需求标上三档——必须(不做就不能发布)、期望(有它更好)、 可选(没有也行),英文行话 M/D/O(Mandatory/Desirable/Optional); 或者每条打 0~10 的分。第 03 章那个「客户可忍 10% 必需功能按时、90% 晚拿」的判断5, 就是靠这张优先级表才可能说出口的。
3.5 主走查:同一件事,两种写法——形式化模型当探误器
现在到本章核心:歧义战争。自然语言(人写的日常语言)天生一词多义; 彻底消灭歧义只有一条路——用形式化方法(用数学符号代替日常语言来写规则的做法)。 但纯形式化写法别人读不懂,怎么办?原则 53~55 给出的答案是「配合」,并且配了一组对照原文,值得整段走完6。
第一步:先写自然语言(注意顺序,后面解释为 什么):
用户为拨打长途电话,应该拿起电话。系统应该在 10 秒内返回一个拨号音。 用户应该拨「9」。系统应该在 10 秒内返回一个不同的拨号音。
第二步:再写形式化模型。 这里用有限状态机(把系统写成「若干状态 + 什么事件让它从哪个状态跳到哪个状态」的图)改写:
系统包含四种状态:空闲、拨号音、不同的拨号音和接通。要从「空闲」转换到「拨号音」,应拿起电话。 要从「拨号音」转换到「不同的拨号音」,应拨「9」。
第三步:用模型反咬自然语言。 把第二段摆到第一段旁边,自然语言立刻显出三个漏洞: 「拿起电话后 10 秒没拨呢」——模型里没有这个状态,回填;「拨了非 9 的键呢」——模型里也没写;「『接通』状态怎么进」——原文根本没说。 作者的原话:试写形式化版本,会帮你在自然语言里发现问题;修掉这些问题,你就得到了更好的文档7。
为什么顺序必须是「先自然语言」? 作者用第三段对照展示:如果先写状态机, 再回头写「自然语言版本」,写出来的会是——
系统包含四种状态:空闲、拨号音、不同的拨号音和接通。要从「空闲」状态转换到「拨号音」状态,应拿起电话……
这不是自然语言,这是把模型用日常词复述了一遍——它描述的是那个模型,而不是要解决的问题。 读者从这段里得不到任何模型之外的信息,所以「完全没给读者提供帮助」8。
两步的分工由此清楚:自然语言负责让人读懂系统该干什么,形式模型负责把「干什么」逼到无歧义; 后者是探误器,不是替代品。 书里建议两者并排放在对开页上,人工核对一致9。
作者还顺手给了一个务实的尺寸:不必整份需求形式化——先写自然语言全文,再挑最要命、最容易出事的几节试写形式模型, 写完把发现的问题修回自然语言,形式版可以删掉不留10。每项目配一个熟手即可10。
3.6 三类「没说清」,必须显式写死
走查之外的三个机制,每个都是「不写就一定出事」的类型。
其一:可靠性到底是哪个口径。 原则 57 说,「这个系统有 99.999% 的可靠性」这句话没有任何意义11—— 它至少能读成三种不同的合同(年=525,600 分钟;525,600 × 0.001% ≈ 5.3 分钟,除法是我们做的):
| 口径 | 问题 | 例句 |
|---|---|---|
| 需求失效 | 系统被要求动作时,做对的百分比 | 「正确报告 99.999% 的病人生命体征异常」——每十万次漏报不超过 1 次 |
| 失败率 | 单位时间内错的次数 | 「错报病人生命体征每年不超过 1 次」 |
| 可用性 | 时间上可用的百分比 | 「一年 99.999% 时间可用」=全年宕机不超过约 5 分钟 |
病人监护设备和电话系统,该选的口径完全不同——不写死口径,等于让每个下游各选一个11。
其二:环境越界时系统怎么办。 原则 58 的例子:空管系统的需求写着「一个区域最多同时处理 100 架飞机」, 系统照此建成并正确。三年后的某一天,第 101 架进来了。软件怎么办?书里列了四个选项: 打印错误信息、崩溃、忽略第 101 架、处理它但可能突破时间约束(如屏幕刷新变慢)。 前三个显然不可接受,但基于那份(没写的)需求,它们都算正确响应。 正解:凡是为环境定义的限制,都要在需求里写明「越界时预期的系统行为」12。 这和第 08 章「压力测试要测 x+1」是同一枚硬币的正反面。
其三:没想好的事,给它一个死线。 原则 59:需求文档里的待定项(TBD,To Be Determined,意思是「此处待定」) 本身可以接受,但每条必须带「自毁注释」——到何时、由谁解决。 书里的样例格式:「开发经理将在 1995 年 12 月之前解决这个待定项」13。 没有死线的 TBD 会在文档里住到退役。
3.7 配套的写作纪律(一张表说完)
围绕「让文字可检查」这个唯一主题,书里还散落了几条小纪律,合并在一张表里:
| 纪律 | 一句话内容 | 它防什么 |
|---|---|---|
| 书写简洁14 | 「目标跟踪功能应提供显示所有活动目标的当前跟踪坐标的能力」改成「跟踪时,系统应显示所有活动目标的当前位置」 | 读者在读套话时漏掉真正的承诺 |
| 不在需求里做设计15 | 需求只写外部行为;实在要用图辅助,加注「此处包含设计,仅供理解,设计者可另选方案」 | 把架构决定走私进需求,设计者失去选优空间 |
| 用对方法、多视角16 | 数据密集用实体关系图,实时系统用状态机,部件之间配合的难题用 Petri 网,决策密集用决 策表——并用组合 | 一种符号盖不住系统的全部侧面 |
| 按读者习惯组织17 | 电话交换系统按「单方通话/双方通话/多方通话→主叫/被叫视角」分层 | 读者找不到自己关心的那片 |
| 需求进数据库18 | 编号、文本、来源、优先级、预期变更率、适用版本都存库;文档=数据库的导出 | 变更时查不到影响面 |
| 同一概念同一名字19 | 「有三类特殊命令。常规命令有四种类型」改成「有三类特殊命令。有四类常规命令」 | 读者疑心「重述里藏了新信息」 |
| 每份文档配术语表和索引(索引:全书词目与其出现位置的对照表)20 | 术语表定义里避免再用需查表的词 | 读者被生词卡住;维护者找不到东西 |
3.8 验收这一仗的地面部队:评审
写完不等于写对。原则 45:需求基线(定稿封版、改动要走流程的那个版本点)确立前, 必须让用户、客户、市场、开发、测试、质量保证全都正式过一遍21。 自然语言评审没有自动化办法,靠人多眼杂;凡 3.5 节写了形式模型的部分, 不但可以人工核对,有的还能直接「执行」给相关方看(书里举了可执行需求语言 PAISLey)21。
4. 作者的判断与证据
| 说法 | 谁的 | 证据 |
|---|---|---|
| 错误汇率 5×/10×/20×/200× | 作者引 Boehm1 | 行业经验数据;书里未给样本构成 |
| 先自然语言、后形式模型 | 作者本人(原则 55)8 | 对照例证(拨号音两段),论证清晰 |
| 99.999% 无意义,须拆三口径 | 作者引 Sommerville11 | 概念澄清,非实测 |
| 空管 101 架飞机 | 作者自设案例12 | 思想实验;结论由「需求没写=怎么都行」推得 |
| 2021:MVP 环境下自然语言+跟踪工具就够 | 作者 2021 自述22 | 他自己的实践;且声明这正是原则 60 的精神 |
判断(我们的,不是书里的): 「自然语言为主、形式模型为辅」的配比,前提是需求的主要读者是人。 如果某天下游的主要读者变成了机器(比如直接消费需求的代码生成系统),形式化的配比就该反过来涨—— 因为机器不会「感觉」出歧义,它只会随机锁死一种读法。作者 2021 年自己还在用自然语言加跟踪工具22, 但他的下游是人。这条边界,读的人要按自己的下游重算。 如果错,会错在: 如果机器消费需求前仍要经过人的评审与拍板(现实里几乎总是),那自然语言就仍是主干, 形式化加码就还是锦上添花——本判断只在「人退出评审环节」这个前提下才成立。
5. 边界与局限
- 形式化方法的现实使用率极低——作者自己招的。 1995 年要求每项目配一名熟手10; 2021 年承认 26 年里没有一个共事工程师真用过,虽然他们都会「以形式化方式思考」23。 本章教的「探误器」用法(3.5)正是低成本的折中,但要不要用,团队得自己赌。
- 汇率表的倍数依赖流程。 5×~200× 出自瀑布式分阶段的世界;今天小步快跑、错误几天内就被测试逮住的团队, 实际倍数会小得多。但「越晚越贵」的单调关系不依赖流程成立。
- 评审参与方越多越慢。 原则 45 列了六类相关方全过一遍21;书里没有讲怎么对付「评审会变成人肉慢放」, 这笔账要和 3.1 的汇率表对冲着算。
- 工具更替。 「存进数据库」在 2021 年已是问题跟踪工具(Jira 之类)的默认能力22; 书里的字段(表格里一格一格的属性)清单(编号/文本/来源/优先级/预期变更率/适用版本) 仍然就是今天需求条目的字段清单。
6. 可带走的
- 需求错误按阶段升值:5×、10×、20×、200×——需求评审多花的一天,是全项目最便宜的保险;
- 每条需求给唯一编号,记下来源(日期/人/场合)——后 续设计追溯、测试覆盖、变更谈判全靠它;
- 优先级必须写:M/D/O 或 0~10 分;「橙汁和生命维持系统」不在同一张必须清单里;
- 歧义战的打法是配合不是替换:先写自然语言,再试写形式模型,用模型反咬文字,修回文字;
- 先写模型的陷阱:回头写的「自然语言」只是模型的复述,读者一无所获;
- 「99.999% 可靠」必须拆成三口径之一(做对百分比/每年错几次/可用时间占比),不拆=授权下游各选各的;
- 每个环境限制旁写一句「越界时系统该怎么办」——需求没写,崩溃也算「正确」;
- 每条 TBD 带死线和责任人:「到何时、由谁解决」;
- 同一概念永远同一个名字;文档配术语表——这两条不为优雅,为可检查。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 错误汇率 5×~200× | 第3章 需求工程原则 | text/15-ch03.txt:67(搜「200倍」) |
| 需求编号三法 | 第3章 需求工程原则 | text/15-ch03.txt:219(搜「R27」) |
| 记录需求动机(两秒例) | 第3章 需求工程原则 | text/15-ch03.txt:87(搜「两秒」) |
| 优先级(橙汁/MDO) | 第3章 需求工程原则 | text/15-ch03.txt:195(搜「橙汁」) |
| 歧义三招(范根检查/形式模型/对开页) | 第3章 需求工程原则 | text/15-ch03.txt:235(搜「范根」) |
| 自然语言辅助增强而非替换 | 第3章 需求工程原则 | text/15-ch03.txt:253(搜「对开的页面」) |
| 拨号音对照、先自然语言 | 第3章 需求工程原则 | text/15-ch03.txt:219(搜「拨号音」) · text/15-ch03.txt:267(搜「四种状态」) |
| 形式化方法的务实用法 | 第2章 一般原则 | text/14-ch02.txt:275(搜「离散数学」) · text/14-ch02.txt:277(搜「某些部分」) |
| 可靠性三口径 | 第3章 需求工程原则 | text/15-ch03.txt:285(搜「99.999%」) |
| 101 架飞机、四个选项 | 第3章 需求工程原则 | text/15-ch03.txt:303(搜「101架」) |
| 待定项自毁 | 第3章 需求工程原则 | text/15-ch03.txt:319(搜「自毁」) |
| 简洁(跟踪坐标例) | 第3章 需求工程原则 | text/15-ch03.txt:207(搜「跟踪坐标」) |
| 需求里别做设计(警告注记) | 第3章 需求工程原则 | text/15-ch03.txt:131(搜「辅助理解」) |
| 多视角(实体关系图/状态机/决策表) | 第3章 需求工程原则 | text/15-ch03.txt:141(搜「决策表」) · text/15-ch03.txt:151(搜「决策树」) |
| 按读者组织(电话交换) | 第3章 需求工程原则 | text/15-ch03.txt:161(搜「电话交换」) |
| 需求进数据库 | 第3章 需求工程原则 | text/15-ch03.txt:329(搜「数据库」) |
| 同名同义(特殊命令例) | 第2章 一般原则 | text/14-ch02.txt:349(搜「三类特殊命令」) |
| 术语表 | 第2章 一般原则 | text/14-ch02.txt:327( 搜「沮丧」) |
| 评审需求(可执行需求) | 第3章 需求工程原则 | text/15-ch03.txt:117(搜「正式的评审」) · text/15-ch03.txt:119(搜「PAISLey」) |
| 2021:自然语言+跟踪工具 | 作者序 | text/08-fm.txt:37(搜「优先级」) |
| 2021:形式化方法没人真用过 | 作者序 | text/08-fm.txt:33(搜「形式化方法」) |