学习指南: 第7部分:功能规格说明

模块「第7部分:功能规格说明」中第 2 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

主题:第7部分. 功能规格说明

难度级别:中级

预计学习时间:4-6小时(理论2小时,实践3-4小时)

前置条件: 基础理解Git和分支操作

熟悉Markdown和文档结构

理解项目路线图概念(本课程第6部分)

TypeScript和Web框架(Hono、Express或类似框架)的基础知识

命令行和npm的使用经验

学习目标: 创建由三个文件组成的完整功能规格说明(requirements.md、plan.md、validation.md),防止在与AI代理协作时出现任务"范围蔓延"

在validation.md中制定可验证的断言,将模糊的期望替换为具体的命令和预期结果

应用否定性需求("不应改变的内容")来保护现有功能免受代理的不必要修改

在代码实现之前执行澄清步骤(/clarify)以识别歧义

使用EARS和Given/When/Then格式消除需求和验证场景中的歧义

概述:功能规格说明是路线图与编写代码之间的关键阶段,防止将一个小阶段变成半个产品。在与AI代理(Qwen Code)协作的语境中,规格说明充当"合同",限制代理的自由度并提供具体的验收标准。本学习指南围绕真实项目"Hello Hono"构建:安装Hono框架、启动TypeScript服务器、提供最小HTML页面以及类型检查。关键原则:文本规格说明指导代理,但决策的合并权仍归人类,基于可验证的事实。

核心概念: 功能边界(scope boundaries):明确定义任务包含和不包含的内容。包括两个方面:"边界之外"(不添加什么)和"不应改变的内容"(已有功能应继续正常运行)。没有这些限制,代理可能会"改进"未损坏的内容,或添加阶段未计划的功能。

否定性需求(negative requirements):明确禁止更改:哪些URL保持不变、哪些字段不更名、哪些依赖不"顺便"更新、哪些配置文件不改动。这是针对测试无法捕获的回归的防护,因为针对旧行为的测试尚不存在。

可验证事实(verifiable facts):validation.md中具有具体验证命令和预期结果的标准。与"一切应正常工作"等模糊表述相对。每个事实包括:命令、期望、负责人(手动/自动验证)、状态。

澄清步骤(/clarify):意图与计划之间的独立阶段,代理就存在替代解释的地方提出针对性问题。需要此步骤的迹象:代理自身提及双重理解;外部已协商的部分需要互斥决策;决策依赖外部服务;您的回答相互矛盾。6个以上问题——信号表明阶段过大。

EARS格式:Лёгкий Approach to Requirements Syntax——功能性需求的微格式:"WHEN <条件/事件> THE SYSTEM SHALL <预期行为>"。消除关于谁在何种条件下应做什么的歧义。

Given/When/Then格式:validation.md中验收场景的结构:"Given <前置条件> When <动作> Then <预期结果>"。将散文转化为具体可测试的场景。

specs/结构:命名约定:specs/YYYY-MM-DD-feature-name/ 包含三个文件。日期使工作顺序清晰,kebab-case将文件夹与Git分支关联。三个文件:requirements.md(意图和边界)、plan.md(实现顺序)、validation.md(如何验收结果)。

规格说明就绪度(readiness checklist):七个必须在转向代码前完成的检查项。关键项:"编写规格说明的同一个人能够说出'此阶段已完成'的标准"。如果人无法回答——代理更无法回答。

实践练习: 标题:练习1:将模糊需求转换为EARS格式

问题:给定功能"Hello Hono"的三条非正式需求:

  1. "服务器应快速运行"
  2. "用户看到主页"
  3. "TypeScript不应报错"

将每条转换为EARS或Given/When/Then格式,消除所有歧义。确定需要向客户提出的额外澄清问题。

解答:步骤1:分析原始表述的问题。

  • "快速"——不可测量;需要具体阈值(响应时间<<100毫秒?)
  • "用户"——未定义;使用什么HTTP客户端?
  • "不报错"——主观;需要具体命令和预期退出码

步骤2:转换为EARS/Given-When-Then。

  1. EARS:"WHEN 执行GET /请求时,THE SYSTEM SHALL 在本地端口3000运行时于100毫秒内返回响应。"

澄清问题:"使用什么测量工具?(curl -w "%{time_total}"?k6?内置日志?)"

  1. Given/When/Then:"Given 服务器在端口3000运行。When 执行curl -s http://localhost:3000。Then HTTP状态码等于200,Content-Type包含text/html,正文包含<h1>AgentClinic</h1>。"

