上一章把 Spec Kit 装好、初始化完,也看清了整条流水线。流水线的第一份业务产物是 spec.md,这一章专门讲它。很多团队第一次写规格,交出来的东西读起来像 PRD 的缩写版,或者像一份提前做好的技术设计,更常见的是把两者组合在同一份文件里。结果 What、Why、How 重新混作一团,后面的计划、任务、代码全部从一份含混的地基上长出来。
这一章要掰扯三件事,Specification 和 PRD、技术方案到底差在哪,为什么它被严格限制在 What 和 Why,以及一份模糊需求经过什么处理才能变成可测试、可验证的规格。最后一节集中处理几种最常见的错误写法。读完再写 spec.md,至少能判断每句话该留在规格里,还是该挪到别的地方。
一、PRD、Feature Spec 与技术方案的区别
这三样东西最常见的混淆方式,不是分不清名字,而是有人试图用一份文件同时干三件事。先把三者各自覆盖的范围摆出来,后面所有讨论都建立在这张表上。
| 文档 | 视角和范围 | 回答的问题 | 主要读者 | 在 Spec Kit 中的位置 |
|---|---|---|---|---|
| PRD | 整个产品或产品线 | 做这个产品值不值,解决谁的问题,路线怎么排 | 业务、管理层、产品 | 上游输入,仓库里没有对应模板 |
| Feature Spec | 单个功能 | 这个功能提供什么能力,为什么需要,怎样算完成 | 产品、开发、测试 | specs/NNN-名称/spec.md |
| 技术方案 | 单个功能的实现 | 用什么技术,数据和接口长什么样,分几步做 | 开发、架构 | plan.md及contracts/ |
PRD 的视角是整个产品。它关心市场、目标用户、商业模式、竞品和路线图,一份 PRD 里通常排着多个功能,还要回答凭什么先做这个、后做那个。它给规格提供背景和上游决策,但它的绝大部分内容不该出现在 spec.md 里,市场规模、营收预期和某个功能的验收行为没有直接关系,抄进来只会稀释重点。
Feature Spec 的视角收窄到一个功能。它不关心整个产品怎么赚钱,只回答这一个功能要给使用者提供什么能力,为什么现在需要,什么样子算做完。短链接项目里,提交网址拿到短链、访问短链完成跳转,是一个功能规格的范围,访问统计是另一个,过期和链接管理是第三个。每个功能一个目录,各自有独立的用户故事和验收场景,互不借用编号。
技术方案的视角再往下一层,回答同一个功能怎么实现。用什么语言和框架,数据怎么存,接口契约长什么样,实施分几个阶段,这些是 plan.md 的内容。规格里如果已经把这些定死,计划阶段就无事可做,所谓 What 和 How 的分层也就名存实亡。
三者不是竞争关系,是同一条链路上分工不同的三个产物。PRD 是规格的输入,规格是技术方案的输入。判断一句话属于哪一层有个实用的判据,换一个技术栈之后这句话还成不成立。访问者点击短链接后到达原网址,换成任何技术栈都成立,它是规格。用 Redis 的 GET 命令取回原始 URL,换个存储就不成立,它是方案。
二、为什么 Specification 只描述 What 和 Why
Spec Kit 的规格模板在 Quick Guidelines 里把要求写得很硬,聚焦用户需要什么、为什么需要,回避怎么实现,不写技术栈、API 和代码结构,读者设定为业务干系人而不是开发者。这不是写作偏好,是由规格在整条流水线里的位置决定的,原因有三层。
第一,过早写 How 等于提前锁死选项。需求阶段真正要回答的是系统必须表现出什么行为,至于这个行为靠什么构件达成,本来应该在掌握了语言、运维条件、团队经验之后再权衡。规格里一句"用 Redis 存映射",会把一个可以讨论的选型变成既成事实,评审的人再也分不清哪些是业务要求、哪些是某人的随手选择。第 1 章短链接项目的存储和过期策略,就是在生成代码的同一秒钟被这样定掉的。
第二,混入实现细节会改变这份文件的读者结构。规格是产品、开发、测试三方共同评审的契约,产品同学需要能逐句判断"这是不是我要的行为"。满篇表结构、缓存键和状态码的规格,产品方实际上被排除在评审之外,歧义于是失去了最后一次被人拦住的机会。
第三,规格要支持被重新实现。SDD 的权力反转主张代码是规格在某个技术栈下的一次具体表达,既然是一次表达,就应该可以换一种技术栈再表达一次。规格里焊死了框架和存储,它就从可复用的意图退化成了某个实现的说明书,重新生成无从谈起,规格和代码之间再次出现需要人手工填平的鸿沟。
这里有一个需要拿捏的边界,只谈 What 和 Why,不等于技术名词一个都不能出现。短链接本身是技术产品,网址、链接、跳转这些词是问题域的语言,不是实现选择。判据是这个词在描述业务行为,还是在指定解空间的构件。访问者打开短链接后到达原来的页面,这是行为描述,服务端返回 302 状态码、在 Location 头里放目标地址,这是 HTTP 协议给的实现机制,属于计划阶段。前者不随技术变化,后者换个协议环境就可能改写。
还有一类东西容易被误删,就是真实的外部约束。必须部署在公司内网、不能依赖外部 SaaS、要符合某条审计规定,这些话虽然带着技术字眼,约束的却是解空间本身,来源是业务环境而不是技术选型,它们应当留在规格里,或者进入跨功能共享的项目原则。第 4 章会讲项目原则怎么承载这类约束。
三、如何避免过早引入技术实现
道理清楚之后,真正写的时候还是会不自觉地滑向 How,因为写方案对工程师来说比写行为更顺手。下面几个做法可以在写作时直接用上。
最有效的一条是用使用者的语言写,假设读这份文件的人不会编程。每写一句话就问一次,一个不写代码的产品同事能不能判断这句话的真假,答不上来,这句话大概率已经滑到实现里去了。
其次是做一遍名词扫描。数据库、表、字段、缓存、队列、索引、接口、JWT、定时任务,这些词本身不是罪,但它们出现的地方都值得停下来追问,这是在描述需求,还是已经在安排构件。多数情况下,把名词替换成使用者看到的行为,句子照样成立,而且更准确。
被删掉的技术想法不要直接扔掉,它们是下一阶段的资产。可以在规格之外单独记一份技术备忘,比如"映射读写比极高,候选方案 Redis 或 KV 存储,过期机制待验证",等到运行/speckit.plan时作为技术上下文喂进去。What 和 How 分开,意思是 How 去它该去的地方,而不是不许想 How。
下面是一个典型的改写。改写前的这段把选型、数据结构、接口路径全部写进了需求。
短链映射保存在 Redis 里,key 是 6 位 slug,value 是原 URL, 过期时间用 TTL 控制。服务端用 FastAPI 暴露 POST /api/links 创建短链,用 GET /:slug 处理跳转,跳转返回 302。改成行为描述之后是下面这样,三句话全部可以由产品同学逐句确认,也全部不依赖任何具体技术。
用户提交一个合法网址后,系统返回一个可以直接分享的短链接。 创建短链接时可以指定有效期,超过有效期的链接不再把访问者带到原网址。 访问者打开短链接,到达创建时提交的那个页面。Redis、FastAPI、302 这些东西并没有浪费,它们带着"候选"的身份进入技术备忘,在计划阶段和其他方案一起被比较。规格保住了中立性,技术想法也没有丢,这才是分层想要的效果。
四、什么是可测试、可验证的需求
一份不含技术细节的规格,如果它的每句话都没法判定真假,照样可能是不可用的。可测试需求的定义很朴素,一个事先不知道答案的人,只通过观察系统的外部行为,就能独立判断这条需求满足了没有,不需要再去问写作者"你说的快是什么意思"。
模糊需求的标志是一批形容词和副词,快速、稳定、友好、简洁、尽量、支持、高性能、体验好。这些词的问题不是不优美,而是每个人都能往里填自己的意思,于是评审时全员点头,验收时各自表述。页面加载快,在写作者心里可能是 1 秒,在测试那里是 5 秒,在用户那里是 3 秒但白屏不算,争议在项目末期才爆发。
修法是把形容词换成可观察的事实,带上数字和观察点。页面加载快可以写成,使用者打开短链接创建页,2 秒内看到可以输入网址的状态。数字必须有依据,来自业务要求、用户预期或者现有系统的基线,拍脑袋编一个精确数字是伪精确,比形容词更危险,因为它看起来像经过了论证。如果确实没有依据,正确的位置是澄清问题或者假设清单,而不是硬填。
验收场景是可测试性的主要载体,Spec Kit 模板统一采用 Given、When、Then 的形式,给定什么前置状态,触发什么动作,应当观察到什么结果。每条场景将来都可以直接翻译成一个测试用例,这也是第 1 章所说验收先于代码存在的落点。以过期行为为例,写清楚之后是下面这样。
Given 一条设置了 1 小时有效期的短链接 When 访问者在 2 小时后打开它 Then 系统不执行跳转,访问者看到链接已过期的说明这条场景没有任何实现细节,但任何一个测试拿到它都知道怎么构造数据、怎么操作、断言什么。反过来对比一下,过期功能要好用,这句话无法测试,因为"好用"没有外部观察点。
可测试不等于只覆盖顺利路径。模板专门有 Edge Cases 一节,要求把边界和异常摆出来,提交一个不是网址的字符串会怎样,同一个网址反复提交会不会得到新链接,短链字符恰好撞了怎么处理。这些场景在演示里永远不会走到,第 1 章那条 slug 碰撞导致随机跳错的路径,就是因为从来没有作为验收场景存在过,才隐身了整整两周。
成功标准同样要过可验证这一关,而且要求更严,必须可量化、技术无关、站在使用者一侧。95% 的首次使用者不看帮助就能在 1 分钟内创建出第一个短链接,这是合格的成功标准。接口 p99 低于 200 毫秒、缓存命中率 90%,这些是内部实现指标,使用者无法观察,应当出现在计划的性能考量里,而不是规格的成功标准里。
五、从模糊需求到可执行规格
把前面几条合起来,跟着第 1 章那句一次性 Prompt 走一遍转换。原始输入还是那一句话,帮我写一个短链接服务,用户提交一个长 URL,返回一个短链接,访问短链接能跳转到原来的地址。它描述了一条窄路径,除此之外什么都没说。转换不是把这句话润色得更长,而是补齐结构,让隐含的东西全部显形。
第一步是找出角色和用户旅程,按重要性排优先级。这句话里至少有一个旅程,创建短链并通过它到达目标页,这是 P1,因为没有它整个服务不成立。过期管理、访问统计是后续旅程,分别成篇,不塞进同一个故事。模板要求每个故事可以独立开发、独立测试、独立演示,只实现 P1 也必须是一个能交付价值的最小产品。
第二步是清点这句话没说的东西,但不许替它默默作答。过期后还能不能跳,同一个网址重复提交怎么处理,slug 碰撞怎么办,字母大小写算不算同一个链接。影响面大、又没有合理默认值的问题,按模板要求标成[NEEDS CLARIFICATION],总数不超过三个,其余可以用行业惯例先给默认值,但必须写进假设清单,让默认值暴露在纸面上。
第三步是为每个故事写验收场景,先于任何代码。正常路径一条,非法输入一条,边界情况按需补充。第四步才是从场景里提炼功能需求,每条需求都能被至少一条场景验证。第五步补上可量化的成功标准。走完这五步,P1 故事在规格里大致是下面这个样子。
### User Story 1 - 创建并访问短链接 (Priority: P1) 任何拿到长网址的人,都可以提交网址得到一个短链接, 再把链接分享出去。访问者打开短链接,到达原来的页面。 **Why this priority**: 这是服务存在的最小理由,没有它, 过期、统计等功能都没有附着对象。 **Independent Test**: 只实现这一条故事,用户就能完成 提交网址、拿到短链、打开短链到达原页的完整闭环。 **Acceptance Scenarios**: 1. **Given** 一个合法网址,**When** 用户提交它, **Then** 系统返回一个只含字母和数字的短链接 2. **Given** 一个已创建的短链接,**When** 访问者打开它, **Then** 浏览器到达创建时提交的原始网址 3. **Given** 一个格式不合法的网址,**When** 用户提交它, **Then** 系统拒绝创建并说明原因故事之后是功能需求和成功标准,它们和上面的场景一一对应,读规格的人可以顺着编号互相核对。
- **FR-001**: 系统必须为每个成功提交的合法网址生成唯一短链接 - **FR-002**: 短链接长度固定为 6 位,只包含字母和数字 - **FR-003**: 打开短链接时,系统必须把访问者带到原始网址 - **FR-004**: 网址不合法时系统必须拒绝创建,并告知原因 - **SC-001**: 95% 的首次使用者不看帮助,就能在 1 分钟内 完成第一个短链接的创建 - **SC-002**: 访问者点击短链接后,95% 的情况下 1 秒内 开始到达目标页面注意整份产物里没有框架、数据库和接口路径,但它远比第 1 章那句 Prompt 精确。Agent 拿到这份规格,无法再替你决定过期行为,因为问题已经标出来等你回答,也无法只做 happy path 就宣布完成,因为非法输入和拒绝创建都有场景盯着。这就是可执行规格和一次性 Prompt 的差别,它约束的不是某一次生成,而是后续每一步。
六、常见错误规格及修改方法
最后集中过一遍实战中最高频的错误写法。每种都给出症状、问题所在和改法,写完规格之后可以拿这一节当检查单过一遍。
技术方案伪装成需求。症状是规格里出现具体产品名和数据结构,比如系统使用 Redis 保存短链映射,key 为 slug,value 为原 URL,过期通过 TTL 实现。它的问题是把选型焊死成要求,评审无法区分 What 和 How,将来换存储还要先改"需求"。改法是还原成外部行为,使用者提交网址得到短链,过期链接不再跳转,技术名词连同候选身份移到技术备忘里,留给计划阶段。
形容词充当验收标准。症状是页面简洁美观、加载迅速、系统稳定支持高并发这一类句子。问题是没有外部观察点,评审时全员同意,验收时各自解释。改法是补出数字、观察点和时间范围,新用户不看帮助 1 分钟内完成首次创建,常规网络下创建页 2 秒内可输入。数字要有业务依据,没有依据就走澄清,不允许用精确的假象掩盖没想清楚。
任务清单写进需求。症状是规格里列着新建 links 表、编写 generate_slug 函数、给跳转逻辑补单测。这些是动作,不是行为,它们属于 plan.md 和 tasks.md,出现在规格里会导致需求和实现步骤一一焊死,换一种做法就"不符合需求"。改法是把动作背后的意图提出来,每个短链对应唯一 slug,两个不同网址不会得到相同短链,至于靠表约束还是应用层校验保证,由计划和任务决定。
用沉默给歧义定生死。症状是一句系统支持过期,旁边什么都没有,过期后跳不跳、提示什么全部留白。更糟的是写作者自己补一个默认答案却不声明。改法是二选一,有合理默认值就写进行为并在假设里登记,没有默认值就标成[NEEDS CLARIFICATION],把问题本身写具体。
短链接可以设置有效期。 [NEEDS CLARIFICATION: 过期后访问是完全不跳转, 还是跳转到一个提示页,两种行为对访问者影响不同,需产品确认]这个标记会在第 6 章的澄清环节被正式提给你,结论写回规格后标记消失。歧义的处理原则始终是同一条,由人在动手前决定,而不是由模型在生成时静默填掉。
七、小结
这一章解决的是第一份产物长什么样的问题。PRD、Feature Spec 和技术方案覆盖的范围不同,PRD 面向整个产品提供上游决策,规格收窄到单个功能的 What 和 Why,技术方案才回答 How。规格只谈 What 和 Why 不是文风选择,而是为了不提前锁死选项、保住三方共同评审的读者结构、并让同一份意图可以被不同技术栈反复实现。
我们也给出了可操作的判据和流程,换技术栈后还成立的话才属于规格,模糊形容词必须换成带观察点的事实,每条需求都要有 Given、When、Then 场景可以验证。跟着第 1 章那句 Prompt 走完五步转换之后,短链接的 P1 故事已经是一份没有框架名、却精确到 Agent 无法自由发挥的规格。
但这份规格里还留着[NEEDS CLARIFICATION]标记,过期后到底跳不跳、删除要不要鉴权,这些问题还悬着。下一章先处理一个更上游的东西,在写任何功能规格之前,整个项目的工程原则应该先立起来,也就是 Constitution。