用户故事模板:把价值和验收写在一起
用户故事要连接价值和实现
上周我在评审一个 Sprint 的用户故事,产品同学写的描述是:「作为用户,我希望能够导出报表」。开发问了一句「导出成什么格式」,产品说「Excel 就行」;测试又问「数据量多大呢」,产品说「应该不大」。两周后验收的时候,运营跳出来说:「我要的是带图表的 PDF,Excel 没法直接发给客户」。
这种场景我见过很多次。问题不在谁漏说了什么,而在于我们写用户故事时,习惯性地把「价值」和「验收」分成两件不同的事。价值写在故事里,验收留在脑子里。等到开发、测试、产品各自理解出现偏差,返工就变成了常态。
用户故事不只是三句话
用户故事最早由 Kent Beck 在极限编程(XP)中提出,后来由 Ron Jeffries 总结为 3C 模型:
- Card(卡片):对需求的简短描述,通常用「作为…我希望…从而…」的句式
- Conversation(对话):围绕这个需求,团队、产品、用户之间的讨论
- Confirmation(确认):判断这个需求是否完成的验收标准
Agile Alliance 在 INVEST 原则中也把「Testable(可测试)」作为用户故事的六项标准之一——一个写不出验收标准的故事,要么拆得不够细,要么价值还没想清楚。
Mike Cohn 在 Mountain Goat Software 的指南里反复强调:「每个用户故事都是一段对话的占位符」,但对话的结果必须落到具体的验收标准上,否则团队永远无法对齐「什么时候算做完了」。
我在实际项目中发现,多数团队在 Card 和 Conversation 上都做得不错,但在 Confirmation 这一环要么省略,要么写得过于模糊。没有验收标准的故事,就像一份没有签字条件的合同——大家都知道有这回事,但对具体内容的理解可能完全不同。
一个实用的用户故事模板
经过多次迭代,我整理出一个模板结构,覆盖从需求到验收的完整链路:
- 角色:作为 [某类用户],明确是谁需要这个能力
- 目标:我希望 [完成某件事],说清楚要做什么
- 价值:从而 [获得某个结果],解释为什么要做
- 业务规则:列出限制条件、特殊情况、边界处理
- 验收标准:用 Given-When-Then 格式,描述可观察的行为
- 非目标:本故事不覆盖什么,避免范围蔓延
- 补充说明:技术约束、依赖关系、设计稿链接等
每个字段都不是摆设。角色帮开发理解上下文,价值帮团队在资源紧张时判断优先级,业务规则和验收标准则把模糊的期望变成可验证的条件。
模板结构对比
| 维度 | 只有「角色-目标-价值」 | 加上业务规则 | 加上验收标准 | 完整模板 |
|---|---|---|---|---|
| 开发理解需求 | 知道要做什么 | 知道边界在哪 | 知道怎么实现才算对 | 完整闭环 |
| 测试写用例 | 靠猜 | 有方向 | 有明确依据 | 直接映射 |
| 产品验收 | 凭感觉 | 有参照 | 有清单 | 可量化 |
| 返工概率 | 高 | 中 | 低 | 最低 |
| 适合场景 | 探索期原型 | 内部工具 | 面向用户的功能 | 生产级交付 |
案例一:购物车「保存商品」功能
场景:电商网站用户希望在浏览时保存感兴趣的商品,方便后续购买。
原始需求:「作为用户,我希望保存商品到购物车。」
翻车现场
开发完成后发现:
- 库存不足时,保存的商品无法加入购物车,但用户不知道
- 商品价格发生变化,购物车还是显示旧价格
- 用户在手机端保存,电脑端看不到
- 用户注销账号后,购物车数据还在
这些问题并非无法预见。如果写故事时把异常情况和业务规则列出来,开发自然会提前考虑。
修复后的故事
角色: 已登录的购物用户
目标: 将商品保存到购物车,方便后续下单
价值: 减少重复搜索商品的时间,提升下单转化率
业务规则:
- 库存为 0 时,商品仍可保存,但标记为「缺货」
- 保存后价格变动,显示「下单时以实际价格为准」提示
- 购物车数据跨设备同步,基于用户账号
- 账号注销后 30 天清除购物车数据
验收标准:
- Given 用户已登录, When 点击「加入购物车」, Then 商品出现在购物车列表中
- Given 商品库存为 0, When 用户保存商品, Then 显示「缺货」标记且不阻塞保存
- Given 保存后商品价格变动, When 用户进入购物车, Then 展示新价格并附变动提示
- Given 用户在手机端保存商品, When 在电脑端登录同一账号, Then 购物车包含该商品
非目标:
- 不涉及购物车结算流程
- 不涉及优惠券和满减逻辑案例二:SaaS 平台的密码重置
场景:用户忘记密码时需要通过邮箱重置。
原始需求:「作为用户,我希望能重置密码。」
翻车现场
功能确实实现了——用户能收到重置邮件,也能设置新密码。但上线后投诉不断:
- 锁定状态的账号仍能发起重置,用户以为修好了,实际登录不了
- 有人一分钟内点了 5 次「发送重置邮件」,邮箱被塞满
- 用户在两个标签页同时点重置链接,先设的密码被后设的覆盖
- 重置邮件里写了「点击链接修改密码」,但没有说明链接多久失效
修复后的故事
角色: 已注册但忘记密码的用户
目标: 通过邮箱验证身份并重置密码
价值: 恢复账号访问,不需要联系客服
业务规则:
- 账号处于锁定时,不允许发起重置
- 重置链接 24 小时内有效,且一次性使用
- 同一邮箱 24 小时内最多发送 3 次重置邮件
- 重置成功后,所有已登录会话失效
验收标准:
- Given 用户账号正常, When 输入邮箱请求重置, Then 收到含重置链接的邮件
- Given 账号已被锁定, When 请求重置密码, Then 提示「账号已锁定,请联系管理员」
- Given 24 小时内已发送 3 次邮件, When 再次请求, Then 提示「请稍后再试」
- Given 用户点击重置链接, When 设置新密码并保存, Then 旧密码失效且所有会话登出
- Given 重置链接已超过 24 小时, When 用户点击, Then 提示「链接已过期,请重新申请」
非目标:
- 不涉及双因素认证(2FA)的设置和验证
- 不涉及管理员强制重置密码的流程案例三:API 分页接口
场景:后端需要提供一个分页查询接口,前端用它展示列表。
原始需求:「作为前端开发,我希望接口支持分页,并返回总数。」
翻车现场
后端返回了 page 和 total,但没有说明:
page从 0 开始还是从 1 开始page_size的默认值和最大值- 请求超出最大页码时返回什么
- 总数
total是精确值还是估算值 - 数据为空时返回空数组还是
null
前端按 page=0 写了一版,后端默认 page=1,联调时花了一天对齐。后来又发现 total 在大数据量时是估算值,产品不接受,又返工了 3 天。
修复后的故事
角色: 前端开发者
目标: 调用分页接口获取列表数据
价值: 实现列表展示和翻页功能,用户体验流畅
业务规则:
- page 从 1 开始,默认值为 1
- page_size 默认 20,最大 100
- page 超出范围时返回空数组和实际总数
- total 为精确计数(非估算)
- 数据为空时返回空数组,不返回 null
- 支持按 created_at 排序,默认降序
验收标准:
- Given 数据库有 150 条记录, When 请求 page=1&page_size=50, Then 返回 50 条且 total=150
- Given 数据库有 150 条记录, When 请求 page=4&page_size=50, Then 返回空数组且 total=150
- Given 未传 page_size, When 发起请求, Then 按 page_size=20 返回数据
- Given 请求 page_size=200, When 发起请求, Then 返回错误码并提示「page_size 不能超过 100」
- Given 数据库无匹配数据, When 发起请求, Then 返回空数组而非 null
非目标:
- 不涉及游标分页(cursor-based pagination)
- 不涉及全文搜索和高亮从故事到验收:完整工作流
用户故事从提出到验收,需要经过多个环节的流转。下面是一个典型的流程:
用户故事 vs 任务 vs 技术 Spike
很多团队分不清用户故事和技术任务的边界。用户故事应该从用户视角描述价值,技术任务是实现过程中的具体步骤,Spike 则是用来调研不确定方案的技术探索。
| 维度 | 用户故事 | 技术任务 | 技术 Spike |
|---|---|---|---|
| 视角 | 用户 / 业务方 | 开发者 | 开发者 |
| 目的 | 交付用户价值 | 完成实现步骤 | 降低技术不确定性 |
| 估算者 | 团队共同 | 执行开发者 | 执行开发者 |
| 完成标准 | 验收标准 + 用户确认 | 代码合并 + 测试通过 | 输出调研报告 |
| 举例 | 作为用户,我能导出报表 | 实现导出接口的分页逻辑 | 调研导出方案的可行性和性能 |
用户故事可以拆出技术任务,但技术任务不应该反过来被当成用户故事。如果你发现一个故事写成了「作为开发者,我希望重构数据库」,那它更适合作为技术任务或技术 Spike 来管理。
编写验收标准的常见错误
验收标准写得好不好,直接决定了开发能不能一次做对。以下是我在实际项目中见过的问题:
| 常见错误 | 为什么有问题 | 正确做法 | 示例 |
|---|---|---|---|
| 写了实现细节 | 限制了开发方案选择 | 描述可观察行为 | 坏:「调用 /api/publish 接口」;好:「点击发布后,文章状态变为已发布」 |
| 只覆盖正常路径 | 异常场景漏测 | 补充异常和边界情况 | 补充:「当网络断开时,提示用户重试」 |
| 验收条目过多 | 暗示故事太大 | 拆分为多个故事 | 超过 5 条考虑拆分 |
| 使用被动语态 | 不清晰谁做了什么 | 用主动语态 | 坏:「数据会被保存」;好:「系统将数据保存到数据库」 |
| 模糊的量化词 | 无法测试 | 用具体数值 | 坏:「响应要快」;好:「P95 响应时间低于 200ms」 |
| 缺少前置条件 | 测试无法复现 | Given 写明上下文 | 「Given 用户已登录且购物车有 3 件商品」 |
| 把业务规则混入验收 | 边界不清晰 | 业务规则和验收标准分开写 | 规则:「密码至少 8 位」;验收:具体的测试场景 |
好 / 坏写法对比
对比一:验收标准是否描述行为
# ❌ 坏:写成了实现细节
验收标准:
- 调用 POST /api/cart/items 接口添加商品
# ✅ 好:描述用户可观察的行为
验收标准:
- Given 用户在商品详情页, When 点击「加入购物车」, Then 购物车数量 +1 且弹出「已添加」提示实现细节应该交给开发决定。验收标准只关心「用户能感知到什么变化」。
对比二:是否考虑边界情况
# ❌ 坏:只覆盖正常路径
验收标准:
- Given 用户已登录, When 提交订单, Then 订单创建成功
# ✅ 好:覆盖正常和异常路径
验收标准:
- Given 库存充足, When 提交订单, Then 订单创建成功,库存扣减
- Given 库存不足, When 提交订单, Then 提示「库存不足」,不创建订单
- Given 用户有未支付订单, When 提交新订单, Then 提示「请先完成未支付订单」每个关键操作都应该问一句:「如果前置条件不满足,会怎样?」把答案写进验收标准。
对比三:是否有具体数值
# ❌ 坏:模糊描述,无法测试
验收标准:
- 页面加载要快
- 支持大量数据导出
# ✅ 好:有具体数值,可直接验证
验收标准:
- Given 网络条件正常, When 访问列表页, Then 首屏加载时间低于 1.5 秒
- Given 数据量为 10000 条, When 触发导出, Then 在 30 秒内完成且不超时所有性能要求、数量限制、时间约束,都应该有具体数值。模糊的描述最终会变成争论的源头。
对比四:是否从用户视角书写
# ❌ 坏:从系统视角描述
验收标准:
- 数据库记录状态更新为 published
- 缓存失效并重新加载
# ✅ 好:从用户视角描述
验收标准:
- Given 编辑点击「发布」按钮, When 操作成功, Then 文章在列表页显示「已发布」状态
- Given 文章已发布, When 用户访问文章页, Then 看到最新内容而非缓存版本用户不关心数据库和缓存,他们关心的是界面上看到什么、能做什么。验收标准应该用用户能理解的语言来写。
对比五:业务规则和验收标准是否分离
# ❌ 坏:规则混在验收里
验收标准:
- 密码长度至少 8 位
- 必须包含大小写字母和数字
- 不能和最近 3 次密码相同
- 用户设置新密码时成功保存
# ✅ 好:规则和验收分开
业务规则:
- 密码长度 8-32 位
- 必须包含大写字母、小写字母和数字
- 不能和最近 3 次密码相同
验收标准:
- Given 用户输入符合规则的密码, When 点击保存, Then 密码更新成功并提示「修改成功」
- Given 用户输入 6 位密码, When 点击保存, Then 提示「密码长度至少 8 位」
- Given 用户输入与前 3 次相同的密码, When 点击保存, Then 提示「不能使用最近 3 次用过的密码」业务规则是「系统的一般性约束」,验收标准是「具体场景下的可验证行为」。分开写,开发和测试各自都能快速找到自己需要的信息。
用 INVEST 原则检验故事质量
写完用户故事后,我会用 INVEST 原则过一遍。这六个维度能帮你快速发现故事的结构性问题:
| 原则 | 含义 | 检验问题 | 常见问题 |
|---|---|---|---|
| Independent | 独立的 | 这个故事能否独立交付? | 和其他故事强耦合,需要一起上线 |
| Negotiable | 可协商的 | 描述是否留有实现空间? | 写得太像合同,限制了技术方案 |
| Valuable | 有价值的 | 用户或业务能从中获益吗? | 纯技术优化,没有可感知的价值 |
| Estimable | 可估算的 | 团队能估算工作量吗? | 需求太模糊,没人知道有多大 |
| Small | 小的 | 能在一个 Sprint 内完成吗? | 一个故事覆盖了整个模块 |
| Testable | 可测试的 | 能写出验收标准吗? | 价值太虚,无法定义「完成」 |
如果一个故事在多个维度上不合格,最有效的做法通常是拆分。比如「作为用户,我希望管理我的账户」就太大了,可以拆成「修改头像」「更换邮箱」「修改密码」「注销账号」等多个独立故事。
交付前检查清单
每次评审用户故事之前,我会对照以下清单逐项检查。不一定每条都适用,但它能帮你避开大部分常见坑点。
一、故事结构
- 有明确的角色定义,不是泛泛的「用户」
- 有清晰的目标描述,说明要做什么
- 有价值说明,解释为什么要做
- 有业务规则,列出边界条件和特殊情况
- 有验收标准,用 Given-When-Then 格式
- 有非目标,说明本故事不覆盖什么
二、INVEST 检验
- 故事相对独立,不依赖其他故事才能交付
- 描述留有协商空间,不限制具体实现方案
- 对用户或业务有明确价值
- 团队能估算工作量
- 足够小,能在一个 Sprint 内完成
- 可测试,能写出明确的验收标准
三、验收标准质量
- 覆盖正常路径和至少 2 条异常路径
- 条目控制在 3-5 条,过多则考虑拆分故事
- 描述可观察行为,不包含实现细节
- 使用主动语态,主语明确
- 有具体数值和明确的状态描述
- 每条标准可独立测试验证
- 边界情况已考虑(空值、并发、超时、权限)
四、可交付性
- 开发人员读完知道怎么实现
- 测试人员读完能直接写测试用例
- 产品人员读完知道怎么验收
- 新加入的团队成员读完能快速理解上下文
模板文件下载
如果你需要在团队内推广这个模板,可以把下面的结构复制到你的项目管理工具中:
# 用户故事模板
角色: 作为 [某类用户]
目标: 我希望 [完成某件事]
价值: 从而 [获得某个结果]
业务规则:
- [限制条件 1]
- [限制条件 2]
- [边界情况处理]
验收标准:
- Given [前置条件], When [用户动作], Then [预期结果]
- Given [前置条件], When [用户动作], Then [预期结果]
- Given [前置条件], When [用户动作], Then [预期结果]
非目标:
- [本故事不覆盖的内容 1]
- [本故事不覆盖的内容 2]
补充说明:
- [技术约束]
- [依赖关系]
- [设计稿链接]写在最后
用户故事模板不是为了增加文档负担。它的真正价值在于:当你把角色、价值、业务规则和验收标准写在一起时,团队对「做完」这件事就有了共识。
好的用户故事不需要开会反复确认。开发读完知道怎么实现,测试读完能写用例,产品读完知道怎么验收。如果做到了这一点,模板就发挥了作用。
如果你发现团队总是因为「理解不一致」而返工,不妨从一个故事开始,把验收标准补齐,看看效果。
参考资料
- INVEST - Agile Alliance - INVEST 原则的官方定义和解读
- User Stories With Examples and a Template - Atlassian - 用户故事模板和最佳实践
- Acceptance Criteria: Purposes, Formats and Best Practices - AltexSoft - 验收标准的格式对比和编写指南
- User Stories - Mike Cohn / Mountain Goat Software - 用户故事权威指南,包含大量真实案例
- What is Acceptance Criteria? - Atlassian - 验收标准定义、类型和常见错误
- Best Practices in Writing User Stories - Scrum.org - 关于验收标准数量和最佳实践的社区讨论
- How to Write a Good User Story - Miro - 用户故事编写指南
- 用户故事验收测试驱动开发实践 - 阿里云开发者社区 - ATDD 视角下的验收标准实践