1. 为什么“给AI写规格说明书”和给人写完全是两码事
很多人第一次尝试让AI帮忙写代码或者做系统设计时,都会遇到一个很尴尬的局面:你觉得自己已经说得很清楚了,但AI产出的东西要么跑偏,要么缺胳膊少腿,要么在细节上自作主张。于是你开始怀疑是不是模型不够强,换了一个又一个工具,结果发现换汤不换药。问题大概率不在模型身上,而在于你给它的那份“需求描述”根本算不上规格说明书。
给人写规格说明书,读者是有常识、有上下文、会主动追问的工程师。你写一句“用户登录后跳转到首页”,对方不会问你“首页是哪个页面”“登录失败怎么办”“token存哪里”。但AI不一样,它没有你们团队的隐性知识,不知道你们的技术栈约定,不了解你们的产品调性,更不会在动手之前先来找你确认。它只会基于你给的文字做最大概率的合理推测,而“最大概率的合理推测”往往就是最平庸、最泛化的那种实现。
所以给AI写规格说明书,本质上是在做一件事:把原本存在于你脑子里、团队默契里、历史代码里的隐性约束,全部显性化成AI可以直接消费的结构化文本。这件事的难度不在于写多少字,而在于你能不能识别出哪些信息是AI“不知道但必须知道”的。
我自己的经验是,一份能让AI产出可用结果的规格说明书,通常需要覆盖六个维度:角色与边界、输入与输出、状态与流转、异常与兜底、约束与偏好、验收与自检。缺了任何一个维度,AI都会在那个维度上自由发挥,而自由发挥的结果十有八九不是你想要的。
下面这张表是我总结的“人类规格书”和“AI规格书”的核心差异,你可以对照看看自己平时写的东西落在哪一边:
| 维度 | 给人看的规格书 | 给AI看的规格书 |
|---|---|---|
| 读者假设 | 有领域常识,会主动追问 | 零上下文,不会追问 |
| 容错空间 | 模糊处可由人补全 | 模糊处由模型猜测 |
| 结构要求 | 逻辑清晰即可 | 需要显式分节、编号、标签 |
| 异常处理 | 可口头补充 | 必须逐条写明 |
| 技术约束 | 团队默认约定 | 必须显式声明 |
| 验收标准 | 可事后评审 | 最好前置为自检清单 |
理解了这层差异,后面的内容才有落脚点。接下来我会按实际写一份规格说明书的完整流程来展开,每一步都告诉你为什么要这样写,以及不这样写会出什么问题。
2. 角色设定与边界划定:让AI知道自己是谁、不做什么
2.1 角色描述不是客套话,是约束条件
很多人写AI规格书时,开头喜欢写一句“你是一个资深工程师”,然后就直接进入需求。这句话不能说没用,但它的作用非常有限。因为“资深工程师”这个标签太宽泛了,AI无法从中推断出你希望它用什么语言、什么架构风格、什么命名习惯来做事。
有效的角色描述应该包含三层信息:技术身份、经验取向、行为偏好。技术身份告诉AI它该用什么工具集思考,经验取向告诉它遇到取舍时往哪个方向倾斜,行为偏好告诉它输出时该注意什么。
举个例子,如果你要做一个数据处理脚本,角色描述可以这样写:
你是一名专注于数据清洗与ETL流程的后端开发者,习惯用Python处理结构化数据,偏好用pandas做批量转换、用pydantic做数据校验。你在写代码时优先考虑可读性和可测试性,而不是极致性能。遇到不确定的字段含义时,你会先在注释中标注假设,而不是自行编造映射关系。
这段描述里,“Python + pandas + pydantic”是技术身份,“优先可读性而非性能”是经验取向,“不确定时标注假设而非编造”是行为偏好。三条加起来,AI在后续生成代码时就有了明确的决策依据。
2.2 边界划定:明确写出“不要做什么”
比“要做什么”更容易被忽略的是“不要做什么”。AI有一个很强的倾向,就是帮你“补全”你没提到的东西。你觉得它很智能,但补出来的往往是你不需要的。
比如你让它写一个用户注册接口,你只说了“接收邮箱和密码,存入数据库”。它可能会自动帮你加上:密码强度校验、邮箱格式校验、重复注册检查、验证码发送、注册成功后的欢迎邮件、日志记录、限流……这些东西单拎出来都是合理的,但如果你当前阶段只想先跑通主流程,这些“贴心”的补充就会变成负担。
所以边界划定要显式写出来。我通常会用这样的格式:
- 本次范围内:接收邮箱和密码,做基本非空校验,写入用户表,返回用户ID
- 本次范围外:不做密码强度校验、不发验证码、不发邮件、不做限流、不写操作日志
- 后续可能扩展但当前不实现:邮箱唯一性检查、密码加密存储
这样写的好处是,AI知道哪些是它该操心的,哪些是它不该碰的。你可能会问,密码加密存储怎么能不做?这就是边界划定的意义——也许当前只是一个内部原型,也许加密逻辑在另一层统一处理,总之你明确说了不做,AI就不会自作主张。
2.3 用“假设清单”替代“隐含前提”
还有一个很实用的技巧,就是在规格书开头单独列一个“假设清单”。把你认为理所当然、但AI可能不知道的前提全部写出来。比如:
- 假设运行环境是Python 3.11,已安装pandas 2.x
- 假设数据库是PostgreSQL 15,连接信息通过环境变量传入
- 假设输入数据中时间字段统一为UTC时间戳
- 假设调用方已经完成了身份认证,本模块不处理鉴权
这些假设写出来之后,AI在生成代码时就不会去重复实现这些已经存在的东西,也不会因为不知道环境而选择错误的库版本。我踩过的一个坑就是没有声明Python版本,结果AI用了一个3.12才有的语法特性,在3.10环境上直接报错。从那以后,假设清单成了我每份规格书的标配。
3. 输入输出契约:把“大概这样”变成“必须这样”
3.1 输入定义要细到字段级别
“接收用户信息”这种描述对AI来说等于没说。它需要知道:字段名是什么、类型是什么、是否必填、取值范围是什么、默认值是什么、字段之间有没有依赖关系。
我通常会用表格来定义输入,因为表格的约束感比段落强得多。比如一个订单创建接口的输入定义:
| 字段名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| user_id | string | 是 | 用户唯一标识,UUID格式 | "a1b2c3d4-..." |
| items | array | 是 | 商品列表,至少1项 | 见下方items结构 |
| coupon_code | string | 否 | 优惠券码,为空表示不使用 | "SAVE10" |
| remark | string | 否 | 备注,最长200字符 | "尽快发货" |
items数组内部还要继续定义:
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sku_id | string | 是 | 商品SKU编号 |
| quantity | integer | 是 | 数量,最小1,最大99 |
这样定义之后,AI在生成校验逻辑时就有据可依,不会漏掉某个字段,也不会把可选字段当成必填。
3.2 输出定义要包含成功和失败两条路径
很多人只定义成功时的返回结构,失败时的返回就交给AI自由发挥。结果就是AI可能返回一个字符串错误信息,也可能抛异常,也可能返回一个包含error字段的对象,风格完全不统一。
正确的做法是把成功和失败都定义清楚。成功路径定义返回数据的结构,失败路径定义错误码、错误信息格式、以及不同错误场景对应的错误码。
| 场景 | HTTP状态码 | 错误码 | 错误信息模板 |
|---|---|---|---|
| 参数缺失 | 400 | INVALID_PARAM | "缺少必填字段: {field}" |
| 用户不存在 | 404 | USER_NOT_FOUND | "用户 {user_id} 不存在" |
| 库存不足 | 409 | OUT_OF_STOCK | "商品 {sku_id} 库存不足" |
| 系统内部错误 | 500 | INTERNAL_ERROR | "服务暂时不可用,请稍后重试" |
这张表写出来之后,AI生成的错误处理代码就会和你的预期高度一致,而不是每个接口一套风格。
3.3 用示例代替长篇描述
有时候一段文字描述不如一个具体示例来得清楚。特别是涉及嵌套结构、复杂映射关系的时候,给一个完整的输入输出示例,AI的理解准确率会大幅提升。
比如你要描述一个数据转换逻辑,与其写“将源数据的create_time字段从毫秒时间戳转换为ISO 8601格式字符串”,不如直接给一个示例:
输入:
{"create_time": 1700000000000, "name": "test"}输出:
{"create_time": "2023-11-14T22:13:20Z", "name": "test"}示例的好处是消除了歧义。文字描述里“ISO 8601格式”可能有多种写法,但示例只有一种,AI会直接照着示例的格式来。
注意:示例要覆盖边界情况。比如空值、超长字符串、特殊字符等,最好各给一个示例,否则AI可能只处理正常情况。
4. 状态流转与异常兜底:AI最容易糊弄过去的地方
4.1 状态机要显式画出所有合法迁移
如果你的系统涉及状态变化,比如订单从“待支付”到“已支付”到“已发货”,那状态流转必须显式定义。AI在没有明确指示的情况下,往往会实现一个“万能更新接口”,允许任意状态跳到任意状态,这在业务上通常是灾难。
我习惯用一张状态迁移表来定义:
| 当前状态 | 允许迁移到 | 触发条件 | 副作用 |
|---|---|---|---|
| 待支付 | 已支付 | 支付成功回调 | 扣减库存、发送通知 |
| 待支付 | 已取消 | 用户取消或超时 | 释放库存 |
| 已支付 | 已发货 | 管理员操作 | 生成物流单 |
| 已发货 | 已完成 | 用户确认收货 | 无 |
| 已支付 | 已退款 | 退款审核通过 | 返还库存、退款 |
这张表写出来之后,AI在实现状态更新逻辑时就会去校验当前状态是否允许迁移,而不是无脑更新。
4.2 异常兜底要逐条列出,不能靠AI“合理推测”
异常处理是AI最容易偷懒的地方。如果你不写,它可能只处理最明显的那个异常,其他一律往上抛。但实际生产中,每个异常都需要有明确的处理策略。
我通常会把异常分成三类来写:
第一类:可预期且可恢复的异常。比如网络超时、第三方接口返回限流。这类异常的处理策略是重试,但要写明重试次数、重试间隔、退避策略。
第二类:可预期但不可恢复的异常。比如参数校验失败、资源不存在。这类异常的处理策略是直接返回错误,但要写明返回什么错误码、什么信息。
第三类:不可预期的异常。比如空指针、类型错误。这类异常的处理策略是捕获后记录日志,返回统一的内部错误,不暴露堆栈信息给调用方。
每一类下面再列出具体的异常场景和处理方式。这样AI在生成代码时,就会按照你的分类去写try-catch,而不是把所有异常混在一起。
4.3 并发和幂等:不写就等着出问题
如果你的接口可能被并发调用,那并发控制必须写进规格书。AI默认生成的代码通常没有并发保护,因为它假设一切按顺序发生。
需要写明的并发相关信息包括:是否存在竞态条件、用什么机制保证一致性(乐观锁、悲观锁、分布式锁、唯一约束)、幂等键是什么、重复请求如何处理。
比如一个支付回调接口,规格书里应该写:
该接口可能被支付平台重复调用。以支付平台的transaction_id作为幂等键,首次处理时记录该ID,后续相同ID的请求直接返回成功,不重复执行业务逻辑。使用数据库唯一约束保证并发情况下的幂等性。
这段话写进去,AI就知道要加唯一索引、要先查后写、要处理唯一约束冲突。不写的话,它大概率就是一个简单的“查到就更新”,并发下直接出问题。
5. 技术约束与风格偏好:让产出直接能进代码库
5.1 技术栈约束要具体到版本和库
“用Python写”和“用Python 3.11 + FastAPI + SQLAlchemy 2.0 + Pydantic v2写”是两个完全不同的约束。前者AI可能给你Flask,也可能给你Django,还可能给你一个纯脚本。后者才能保证产出和你的项目无缝对接。
除了语言和框架,还要写明:包管理工具(pip/poetry/uv)、代码格式化工具(black/ruff)、类型检查要求(mypy严格模式)、测试框架(pytest/unittest)。这些信息看起来琐碎,但决定了AI产出的代码能不能直接放进你的项目里跑。
我一般会在规格书里单独开一节叫“技术约束”,用列表形式写清楚:
- 语言:Python 3.11
- Web框架:FastAPI 0.100+
- ORM:SQLAlchemy 2.0,使用声明式模型
- 数据校验:Pydantic v2
- 包管理:poetry
- 格式化:ruff format + ruff check
- 类型检查:mypy --strict
- 测试:pytest + httpx
5.2 命名和目录结构约定
AI生成的代码经常出现命名风格不统一的问题,因为你不说,它就随机选。所以命名约定要写清楚:文件名用什么风格、类名用什么风格、函数名用什么风格、常量用什么风格、数据库表名和字段名用什么风格。
目录结构也一样。如果你希望代码放在特定的目录下,就在规格书里画出目录树。比如:
src/ api/ routes/ schemas/ core/ config.py exceptions.py models/ services/ tests/有了这个目录树,AI就知道该把文件放在哪里,不会把所有代码塞进一个文件里。
5.3 注释和文档字符串要求
AI默认生成的注释要么太多(每行都注释),要么太少(完全没有)。你需要明确告诉它注释的粒度:哪些地方必须写注释(复杂逻辑、非显而易见的决策、外部系统交互),哪些地方不需要(自解释的代码)。
文档字符串的格式也要指定。是Google风格、NumPy风格还是Sphinx风格?参数和返回值要不要写类型?异常要不要列出来?这些都说清楚,产出的文档字符串才能和你的项目风格一致。
提示:如果你用的是ruff,可以在规格书里直接写“注释和文档字符串需通过ruff的pydocstyle检查”,这样AI就知道该按什么标准来写。
6. 验收标准与自检清单:让AI自己先过一遍
6.1 把验收标准写成可执行的检查项
“功能正确”这种验收标准对AI没有意义。它需要的是可以逐条勾选的检查项。比如:
- 所有必填字段缺失时返回400和对应错误码
- 所有可选字段为空时使用默认值,不报错
- 输入超长字符串时返回400而不是500
- 并发相同幂等键请求时只执行一次业务逻辑
- 数据库操作失败时回滚事务并返回500
- 所有公开函数都有类型注解和文档字符串
- 测试覆盖率不低于80%
这些检查项写出来之后,AI在生成代码时会主动去满足它们,而不是等你事后发现缺了再补。
6.2 让AI在输出前做自检
一个很实用的技巧是在规格书末尾加一段“输出前自检”指令,要求AI在给出最终结果之前,先逐条核对验收标准,并说明每条是如何满足的。这样做有两个好处:一是AI在自检过程中会发现自己遗漏的地方并主动补上,二是你拿到结果时已经有了一个对照清单,验收效率大幅提升。
自检指令可以这样写:
在输出最终代码之前,请先逐条核对上述验收标准,对每一条说明你的实现方式。如果某一条无法满足,请明确说明原因,不要跳过。
实测下来,加了这段指令之后,AI产出的完整度明显提升,尤其是异常处理和边界条件这两块,遗漏率大幅下降。
6.3 迭代式规格书:先跑通再细化
最后分享一个我常用的工作方式:不要试图一次性写出完美的规格书。第一版只需要覆盖核心路径和关键约束,让AI先产出一版能跑的东西。然后你拿着这版结果去对照,看哪些地方偏离了预期,把偏离的地方作为补充约束写进规格书,再让AI重新生成。
这个过程通常迭代两到三轮就能收敛。第一轮解决“有没有”的问题,第二轮解决“对不对”的问题,第三轮解决“好不好”的问题。比起憋一份大而全的规格书然后一次性生成,这种迭代方式效率更高,而且每一轮你都能学到AI在哪些地方容易理解偏差,下一轮写规格书时就会更有针对性。
我自己的体会是,写AI规格书这件事,本质上是在训练自己把模糊需求转化为精确约束的能力。这个能力一旦练出来,不仅对AI有效,对跟人协作也同样有效。毕竟,能把事情说清楚,本身就是一种稀缺能力。