需求文档写了快十年我最大的感受是大多数人不是不会写需求文档而是不敢写细。写细了怕被研发抠字眼写粗了又怕开发出来不是自己要的东西。结果文档里全是“智能、自动、流畅、高效”这种词研发看完一脸懵测试拿到手不知道查什么最后全靠口头沟通补课。今天这篇是系列第三篇不聊模板、不聊排版专门聊三个能把需求文档从“文字描述”变成“工程图纸”的实战武器状态机、TDD、上下文管理。这三个东西听起来都偏技术但产品经理、系统工程师、嵌入式开发、甚至测试负责人只要在团队里承担需求定义工作都应该掌握。用好了需求文档才能从“大家看得懂”进化到“大家做不错、测不偏”。1. 状态机把需求从“散文”变成“状态表”1.1 “散文式需求”是万恶之源先看一句很典型的需求描述设备能够根据环境温度自动调节加热功率保证温度恒定在温度过高时停止加热并提示用户。这句话你拿去问十个研发能出来八种实现方案。有人做成PID连续调节有人做成简单开关控制“温度过高”是多少度“提示用户”是亮红灯、响蜂鸣还是App弹通知“环境温度”是设备内部温度还是房间温度这些全都没定义。问题就出在我们习惯用“形容词动词”来描述系统行为但系统的真实行为是一连串确定的状态和事件。需求文档如果用散文写的等于把判断题变成了阅读理解开发和测试每个人理解都不一样最后交付当然对不上。状态机的核心思路就是把系统拆成四样东西状态、事件、动作、迁移。翻译成人话就是系统现在处于什么状态发生了什么事应该执行什么操作接下来去哪个状态。这四样东西一旦定义清楚行为就完全确定了没有二义性。1.2 一个能直接抄作业的状态需求模板拿一个非常常见的场景举例智能恒温器。先定义状态不要多够用就行状态说明待机通电但未启动界面显示当前温度加热正在加热目标温度由用户设定保温温度达到目标范围暂停加热异常传感器故障或加热器过温停止工作关机用户手动关闭所有输出断电再定义事件也就是外部或内部触发条件事件触发条件开机用户按下电源键关机用户按下电源键长按2秒温度低当前温度低于目标温度减1℃温度到当前温度达到目标温度加/减1℃容差传感器断线连续5次采样无效或通信超时故障恢复传感器恢复有效且温度低于保护阈值最后是核心的状态迁移表。这张表是整个需求文档里价值最高的部分之一当前状态事件动作下一状态待机开机显示当前温度读取目标温度加热加热温度到关闭加热器显示“保温中”保温加热关机关闭加热器断开显示关机保温温度低启动加热器显示“加热中”加热待机/加热/保温传感器断线关闭加热器蜂鸣器响3声显示故障码E1异常异常故障恢复停止蜂鸣显示当前温度待机这张表一出来研发要写的是开关逻辑还是PID算法、状态切换条件是什么、异常怎么处理全都清楚了。测试拿着这张表可以一比一列出用例每个“当前状态事件”的组合都是一条测试路径。开发写代码时无论用 switch-case、用状态表驱动还是上重量级状态机框架他们只关心这张表够不够完整。1.3 别被 Moore、Mealy 这些名词吓到你在网上搜状态机一定会看到摩尔状态机Moore和米利状态机Mealy还有热搜里的“三段式状态机”“两段式状态机”。做单片机、写 Verilog、做 PLC 的朋友对这些应该很熟。摩尔状态机通俗说就是“输出只看当前状态”比如你处于保温状态不管什么事件来只要还没离开这个状态显示就是“保温中”。米利状态机则更敏感“输出由当前状态输入事件共同决定”比如同样是加热状态收到“温度低”事件时只是继续加热但收到“传感器断线”事件时立刻报警并跳转。对写需求文档的人我的建议是你不需要在文档里写“本设计采用摩尔状态机”但你要知道有这么两种典型实现不然你状态表里的“动作”这一列会让开发纠结。如果你希望动作只跟状态严格绑定就把动作写进状态定义里如果你希望动作和事件强相关就把动作放在迁移表的“动作”列。开会时跟研发对齐一句“这里我按当前状态事件来决定动作”就省掉很多后续理解偏差。至于“三段式状态机”本质是把状态跳转、输出动作、时序控制分开写工程上更清晰。但到了需求文档层面你只需要输出迁移表这个表已经涵盖了状态跳转和动作剩下的是开发自由发挥的空间。1.4 需求的“最小状态机”产出物三张表一张图给团队定个规矩凡是涉及设备模式、业务流程、多步骤操作的需求必须带三张表一张图。状态定义表全部状态的名称、含义、约束。状态集合要互斥且完整系统任何时刻必须落在某个状态里。事件定义表所有触发条件、来源用户操作、定时器、外部信号、参数。事件要尽量原子化不要写“当温度合适时”这种大而化之的条件。动作定义表系统要执行的具体输出行为包括显示内容、通信报文、物理量输出等。状态迁移图把状态表和事件表画成一张有向图能肉眼发现“这条路通不通、那条路会不会死循环、有没有回不去的状态”。状态迁移图是重中之重。很多需求问题光看文字是看不出来的但是一画图就暴露了——比如从某个状态发生了某个事件之后居然没有定义下一状态这属于迁移缺失又比如正常流程里某个状态只能进不能出这就是状态死锁。画完图后建议对着图逐条问自己一个极其有用的问题“所有状态退出路径是否都覆盖了”很多需求文档只写了用户正常操作路径拔电、断网、传感器失效、权限过期、重复点击这些路径全是空白而这些恰恰是生产环境里最常出事的地方。顺便说一句如果你接触过单片机、FPGA 或 PLC会发现状态机应用极其常见。热搜里“当单片机遇上状态机”“Verilog 三段式状态机”“PLC 编程状态机写法”其实就是不同工程领域各自的状态建模习惯。无论实现载体是什么需求文档里的状态表都是通用的因为它描述的是系统行为本身和代码语言无关。2. TDD用验收测试倒逼需求想清楚2.1 需求评审最该问的一个问题很多需求评审会开成“念文档大会”台上念念台下听听最后问“有问题吗”没人吭声就散了。等开发做了两三个月测试拿到的需求描述还是“能够正常显示实时数据”“在异常情况下能够提示用户”完全没法验收。后来我们团队定了一条铁律评审任何需求时只问一个问题——“这条需求怎么测”如果需求负责人连一条具体的操作路径和预期结果都说不出来这条需求直接打回重写。光靠这个问题的倒逼需求文档质量提升了一个大台阶。这个方法本质上就是 TDD 的思维。TDD 是测试驱动开发开发先写失败用例再写实现代码。放到需求侧我们做的就是 Requirements-Driven Testing先定义清楚可验证的验收测试再回头去推导业务逻辑和功能描述。需求不是“拍脑袋想出来的功能列表”而是“一组可执行的测试期望”。2.2 Given/When/Then 写验收标准比口语描述好一百倍很多团队写验收标准还是“系统应在5秒内完成识别”“页面应显示对应信息”。这类写法最大的问题是没有前置条件和触发动作研发无法复现测试无法自动跑。推荐直接用行为驱动开发里的 Given/When/Then 三段式来写Given前置条件系统处于什么状态满足什么环境When触发动作用户干了什么事件发生了什么Then预期结果系统应该输出什么状态迁移到哪里举个例子需求编号FR-LOCK-021需求描述门锁设备在低电量状态下仍允许用户通过密码开锁。验收测试Given 门锁电量低于10%当前处于待机状态When 用户在键盘上输入正确密码并按下确认键Then 门锁执行开锁动作同时向云端上报“低电量开锁”事件本地蜂鸣器短鸣1次这样一写测试直接可以把它抄成用例脚本开发也清楚自己要输出哪些日志和事件。更重要的是前置条件里的“电量低于10%”和“待机状态”都是第1章状态机表格里定义过的状态两者是天然联动的。2.3 从需求文档推导完整测试列表的“标杆法”平时帮团队评审需求时我总结了一个非常实用的方法叫“标杆法”每个功能需求至少要能推导出四类测试测试类型说明举例智能加热器过温保护正常路径所有条件满足功能正常执行加热到60℃时自动断电边界条件数据处于阈值临界时行为正确温度到59.9℃时是否继续加热异常路径设备故障、输入非法时行为正确温度传感器断线时不误触发加热恢复路径从异常恢复到正常时的状态正确故障恢复后设备回到待机而非加热四个类型合起来就是一张完整的验收矩阵。正常路径保证功能能用边界条件保证精度异常路径保证不出安全事故恢复路径保证系统不会卡死。很多需求只写了正常路径恰恰把最容易出问题的三类全丢了。比如一个需求“智能插座支持过温保护”标准做法是什么我们的产品经理照着状态机表列出了这样的验收列表正常功率高于设定值插座在3秒内断电App推送告警。边界温度阈值设为80℃温度到79.9℃时不断电到80℃断电。异常温度传感器无响应进入异常状态断电并显示E2故障码。恢复温度下降后需要手动按按键恢复供电不能自行恢复。这四条一列出来开发立刻就知道要做哪些逻辑测试也知道要造哪些数据。对照上一个版本“要确保过温时及时断电”——你们感受一下差距。2.4 TDD 里的“红灯、绿灯、重构”对应到需求阶段TDD 原教旨是红绿灯循环先写一个失败测试让它红灯再写最简实现让其变绿最后优化代码结构。这个循环放到需求文档里意外地合适。红灯阶段对应写需求时的“矛盾检测器”如果你发现某个需求写不出测试或者一写测试就发现前后逻辑打架那么这条需求就是红灯不能流入开发。举个例子“用户可修改设备名称”和“设备名称在配网完成后不可变更”如果同时出现你用 Given/When/Then 一写两条验收测试直接冲突。红灯的意义就是让冲突在文档阶段暴露而不是等开发做完了才发现。绿灯阶段对应需求满足测试用例全部通过说明系统行为符合预期。重构阶段对应需求本身的迭代上线后发现用户操作习惯和我们预设的不同需求也需要持续修订。修订完成后测试用例也要同步更新保证“文档-用例-代码”三者始终对得上。在需求评审会里可以尝试这样的议程先不展开业务背景直接把核心需求的 Given/When/Then 用例过一遍。一边过一边问“这个测试我们能不能执行条件能不能构造预期结果是否唯一”能过完一圈的需求基本都是靠谱的过不完的需求多半是逻辑有洞。2.5 嵌入式/硬件场景里的 TDD 变体TDD 这个词很容易让人想到软件单元测试但硬件开发、嵌入式开发同样需要 TDD 思维只是方法不同。你没法在产品原型出来前对一颗温度传感器做理想化的单元测试但可以在需求文档里明确测试前置条件和允许的测试手段。比如一条需求当设备连续3次与云端通信失败后进入离线模式并在本地缓存关键数据。这条需求的验收测试需要前置条件“设备处于弱网环境能够模拟云端不可达”。如果真机测试条件过于苛刻就要写清楚“允许通过串口/网络模拟工具注入通信失败场景或使用HIL硬件在环测试装置验证”。这些信息不写进需求文档测试团队后期一定会来反复找你确认甚至干脆不测这条逻辑。所以在需求文档模板里建议给每条核心需求增加两列测试前置条件和测试环境备注。这两列对于嵌入式、物联网产品尤其重要因为真实环境里的温度、信号、断电场景往往无法稳定复现必须允许模拟手段。3. 上下文管理需求文档里最容易被忽视的“隐形需求”3.1 “上下文”在需求文档里到底指什么说到上下文很多人第一反应是程序里的 Context 对象、线程切换时保存的现场或者语境分析。但在需求文档里上下文管理至少有三层含义每一层都能决定需求质量。第一层是业务场景上下文。用户在什么场景下触发了这个需求场景里有什么前置条件。同样是“开门”这个动作管理员用密码开门、住户用指纹开门、访客用临时密码开门、运维人员用机械钥匙开门这四种场景对系统的要求完全不同。需求文档里如果不写明业务场景开发和测试只能凭自己的理解去脑补。第二层是系统状态上下文。设备当前处于什么模式、什么状态直接决定了同一功能的处理逻辑。比如一个带屏智能门锁在正常模式下按门铃键会播放门铃提示音但在低电量模式下按门铃键可能只亮屏提示“电量不足”不播放声音甚至为了避免电压跌落导致锁死直接禁用门铃功能。不区分状态上下文需求就没法写准。第三层是文档语义上下文。同一个术语在文档不同章节的含义必须一致。这个看似简单实际执行起来问题最多后面单独说。3.2 文档语义上下文管理名词统一是刚需我以前吃过一个亏。需求文档里写“设备启动后显示温度”另一章写“设备上电后进入待机模式”。研发问“启动”和“上电”是一回事吗我当时觉得当然是但后来发现上电是指物理通电启动是指系统初始化完成中间还有一段时间屏幕是先亮但不显示数值的。因为没区分研发把“上电”和“启动完成”当成同一个点导致开机阶段有大概800毫秒的时间数据是空白的用户看着屏幕以为设备坏了。后来我们专门建了一张术语表强烈建议每个团队都搞一份术语定义取值范围/单位示例/说明上电设备接通电源的物理动作无插上电源插头指示灯点亮启动完成系统自检通过业务服务可用的状态无屏幕显示主界面当前温度设备内置温度传感器实时采集值单位℃范围-20~80每1秒刷新一次目标温度用户通过设置界面输入的目标加热温度单位℃范围5~35默认22℃待机设备通电、无业务运行、可接受新指令的状态无等效于系统的睡眠浅层这张表里的每个名词功能描述、状态表、验收测试里都要严格遵守。尤其要注意的是不要用“差不多、大概”这种词去定义名词。像“低电量模式”到底电量低于多少触发哪怕设计时还没定也必须先写一个v0.1的临时阈值比如20%并标注“待硬件确认”。宁可有一个暂定值也不能让全文到处是模糊引用。3.3 系统状态上下文管理多模式系统的需求怎么落地我之前和一个做智能门锁的团队合作他们的需求文档里有一个场景设备在夜间时段如果有人在门口停留超过30秒自动抓拍并推送告警。听起来很清晰对不对但评审会议上测试问了一句“那用户在门口正常输密码回家时停留超过30秒算不算告警”全场安静了。显然不算因为主人的开门动作和告警逻辑是冲突的。这里缺少的就是“布防模式”“撤防模式”这两个上下文状态。后来我们把需求改成了“设备处于布防模式比如夜间、离家且检测到人员逗留超过30秒自动抓拍并推送告警若处于撤防模式仅记录日志不推送”。加了模式上下文后行为才清晰。处理多模式系统最有效的需求工具是“模式×功能行为矩阵”功能/事件正常模式低电量模式布防模式OTA升级模式响铃播放门铃声仅亮屏提示播放门铃声不响应密码开锁允许允许且上报事件允许且撤防不响应逗留告警不触发不触发触发推送告警不触发本地日志记录记录并限制长度记录暂停记录这个矩阵的本质就是把“模式”当作状态机的状态把功能响应当作不同状态下的事件动作。所以第1章的状态表和第3章的上下文矩阵是一体的。任何有模式概念的系统需求文档都必须有一张这样的表否则跨模块开发时App端、固件端、服务端对同一个模式的理解必然产生偏差。3.4 上下文丢失的典型事故复盘有个充电桩项目的教训让人印象很深。需求文档写了“充电过程中屏幕显示实时功率、已充电量、当前费用”。开发按字面实现了但只要充电桩进入故障状态比如绝缘检测失败屏幕照样按照常规模式刷新“实时功率”。用户看到功率数值还在变化以为正在充电实际上设备已经停止充电了。直到有人摸了充电枪发现没有电流才意识到出了问题。问题根源就在于需求只定义了“充电中”这个状态下的显示逻辑没有定义“充电中→故障”状态迁移发生时屏幕内容应该怎么切换。页面上的数值显示在不同上下文里承载的信息含义完全不同正常充电时它是“正在进行的业务数据”故障时它是“误导用户的异常数据”。后来我们给所有带显示或通知功能的需求加了一个强制字段异常上下文行为。每次定义正常功能时必须同时写明“当系统不在正常状态时该功能如何表现”。不需要长篇大论只要一行比如“故障时屏幕固定显示故障码E3隐藏实时功率”。就是这一行后来避免了很多类似的事故。3.5 上下文管理的好用工具需求上下文卡除了状态机表和模式矩阵我们还在每份重要需求前加一个轻量的“需求上下文卡”本质上是把背景信息固化下来防止上下文丢失。字段内容示例需求编号FR-CHARGER-037业务场景扫码启动充电后用户在手机端查看充电状态前置状态充电枪已连接车辆设备鉴权通过处于可充电状态后置状态充电完成后自动跳转至“充电完成”状态关键决策背景故障状态下不展示实时功率因为该功率数据已不代表有效充电约束条件充电过程中用户不允许拔枪除非点击“停止充电”并等待泄压这张卡把一条需求的业务场景、状态迁移、决策背景都锁定在一个小格子里产品、开发、测试都能快速了解“这条需求是在什么情况下成立的”。这比在长文档里反复翻上下文要高效得多而且对后来接手的人特别友好。无论是软件系统的会话保持、登录状态还是嵌入式系统的低功耗模式、错误恢复流程核心都是“系统时刻要知道自己处于怎样的上下文并据此决策”。上下文管理不是可选项在复杂的现代产品里它已经是刚需。4. 几个长期管用的实战技巧4.1 需求编号体系给每条需求上户口很多团队的需求文档没有编号体系描述全靠“第一点、第二点”或者章节号。一旦测试发现缺陷缺陷单里写“设备温度显示不对详见需求文档3.2节”这个引用还算清楚。但如果需求文档迭代了几版3.2节早就不是原来的内容了追溯就断了。强烈建议建立一套简单的需求编号规则比如“模块-类型-序号”需求编号类型模块描述优先级APP-REG-S01功能注册登录用户使用手机号注册P1APP-REG-N02非功能注册登录注册请求响应时间小于3秒P2IOT-OTA-T01缺陷修复OTA升级修复断点续传失败问题P1有了编号需求文档和测试用例之间可以互相引用开发提测时可以标注“本次涉及需求编号APP-REG-S01”测试回归时直接在用例库里筛选相关编号即可。这个投入回报率极高几乎没有成本但溯源效率提升巨大。4.2 每条需求附一个“验收标准边界条件”“功能正常”“正确地显示”“及时地响应”这类模糊词汇禁止出现在验收标准里。验收标准的写法应该具体到条件、数值、时间、行为结果。好的验收标准示例在环境温度20℃至30℃范围内设备从开机到显示当前温度的时间不超过3秒。当设备电量低于10%时连续响铃3声后自动进入低功耗模式屏幕亮度降为20%。用户在3秒内连续点击两次“锁门”按键仅执行一次锁门操作。每条需求除了正常验收标准还要写边界条件最低湿度条件下设备能否正常工作最大支持在线设备数是多少弱网、断网、断电恢复时行为是否定义快速连续操作时系统有无防抖/防重入机制一个笨办法每条需求写完后拿一支笔画勾确保至少有一条正向验收标准和一条反向/边界验收标准。如果没有反向标准的说明这条需求可能只考虑了理想情况建议补上。4.3 需求文档也要做版本控制代码有 Git、SVN但需求文档普遍还是“新版覆盖旧版”。等到出了问题很难弄清楚哪个版本才是开发依据。最简单的做法是给文档加一个变更记录页每次修订都在顶部更新表格版本日期修改人变更要点决策原因v1.02025-01-10张工初始版本-v1.12025-01-18李工增加低电量模式行为定义评审会遗留问题补充模式矩阵v1.22025-01-25张工修订过温保护阈值从60℃改为58℃硬件反馈传感器精度不足留2℃余量这个记录为什么重要因为需求变更的理由往往比变更本身更重要。开发看到v1.2的阈值从60℃改到58℃如果不知道背景可能会觉得是无理由拍脑袋但看了“决策原因”那列就知道是硬件精度导致的后续还能评估是否要回归测试相关用例。这就是典型的技术上下文管理。4.4 需求评审不是“念文档”而是“跑用例”最后分享一个组织评审会的小技巧。千万不要把评审做成逐条朗读需求文档而是要提前把待评审需求的验收测试打印出来现场一条条“跑”过去的。具体流程可以是给出本轮迭代的业务范围花10分钟讲清楚背景。把核心需求串成用户故事讲正常路径。把状态迁移表和模式矩阵投影出来逐条过。重点回头去看异常状态和边界条件这是冲突高发区。每条需求的验收用例请测试负责人现场认领明确“这个用例条件能否构造预期结果是否明确”。这个方法看起来慢但实际上节省的是后续开发和测试返工的时间。我记得有一次评审一个“远程升级失败回滚”的需求现场把用例跑完发现“升级过程中设备掉电”的场景根本没覆盖大家当场补上了需求。如果按照以前念文档的方式这个漏洞大概率要等到真机测试时才暴露到时候返工成本至少是十倍。说到底需求文档不是写给别人看的是团队协作的基准线。状态机管行为建模TDD 管验收建模上下文管理管语义建模。三件套配合起来文档才能从“散文”变成“图纸”。如果你正为需求文档写不细、做不对而头疼不妨从下一篇需求开始先加一张状态表、三条 Given/When/Then 测试和一个上下文矩阵坚持两个迭代再回头看看效果。