先说一个让我血压飙升的场景。上个月我让AI编程助手写一个用户管理列表页,需求就一句话:“帮我写一个用户列表页面,支持搜索和分页。”AI十分钟就生成了页面,看起来像模像样:表格有了、搜索框有了、分页器也有了。结果一联调,问题全冒出来了——搜索是前端filter硬过滤,根本不是请求后端接口;分页是假分页,一次性把全量数据拉到内存里翻页;没有loading状态、没有空数据提示;接口字段跟后端实际返回对不上;最离谱的是删除用户这种基础操作,它只做了个弹窗确认,接口压根没调。
我当时第一反应是:这AI也太拉了。但冷静下来复盘才发现,真正的问题出在我身上——我给它的是一句“一句话需求”,这句话背后至少藏着十几个没有回答的问题:用户列表的数据从哪来?搜索走前端还是后端?分页是服务端分页还是客户端分页?哪些字段要展示?需不需要批量操作?接口还没定怎么办?加载中、失败、空数据这些状态要不要处理?
从那次之后,我系统性地研究了三个月AI编程的返工规律,最后总结出一个很朴素的结论:AI编程返工率高,绝大多数情况不是模型能力不行,而是需求描述太“薄”。后来我开始用“需求四要素”的方法组织所有交给AI的任务描述——背景与目标、功能与行为、边界与约束、验收标准——返工率肉眼可见地下降。这篇文章就把这个方法完整拆开讲清楚,附上我实测的对比数据和可以直接抄的模板。如果你也在用AI写代码、天天跟返工掰手腕,这应该是你今年最值得花十分钟读完的一篇实操文章。
1. 返工的真凶不是模型,是需求描述太“薄”
1.1 一个让人血压升高的典型场景
我做了一个小实验,让AI实现“用户注册”功能。第一版需求描述是:“写一个用户注册页面。”AI生成的代码里,密码字段用的是明文的type="text",没有确认密码框,没有校验手机号格式,提交按钮也没有防止重复点击的逻辑。更要命的是,它自己定义了一整套REGISTER_API的假想接口,跟后端文档完全对不上。
这不是个例。在我统计的过往两个月任务里,凡是只给了“标题式需求”的任务,平均要来回拉扯四到五轮才能验收;而每拉扯一轮,AI还会引入新的问题——比如改好了校验,又把某个按钮的点击事件改没了。这种“修东墙拆西墙”的循环,是AI编程最大的隐性成本。
为什么会这样?我得解释一下背后的原理。大语言模型不是数据库检索,它本质上是“按概率续写下一个token”。给它一句话需求,它就按照训练语料里“最常见的注册页面长什么样”来续写。而“最常见”恰恰意味着“什么都带一点,什么都是泛化版本”。泛化版本放到真实项目里,大概率跟你的业务逻辑、接口协议、UI规范打架——返工就这么来了。
1.2 一句话需求背后藏着十几个未回答的问题
我把“写一个用户注册页面”这句话,拆成了下面这些问题:
- 注册字段有哪些?手机号?邮箱?用户名?昵称?
- 密码规则是什么?最少几位?要不要大小写混合?
- 校验时机:输入时实时校验,还是提交时统一校验?
- 接口地址和请求方式是啥?参数命名规则?返回结构什么样?
- 登录成功之后跳哪?token存哪里?
- 要不要验证码?图形验证还是短信验证?
- 重复提交怎么防?
- 接口失败怎么提示?loading态怎么处理?
- 适配哪些浏览器?表单样式走组件库还是自定义?
- 是不是注册完还要自动登录?
这些问题AI一个都没问我,因为它没法问,它只能猜。猜对了算运气好,猜错了就是返工。每一个没回答的问题,都是埋在地里的雷,等你在联调、走查、测试阶段一颗颗踩响。
1.3 为什么AI会一本正经地跑偏
这里有个很反直觉的点:AI跑偏的时候往往是“自信满满”的。它不会告诉你“这个需求我没完全理解,我做了几个假设”,它只会默默地在代码里写下const config = { apiHost: 'http://localhost:3000' },看起来无比正确。
这其实是语言模型的概率特性决定的。当信息不足时,模型会选择概率最高的补全路径,而这个路径来自它训练数据里的海量通用代码。你自己项目里的特殊约定、业务含义、历史包袱,在它的概率分布里都是“低频事件”,自然不会被选中。所以你会发现,AI生成的代码在“通用场景”下越漂亮,掺进你的“特定业务”时就越容易水土不服。
想通这一点之后,我意识到一个关键结论:我不能指望AI问我要信息,我必须主动把信息塞给它。需求四要素就是干这个用的——在动手之前,把AI做决策需要的“上下文”一次性喂足,让它的概率空间从“一万种常见写法”收敛到“你唯一想要的那种写法”。下面我详细拆解这四个要素。
2. 需求四要素到底是什么:每一要素精确打击一类返工
2.1 背景与目标:先告诉AI“为什么做”
第一个要素是背景与目标。很多人写需求喜欢直接从功能细节开始,但AI面对一堆功能指令时,缺乏“这件事的语境”,很容易做出不合实际的取舍。
举个例子。你说“用户列表页要把状态列展示出来”,如果AI不知道这是“内部运营后台”,它可能会把状态做成一个纯文本让小字;如果它知道“运营每天要看几十上百条记录、需要快速扫一眼识别异常”,它就会自动把状态做成高亮Tag、加上颜色语义,甚至考虑表格密度和排序列。
背景与目标这一栏要回答三个问题:
- 这个功能给谁用?内部员工还是外部用户?专业程度如何?
- 它解决什么业务问题?前置状态是什么?成功后是什么场景?
- 有没有依赖的现存功能或历史包袱?
比如我会这样写:
背景:这是给公司运营团队使用的订单管理后台,目前订单列表在 order-list.vue 中,只有基础展示功能。运营反馈无法快速筛选异常订单,需要在现有页面上增加按订单状态筛选的能力。
这句背景说明一添加,AI就会知道:这是“改造现有页面”而不是“从零新建”;页面的使用人群是运营;核心痛点是“快速筛选”。它生成的代码,会优先考虑跟现有页面风格保持一致、用现成的筛选组件,而不是自作主张重新设计一个页面。
2.2 功能与行为:把做的过程拆到可执行
第二个要素是功能与行为,这是需求的主体,也是大多数人唯一会写的部分。但问题在于,大家写得太笼统。“支持搜索”四个字,和下面的写法,对AI来说是天壤之别:
- 搜索框放在表格上方,宽度300px,占位文案“请输入订单号/客户名称”
- 输入后点击“搜索”按钮或按回车触发查询
- 搜索条件变化后,重置页码为1,重新请求接口
- 前端不提前过滤数据,所有筛选通过后端接口参数完成
看出区别了吗?“支持搜索”描述的是“结果”,而上面四条描述的是“行为路径”。AI是逐token生成代码的,你给它行为路径越完整,它生成出来的代码就越贴近你的预期,需要你在review阶段补位的脑力就越少。
功能与行为的写作要点:
- 操作流程写成分步列表:用户先看到什么、能做什么、操作后发生什么,1→2→3→4。
- 数据流向写清楚:数据从哪个接口来、用什么参数、响应结构长什么样、前端存到哪里。
- 状态枚举要完整:接口要处理的四种核心状态——正常态、加载态、空态、失败态,直接告诉AI要不要做,以及各自展示什么。
- 交互细节别嫌啰嗦:按钮禁用条件、回车提交、弹窗确认、二次操作提示。细节越明确,AI越不需要猜。
2.3 边界与约束:在动手前划好红线
第三个要素是边界与约束,这是最容易被忽略、但返工收益最大的部分。AI默认的心态是“把这件事做得越多越完整越好”,所以它特别喜欢“超纲发挥”:
你让它做个搜索框,它顺手给你加了个搜索历史;你让它写个接口,它自己封装了一套请求工具类,跟你项目的现有HTTP库冲突;你让它改一个按钮,它把整个文件重排了一遍格式,review起来眼睛都快瞎了。
边界与约束就是用来“踩刹车”的。它告诉AI:
- 明确不要做什么:不做忘记密码、不做验证码、不做导出
- 必须沿用现有技术栈和组件库:不要在Vue2项目里写Vue3语法,不要引入新的npm包
- 禁止触碰的现有代码范围:不要修改路由守卫、不要动公共组件、不要改变原有接口的响应结构
- 性能和兼容性硬指标:列表超过1000条必须分页、只兼容Chrome和Safari最新两个版本
我个人的经验是:边界与约束至少能消掉30%的返工。因为AI最常犯的错误不是“做不出来”,而是“做了你没让它做的事”,然后你还得花时间要么改掉它、要么解释为什么不需要。
2.4 验收标准:让代码可测试、可交付
第四个要素是验收标准。如果说背景与目标是给AI“导航方向”,功能与行为是“画路线图”,边界与约束是“设禁行区”,那验收标准就是“终点线”——你站在终点告诉它什么情况算到了。
很多人觉得验收标准是测试阶段的事,写需求时不用管。但AI编程的场景恰恰相反——验收标准应该前置到需求描述里,因为AI在生成代码时,会主动对着验收标准“自查”,相当于一个内置的自测环节。
验收标准怎么写才有效?核心是“可执行、可验证”,每条都是AI能自己检查的硬指标:
- 手机号格式错误时,输入框下方2秒内出现红色提示文案
- 点击登录后按钮置灰并显示loading,接口返回前不可重复点击
- 接口返回401时,页面跳转回登录页
- 空数据时表格区域显示“暂无数据”占位图
- 批量禁用成功后,表格自动刷新且被选择行状态变为disabled
把这种验收清单贴进需求里,AI生成完代码会自己过一遍,很多明显的问题在第一次生成时就被它自己规避了。这一点是四要素里“性价比”最高的,下文实测数据里你能看到它的效果。
3. 同一需求三种写法实测:返工率从80%降到10%
3.1 测试环境与衡量口径
为了验证“需求四要素到底有多少用”,而不是凭感觉拍脑袋,我做了一组对照实验。
实验环境:同一款AI编程助手、同一个代码仓库、连续一个月内完成10个同类型任务(前端CRUD页面开发),我把它们随机分成三组写法,每组3到4个任务:
- 写法一:一句话需求,如“写一个角色管理页面”
- 写法二:只写功能与行为,不加背景、边界、验收
- 写法三:完整四要素
衡量指标有三个:首次生成后需返工的比例(返工率)、平均每个任务从生成到达到验收标准的往返轮次、人工评审时发现的缺陷总数。参与测试的任务都是真实业务,难度相近,AI工具版本全程固定。
3.2 写法一:一句话需求(对照组)
对照组每个任务的需求就是一句话。结果意料之中:4个任务全部返工,首次生成就能直接用的为0个,平均每个任务来回4.25轮才达到验收标准,评审阶段场均发现5.5个缺陷。
最典型的案例是“角色分配权限”这个页面。AI生成后,权限树直接写死在前端配置文件里,后端角色和权限的关联接口一概没有。我问它“权限树数据从哪来”,它回答“mock数据”。我又得花两轮告诉它怎么接真实接口,还要自己动手改权限树的层级结构和选中逻辑。
一句话需求组的总耗时(含生成、评审、返工修改)平均每个任务约47分钟,其中真正的“生成时间”只占5分钟,剩下42分钟全在“发现偏差→指出偏差→等待修改→验证”的循环里消耗掉了。
3.3 写法二:有功能但没边界(中间态)
第二组我写了比较详细的功能描述,但故意不写背景、边界和验收标准。例如角色管理页面我会写:左侧是角色列表、右侧是权限树、保存时提交选中节点、接口地址/api/roles/{id}/permissions等。
结果比一句话好不少,返工率从100%降到了约60%,平均轮次降到2.75轮。但出现了两类新问题:
一是AI“过度设计”。功能描述里没提“不要做什么”,AI自作主张给权限树加了“半选状态同步”“父子联动动画”“节点拖拽排序”这些我没要求的能力。有些确实加了觉得还行,但开发时间成本上去了,而且这些功能后续维护都是负担。
二是因为没写验收标准,AI不知道自己做的“算不算完”。它的代码经常在“表面功能”上满足了我的描述,但边界情况一塌糊涂:角色列表超过20条时没有滚动或分页、树节点展开状态刷新后丢失、点击保存时没有做并发保护。
这个结果说明一个道理:光写清“做什么”还不够,“不做什么”和“怎样才算完”同样决定了返工率。
3.4 写法三:完整四要素(实验组)
第三组我用了完整四要素写法,每个任务在动手前花三五分钟把背景、功能、边界、验收写清楚。结果让我自己都有点意外:3个实验组任务里,2个一次通过验收,1个只经过一轮小幅修改就达标;返工率按“需要返工的任务占比”来算是约33%,但如果按“真正需要大改的任务占比”算,只有0个。
那个唯一经过一轮修改的任务,问题出在验收标准里有一条没写清楚——“权限树节点默认展开到二级”。我写了“默认展开层级”,但没写清“二级”这个具体数字,AI按自己理解展开了全部层级。我补上“展开到二级,根节点收起”后,一轮就改好了。
实验组的评审缺陷数也低得离谱,平均每个任务只有0.7个,且全是样式微调级别的,没有一个是逻辑性、架构性缺陷。几乎不用再花时间帮AI“擦屁股”。
3.5 三轮测试结果对比表
我把三轮结果汇总成一张表,可以很直观地看到差异:
| 需求写法 | 任务数 | 返工率(需返工/总数) | 平均往返轮次 | 平均缺陷数 | 平均总耗时 |
|---|---|---|---|---|---|
| 一句话需求 | 4 | 100%(4/4) | 4.25轮 | 5.5个 | 47分钟 |
| 只写功能行为 | 3 | 约67%(2/3) | 2.75轮 | 2.3个 | 28分钟 |
| 完整四要素 | 3 | 约33%(1/3) | 1.0~2.0轮 | 0.7个 | 14分钟 |
注意,第三组的“33%返工率”里包含了“一轮小幅修改”的情况,如果严格按“需要大的逻辑重写”定义返工,这个数字其实是0。换句话说,四要素写法把这批任务的返工率从100%降到了接近0,耗时压到原来的三分之一。虽然样本不大,但结合我后来三个月的长期使用经验,这个结论是稳定的。
4. 可直接照抄的四要素Prompt模板与改造示例
4.1 通用模板
具体到实操环节,我把自己现在每天在用的模板贴在下面。这个模板你可以直接复制,替换成自己的内容就能用。格式上我用“标签加分条”的形式,因为实际测试下来,AI对结构化的Markdown分条列表理解得最好,比一大段散文式的描述效果好得多。
【背景与目标】
- 功能使用人群:谁在用这个功能,内部/外部
- 要解决的问题:现在是什么状态,痛点是什么,做完之后是什么状态
- 前置依赖:依赖哪些现有功能/接口/数据表
【功能与行为】
- 操作流程:1. 用户先看到什么;2. 操作什么;3. 发生什么反馈
- 页面模块/接口明细:包含哪些区域、字段、参数、返回结构
- 数据流向:数据来源、请求方式、存储位置、更新时机
- 状态齐全:正常态 / 加载中 / 空数据 / 失败分别怎么展示
【边界与约束】
- 明确不做:列出本次范围外的事
- 技术栈:使用的框架、组件库、请求库、语法版本
- 不要修改:现有的哪些文件/逻辑保持不动
- 性能与兼容:并发、分页、体积、浏览器要求
【验收标准】
- 用具体用例描述:输入什么、操作什么、期望什么结果
- 代码要求:命名规范、注释要求、不允许使用什么
- 每一条都要可验证
4.2 前端页面类需求示例
前面那个“用户注册页面”我按模板重写了一遍,效果完全不一样:
【背景与目标】 这是面向C端用户的产品注册页,用户通过手机号注册后自动登录并跳转到首页。现有项目已封装好 axios 实例
request,后端注册接口已就绪。【功能与行为】
- 页面顶部展示产品Logo和标题“注册”
- 表单包含三个字段:手机号、密码、确认密码
- 手机号输入框失焦时校验格式(11位、1开头),格式错误时下方红色提示
- 密码至少8位,需包含字母和数字
- 确认密码与密码不一致时提示“两次输入的密码不一致”
- 点击“注册”按钮后调用
POST /api/register,参数为{ phone, password }- 请求期间按钮置灰、显示loading文案,防止重复提交
- 成功后提示“注册成功”,把返回的 token 存入 localStorage,跳转
/home- 失败时展示后端返回的错误消息
【边界与约束】
- 不做图形验证码、短信验证码、忘记密码、登录功能(登录页已有)
- 使用现有
request实例,不重新封装请求库- 样式使用项目现有的 antd-mobile 组件,不新增UI库
- 不修改路由文件,只新增本页面路由
【验收标准】
- 输入 11 位不是1开头的手机号,失焦后出现“请输入正确的手机号”
- 输入两次不一致的密码,点击注册后提示“两次输入的密码不一致”,不发请求
- 正常提交后,localStorage 中出现 token
- 接口失败时提示错误信息,按钮恢复可点击
- 生成页面在 Chrome 和 Safari 最新版显示正常
这套描述我实测生成出来的代码,几乎不需要改动就能直接用。关键的接口调用、校验规则、loading控制全部一次到位。
4.3 后端接口类需求示例
后端接口的需求描述,重点要放在参数、返回结构和异常处理上:
【背景与目标】 为管理后台提供订单列表查询接口,供订单管理页面调用。订单数据在
orders表中,目前已有基础的分页查询工具类PageHelper。运营需要按状态和时间范围筛选订单。【功能与行为】
- 接口路径:
GET /api/admin/orders- 请求参数:
page(默认1)、pageSize(默认20)、status(可选)、startDate、endDate(时间戳)- 返回结构:
{ code: 0, message: "success", data: { list: [...], total: 100 } }- 排序:按
createTime倒序- 鉴权:请求头
Authorization携带管理员token,无token返回401- 参数校验:
pageSize最大100,超过返回参数错误;startDate大于endDate返回参数错误【边界与约束】
- 不做订单导出、不做订单详情接口
- 使用现有
PageHelper和统一返回结果类Result<T>,不新造轮子- 不修改
orders表结构- SQL只查询必要字段,不使用
select *【验收标准】
- 用 curl 携带token调用,返回
code=0且data.total与数据库总数一致- 不带token调用返回401
status传无效值时返回参数错误提示pageSize=1000时返回参数错误- 列表按
createTime倒序排列
4.4 存量代码修改类需求示例
存量代码修改是四要素最被低估的应用场景。改现有代码时,AI最大的风险是“改一个地方带崩另一个地方”,所以边界与约束这一块要写得更重:
【背景与目标】 现有订单列表页
order-list.vue已上线,用户需要批量禁用订单能力。当前表格支持单选操作,新增批量操作后需要再给表格增加复选框列。【功能与行为】
- 表格首列增加复选框,表头有“全选”按钮
- 选中至少1条后,表格上方出现“批量禁用”按钮
- 点击按钮弹出确认框“确定禁用选中的 N 个订单吗”
- 确认后调用
POST /api/orders/batch-disable,参数为{ ids: [...] }- 成功后
ElMessage.success提示并刷新列表,清空选中状态- 失败时提示错误,列表不刷新,选中状态保留
【边界与约束】
- 只修改
order-list.vue和它用到的 store 文件,不修改其他页面组件- 不改变现有单条禁用逻辑和接口
- 保持现有 el-table 和 ElMessage 的UI风格
- 不引入新的npm包
【验收标准】
- 勾选2条数据,点击批量禁用并确认,请求负载包含两个id,成功后列表刷新且两行状态变为 disabled
- 不勾选任何行时,批量禁用按钮不可见或置灰
- 接口失败时出现错误提示,勾选状态保持不变
- 原有单行禁用功能不受影响
这种“存量修改型”需求用四要素写清楚之后,AI给出的 diff 非常可控,review 只需重点看新加的逻辑,不用担心它顺手把整段表格组件重写一遍。
5. 实践中容易踩的四个坑,以及我的应对方式
5.1 坑一:四要素写成了小作文,AI抓不住重点
我一开始也走过极端,把四要素写得事无巨细,背景写了三行、功能写了二十条,排版密得像技术方案文档。结果AI反而变笨了,经常顾此失彼。
后来我总结出一个原则:每个要素控制在3到8条,超过8条就要考虑拆分任务。如果一个功能细节多到写不完,说明这个任务本身就太大了,应该拆成两到三个子任务分多次对话完成。另外,每条尽量一句话讲完一个动作,不要在一行里塞两个逻辑,比如“点击按钮后调用接口且成功后跳转且失败后提示”——这种复合描述让AI很难准确映射到代码分支。
5.2 坑二:验收标准写成了空话
“代码要高质量”“性能要好”“体验要流畅”“命名规范”这类验收标准等于没写。AI看到这些词的时候,无法把它们转化为可执行的检查项,所以大概率还是按自己的理解生成。
我把验收标准的写法纠正成了“具体输入+操作+期望输出”的模式。不要写“性能要好”,要写“列表渲染1000条数据时页面无明显卡顿,滚动帧率不低于50fps”;不要写“命名规范”,要写“接口路径使用 RESTful 风格,动词不放进URL”。AI是能吃下这种指令的,而且吃下之后会体现在代码里。
5.3 坑三:边界和约束自相矛盾
有一次我写约束说“沿用现有请求封装 request,不要重新封装”,验收标准里却写“所有接口请求需要统一在请求拦截器里加token”。仔细一想,加token是修改 request 封装内部逻辑,这不就跟“不要重新封装”冲突了吗?AI生成的代码果然纠结了——它既不想动 request,又想在拦截器里加逻辑,最后生成了个在页面里手动拼header的折中方案,丑得没法看。
解决方案很笨但很有效:提交前自己把四要素从头到尾读一遍,重点交叉检查边界与验收之间有没有冲突。如果确实需要改公共封装,就把“修改 request 的拦截器”明确写进功能与行为里,同时边界里注明“只改拦截器,不改其他方法签名”。
5.4 坑四:连续对话中需求被“污染”,旧需求混进新任务
同一个对话窗口里我连续让它改完一个页面又新建另一个功能,结果新功能的代码里居然带着旧页面的样式片段和变量名。这就是“上下文污染”——AI把对话历史里旧任务的需求信息,错误地套用到了新任务上。
我的应对方式是:一个任务开一个新对话,四要素完整复制进去。如果是同一个任务的迭代修改,也要用“只改第X条”的方式精确定位,而不是把整个需求重贴一遍,否则AI会分不清哪些是更新后的、哪些是作废的。另外,每次新对话的第一句,我会明确写“这是一个全新任务,请忽略之前的任何对话内容”,进一步切断上下文串扰。
6. 用了一段时间之后的个人体会
说实话,需求四要素这个框架本身一点都不高深,拆开看都是常识。但真正用了三个月之后,我发现它最大的价值其实不在“哄AI”——而在逼我自己先把需求想清楚。
以前我写需求是“想到哪写到哪”,脑子里模模糊糊觉得“大概就是做个筛选功能吧”,然后扔给AI去填空。AI填出来不对,我骂它笨,其实是我自己的需求脑图里连“筛选条件要不要重置页码”这种基础问题都没想好。现在每次写四要素,我必须把背景、功能、边界、验收过一遍脑子,很多原本在开发中期才会发现的矛盾,在写需求的五分钟里就暴露了。哪怕完全不考虑AI,光凭这个“想清楚”的过程,返工率也该降下来。
当然,四要素也不是万能药。简单到“把按钮颜色改成主题蓝”这种任务,写全四要素纯属浪费时间,一条功能描述加一条验收标准就够了。我现在的习惯是:十分钟能说清的任务,写两要素;需要跨文件改动、涉及业务流程的任务,才用完整四要素。判断标准很简单——你闭上眼睛能不能在脑子里把代码跑一遍,能跑明白就少写点,跑不明白就老老实实把四要素补齐。
最后分享一个小技巧。我把自己常用的几类任务——前端页面、后端接口、存量改造、BUG修复——都做了固定的四要素模板,存成文档。每次新任务只需要在模板里填空,五分钟就能产出一份高质量的需求描述。写多了之后你还会发现,AI编程的体验上限,大概率取决于你的需求描述下限。把需求说清楚这个基本功补上之后,AI带来的效率提升是实打实的。