ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用状态机、TDD和上下文管理写出高质量需求文档

用状态机、TDD和上下文管理写出高质量需求文档 需求文档这件事很多团队其实一直没想明白。你以为需求文档就是“把用户想要的东西写清楚”那只是及格线。真正能把需求文档写出价值的人写的是“需求背后的行为逻辑和判定规则”是让开发、测试、产品三方能对着同一份文档吵不起来、猜不透、改不悔的东西。我这几年给好几个团队做过需求工程相关的梳理也亲手写过几版内部的需求模板最大的感受是需求文档写得烂不一定是文笔问题很多时候是思维方式的问题。光靠“用户故事验收标准”这种套路撑不起稍微复杂一点的业务。所以这次我想分享三个我从实操里摸出来的技巧——状态机、TDD测试驱动开发、上下文管理。这三个词听起来偏研发但其实放到需求文档的写作里完全是降维打击能把很多说不清的隐性需求逼到台面上来。1. 需求文档的痛点到底在哪先说个很常见的现象。你去翻一个项目的历史需求文档大概率会看到这种写法用户可以登录系统。登录失败时系统给出错误提示。用户可以退出登录。看完这种需求开发一般会追问登录失败具体是啥情况密码错了提示啥账号冻结了提示啥网络超时又提示啥退出登录的时候如果有未保存的数据要不要拦截这些追问恰恰是需求文档应该提前回答的。但很多文档根本不覆盖这些分支因为写文档的人脑子里只有“主流程”没有“状态空间”。状态空间这个词听起来抽象其实就是“系统在任意时刻可能处在哪些状态哪些事件会触发状态迁移”。操作类需求天然适合用状态去描述登录前、登录中、登录成功、登录失效、退出中、已退出这六个状态一梳理要写清楚的东西立刻就从“两三句话”变成了“一张严谨的迁移表”。再有一个常见坑是需求和实现混合。产品文档里写“这里调接口获取数据”或者“点击按钮后进入下一页”这种表述本身没问题但如果不小心把“翻页”这种交互同时写进“业务规则”里开发实现时和测试验证时就会把页面跳转误当成业务逻辑导致后来业务流程调整时页面结构也被连带改掉牵一发动全身。我在团队里推行过一个“三分离”的写法——业务状态、页面状态、数据状态分开描述。业务状态是用户视角能感知的阶段比如“待审核”“审核中”“已通过”页面状态是界面交互的表现比如“加载中”“空数据”“错误提示”数据状态是底层字段的生命周期比如“草稿”“已提交”“已归档”。这三层如果混在一起写文档基本就成了毛线团谁也理不清。2. 状态机让行为需求变得可验证状态机的核心价值不是炫技而是把“带时序的行为规则”变成一张让人无可辩驳的表。尤其在两类场景里状态机能非常明显地收敛复杂度。一类是长流程审批比如OA里面那种领导逐级审批中间可能涉及撤回、驳回到发起人、转交代理、会签加签不加状态机光是穷举流程路径脑细胞能烧掉一半另一类是带预设条件的业务对象比如工单系统里一个工单从创建到关闭中间的流转或者退款单从申请到退款成功的全链路。这两类需求用自然语言描述很容易出现逻辑漏洞但用状态图一画漏洞自动浮出水面。2.1 为什么状态机能收敛复杂度你想想看一个工单系统里工单的字段可能有几十个但在任何时刻工单一定处于某一个明确的状态待分配、处理中、待客户确认、已关闭。状态的数量往往只有个位数而状态转移的事件也不多。把这两者抓出来其他字段再多也只是“状态挂载的信息”而已。之前我帮朋友看过一套嵌入式设备的软件需求他们用自然语言写了一百多页里面反复描述“设备在某种告警情况下应该怎么做”写得极其冗长。我用状态机重新梳理了一遍把设备工况拆成正常、预警、故障、停机四个状态再把触发切换的条件整理成一张表一百多页的内容收敛成了二十几页缺漏的地方也一目了然——比如从“故障”自动恢复后到底回“正常”还是“预警”他们原先根本没写清开发按自己的理解实现了测试也没较真直到现场设备一连串误动作才暴露。这就是状态机带来的直接收益把没想清楚的问题暴露在评审阶段而不是上线之后。2.2 需求文档里怎么表达状态机在需求文档里我推荐不要画特别复杂的图而是用“状态事件动作下一状态”的四列描述法。举个例子假设我们要写“用户登录”的需求当前状态事件条件/动作下一状态未登录输入账号密码并点击登录校验通过下发Token已登录未登录输入账号密码并点击登录校验失败提示“账号或密码错误”未登录已登录收到401响应清除本地Token跳转登录页就绪态会话过期会话过期用户重新登录成功刷新Token已登录这张表一旦写出来产品、开发、测试其实都能对着它工作。开发写代码时看的是“事件”和“动作”列测试写用例时看的是“条件”和“下一状态”列产品评审时检查的是有没有漏掉某个分支——这张表的出现本身就是一份初稿测试用例。对于特别核心的流程我还会再加一列“异常分支补充”把超时、重复提交、权限不足这些共性异常统一挂在表下面避免每个状态都重复描述一遍。2.3 三段式和表驱动的状态机这里我顺着热点词多说两句。最近“三段式状态机”“表驱动状态机”在嵌入式圈子里讨论很热很多开发把状态机写进了嵌入式固件、单片机逻辑里。作为需求文档的撰写者我们需要懂得研发伙伴的词汇因为需求文档里如果能用他们熟悉的建模语言去描述行为沟通成本会直线下降。三段式状态机简单说就是状态判断、事件触发、动作执行三段分开写。第一段判断当前状态第二段判断触发事件第三段执行动作并迁移状态。需求文档里描述业务规则时也可以模仿这种三段式业务前提、触发条件、业务动作。这样写出来的需求开发在落地时几乎不需要翻译。表驱动状态机则是指用查表代替if-else链把状态转移矩阵写进配置文件。这个思想映射到需求文档上就是刚才说的那张四列状态表——本质上我们是用“查表”的方式把需求分支收敛起来避免大段大段的if-else式文字描述。2.4 实战案例用状态机拆解智能门锁需求我再给你一个完整的迷你案例演示一下状态机怎么用在需求描述里。假设我们要写一款智能门锁的部分需求涉及“门锁”“用户”“App”三方联动。不写状态机的人会这么描述用户可以通过App远程开锁也可以通过密码开锁指纹开锁管理员可以添加用户删除用户门锁电量低时提醒门锁被撬时报警。这种描述你没法评审。门锁当前处于“已锁定”和“未锁定”两个基础状态再叠加“离线”和“在线”两个通信状态基础迁移表就清晰了当前状态事件条件/动作下一状态已锁定指纹验证通过开锁记录操作日志未锁定已锁定指纹验证失败记录失败次数若连续失败5次则冻结指纹模块30秒已锁定已锁定撬锁传感器触发触发本地报警通知App和物业已锁定报警未锁定门关闭且检测到锁舌伸出执行上锁已锁定未锁定门持续打开超过60秒推送“未关门”提醒未锁定提醒写到这里需求文档已经不再是“给人看的故事”而是一份“可以推演的图纸”。开发拿到这张表状态模式、状态表的代码结构直接出来了测试拿到这张表等价类和边界值的组合直接列出来了。3. 需求文档中的TDD先写规则再写描述TDD测试驱动开发在代码界提倡的是“红-绿-重构”先写测试让它失败再写实现让它通过。这个思想可以好好地移植到需求文档写作里我在自己团队叫它BDD式需求先行——先把验证规则写出来再倒推业务流程。3.1 把验收标准当作“测试用例”来写多数需求文档里的验收标准是“系统应支持用户修改个人信息”这种话术基本没有约束力。测试人员看到这种标准只能凭自己的理解去写执行用例。正确做法是把验收标准写成“可自动化执行的测试用例”明确前置条件、操作步骤、预期结果。我一般建议团队用“Given-When-Then”的句式来写验收标准效果比“应支持”好得多Given用户在订单详情页且订单状态为“待付款”When点击“取消订单”Then弹窗确认提示点击“确定”后订单状态变更为“已关闭”且库存数量回滚这个写法和TDD里“先写一个失败测试再实现”的逻辑是一模一样的——需求文档先抛出批量验证用例开发实现后只要拿这组用例去跑通过就意味着需求被正确理解。3.2 需求评审里的“红-绿-重构”在需求评审时我们可以学TDD里的“红”阶段评审委员不是只“看”文档而是随手设计用例去“击打”文档。一旦用例里出现文档没法直接回答的场景那就是一个红。比如文档写了“用户可取消订单”评审人可以追问已经发货的订单能不能取消取消有没有次数限制取消后退款多久到账如果文档都无法回答说明这块规则有缺口。等这些缺口在文档里补上需求就“绿”了。最后的“重构”阶段可以放到两个版本之后等文档沉淀了两个迭代我们再回头删减冗余分支优化语气和口径。这里有一个小技巧迭代结束后把开发过程中产生的“问答记录”和“变更记录”归档进需求文档的版本历史里。这不是行政任务而是让文档的演进脉络清晰后面的人维护起来才不会像考古。3.3 从状态机推导用例覆盖状态转移全覆盖测试把状态机和TDD结合能衍生出更强的效果。针对上面那张门锁状态表测试用例的覆盖面直接对应状态表里的每一行每条“迁移路径”都是主用例每个异常的“条件列”都是搅乱测试的原料。状态表里若有N行测试用例起码要有N条基本覆盖再补充状态组合、非法事件、重复事件等。这样评审时状态表的完整性决定了测试覆盖的下限推荐所有需求文档都这么干。4. 上下文管理别让读者迷失在细节里写需求文档最容易被忽视的是上下文管理。有的文档动辄几十页从操作手册到技术方案什么都有读到后面忘了前面。好的需求文档应该跟好的代码一样清晰的上下文合理解耦必要的兜底。4.1 什么是需求文档中的上下文我理解的文档上下文包含三层信息用户当前处于业务的哪个阶段、文档当前在讲哪个对象的行为、这段规则依赖哪些外部条件。如果这三层信息没交代清楚读者读起来就会像失忆了一样。举例说明同样是“点击提交”四个字在“下单流程”和“申请退款流程”里含义完全不同。所以需求文档里第一次出现某个概念时我都会加一段“概念与边界”说明。比如写“订单”之前先声明本系统中订单分为普通订单、赠品订单、补差价订单本文档的“订单”默认指普通订单其余类型如无特别说明不适用本规则。这段声明就像编程里的命名空间能挡住大量的误读。4.2 使用文档地图和术语表引导读者阅读几十页的文档就像逛一座大商场没有地图的话只能瞎转。我写长文档时会在开头放一个“文档地图”告诉读者第一章是整体业务背景第二章是核心流程的状态机第三章是异常规则第四章是数据字典。每个读者可以根据自己的角色直接跳到对应章节。术语表也很有用。业务团队说的“核销”和技术团队说的“核销”往往不是一回事。术语表里统一口径能消掉很多扯皮。我见过最离谱的一次一个项目里“库存”这个词在需求文档中出现了一百多次至少有三种含义可售库存、物理库存、锁定库存。后来统一术语把系统里实际存储的三个字段分别命名业务语言的歧义才被彻底拔掉。4.3 状态上下文与角色权限的联动状态机里还藏着一个上下文信息——每个状态下谁有权限执行什么操作。这个在需求文档里如果不单独描述后面做交付时就容易乱套。以订单状态举例订单在“待付款”状态时“取消订单”这个按钮对用户可见对客服也可操作“删除订单”在“已完成”状态才允许且仅用户本人可操作。把这些权限约束显式挂到状态上下文下面权限设计相关的需求就不用另起炉灶了也会顺利衔接上RBAC基于角色的访问控制的设计。4.4 用“用户故事地图状态机”双视角描述我习惯在需求文档里同时提供两条阅读路径。一条是用户故事地图按用户旅程从头到尾描述方便业务方快速理解整体流程另一条是状态机描述按状态迁移组织规则方便开发和测试去查分支和验证。这两者不是重复而是互补。用户故事地图回答的问题是“用户怎么走完这条路”状态机回答的是“这条路上每个路口受什么规则约束”。两条路径交叉起来需求文档才有了立体的视角。写的时候我会先从用户故事地图开始梳理出主要角色、关键任务、分步流程再根据这些步骤提取状态和事件生成状态表。5. 跨岗位写作一份需求三类读者需求文档的读者至少有三类业务方看的重点是“这个功能对我的业务意味着什么”开发看的重点是“系统里到底怎么变”测试看的重点是“我需要验证哪些场景怎么去构造这些场景”。这三类读者的阅读前缀完全不一样。所以我在写文档的时候基本会按这个结构组织这篇文档的适用范围和业务目标用大白话说清“为什么做”现在业务的现状以及改完之后业务的预期形态核心业务规则优先用状态机和规则表表达异常与边界流程尽量用场景化描述验收标准采用Given-When-Then给出指到可测附录术语表、涉及外部系统接口、开放的待确认问题这个结构是站在“文档是产品与研发共同签署的契约”这个前提下设计的。既然是契约就不能只有甲方的愿望也不能只有乙方的理解必须是一个双方都认可的可执行文本。5.1 用“异常分支”把各角色的盲区拉回很多业务方描述需求的时候只顾得上“正常的流程”开发在评审时却总在追问“异常怎么办”。建议写文档之前先组织一个关于“异常场景”的脑暴会流程中哪些环节可能失败失败之后数据怎么处理是否需要用户介入费用怎么结算把这些脑暴结果直接写进异常分支清单这个文档就会立刻变得不一样。异常分支清单对测试来说是天然的用例库对开发来说是防御式编程的指引对业务方来说是了解系统边界最直接的窗口。有一次一个支付项目整理异常分支时发现原有设计对“支付回调到达但本地订单已超时关闭”的情况没有定义差点在真实业务里产生资金差错。后来大家常说这个异常脑暴会帮整个项目省下至少一个P0事故。5.2 嵌入式场景的启示与“状态动作表”咱们再拉回热搜词里大量出现的嵌入式相关话题。嵌入式软件开发和需求文档天然亲近状态机因为硬件设备就那么几个物理状态状态之间切换的触发源也很明确。我在写智能设备的需求文档时发现直接从硬件角度给的状态机表往往很粗糙——传感器值、超时、按键这些触发源得归类、得细化。这里有一个特别实用的工具叫“状态动作表”State-Action Table我在需求文档里会额外增加一张“触发源清单”触发源类型示例捕获方式需求关注点用户操作按下物理按键中断/轮询防抖、长按/短按区分时间事件超时未操作定时器超时时长、超时后动作传感器数据温度超过阈值ADC采样/滤波阈值、滤波算法、上报频率通信消息收到控制指令协议解析丢包重传、指令优先级这张表放进需求文档的附录里对嵌入式团队帮助很大它能倒逼产品经理去想这个“阈值”到底是谁定义的能不能在设备端配置上报频率是多少会不会造成数据风暴这些都是纯自然语言描述很难逼出来的问题。6. 实操避坑指南需求文档的三处暗礁最后这部分我把自己踩过的坑集中汇总一下希望能帮大家省去一些试错成本。第一处暗礁把用户操作路径当成了业务状态。有些文档里画的状态机是把“点击A按钮”“打开B弹窗”当成状态变化这其实是交互流程不是业务状态。业务状态应该是脱离界面存在的。比如“待付款”是业务状态“弹出收银台”是交互动作这两者不是一回事。如果混在一起后面页面重新设计时需求文档整篇都要推翻重写。第二处暗礁状态机的过度设计。状态拆分太细会导致文档膨胀比如把“处理中”拆成“处理中-已接单”“处理中-已出发”“处理中-进行中”如果这几个状态没有完全不同的规则组合拆分就是自找麻烦。状态粒度的判断标准就一条对下一步操作或判定有实质性影响的状态差异才值得拆出来。第三处暗礁需求文档里写了“实现建议”。无可否认“需求方还是会忍不住写实现细节”。比如“系统应使用Redis缓存用户信息”这种描述看似贴心实际是给研发带上了镣铐。如果有一天缓存方案要换掉按流程你还得改一遍需求文档吗正确的做法是要么写“用户信息在有效期内的重复查询不应产生额外费用”要么写“对用户信息读取的响应时间不应超过XXX毫秒”把“为何”说清让研发去决策“如何”。7. 收尾前的最后一点私货写了这么多年需求文档我最大的体会是需求文档的质量反映的是一个团队的思考深度。状态机、TDD、上下文管理这些技巧是把思考从“直觉得到结论”转变成“结构推导结论”的工具。写文档的时候多用一点点严谨后面开发、测试、运维节省的时间是成倍的。尤其是再分享一个小习惯写完一个章节随手把状态表打印出来贴在工位上接下来三天再改接口、再画原型的时候反复对着这个表看一眼你都会发现下一次需求的坑比之前少好几个。对我来说文档不是写给别人看的一个交付物而是团队共同使用的思考脚手架。工具和方法会过时但“把复杂事物拆成可验证规则”的这个习惯什么时候都不过时。希望这篇分享对你的需求文档写作有所启发也欢迎你到留言区聊聊自己写需求文档时遇到过的那些“说不清”的时刻。
返回列表