第9部分。功能验证:从规范到事实
功能验证不是编码后的形式主义。这是一种独立的工作模式,文本规范在此转化为可验证的事实。规范解释了意图,但本身并不能证明意图已被实现。事实是一种断言,机器或人可以在不重新解释冗长散文的情况下进行验证。
简短公式:
规范指引方向。
事实决定能否合并。
flowchart LR
A["文本规范<br/>意图和边界"] --> B["事实集合<br/>validation.md"]
B --> C["验证<br/>命令、测试、手动场景"]
C --> D{"事实已确认?"}
D -- "是" --> E["可以合并分支"]
D -- "否" --> F["修复代码<br/>或澄清规范"]
F --> B对于与编写代码的代理一起工作,这一点至关重要。模型在不同会话或版本中可能对同一文本规范有不同的解读。测试、退出代码、HTTP状态、数据库不变量或显式契约对解释的依赖较小。因此,validation.md 不应仅仅是核对清单,而应是一组用于合并准入的事实。
什么是事实
事实是一种可执行或可明确验证的断言:
npm run typecheck以代码 0 退出;GET /返回 200;- 响应包含
<h1>AgentClinic</h1>; - 过期的 JWT 返回 401;
- 不带消息的反馈提交无法通过验证;
- 迁移可以运行两次而不会意外改变模式;
- 对于每个初始代理,详情页面返回相关的疾病列表。
糟糕的验证项:
确保页面看起来不错。
更好:
在 375px 宽度下,标题、主要内容区域和页脚不重叠。
主标题保持可见,无需水平滚动。
事实集合的层次
使用四个层次。
示例 验证具体的输入输出对:
curl -s http://localhost:3000 | rg "<<h1>AgentClinic</h1>"
不变量 描述应始终为真的内容:
每条反馈记录都有非空消息。
属性 验证一类情况:
任何超出 1..5 范围的评分都会被拒绝。
契约 固定前置条件、动作和后置条件:
如果会话未认证,
当请求 GET /dashboard 时
响应重定向到 /login。
并非每个功能都需要全部四个层次。但每个功能至少应有一些机器可验证的事实。
根据风险类型确定所需的事实层次
并非所有功能都相同。简单的 UI 横幅和数据库迁移需要不同的验证密度。最低足够的事实层次可以根据矩阵选择:
| 功能/风险类型 | 示例 | 不变量 | 属性 | 契约 | 手动事实 |
|---|---|---|---|---|---|
| 视觉/UI 更改 | 必需 | 必需 | |||
| CRUD 路由 | 必需 | 必需 | |||
| 表单验证/输入 | 必需 | 必需 | 必需 | ||
| 数据迁移 | 必需 | 必需 | |||
| 授权/访问 | 必需 | 必需 | |||
| 支付/副作用 | 必需 | 必需 | |||
| 第三方 API 集成 | 必需 | 必需 | 必需 | ||
| 后台任务/调度器 | 必需 | 必需 |
如何阅读表格:
- 示例 — 具体的输入输出对(一个命令、一个 curl、一个通过的测试)。
- 不变量 — 动作后应始终为真的内容。对于迁移,这是"再次运行不会改变模式"。对于后台任务,这是"成功运行后计数器不会减少"。
- 属性 — 验证一类情况。对于验证,这是"任何超出 1..5 的评分都会被拒绝"。对于授权,这是"任何无会话请求返回 401"。
- 契约 — 正式的"在条件 X 下,动作 Y 导致 Z"关联。
- 手动事实 — 人用眼或手检查的内容。对 UI 是必需的,因为自动验证视觉层次通常不充分。
矩阵的目的不是将其变成强制核对清单,而是帮助发现遗漏。如果"数据迁移"类型的功能只通过示例而没有不变量,这是重写 validation.md 的信号。
validation.md 的结构
示例:
# 验证 — 反馈表单
## 事实集合
### F1 — TypeScript 编译通过
- 命令:`npm run typecheck`
- 预期:退出代码 0
- 负责人:自动验证
- 状态:草稿
### F2 — 测试通过
- 命令:`npm test`
- 预期:退出代码 0
- 负责人:自动验证
- 状态:草稿
### F3 — 空消息被拒绝
- 命令:`npm test -- feedback`
- 预期:POST /feedback 带空消息返回 400
- 负责人:自动验证
- 状态:草稿
### F4 — 正确反馈被保存
- 命令:`npm test -- feedback`
- 预期:正确的 POST /feedback 添加一行并重定向到 /feedback
- 负责人:自动验证
- 状态:草稿
### F5 — 页面在移动屏幕上保持可用
- 验证:在 375px 宽度下打开 /feedback
- 预期:表单字段和提交按钮无需水平滚动即可见
- 负责人:手动验证
- 状态:草稿
## 就绪标准
- 所有自动事实通过。
- 手动事实已验证。
- 无法实现的事实从边界中删除或退回草稿并附解释。
- 路线图和变更日志在合并前更新。
生命周期有助于区分意图和证据:
草稿:事实已提出,但尚未确定;必需:事实被接受为功能的必需项;已实现:有测试、命令或手动确认;延期:事实被有意识地推迟到未来阶段。
从差异开始
git status --short
git diff --stat main...HEAD
要求 Qwen Code 不仅验证是否符合规范,还要验证事实集合的状态:
/clear
将此分支与 @specs/2026-05-01-hello-hono/validation.md 进行比较。
显示:
1. 已实现并通过的事实;
2. 缺少的事实;
3. 含糊不清需要重写的事实;
4. 实现中未在 requirements.md 中描述的决策;
5. 规范中的过时断言。
暂时不要修改文件。
如果 Qwen Code 无法确定验证项的通过/未通过状态,那就不是事实,而是散文中的愿望。重写它。
人工参与的验证
代理可以发现机械性不一致,但人必须评估产品和架构方面:
- 页面是否符合使命;
- 任务边界是否过度膨胀;
- 是否存在未说明的依赖;
- 新开发者是否理解文件为何如此组织;
- 每个有风险的行为是否有事实;
- 重要决策是否只留在聊天中。
典型示例:首次实现后,通常会清楚页面结构不应是一个单体组件,而应是一组 Header、Main、Footer。这不仅仅是"修复代码":需要更新 plan.md 和 validation.md 中的事实,以便未来会话不会回到旧解释。
同时修复代码、规范和事实的请求
实现需要更清晰的页面结构。
更新 @specs/2026-05-01-hello-hono/plan.md 并要求:
- Layout 组件;
- Header 组件;
- Main 组件;
- Footer 组件;
- 从 Layout 连接 static/style.css。
更新 @specs/2026-05-01-hello-hono/validation.md 的事实,验证:
- 响应包含 header/main/footer 地标;
- /static/style.css 由服务器提供;
- npm run typecheck 以代码 0 退出。
然后更新实现以符合新计划和事实。
保持在此功能边界内的更改。
这样,您同时防止了规范偏离和事实偏离。
自动验证
最低要求:
npm run typecheck
如果已有测试:
npm test
如果有开发服务器:
npm run dev
curl -s http://localhost:3000
curl -s http://localhost:3000/static/style.css
在 validation.md 中记录确切的命令和预期结果。如果可以写命令和预期退出代码,就不要写"检查是否正常工作"。
手动事实
如果具体,手动事实不比自动事实弱。弱手动验证:
检查界面。
正常的手动事实:
在 375px 宽度下,/feedback 页面显示姓名字段、消息字段、
提交按钮和最近三条记录,无需水平滚动和重叠。
手动事实对色调、视觉层次、基本无障碍检查和边界膨胀控制很有用。但如果手动事实在每个功能中重复出现,考虑如何通过 Playwright 或单元和集成测试将其自动化。
CI 中的验证
事实应达到合并准入。对于小型项目,本地命令足够。对于团队,最好添加 CI:
在以下情况满足前,不能接受合并请求:
- npm run typecheck 未通过;
- npm test 未通过;
- 必需的路由测试未通过;
- validation.md 中的事实集合未更新。
这是对"规范被解释"批评的实际回应。文本规范指引代理,但合并由事实决定。
合并的证据包
当功能接近合并时,审查者应有一个紧凑的工件,通过它可以理解:具体实现了什么、哪些事实通过了、哪些被推迟了。这个工件方便称为"证据包"(英文来源中 — evidence bundle)。
这不是单独的新文件 — 这是合并请求描述的格式。好的证据包包括:
- 规范文件夹的链接(
specs/YYYY-MM-DD-feature/); validation.md中的事实列表及状态:已确认、失败、已推迟;- 命令执行痕迹:命令名称、退出代码、最后一行输出或简短摘录;
- 手动验证结果:人具体检查了什么、在什么屏幕上、看到了什么;
- 实现过程中做出的、原始规范中未包含的决策列表;
- 反映这些决策的提交链接。
这种合并请求描述模板在附录 C 中。主要思想:审查者不应重新运行一切来确认就绪。他应能根据证据包理解作者具体做了什么和验证了什么,并在有怀疑时精确地重新运行所需命令。
如果证据包中出现"事实在失败后更改"的项,这不是隐藏它的理由。相反:明确解释的事实更改是 SDD 的正常部分,隐藏的更改是反模式(见第20部分)。
更新路线图
通过事实集合后:
## 阶段 1:Hello Hono(已完成)
- [x] 安装 Hono 和 tsx。
- [x] 创建 GET / 路由。
- [x] 返回最小服务器渲染 HTML。
- [x] 添加类型检查脚本。
提交:
git add specs/roadmap.md specs/2026-05-01-hello-hono
git commit -m "Validate Hello Hono feature"
合并:
git checkout main
git merge phase-1-hello-hono
git branch -d phase-1-hello-hono
实践
- 运行所有自动事实。
- 要求 Qwen Code 将代码与
validation.md进行比较。 - 将含糊的验证项重写为事实。
- 如果代码和规范不一致,修复代码或规范。
- 标记事实状态。
- 在路线图中标记阶段。
- 执行合并。
检查问题
- 为什么文本规范不应是合并的唯一准入条件?
- 事实与验证中的愿望有何不同?
- 何时可以将手动验证视为事实?
- 如果测试通过但实现不符合
requirements.md,该怎么办? - 为什么"规范指引方向,事实决定能否合并"比"把规范写更好"更好?