澄清问题:"页面是否应仅包含h1还是其他元素?标语的确切文字是什么?"

  1. EARS:"WHEN 执行命令npm run typecheck时,THE SYSTEM SHALL 以退出码0完成,无错误或警告。"

澄清问题:"是否允许警告(warnings)还是要求严格的零?TypeScript版本是什么?"

步骤3:通用原则:每个指标必须有测量工具,每个条件必须有复现方式,每个结果必须有验收阈值。

难度:初级

标题:练习2:为功能"API健康检查"创建完整规格说明

问题:您的路线图包含阶段:"添加GET /health端点用于监控服务器状态"。为此功能创建由三个文件组成的完整规格说明。条件:

  • 项目已使用Hono + TypeScript
  • 已有返回HTML的GET /路由
  • 不能在tech-stack.md之外添加新依赖
  • 需要通过curl验证可用性
  • 功能应刻意保持小型

编写requirements.md、plan.md和validation.md。然后按就绪度检查清单进行自我检查。

解答:步骤1:创建requirements.md

# 需求 — API健康检查

## 边界
- 添加返回服务器状态JSON的GET /health路由。
- JSON包含status字段(值为"ok")和timestamp字段(当前时间的ISO字符串)。

## 边界之外
- 不连接真实外部服务(数据库、Redis等)。
- 端点无需认证。
- 此路由不使用JSON以外的响应格式。

## 不应改变的内容
- GET /路由继续返回包含<h1>AgentClinic</h1>的HTML。
- TypeScript严格模式保持启用。
- package.json不获得新依赖。
- 服务器端口保持3000。

## 决策
- 使用内置Date获取时间戳,不使用外部库。
- 返回200状态码表示"ok",无复杂状态逻辑。

## 上下文
阶段刻意保持最小。健康检查用于未来监控,非诊断。

步骤2:创建plan.md

# 计划 — API健康检查

## 组1 — 路由
1. 在现有Hono应用中添加GET /health处理器。
2. 返回对象{ status: "ok", timestamp: <ISO string> }。
3. 设置Content-Type: application/json头。

## 组2 — 类型
4. 在src/types.ts中或内联定义HealthResponse接口。
5. 确认TypeScript编译无any。

## 组3 — 验证
6. 运行npm run typecheck。
7. 运行npm run dev。
8. 执行curl -s http://localhost:3000/health | jq .
9. 确认curl http://localhost:3000/仍返回HTML。

步骤3:创建validation.md

# 验证 — API健康检查

## 事实集

### F1 — TypeScript编译通过
- 命令:npm run typecheck
- 期望:退出码0
- 负责人:自动验证

### F2 — health端点可用
- 命令:curl -s http://localhost:3000/health
- 期望:HTTP 200,Content-Type: application/json,正文可解析为JSON
- 负责人:手动或自动验证

### F3 — 响应格式正确
- 命令:curl -s http://localhost:3000/health | jq -e '.status == "ok" and .timestamp != null'
- 期望:jq返回true(退出码0)
- 负责人:手动验证

### F4 — 现有路由未损坏
- 命令:curl -s http://localhost:3000/ | grep -o '<h1>AgentClinic</h1>'
- 期望:找到子串,grep退出码为0
- 负责人:自动验证

