ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

给 AI 的任务描述,为什么应该写验收条件而不只是写需求?

给 AI 的任务描述,为什么应该写验收条件而不只是写需求? 一、一个下午的返工周三下午你收到产品经理的一条消息给订单服务加上取消订单功能。你把它转发给 Agent补了一句记得写测试然后去开会。两个小时后Agent 交活。新增了POST /orders/{id}/cancel接口改了一个 service 方法写了三个测试跑出来全绿。你打开 diff 准备合并越看越不对劲。第一处代码查完订单之后只判断了当前状态是不是已经取消没有判断是不是待处理。也就是说一笔已经支付的订单也能被取消。第二处取消订单时没有写审计事件。你问它为什么它说“你没有说过要写审计。”第三处它顺手把repositories层三个方法改了名字理由是命名更统一。domain/order.py里还多出一个没人提过的字段cancelled_by值永远是空的。第四处三个测试里有一个断言是接口返回 200仓储层全被 mock 掉了从头到尾没有碰过数据库也没有断言订单状态真的变成了CANCELLED。你花了一整个下午修它把状态判断补回去把审计事件补上把改名回滚把那三个测试重写。晚上复盘的时候你意识到一件事Agent 没有偷懒它写了 200 行代码、跑了测试、还认真解释了命名。真正的问题是你只给了它一句话。这句话里没有哪些状态可以取消没有取消时要写审计事件没有这次不要动别的模块。凡是没说的地方它都用自己的理解补上了。补得对不对全靠运气。这篇要解决的就是这件事怎么把我脑子里的要求变成一份 Agent 能执行、你能验收的任务描述。核心动作只有一个——除了写要求还要写验收条件。二、先把几个词讲明白需求你想要的结果。“我要一个能取消订单的功能”这是需求。需求描述了目标但不描述边界。任务描述这一次要做什么、做到什么程度算完成、哪些东西不许碰。举个例子装修时你说我想要客厅亮一点这是需求你说把客厅主灯换成 4000K 色温的吸顶灯今晚八点前装好并当场试亮这是任务描述。前者可以有一百种实现方式后者你能验收。验收条件一组能判断真假的句子每句话对应一个可以执行的检查。“只有待处理的订单能取消是一句验收条件因为它能翻译成一条断言“注意数据一致性不是因为它没法翻译成任何检查。体检报告是很好的类比报告不会写你要健康一点”它会写空腹血糖 5.2参考区间 3.9 到 6.1”每个数值都有判定标准。可判定不需要讨论看一眼或者跑一次就能得出通过或不通过。门锁装没装好伸手推一下、拧一下钥匙就知道门装得好不好这种问题推一下未必知道但锁上之后推不开、钥匙能打开是可以判定的。意图对齐代码做的事和你脑子里想的事是同一件事包括边界情况。跟餐馆点菜说少放盐厨师会按自己的习惯放说不放盐结果就是确定的。多出来的那个少字就是意图没对齐的缝隙。断言程序里的一句判断条件不成立就报错。它是程序世界的红绿灯绿灯放行红灯停下并喊出来。验收条件最终要落到断言或者命令上不然它只是愿望。任务单交给 Agent 的一页短文档。它通常有五段目标、范围、行为、验证、不做。这篇后面会给一份可以照抄的模板。契约双方都按同一份东西执行的依据。验收条件就是需求方和实现方之间的契约——对 Agent 来说它同时是要求清单和交活标准。三、为什么一句话的需求一定会被补全先看语言模型本身。它生成内容的方式是根据已有的上下文一个字一个字地预测最可能的延续。这个机制有一个直接后果信息缺口不会被留空只会被最可能的写法填上。你说实现订单取消模型在训练数据里见过的关于取消订单的最常见写法大概是查到订单、改状态、保存。状态限制这一段在很多教程和示例代码里是被省略的因为它属于业务细节。于是它省略了。审计事件同理——不写也能跑通测试于是它没写。搬运工的例子可以帮我们看清这一点。你说把箱子搬到楼上你会默认他知道是二楼但在他见过的绝大多数场景里楼上指的是三楼、四楼甚至更高。他挑了一个最常见的做法然后把活干完了。他没有违反任何指令因为指令里本来就没有楼层。语言模型和搬运工的区别只在于搬运工可能会问一句几楼而模型更倾向于直接给出一个完整、通顺、看起来合理的答案。它很少有我信息不足先提问的默认行为除非你在任务里明确要求它遇到歧义就停下来问。所以一句增加订单取消功能在系统里实际执行时等价于这样说做你认为最标准的取消功能边界你看着办改动范围你看着办验证方式你看着办。结果对不对取决于它看着办的那套默认值和你脑子里的预期是否碰巧一致。再看第二个问题即使它写对了你怎么知道它对了如果没有验收条件你只能逐行读代码。这个动作的成本很高而且不可靠——人读代码时很容易被看起来合理的写法说服。真正低成本的确认方式是跑检查而检查的前提是你要事先知道什么算通过。把要求翻译成验收条件还有一层副作用是好事写得出来说明你想清楚了写不出来说明需求本身还是模糊的。一个说不清重复取消应该返回什么的人实际上还没有决定自己的产品行为只是把这个决定留给了写代码的人——以前是人现在是模型。意图对齐可以拆成三层验收条件要覆盖这三层第一层行为对能取消的订单能被取消取消之后状态和数据都正确。第二层边界对不能取消的订单会被拒绝而且拒绝的方式是事先约定的那个比如返回 409而不是 500也不是静默成功。第三层不做多余没有顺手重构、没有顺手加字段、没有顺手升级依赖。第三层最容易被忽略也最容易引发意外。模型在完整解决方案的语境里被训练过所以它天然倾向于把周边收拾干净统一命名、提取公共函数、补上它认为缺的字段。这些动作在它自己的逻辑里是加分项在你的评审清单里是风险项。明确的不做清单就是给这种倾向划出的边界写下本次不重构 repository 层比事后逐条回滚省力得多。从控制的角度看任务描述和验收条件分工不同。任务描述属于事前控制把要求提前说清楚减少错误的第一次发生。执行之后的测试和评审属于事后控制发现已经发生的错误。两者都要有——只有事前控制错误仍然会漏过去只有事后控制你会在每个任务上重复交学费。四、完整例子把一句话需求改写成一份任务单这一节用一个虚构的示例项目从头走一遍。项目叫shop是一个订单服务技术栈是 Python 3.12、FastAPI 和 PostgreSQL。目录分成四层src/orders/ api/routes.py # 路由与 HTTP 协议映射 domain/order.py # Order 实体与取消规则 services/order_service.py # 用例编排取订单、改状态、写审计 repositories/order_repo.py # 持久化接口与实现 tests/orders/ # 订单相关测试核心用例是POST /orders/{id}/cancel。业务约定只有两条只有处于待处理状态PENDING的订单可以被取消取消成功时要往审计表里写一条事件。订单有三个状态PENDING、PAID、CANCELLED。起点是我们最熟悉的那种写法一行字给订单服务加取消订单功能记得写测试。现在把它改写成任务单。下面这份文件可以直接存成docs/tasks/cancel-order.md交给 Agent 时把全文粘进任务描述。注意它的结构五段没有一段在教 Agent 怎么写代码。## 目标 为 shop 订单服务增加 POST /orders/{id}/cancel让待处理订单可以被取消。 ## 范围 - 允许修改src/orders/api、src/orders/services、src/orders/domain、tests/orders - 允许新增tests/orders/test_cancel.py - 禁止修改src/orders/repositories 的方法签名数据库迁移其他模块 - 本次不做重命名任何已有函数新增数据库字段升级依赖 ## 行为 - 只有 statusPENDING 的订单可以取消成功后 statusCANCELLED - 取消成功时在同一事务中写入恰好一条审计事件事件类型 order.cancelled - 已支付订单PAID调用取消返回 409 - 已取消订单CANCELLED再次调用取消返回 409 - 订单不存在返回 404 - 失败响应使用 RFC 9457 的 Problem Details 格式 ## 验证 - 命令pytest -q tests/orders - 命令ruff check . - 通过标准两条命令都以退出码 0 结束 - 交活时必须附带命令原文、退出码、完整测试输出 ## 不做 - 不改动订单创建、支付相关代码 - 不引入新的第三方库 - 不调整既有测试除非它们与上面的行为冲突冲突时先停下来说明这份任务单里最值钱的部分是行为段因为它把一句需求变成了六句可判定的话。把它们再整理一次会得到一张验收条件表每条一个编号方便在评审和对话里点名编号给定当那么A1订单状态为 PENDING调用取消接口返回 200且订单状态为 CANCELLEDA2订单状态为 PENDING取消成功审计表新增恰好 1 条 order.cancelled与状态变更同一事务A3订单状态为 PAID调用取消接口返回 409订单状态仍为 PAIDA4订单状态为 CANCELLED再次调用取消接口返回 409不新增审计事件A5订单 ID 不存在调用取消接口返回 404A6任意失败情形收到响应响应格式符合 RFC 9457A7任意情形运行验证命令pytest 与 ruff 退出码均为 0这张表有两个作用。对 Agent 来说它是实现清单六条行为逐一落实。对你来说它是验收清单交活之后一条一条对不用凭感觉觉得差不多。再加一层对比会更清楚。左边是需求式写法右边是验收式写法需求写法验收条件写法增加订单取消功能仅PENDING订单可取消PAID与CANCELLED返回 409接口要可用成功返回 200响应体里status为CANCELLED注意数据一致性状态变更与审计事件在同一事务提交审计事件恰好 1 条记得测试pytest -q tests/orders与ruff check .均以退出码 0 结束右列的每一句话都有一个特征它能被翻译成一条断言、一次查询或者一条命令。翻译不出来就说明它还不是验收条件。注意数据一致性听起来很专业但它没有告诉任何人去检查什么也没有告诉任何人怎么检查。任务单写好之后还有一个容易被跳过的动作把命令也写清楚。pytest -q tests/orders和ruff check .就是两条命令退出码为 0就是通过标准。退出码是程序世界的红绿灯0 表示这件事做完了非 0 表示没有不写这个标准验收时又要靠人去猜。4.1 把验收条件落到测试代码验收条件表里的每一条最后都要有东西替你检查。A1、A3、A4、A5 靠测试A2 靠一条带数据库的集成测试A6 在一次测试里断言响应结构A7 是命令行本身。下面是一份测试文件的关键部分放在tests/orders/test_cancel.py。importpytestfromfastapi.testclientimportTestClientfromorders.api.routesimportappfromorders.domain.orderimportOrder,OrderStatus clientTestClient(app)deftest_cancel_pending_order_succeeds(repo,audit):orderrepo.add(Order(ido-1,statusOrderStatus.PENDING))respclient.post(/orders/o-1/cancel)assertresp.status_code200# A1状态码assertresp.json()[status]CANCELLED# A1状态变化assertrepo.get(o-1).statusOrderStatus.CANCELLEDassertlen(audit.events_of(o-1,order.cancelled))1# A2恰好一条pytest.mark.parametrize(initial_status,[OrderStatus.PAID,OrderStatus.CANCELLED],)deftest_cancel_rejects_unavailable_status(repo,initial_status):repo.add(Order(ido-2,statusinitial_status))respclient.post(/orders/o-2/cancel)assertresp.status_code409# A3 / A4拒绝方式assertrepo.get(o-2).statusinitial_status# 状态没有被改动deftest_cancel_missing_order_returns_404(repo):respclient.post(/orders/not-exist/cancel)assertresp.status_code404# A5bodyresp.json()assertbody[status]404# A6Problem Details 关键字段assertbody[title]in{Order not found,订单不存在}这段代码里有三个细节值得注意它们都是验收条件翻译过来的第一断言的是业务结果不是函数被调用过。assert repo.get(o-2).status initial_status检查的是订单没有被改动这条断言能挡住先改状态再回滚这类错误。如果只断言仓储的 save 方法被调用了一次代码里把状态改错测试照样是绿的。第二被测对象尽量走真实链路。接口测试里只把数据库换成了内存仓储service 和 domain 都是真代码。这样PENDING的判断逻辑、审计事件的写入逻辑都在测试范围内。第三pytest.mark.parametrize把PAID和CANCELLED两种情形合并了但断言方式统一状态码 409、状态不变。A3 和 A4 各自的不新增审计事件还需要一条针对CANCELLED的补充断言可以加在同一个测试里。实现侧的关键代码大概率长这样两部分领域规则和用例编排。# src/orders/domain/order.pyclassOrderStateError(Exception):订单当前状态不允许执行该操作。defensure_cancellable(status:OrderStatus)-None:ifstatusisnotOrderStatus.PENDING:raiseOrderStateError(forder in state{status}cannot be cancelled)# src/orders/services/order_service.pydefcancel_order(repo,audit,order_id:str)-Order:orderrepo.get(order_id)iforderisNone:raiseOrderNotFound(order_id)ensure_cancellable(order.status)withrepo.transaction():order.statusOrderStatus.CANCELLED repo.save(order)audit.append(eventorder.cancelled,order_idorder_id)returnorderwith repo.transaction():这一段对应的就是 A2 里同一事务的要求。状态保存和审计写入在同一个事务里两者要么一起成功要么一起失败。路由层再把这个异常映射成 HTTP 状态码OrderStateError映射 409OrderNotFound映射 404响应体用 RFC 9457 的字段结构。验收条件写到这里已经从一句话变成了三样东西一张行为表、一份测试、一套异常到状态码的映射。任何一样缺失你都会在交活之后才发现。4.2 交活之后花五分钟做三件事Agent 说做完了之后不要立刻读 diff。先做三件事每件事一分钟左右。第一件看改动范围。运行git diff --stat把文件列表和任务单里的范围段对照。允许改的地方之外出现了文件先停下来问而不是自己顺手回滚。这一步能挡住大部分顺手重构。第二件跑验证命令并且亲眼看到退出码。命令是任务单里写好的那两条$ pytest -q tests/orders 12 passed in 1.21s $ ruff check . All checks passed!上面是示例输出真实的数字并不重要重要的是两件事命令真的跑了退出码是 0。如果 Agent 只回了一句测试全部通过而没有命令原文、退出码和完整输出那这次交活是不合格的——不是因为它在撒谎而是因为它说通过不能当证据。第三件拿着验收条件表逐条对。对的时候有个顺序技巧先对失败路径A3、A4、A5再对成功路径A1、A2。失败路径容易被漏掉也最容易在上线之后变成工单。对不上的条目记录下来改任务单或者改代码别只留在聊天记录里。这三件事做完你才决定要不要逐行读代码。很多任务到这一步就已经可以合并了需要精读的通常是那些改动范围超出预期、或者验收条件本身没写清楚的任务。4.3 交给 Agent 之前先让它复述一遍还有一个小动作能把返工率再压低一截让 Agent 在动手之前用自己的话复述任务单。不用长篇大论三条就够——它理解的目标、它准备改的文件、它准备运行的命令。复述的价值在于暴露歧义。比如它回复我理解要返回 409但我看到CANCELLED再次取消时你们可能希望幂等返回 200我先按 409 做。这句话立刻暴露了一个产品问题重复取消到底应该报错还是静默成功。你在它写代码之前用一句话就能定下来如果在交活之后才发现代价是改代码、改测试、再评审一遍。复述还有一个附带效果它强迫 Agent 先读任务单再动手。任务单里那些不做的条目只有在被读到的前提下才会生效。4.4 一条界线写什么算通过不写怎么实现任务单写到一定长度很容易滑向另一个方向开始教 Agent 怎么写代码。这条界线值得专门划一次。判断标准很简单这条要求能不能被外部观察到状态码、响应体字段、数据库里的状态和审计事件、命令的退出码都能被观察到属于行为写进验收条件。用哪个类、把逻辑放在 service 还是 domain、查询用 ORM 还是手写 SQL通常观察不到属于实现选择不写。看一组对比。左边是把实现写死的写法右边是同一件事的行为写法写成实现说明书写成验收条件必须新建CancelOrderHandler类POST /orders/{id}/cancel返回 200 且状态变为 CANCELLED必须用UPDATE ... WHERE statusPENDING单条 SQL 完成并发两次取消只有一次成功另一次得到 409必须在 service 里raise OrderStateError状态不允许时返回 409响应符合 RFC 9457左列的写法看起来更严格实际上更脆弱它把评审的注意力从行为拉到了写法上。Agent 会认真满足字面要求——新建一个没人需要的类、为了一条 SQL 绕开既有的事务封装——而你要的行为是否成立它反而没被要求证明。有一个例外要记住如果某个实现细节本身会改变可观察行为它就该写进去。比如状态变更和审计事件必须在同一事务里这是一条实现约束但它决定了一条可观察的结果——失败时两者都不应该留下。再比如超时后重试必须使用同一个幂等键这也写。区分方法还是那句话把它反过来想违反这条要求有没有一条测试或者一次查询能发现能就是行为或者关键约束不能就删掉。4.5 一次对照同一句需求两种任务描述把前面的例子做成一次对照记录虚构示例用来演示流程不是研究结论。同一个shop项目、同一个需求增加订单取消功能、同一套验收条件表只换任务描述一次用一句话一次用完整的五段任务单。一句话版本的交活结果是这样接口能跑成功路径通过PAID订单也能被取消A3 不通过重复取消返回 500A4 不通过repositories层被改了名字范围检查不通过审计事件经常缺一条A2 时好时坏因为没有和状态变更放在同一事务。四条要修的问题里三条来自没写进任务单的边界一条来自没写进任务单的范围。任务单版本的交活结果A1 到 A5 一次通过A6 的响应格式里字段名对不上改了一处范围检查通过没有多余改动。整个验收过程是跑两条命令、对一遍表格、改一个字段名。这个对照想说明的不是某种写法一定更好而是一个成本的分布差异。一句话版本把成本堆在了交活之后——你花在识别问题、描述问题、验证修复上的时间全都发生在任务后期。任务单版本把一部分成本提前到了开工前的二十分钟。写任务单的那二十分钟是确定要花的而识别和返工的时间是不确定的任务越复杂、涉及的边界越多后者膨胀得越厉害。还有一个更隐蔽的差别一句话版本的修复过程会把评审变成多轮对话。你要先把问题说出来Agent 改一轮你再验一遍每一轮都可能引入新的偏差。任务单版本的验收一次到位因为A3 不通过这种话你在开工前就已经定义好了——它既是给 Agent 的要求也是给你自己的检查工具。4.6 检查手段从便宜到贵按顺序选给验收条件配检查手段时有一个便宜到贵的顺序可以遵循静态检查、单元测试、集成测试、端到端测试、人工步骤。原则是用能发现这个错误的最便宜的那一档而不是一律上最重的。格式和命名类的条件交给静态检查。比如新代码没有未使用的导入“文件命名符合约定”ruff check .这类工具一条命令就能覆盖不需要写测试。纯逻辑的条件交给单元测试。ensure_cancellable在不同状态下应该抛错还是放行这种规则不依赖数据库几十毫秒就能跑完失败信息也最直接。涉及持久化、事务、审计的条件交给集成测试。A2 要求状态变更和审计事件在同一事务里只有真的连上数据库才能验证失败回滚时两边都不留痕。用内存仓储模拟事务测出来的是模拟物的行为不是数据库的行为。跨模块串起来的条件才轮到端到端测试。它的运行慢、定位问题难所以留给真正需要全链路的检查比如取消成功后查询接口能读到CANCELLED。人工步骤放在最后而且要有明确的检查清单不能写成看看是否正常。人工检查的典型场景是界面文案、通知内容这类暂时没有自动化的部分。每次人工检查之后问一句这条能不能变成自动化检查能就排进任务清单不能就把它写进这次的人工检查表下次照着做。把验收条件按这个顺序分配一遍你会得到一个能力清单哪些错误在提交前就被拦住哪些要等到集成测试哪些只能靠人。这份清单本身就是项目质量结构的一部分。五、反例与代价四种看起来能行的做法5.1 反例一只写需求靠评审兜底做法任务描述就是一句话靠交活后的人工评审保证质量。它为什么看起来能行小团队里评审确实能挡住大部分明显错误。你自己写的任务自己验收凭经验能看出多数问题。最后的代价有三层。第一层评审要在没有验收标准的情况下判断对错注意力只能放在代码看起来是否合理上这类判断很容易被通顺的代码说服。第二层任务越多评审越像抽样检查你看得越潦草漏网越多看得越仔细时间成本越高。第三层也是最隐蔽的一层任务描述里没写的边界会长期缺席这一版漏了PAID的判断下一版重构时照样没有因为它从来没有出现在任何清单上。5.2 反例二验收条件写成口号做法任务里加了一段验收标准——性能要好、代码要健壮、注意数据一致性、日志要清晰。它为什么看起来能行形式上它满足了要写验收条件这个要求评审时看起来也像有标准。最后的代价这几句没有任何一句能被检查。Agent 读完之后仍然不知道要做什么只能按默认理解补全你在验收时也没有可对的东西最终还是回到看着还行。更糟的是口号会给人一种已经想清楚的错觉掩盖了真正的分歧——你在评审时才发现双方对健壮的定义不一样那时代码已经写完了。修法很直接把口号改成可判定的句子。“性能要好改成接口在本地测试环境下的响应时间记录在交付说明里”先做到可观测再谈指标“注意数据一致性改成状态变更与审计事件在同一事务提交失败时两者都不保留”“日志要清晰改成失败时日志包含订单 ID、当前状态、拒绝原因三个字段”。5.3 反例三验收条件写成实现说明书做法为了让标准更硬把实现方式写进验收条件必须建某个类、必须走某条 SQL、必须按某个顺序调用方法。它为什么看起来能行要求越具体看起来越可控评审时逐条对似乎也更快。最后的代价有两条。第一它排除了更好的实现方案而这些方案本来可以在同样的验收条件下写出来。第二更隐蔽——当验收条件里混着大量实现细节时真正重要的行为要求会被稀释。Agent 的注意力是有限的它忙着满足新建某个类PAID返回 409 这种真正要紧的事可能被放到了次要位置。评审时你也一样一条条对实现细节反而漏掉了行为。5.4 反例四任务单只活在聊天记录里做法任务描述写得挺好但发完就算了没有进仓库。它为什么看起来能行这一次任务确实按任务单做完了效果立竿见影。最后的代价在时间维度上展开。下一次有人改取消功能时他不知道当初的边界是怎么定的只能重新猜测试里为什么CANCELLED返回 409 而不是 200也没有任何地方解释。几个月后同一条规则再被讨论一遍上一次的结论已经找不到了。把任务单放进仓库比如docs/tasks/成本是一分钟收益是它成了后续修改的依据、评审的对照表、以及未来 Agent 的输入材料。六、落地步骤从一句话到一份可验收的任务下面是七个步骤按顺序做。每一步都写了做什么、为什么、怎么检查最后附上可以直接复制的模板。第一步把需求写成五段任务单。目标是让任务描述从一段话变成一张图目标、范围、行为、验证、不做。为什么要有不做这一段因为模型的补全倾向需要一个明确的边界而边界只有写出来才算存在。怎么检查五段是否齐全随便挑一段里面有没有尽量“注意”适当这类无法执行的词有就改掉。第二步把行为段翻译成验收条件表。用给定、当、那么的句式一条一条列每条给一个编号。为什么用编号因为验收和沟通需要一个共同的名字说A3 没通过比说那个已支付的订单好像还能取消精确得多。怎么检查每一条读一遍问自己这句话能被判定真假吗不能就改写。第三步给每条验收条件配一个检查手段。检查手段有三种自动化测试、命令、人工步骤。为什么单独走这一步因为验收条件写得再漂亮没有检查手段就只是愿望这一步会把空缺暴露出来。怎么检查验收条件表里每条后面写清楚由哪条测试或哪条命令覆盖出现连续三条都靠人工步骤时考虑补自动化或者把这几条降级为不影响本次交付。第四步把验证命令和通过标准写进任务单。命令要能复制粘贴标准通常是退出码为 0特殊情况才写指标。为什么必须写命令而不是写跑一下测试因为跑一下测试有十几种跑法不同跑法的结果不一样。怎么检查自己在干净的环境里先把这几条命令跑一遍确认它们现在就能跑、失败时的输出对人有帮助。第五步让 Agent 在开工前复述任务。三条它理解的目标、它准备改的文件、它准备运行的命令。为什么在开工前做因为这时候纠错的成本最低一句话就能调整方向。怎么检查复述里出现了你没想到的文件名或者它提出的问题你答不上来都要先停下来把任务单补清楚再开工。第六步交活后按固定顺序验收。顺序是改动范围、验证命令、验收条件表、最后才决定要不要精读代码。为什么是这个顺序因为前三步都是低成本的机器检查能快速排除大部分问题精读代码最贵应该留给真正可疑的任务。怎么检查验收完成后把命令原文、退出码、输出摘要记录进交付记录和任务单放在一起。第七步把任务单归档进仓库。放在docs/tasks/之类的目录PR 描述里引用它。为什么归档因为它记录了当时为什么这样定这是后来的修改者和未来的 Agent 都需要的信息。怎么检查三个月后要改动同一个功能时能不能在这份文件里找到相关边界——找得到归档就是有效的。下面这份模板可以直接复制按项目改文件名和命令## 目标 一句话这次要交付什么解决什么问题。 ## 范围 - 允许修改目录或文件列表 - 允许新增文件列表 - 禁止修改签名、迁移、其他模块 ## 行为 - 正常路径给定…当…那么… - 边界与失败每种拒绝情形的响应与状态 - 数据要求事务、审计、幂等性 ## 验证 - 命令 1可复制的命令 - 命令 2静态检查命令 - 通过标准退出码为 0 - 交活附带命令原文、退出码、完整输出 ## 不做 - 本次明确排除的重构、重命名、依赖变更验收条件检查表交活前对着数一遍[ ] 每条验收条件都能回答做什么、怎么检查 [ ] 失败路径至少覆盖不存在、状态不允许、重复操作 [ ] 每条验收条件有检查手段测试 / 命令 / 人工步骤 [ ] 验证命令可以复制粘贴且有明确的通过标准 [ ] 不做清单非空且写出了最容易顺手改的内容 [ ] 任务单已归档到仓库PR 描述里引用了它七、常见问题问任务很小也要写验收条件吗要但可以很短。判断标准不是任务大小而是做错的代价和验收的难度。改一行文案验收条件可以是页面上的旧文案全部替换rg 旧文案 src/无结果——十秒钟写完。真正可以省掉的不是验收条件而是形式小任务不需要完整五段一句话加上一条命令就够。反过来凡是涉及状态变化、数据写入、权限判断的任务无论看起来多小都值得花五分钟把边界列出来因为这类错误的代价通常不是改一行代码而是修数据。问验收条件和测试用例是一回事吗不是它们是两层东西。验收条件是用人和机器都能读懂的语言写的判定句属于意图层测试用例是代码属于实现层。一条验收条件可能由多个测试覆盖比如 A1 既测状态码又测状态变化也可能暂时没有自动化测试只能用手工步骤检查比如取消后收到通知邮件。反过来说一条测试也可能同时覆盖两条验收条件。把两者分开写的好处是测试可以重构、可以改写验收条件保持稳定评审时对照的应该是验收条件不是测试代码的写法。问需求经常变写这些会不会白费会白费的只有那些被推翻的部分而没写的情况下白费的是返工。拿前面的例子算一笔账写五段任务单大约二十分钟。如果需求变了改的是任务单里的两三句话再重跑一次命令。如果不写任务单需求变了你未必知道——任务描述还是那句话Agent 按它的默认理解往下做你要等到交活之后才能发现方向不对那时候改的是代码、测试和评审记录。另外需求经常变本身就是写验收条件的理由变化的信息如果没有落在任务单里它就只存在于聊天记录里而聊天记录既不会被 Agent 读到也很难被下一个接手的人找到。问我不知道具体的返回码、错误格式这些细节怎么办把它当成任务的一部分先定下来而不是留给 Agent 猜。定法有三种查项目已有的约定其他接口怎么返回错误通常有惯例问相关方前端需要什么字段、监控依赖什么状态码使用通用标准比如 HTTP API 的错误响应可以用 RFC 9457 的 Problem Details 格式。三条路都走不通就把这一条标成待确认并明确写出待确认项没有结论前不要开始实现。模型不会因为你不确定就停下来它只会按最常见的方式补全而最常见未必是你的团队约定。问一条任务写多少条验收条件合适从五到八条起步覆盖三类成功路径、失败与边界、范围和一致性约束。判断数量是否合适不看条数看两件事每条是否可判定每条是否有检查手段。出现下面两种信号就调整一种是连续多条都靠人工检查说明任务本身还没有准备好自动化考虑拆小另一种是所有条目都在描述同一种行为的不同写法比如三条都在说状态码合并它们。验收条件不是越多越好它需要能在几分钟内从头对一遍。问可以让 Agent 自己写验收条件吗可以让它起草但你必须审核尤其是产品语义的部分。它擅长的是补全常见边界不存在返回什么、重复操作返回什么、空输入怎么处理这些在训练数据里有大量先例。它不擅长的是替你做决定重复取消应该是报错还是幂等成功、审计事件里要不要记录操作者、取消之后能不能恢复这类问题取决于你的产品和业务没有最标准的答案。可行的工作方式是让它先读需求产出验收条件草案你逐条审核并把决定写回任务单然后它才开始实现。这样既省了你起草的时间也没有把决策权交出去。问性能、体验这类模糊要求怎么写成验收条件第一步是先把它变成可观测的现象第二步才是定标准。接口要快不可观测记录取消接口在测试环境下的响应时间可观测。先做到可观测你至少有了证据可以讨论再往上加阈值才有意义。体验要好同理先定义用户动作点击取消按钮后界面在两个动作内给出明确结果。需要特别提醒的是不要在验收条件里写你没有来源的性能数字或者比例。没有依据的数字比没有数字更危险它会让评审以为有了标准实际上那只是编的。问项目里已经有测试了为什么还要写任务单因为测试和任务单服务的是不同的读者。测试代码是给机器和写代码的人看的它篇幅长、夹着实现细节评审时想从测试里还原这次到底要什么要花不少时间。任务单是给人和 Agent 看的意图文档一页之内说清楚目标、范围、行为、验证、不做。还有两个现实问题测试可能跑不起来、可能没覆盖这次要改的部分而任务单即使在没有自动化测试的项目里也一样有效——验收条件可以靠命令和人工步骤落地。两者叠加使用效率最高任务单告诉你要什么测试证明做到了。问任务单和 AGENTS.md 有什么区别会不会写重复它们解决的是两个时间尺度的问题。AGENTS.md 是仓库级的长期约定回答这个项目里一贯怎么做事用什么命令验证、代码放哪里、哪些操作禁止。任务单是本次任务的约定回答这一次要做什么、做到什么算完成。用常量和变量类比AGENTS.md 是常量任务单是变量。重复通常出现在验证命令这类条目上这不算浪费——AGENTS.md 里的命令供所有任务复用任务单里的命令指明本次要跑哪几条、通过标准是什么。维护上有一条简单规则某条要求如果下一个任务还需要就把它从任务单挪进 AGENTS.md如果只对本次有效就留在任务单里。冲突时以任务单为准因为它更靠近当前任务也更具体。问任务单写好之后Agent 说做不了怎么办先把做不了拆开看是哪一种。第一种信息不足它需要知道某个约定而任务单里没写。这个最好处理——补上或者明确回答它的问题然后继续。第二种约束冲突任务单里两条要求互相矛盾比如不改动既有测试和某条既有测试与本任务行为冲突。这种情况要你来做取舍通常是让任务单里的行为要求优先并在交付说明里记录改了哪条测试、为什么。第三种任务超出范围它发现需要动数据库迁移、需要新的第三方库或者需要生产凭证才能验证。这一类必须停下来升级给人的决定因为任务单里的范围和不做正是为了把这种情况暴露得早不是为了让 Agent 硬做。三种情况有一个共同的处理原则改动规划任务单而不是直接改实现。任务单是这次工作的依据它变了后面的验证和记录也要跟着变。问验收条件应该由谁写由对结果负责的人写也就是任务的需求方。可以是产品经理、技术负责人或者你自己。Agent 可以参与其中两个环节一是起草常见边界不存在、重复、并发这些情形它比你更不容易忘二是检查你的验收条件里有没有自相矛盾或者写得不可判定的条目。但它不能替你做产品决策也不该是验收条件的唯一作者。原因很简单验收条件是什么算完成的定义谁定义谁验收如果这个定义由实现方写验收就变成了自己给自己出题——写出来的标准很可能恰好覆盖它能做到的事。你的角色是把关两件事边界覆盖是否完整、每条是否可判定。八、动手练习与小结练习把一句话任务改写成可验收任务挑一个你手头真实的小任务按下面四步走一遍产出一份可以直接发给 Agent 的任务单。第一步写下原始需求保持它原来的样子——通常是一两句话。别急着美化这一步只是为了有个对照。第二步回答五个问题每个问题写一到两句话这件事做完之后什么会发生变化允许改哪里、禁止改哪里正常路径是什么失败路径有哪些每种失败应该看到什么这次明确不做什么这五个答案就是任务单的五段。第三步把行为段翻译成验收条件表用给定、当、那么的句式每条编号。然后给每条配一个检查手段测试、命令还是人工步骤。配不出来的条目要么补测试要么删掉。第四步写出验证命令和通过标准自己在改动之前先跑一遍确认命令能跑、失败输出可读。最后把这份任务单存档并在任务结束后记录实际使用的命令和退出码。做完之后你会得到三样东西一份五段任务单、一张带检查手段的验收条件表、一份本次任务的交付记录。下一次同类任务把任务单改几个词就能复用。小结这一篇讲的是一件事需求描述目标验收条件描述完成。前者说我要什么后者说做到什么算完成、怎么证明。模型在没有验收条件时会用最常见的写法补全空白补全对不对取决于运气有了验收条件边界从它自己猜变成事先约定你的验收也从逐行读代码变成对表跑命令。还记住三个边界验收条件要可判定写不出检查手段的条目不是验收条件验收条件写行为不写实现除非该实现细节本身决定可观察结果任务单要归档否则它对下一个任务、下一个人、下一个 Agent 都不存在。这一篇落在链条的起点上。前一篇讨论的是修正循环什么时候该停这一篇处理的是循环开始前的输入——把要求写清楚能减少循环本身的发生。下一篇往输出侧走Agent 交回来的东西如果是机器可解析的结构化数据验收就不再只靠人看。两篇连起来是一进一出进来的任务单要可判定出去的交付物要可校验。
返回列表