一份产品需求文档写完,3000 多行,章节齐、表格密、流程图不少。看着挺像回事。
然后交付形态变了一句话:这份文档不给人看,直接给 Claude Code 拿去实现。
我做的第一件事不是庆祝可以省掉评审,而是把整份文档的写法推翻重来。
人类读者会替你补全,AI 不会
文档里有这么一行字段定义:
出库原因 | 枚举 | 必填 | 进货 / 退货 / 销售 / 赠送 / 试用
给人看,没问题。任何一个工程师读到这里,会自然而然地在脑子里生成 SALE、GIFT、TRIAL 这样的常量,甚至会顺手问一句「代码值你们有约定吗」。
给编码 agent 看,它不会问。它会自己编一套英文常量,编得还挺合理。问题是——下一份文档、下一个接口、下一次它自己重新读这段的时候,可能编出另一套。等到两套常量在系统里相遇,就是返工。
所以我把它改成了:
PURCHASE_IN(进货入库)RETURN_IN(退货入库)SALE_OUT(销售出库)GIFT_OUT(赠送出库)TRIAL_OUT(试用出库)
并且在附录单开一张枚举总表:枚举名、适用字段、代码值、中文名、说明。一处查全,不用满文档翻。
这不是「写得更细」,这是把本来在人脑里隐式完成的那一步,显式写下来。
四条改写规则
同一份文档,从「给人复核」切到「给 AI 实现」,我总结出这几条必须改的地方:
1. 字段类型要工程化,不要自然语言
「文本」「数字」「金额」——这些词给人看足够,给 AI 看等于让它猜。改成 string(50)、decimal(18,4)。长度和精度是业务决策,不是实现细节,本来就该出现在需求文档里。
2. 校验规则要写成可判定条件
❌ 「需对客户信息做合理校验」
✅ 「出库原因 = SALE_OUT 时,customer_id 必填,不允许留空」
「合理」是个甩锅词。人类看到会来问你什么叫合理,AI 看到会自己定义合理。
3. 计算公式要给表达式 + 数值演算
写「采用移动加权平均法计算成本」是不够的。得给出公式,再给一组具体数字演算一遍:进货多少、单价多少、出库多少、结存单价变成多少。
AI 能把演算过程直接翻译成代码,而文字描述它只能理解。 理解和翻译之间,隔着一次可能出错的转换。
4. 状态机要写全,包括不可逆节点
有哪些状态、哪个状态允许哪些操作、流转条件是什么、哪些操作过了就回不去了。缺一样,AI 就会自己补一个看起来对的。
最贵的那个坑:范围边界
这份文档里我抓到的最严重问题,不是格式,是范围。
需求方拍板「提醒推送这一期先不做,优先做电脑端」。文档里却留了一句:
推送走 Web Push / 短信降级
人类读者读到这句,会结合上下文判断「哦这是以后的方案」。编码 agent 读到这句,会真的去实现 Web Push。
这就是模糊描述在 AI 协作里的杀伤力:它不会怀疑,只会执行。
订正的时候我没有简单删掉,而是全文搜「推送」「短信」「模板消息」,凡是写成「一期能力」的表述,一律改成明确的否定句:一期不做任何主动推送,待办仅站内展示。
否定句比留白安全得多。留白会被填充,否定句不会。
收敛范围 ≠ 砍功能
顺带说一个我认为更重要的判断。
「推送一期不做」这句话,最偷懒的执行方式是把整个提醒模块划掉。但那是错的——真正该做的是画边界:
- 提醒规则的配置、待办的生成逻辑、待办列表和状态流转,一期完整实现
- 主动推送的通道本身,标注二期
- 推送相关的字段(
push_channel、pushed_at、push_status)保留在数据模型里,标注reserved · 二期启用
理由很直接:生成是生成,推送是消费。 一期只做生成加站内展示,二期挂一个推送消费者就行,核心逻辑一行不用改,表结构也不用动。
如果一期图省事把字段一起砍了,二期就得改表——而改表在一个已经跑起来的库存系统里,是要停机和数据迁移的。
这条解耦设计我专门写进了文档的「给开发的实现提示」章节,并标出二期的接入点在哪。
一句话
给人写文档,你在传递意图,读者会用常识把缝隙填上。
给 AI 写文档,你在定义行为,缝隙会被它自己的想象填上——而它的想象,和你的常识不是一回事。
所以别嫌啰嗦。你少写的每一句,它都会替你补一句。
马启航Marvis 🐉