### F5 — 边界未蔓延
- 检查:git diff --name-only
- 期望:仅修改src/*,package.json无新依赖
- 负责人:手动验证

步骤4:按检查清单自我检查

  • [x] 意图无需聊天历史即可理解:"添加最小健康检查"
  • [x] 边界具体:两个字段的JSON,无外部服务
  • [x] "不应改变的内容"已填写:保护/路由、端口、依赖
  • [x] 所有决策已记录:使用内置Date,200状态码
  • [x] 计划按组划分,有可验证结果
  • [x] 每个事实有命令:typecheck、curl、jq、grep
  • [x] 我能说出完成标准:"F1-F5全部通过"

结果:规格说明已准备好进行实现。

难度:中级

标题:练习3:识别歧义(/clarify步骤)

问题:您收到代理为功能"Hello Hono"提供的以下规格说明草稿。找出所有会导致不同代理或人员产生不同实现的歧义:

# 需求 — Hello Hono(草稿)

## 边界
- 安装Hono并配置服务器。
- 使主页美观。

## 边界之外
- 不添加多余内容。

## 不应改变的内容
- 一切保持原样。

## 决策
- 使用良好实践。

## 上下文
- 快速且高质量。

为/clarify步骤制定具体问题。确定哪些歧义是关键的(阻塞实现),哪些是非关键的。

解答:步骤1:按类别系统分析歧义。

类别"安装和配置":

  • "Hono"——具体哪个版本?使用^/~还是固定版本?
  • "配置服务器"——哪个端口?哪个运行器(tsx、ts-node、node --loader)?
  • "安装"——还有哪些依赖?仅hono还是也包括tsx?开发/生产?

类别"主页":

  • "美观"——无法验证;具体哪些元素?(h1、标语、样式?)
  • "主页"——哪个URL?GET /还是GET /home?
  • "页面"——HTML、JSON还是Hono JSX?服务端还是客户端渲染?

类别"边界之外":

  • "多余"——未定义;什么是"需要"与"多余"?
  • 无具体禁止列表:数据库?认证?测试?CI?

类别"不应改变的内容":

  • "一切保持原样"——第一阶段没有"原样",但即使有——具体是什么?
  • 未保护tsconfig.json、package.json、.gitignore、现有脚本

类别"决策":

  • "良好实践"——谁的?具体哪些?严格TypeScript?linter?
  • 未记录关于保持/更改严格模式的决策

类别"上下文":

  • "快速"——什么时间范围?还是刻意小阶段?
  • "高质量"——按什么标准?

步骤2:为/clarify确定问题优先级。

关键问题(阻塞实现——6个以上问题,需要单独步骤):

  1. Hono的确切版本及安装方式(使用^/~还是固定版本)?
  2. 哪个端口和哪个开发运行器(tsx、ts-node)?
  3. GET /返回HTML是通过Hono JSX还是纯字符串?响应中具体哪些元素?
  4. 边界之外具体是什么:数据库?认证?测试框架?CI?部署?
  5. 安装时哪些文件/设置不应更改(tsconfig严格模式、.gitignore)?

非关键问题(可酌情处理,但最好澄清):

  1. 是否需要特定标语或任何符合使命的即可?
  2. 阶段应"刻意小型"还是期望完整页面?

步骤3:关于是否需要单独澄清的决定。 由于发现7个以上关键问题,这是信号表明要么阶段过大,要么章程(mission.md/tech-stack.md)留下太多开放决策。建议:暂停,更新tech-stack.md(指定版本、运行器、端口),然后返回规格说明。

步骤4:向代理提出请求。

在编写requirements.md、plan.md和validation.md之前,列出所有你仍有替代选择的地方。对每个地方向我提出恰好一个问题:选择什么以及为什么。在我选择之前不要提供你的答案。不要猜测。

预期结果:代理应识别出相同的7个以上歧义。如果代理发现少于3个——这本身就是问题:代理对歧义的批判性不足。

难度:高级

标题:练习4:验证规格说明就绪度

问题:给定功能"添加按标题搜索文章"的规格说明。按就绪度检查清单进行审计,找出违规并提出修正建议。

# 需求 — 按标题搜索

## 边界
- 用户可以搜索文章。
- 结果具有相关性。

## 边界之外
- 不做全文搜索。

## 不应改变的内容
- (空)

## 决策
- 将会很快。

## 上下文
- 对用户很重要的功能。

plan.md包含15个步骤,包括"配置Elasticsearch"、"添加Redis缓存"、"制作管理索引的后台面板"。

validation.md包含:"搜索能用"、"结果看起来不错"、"用户满意"。

解答:步骤1:按就绪度检查清单检查并发现违规。

[ ] 意图和受众无需聊天历史即可理解。 违规:"用户可以搜索文章"——什么用户?注册用户还是任何人?什么搜索界面? 修正:"WHEN 匿名用户在主页搜索框输入字符串时,THE SYSTEM SHALL 返回标题包含该字符串的文章列表(不区分大小写,最多20个结果)。"

[ ] "包含内容"和"边界之外"表述具体。 违规:"结果具有相关性"——不可测量;"不做全文搜索"——但plan.md中有Elasticsearch(这是全文搜索引擎!)。 修正:边界——"仅通过现有articles表中的title字段搜索,使用SQL LIKE加索引。最多20个结果,按发布日期排序。"边界之外——"无独立搜索引擎(Elasticsearch、Meilisearch)。不搜索文章内容。无自动补全。无按日期/作者筛选。"

[ ] "不应改变的内容"块已填写。 违规:为空,尽管存在包含id、title、content、published_at字段的articles表。 修正:"现有articles表不添加新列。title字段不更名。GET /articles/:id的API响应格式不变。"

[ ] 所有已接受决策已记录;开放决策已标记。 违规:"将会很快"——不是决策,而是愿望。无实现决策,但plan.md中有复杂技术。 修正:"决策:使用title上的B-tree索引的SQL LIKE。限制:最多1000篇文章,之后迁移到专用搜索。预期响应时间<<200毫秒(95百分位)。"

[ ] 计划按组划分,有可验证结果。 违规:15个步骤,无分组,不同阶段步骤混杂(Elasticsearch、Redis、后台面板——这是3个独立功能)。 修正:拆分为阶段。当前阶段——"title基础搜索":组1(SQL查询)、组2(API端点)、组3(性能测试)。Elasticsearch和Redis——放入路线图的未来阶段。

[ ] 在validation.md中,每个事实有命令或手动场景。 违规:"搜索能用"、"看起来不错"、"用户满意"——无一可用命令验证。 修正:

  • F1:'curl "http://localhost:3000/api/search?q=test" | jq -e ".results | length > 0"'——期望:退出码0
  • F2:'k6 run search-perf.js'——期望:p95 < 200毫秒
  • F3:'curl使用不存在的查询'——期望:空results数组,状态码200

[ ] 一个人能说出"阶段完成"的标准。 违规:规格说明作者无法说出"用户满意"何时达成。 修正:"当F1-F3自动通过,且手动验证确认搜索'test'找到标题为'Testing Guide'的文章时,阶段完成。"

步骤2:总体裁决。 规格说明未准备好进行实现。需要:缩小阶段,将Elasticsearch/Redis/后台面板移至未来阶段,用EARS重写需求,填写否定性需求,用具体命令重写validation.md。

难度:中级

案例研究: 标题:案例:规格说明如何拯救初创公司AgentClinic免受"永恒测试版功能"

场景:2人开发团队正在Hono + TypeScript上构建医疗助手MVP。路线图包含"Hello Hono"阶段——带一个页面的基础服务器。开发者将实现委托给AI代理(Qwen Code),无预先规格说明,仅说"在Hono上做个漂亮主页"。

挑战:代理无具体边界,在一个"阶段"中实现了:安装Hono、配置Tailwind CSS、创建5个路由(/、/about、/contact、/features、/pricing)、集成反馈表单、准备Vercel部署、甚至GitHub OAuth认证草稿。结果:47个修改文件,12个新依赖,由于TypeScript版本冲突导致现有typecheck脚本损坏。计划2小时的阶段延长到3周修复。团队失去焦点,在真实产品开始前产生技术债务。

解决方案:失败后团队强制实施三文件规格说明。为修复情况:1)创建分支phase-1-hello-hono-v2;2)编写requirements.md,有严格边界("一个GET /路由,带h1和标语的HTML,无数据库、无认证、无部署");3)添加"不应改变的内容"块,保护tsconfig.json和现有脚本;4)在validation.md记录具体命令:curl检查HTML、npm run typecheck检查TypeScript、git diff控制边界;5)执行/clarify步骤,代理发现4个歧义(Hono版本、tsx vs node运行器、HTML响应格式、内联样式是否允许)——全部在编码前解决。

结果:第二次尝试阶段耗时3小时而非3周。修改4个文件,2个新依赖(hono和tsx,如tech-stack.md所述)。typecheck脚本继续工作。团队能直接进入第2阶段(连接数据库),拥有稳定基础。关键指标变为"从规格说明到阶段验收的时间"——从504小时缩短到3小时(168倍改进)。

经验教训: 无规格说明不是"节省时间",而是风险指数增长的彩票;代理通过其知道的所有"最佳实践"的棱镜解释"小功能"

"不应改变的内容"块对防御"改进"至关重要:代理"修复"了tsconfig,关闭strict模式以"解决"版本冲突——无明确禁止则无法阻止

4个问题的/clarify步骤是最佳范围(0-2——批判性不足,6+——阶段过大);4个问题允许一轮解决所有歧义

validation.md作为"可验证事实集"提供客观的完成标准:当curl返回<h1>AgentClinic</h1>且typecheck通过——阶段结束,无可争议

相关概念: 功能边界(Scope Boundaries)

否定性需求(Negative Requirements)

可验证事实(Verifiable Facts)

澄清步骤(/clarify)

规格说明就绪度(Readiness Checklist)

标题:案例:企业迁移中遗漏/clarify导致的失败

场景:大公司(200+开发者)将内部工具从Express迁移到Hono。"Hello Hono"阶段规模扩大:安装Hono,确保与现有中间件兼容。高级开发者编写规格说明无澄清步骤,依赖10年经验。

挑战:规格说明中有隐含假设:"与中间件兼容"意味着"所有47个现有中间件无需更改即可工作"。代理理解不同:"将关键中间件重写为Hono兼容版本"。结果:代理重写了12个中间件,包括15个其他服务使用的自定义日志记录器。日志记录器损坏仅在生产环境发现,当时指标停止流入Datadog。回滚耗时6小时,事件影响2000+用户。

解决方案:事件后团队对所有涉及共享代码的阶段强制实施/clarify。为修复:1)回退到Express;2)新规格说明有明确区分:"被>1个服务使用的中间件不重写,而用适配器包装";3)任何迁移前的单独"中间件审计"阶段;4)validation.md包含验证:"所有依赖服务的/health的curl返回200,且5分钟内Datadog收到日志"。

结果:第二次尝试无事件通过。迁移时间从计划的2天增加到5天(由于额外的审计阶段),但零停机时间和零事件。管理层认可:"慢但可预测"优于"快但出事故"。/clarify流程成为规格说明CI中的强制环节。

经验教训: 经验不能替代/clarify:专家知道"兼容"是多义的,但忘记明确记录;代理没有"共享代码=不动"的上下文

validation.md应包含集成验证,而非仅单元验证:"typecheck通过"不保证Datadog收到日志

"6+个/clarify问题"的标志对专家也有效:如果专家不能快速回答代理的问题——说明其自身理解存在空白

否定性需求不仅针对代码,也针对流程:"被>1服务使用的中间件"——这是代理无法从代码推导的元约束

相关概念: 澄清步骤(/clarify)

否定性需求(Negative Requirements)

可验证事实(Verifiable Facts)

规格说明就绪度(Readiness Checklist)

学习建议: 在真实项目前用"玩具"功能练习:取熟悉的工具(例如为个人博客添加深色主题),编写完整的三文件规格说明。对照就绪度检查清单比较——会发现5-10个违规,即使您认为"一切清楚"

使用"他人视角"技术:编写规格说明后,将其交给同事或甚至用提示"找出歧义"的神经网络。新鲜视角会发现作者认为"显而易见"的内容

视觉学习者:创建物理或数字看板,三列(包含/不包含/不改变)。在看板间移动便签——这比文本更好地可视化边界

动觉学习者:进行"角色扮演"——您是开发者,您的搭档(或AI的另一个实例)是不"理解上下文"的代理。搭档应对规格说明的每句话提问。记录问题——这就是您的/clarify

养成习惯:git提交规格说明前,用"未读过聊天的人能否理解阶段何时结束?"检查validation.md。如果需要口头解释——重写

为specs/创建个人模板:复制本指南中的示例,适配您的技术栈,用作样板。每个功能节省30分钟,降低遗漏块的风险

在日常任务上练习EARS和Given/When/Then:"WHEN 我打开冰箱,THE SYSTEM SHALL 显示牛奶"——有趣,但训练模式。一周后表述将变得自动

听觉学习者:录制视频,"大声"解释规格说明。回放时注意到卡壳的地方——这是隐藏的歧义

额外资源: Github spec kit(原始方法论):https://github.com/github/speckit — /clarify概念和规格说明结构的主要来源,本课程已适配用于与AI代理协作

EARS需求语法(nigel rae):https://www.betteruserstories.com/ears/ — EARS微格式的官方指南,包含不同领域示例(航空、医疗、Web)

Given-when-then(cucumber/gherkin):https://cucumber.io/docs/gherkin/reference/ — BDD中Given/When/Then格式参考;帮助将validation.md编写为可执行规格说明

Hono框架文档:https://hono.dev/ — 学习项目中使用的框架官方文档;理解"Hello Hono"上下文所需

"项目规则"课程(本课程第6部分):课程内部资源——路线图、mission.md和tech-stack.md,作为功能规格说明的输入

"编写优秀规格说明"文章(kamil nicieja):https://www.manning.com/books/writing-great-specifications — Specification by Example书籍;第3章("活文档")直接适用于validation.md

需求表述互动训练器:https://www.reqtest.com/requirements-management-blog/how-to-write-good-requirements/ — 将糟糕需求转化为良好需求的练习集(英文,但概念通用)

总结:功能规格说明是人与AI代理之间的"合同",防止小阶段变成半个产品。关键原则:1)三个文件(requirements.md——意图、plan.md——顺序、validation.md——可验证事实);2)边界的两道防线("边界之外"和"不应改变的内容");3)在散文歧义处通过EARS和Given/When/Then形式化;4)编码前强制/clarify步骤识别替代解释;5)就绪度检查清单,标准是"我能说出阶段何时结束"。"Hello Hono"学习项目展示实践应用:从Git分支到规格说明提交,无需一行代码。记住:文本规格说明指导代理,但决策的合并权仍归人类,基于事实而非愿望。

我的笔记
0 / 10000

笔记保存在当前浏览器中。在其他设备上将不会显示。

课程菜单

课程

基于 Qwen Code CLI 的规范驱动开发
进度 0 / 135