AI 辅助开发的方法
通过上下文、任务边界、差异审查和真实验证,把 AI 协作变成可控的开发过程。
建议先读:需求拆解与方案设计
本页内容
把 AI 当作可以分工、需要验收的开发协作者#
本章目标是 L3:你负责界定问题、选择方案和接受结果,AI 帮助调查、实现、解释和验证。多年开发经验最有价值的部分是对需求和边界的判断。转型时应把这种判断延伸到后端与 AI 系统,而不是只追求一次生成更多文件。
一个高质量的协作请求包含目标、上下文、约束、输入输出和验收证据。它不必很长,但需要具体。让 AI “写一个完整知识库系统”会把大量未明确决定交给它;先确定一次资料上传的状态和接口,再让它完成这一条路径,通常更容易审查。
什么上下文真正有用#
提供当前代码、相关接口、错误日志、复现步骤和项目约定。明确哪些信息已经证实,哪些是你的猜测。不要把整仓库或全部聊天历史都塞进去,然后假设模型一定能抓住重点;上下文越多,越需要结构和优先级。
对于跨页面功能,先给出字段链:用户看到什么、服务端字段是什么、类型如何定义、空值如何展示。对于 Bug,提供最小复现和预期行为。对于学习,让 AI 先讲 API 契约,再给例子,避免用一串未解释代码制造“已经懂了”的感觉。
一份可直接复用的任务说明#
下面是协作文本,保存到任务记录即可,无需运行。它的每个字段都对应一个常见风险。
# 为文档上传增加异步解析状态
## 当前事实
- 上传接口会返回 documentId,解析目前在同一请求内完成。
- 大文件会导致网关超时,前端无法知道是否已保存。
## 目标
上传成功后立即展示“解析中”,完成后展示“可检索”。
## 约束
- 用户只能查看自己有权限的文档状态。
- 保留已有文件格式,不改变无关页面。
- 解析失败可重试,但重复请求不能创建重复解析任务。
## 交付
先说明状态模型和接口契约,再实现必要改动。
对成功、失败、无权限、重复重试给出验证结果。
## 未确定的问题
文档更新时是否继续保留上一版可用索引?请明确影响。
当前事实限定 AI 的推理起点;目标避免它解决相邻但不同的问题;约束保护兼容性与权限;交付让“完成”有可观察标准;未确定问题防止猜测被静默固化进代码。这类文档也适合自己写代码时使用。
工作循环:理解、实现、验证、收敛#
先要求 AI 说明根因或方案依据,必要时直接指向代码位置。然后限定一个可审查的改动范围。收到结果后审查 diff,而不只读总结:新增依赖为什么需要、错误处理是否吞掉异常、测试是否真的触发了目标行为、是否修改无关文件。
测试应针对可观察行为,不要把实现逐字复刻为测试。比如要证明用户无法读别人的文档,应构造两个用户请求;仅断言某函数名存在没有意义。AI 可能同时写错实现和测试,因此测试设计也需要你的判断。
# 查看本次变动的范围与内容,以下命令不修改历史。
git status --short
git diff --stat
git diff
# 在项目已经定义对应脚本时执行,不存在的脚本不能假设有效。
npm run typecheck
npm test
这些命令用于获得证据,不是固定仪式。纯文案修改与事务逻辑修改需要不同验证范围。明确列出“通过、失败、未执行”三类结果,不能把工具启动失败写成测试通过。代码可以由 AI 生成,执行结果必须来自真实运行。
如何追问,才能补上理解缺口#
当你看不懂一段代码,可以问:“这个参数是谁提供的?为空会怎样?返回的是立即值还是 Promise?异常在哪一层被处理?删除这一行会出现什么可复现问题?”这些问题比“解释一下全部代码”更容易获得可验证的理解。
当 AI 给出一个结论,要求区分源码事实、官方文档和推断。对于新版本 API,要求核对官方资料,并记录版本。第三方文章可以帮助理解,但不能证明当前接口契约。不要因为一段代码看起来熟悉,就跳过版本与运行环境检查。
并行协作何时有效#
可以把稳定接口下的独立组件、教材章节、只读调查拆给多个协作者。不要同时让两个人改共享类型、路由和全局样式却没有边界约定。并行带来的速度必须扣除合并和验收成本。
分工单要写出拥有的文件范围、输入输出、不能修改的区域和完成条件。主负责人统一术语、错误码、依赖版本和用户体验。每个子任务“自己测试通过”仍然不能替代集成后的总体验收。
常见误区#
运行起来就算完成:只能证明启动路径,不能证明鉴权、并发和失败恢复。一次让 AI 重写整个模块:diff 难以检查,原本正确的行为容易被覆盖。把密钥和私人数据直接贴入上下文:应使用脱敏日志和必要片段。错误后连续要求再试一次:如果没有新证据,通常只是在改变表面实现。
更好的排障方式是每轮记录一个假设和一个实验。例如“代理缓冲了流”可以通过直连后端与经过代理的首事件时间比较来验证;“模型太慢”则需要模型请求的独立耗时证据。不要让前端症状自动变成后端结论。
真实任务拆解:文档更新后一直显示“解析中”#
假设你接到一个反馈:用户上传资料后,列表长期显示解析中,刷新偶尔又恢复正常。这个现象还不能证明解析器有问题。可能是后台确实未完成,也可能是状态查询缓存未失效,或者旧请求覆盖了新请求的结果。你的第一项工作是缩小问题空间,让 AI 调查的对象可定位,而不是立即要求重写上传组件。
先记录具体文档、当前版本、解析任务编号和观察时间。前端保存发起上传、收到响应、开始轮询、收到状态和停止轮询的事件;后端查询该任务的实际状态。日志不需要完整文档正文,只需足够关联的标识。若后台已完成而页面仍解析中,调查重点就转到接口返回、状态映射和前端更新条件。
再提出可以被实验推翻的假设。假设一是请求乱序:旧轮询较晚返回,覆盖了新状态;可以通过延迟不同响应复现。假设二是版本混用:用户上传第二版后仍在查询第一版任务;可以比较前端活跃版本与服务器任务所属版本。假设三是后台任务未提交成功;可以查任务表和错误记录。三种假设对应不同证据,不应同时大范围改动。
给 AI 的调查任务可以明确为:只读追踪 documentId、versionId、jobId 从上传响应到列表状态的字段链;列出每个赋值位置和可能的旧结果覆盖;先不改变接口与数据结构。调查完成后,你再决定修复前端关联、后端返回合同还是任务创建事务。把“调查”和“实现”区分开,可以避免未经证实的猜测直接变成代码。
从业务规则写出接口合同和状态不变量#
业务首先要决定更新资料时怎样展示。旧版仍可检索、同时新版解析中,与更新后整份资料暂不可用,是两种不同产品语义。它会影响 Document 的当前可用版本、正在处理版本,以及列表和问答页面的展示。AI 可以解释两种方案的后果,但不能因为某种代码更容易写就替业务默默选择。
接口合同应把这些含义展开。例如上传响应返回文档标识、新版本标识和任务标识;状态响应说明任务属于哪个版本;问答使用已发布的索引版本。这里是自建应用的设计示例,不是任何外部平台的固定 API。字段名称可以改变,语义必须一致,尤其不能让一个模糊的 status 同时表示文件上传、解析和索引可用。
你可以先写几个不变量作为实现依据:其他文档的事件不能修改当前行;旧版本任务不能覆盖新版本;同一任务的重复事件不能重复应用;终态不能被旧进度回退;无权限响应不能显示为“没有资料”。不变量描述必须始终成立的事实,比要求 AI “做好状态管理”更容易形成可验收实现。
再把规则映射到数据来源。userId 从验证过的会话取得,versionId 与 jobId 来自服务器创建结果,前端请求序号只管理自己的异步关联。不能让前端自行生成一个伪版本并假设数据库也存在。类型定义只是这个合同的编码表现,后端仍要校验实际输入,前端也要处理缺失和未知状态。
一个完整的离线状态实验#
下面例子帮助你审查“乱序覆盖”的修复思路。保存为 document-state.mjs,Node.js 22,无依赖、无网络。它是应用内部状态函数,不是已经实现的上传服务。教材中的标识和事件都是教学数据。
// document-state.mjs
import assert from "node:assert/strict";
const transitions = {
queued: new Set(["processing", "failed"]),
processing: new Set(["ready", "failed"]),
ready: new Set(),
failed: new Set()
};
function applyEvent(state, event) {
if (event.documentId !== state.documentId ||
event.versionId !== state.versionId ||
event.jobId !== state.jobId) return state;
if (!Number.isSafeInteger(event.sequence) || event.sequence < 0) {
throw new Error("事件序号无效");
}
if (event.sequence <= state.lastSequence) return state;
if (!Object.hasOwn(transitions, event.phase)) throw new Error("未知任务状态");
if (event.phase !== state.phase && !transitions[state.phase].has(event.phase)) {
throw new Error("非法状态转移");
}
return { ...state, phase: event.phase, lastSequence: event.sequence };
}
const current = {
documentId: "d1", versionId: "v2", jobId: "j2",
phase: "processing", lastSequence: 3
};
const old = {
documentId: "d1", versionId: "v1", jobId: "j1",
phase: "ready", sequence: 999
};
assert.equal(applyEvent(current, old), current);
const completed = applyEvent(current, {
documentId: "d1", versionId: "v2", jobId: "j2",
phase: "ready", sequence: 4
});
assert.equal(completed.phase, "ready");
assert.equal(applyEvent(completed, {
documentId: "d1", versionId: "v2", jobId: "j2",
phase: "processing", sequence: 3
}), completed);
assert.throws(() => applyEvent(completed, {
documentId: "d1", versionId: "v2", jobId: "j2",
phase: "processing", sequence: 5
}), /非法状态转移/);
console.log("旧版本、重复事件和终态回退检查通过");
执行 node document-state.mjs,预期输出检查通过。将第一段三种标识检查删除,旧版本 ready 事件就会错误修改当前状态,对应断言应失败。这是一个可控制的红绿实验:测试先证明会捕获目标错误,再证明修复使它通过,而不是因为测试文件存在就算有回归保护。
sequence 必须由真实协议定义。如果服务端只是返回无序状态快照,就不能照抄这个序号设计并假装它有全局顺序。你可以采用请求代次、对象版本或服务器事件序号,各自解决的问题不同。本例假设同一任务事件有单调序号,目的是让这一前提可见。重试创建新 jobId 时,应用还需要单独切换活跃任务,不能把旧任务复活。
怎样审查 AI 提交的修改,而不是只看最终页面#
先检查改动范围是否与根因一致。乱序更新问题通常可以在请求关联和状态更新处修复;若 AI 同时更换状态库、删除原接口层和引入新依赖,应该要求解释必要性。改动越大并不代表方案越完整,反而增加你必须重新验证的行为数量。
然后从用户入口顺着数据走一遍:事件由谁创建、携带什么字段、在哪一层校验、何时写入状态、失败如何结束。检查正常路径之外的分支,尤其是 catch、finally、清理函数和默认值。把异常转换为空数组可能使页面“看起来没报错”,却隐藏服务失败;在 finally 无条件重置共享 loading 也可能让并发请求状态混乱。
测试也要沿行为检查。断言请求函数被调用一次,只证明调用次数;断言旧版本事件不会改变当前状态,才对应这个任务的风险。测试输入应能使错误实现失败,而不是仅验证一份写死 fixture。对于后端权限,应使用不同身份和资源关系;对于事务,应注入写入中途失败,而不是只检查成功记录存在。
查看 AI 的验证报告时,把命令、执行目录、退出码和关键输出对应起来。依赖未安装导致测试启动失败,与测试断言失败是不同情况;浏览器跳回登录页,与目标页面验收通过也不同。你可以接受某些验证暂时受阻,但必须知道还缺什么证据,不能让“应该没问题”替代实际结果。
接管不熟悉代码的学习方法#
接管 AI 代码不要求你背下每行实现,而是能解释模块的输入、输出、不变量、外部依赖和失败行为。先从一个真实请求走通,画出简短调用链,再选择其中最不熟悉的一层读官方文档。这样学习服务于当前任务,既不会停留在只会运行,也不会陷入无限阅读所有底层实现。
对每个陌生 API,至少确认参数、返回值、默认行为和异常。比如一个函数返回 Response 还是已经解析的 JSON,会决定工具循环能否读取 output;一个取消信号是否被底层库实际处理,会决定页面取消后任务是否继续。把这些合同写在模块边界,比让 AI 生成一大段泛泛解释更有用。
可以使用反事实问题检查理解:“去掉这个权限条件会让哪条测试失败”“同一响应到达两次会发生什么”“数据库成功后网络断开,用户怎样恢复”。如果你只能复述注释,无法预测改动后果,说明还没掌握边界。让 AI 构造小实验,然后自己先预测结果再运行,是有效的练习方式。
学习阶段可以要求 AI 延迟给出答案:先提供一个具体练习和验收条件,你完成后再对照实现。遇到错误时先给一个定位提示,再根据证据补充解释。这样的节奏能保留你的推理过程;让 AI 同时给题目、答案和“你已经掌握了”的评价,只会制造完成感。
把需求拆成可以集成的工作包#
适合独立分工的任务通常共享已经稳定的合同。例如接口和状态枚举已确定,可以分别实现列表展示、后台状态查询和只读测试设计。若三个任务都需要决定字段含义或修改共享类型,实际上还没有达到可并行条件,应先由主负责人收敛合同。
工作包应写明拥有的文件、输入输出、不可改动区域、依赖的版本和验收方式。完成后主负责人检查接口是否真的一致,再运行跨模块流程。各自局部测试通过不保证集成正确:前端可能把空引用当空数组,后端可能返回 null;两边都有测试,组合起来仍然会出错。
集成时保留每个工作包的实际变化和未验证项。发现冲突不要简单选择“更新的一边”,应该回到业务语义判断。一个代理可能修复请求取消,另一个可能新增重试;合并后若取消触发重试,就必须重新验证两种能力的组合。并行收益来自独立性,不来自代理数量。
综合练习:写一份能接管的修复交付说明#
用本章文档状态案例完成交付说明,要求包含根因证据、合同前提、修改范围、红绿验证、未覆盖边界和后续观察。不要写“优化了状态管理”这类无法核查的概括。
参考交付实现
根因:第二版解析期间,第一版任务的较晚响应进入同一更新函数,因未检查 versionId 和 jobId,覆盖了当前行状态。证据是延迟第一版响应后可以稳定复现,而服务器第二版任务实际仍在处理中。
合同:同一任务事件具有递增 sequence;不同任务序号不可比较。修改仅限状态更新函数和对应行为测试,不改变上传协议及文档权限规则。增加文档、版本、任务三重关联,忽略旧序号,拒绝终态回退。
验证:删除关联检查时,旧版本事件断言失败;恢复检查后,旧版本、重复事件和终态回退检查通过。真实 HTTP 轮询、浏览器刷新恢复和后台重试尚需在项目环境测试,本离线实验不能证明这些已完成。
接管要求:维护者能解释为何不能只比较 sequence,能指出服务器没有顺序保证时需要重新设计合同,并能用相同案例验证未来重构。上线或本地实训观察应记录任务标识和状态变化,不记录完整用户文档。
练习#
把“帮我修一下知识库回答不准确”改写成一个可执行任务。包含至少两个可能原因、每个原因的验证方法,以及你允许修改的范围。
参考答案
先准备三条有已知来源的问题,记录原始查询、检索片段与最终回答。假设一是检索遗漏:检查正确文档是否进入候选集;假设二是生成未遵守证据:在提供正确片段的条件下单独测试生成。先限定只读诊断,确认问题层次后再修改分块、排序或提示词,并在同一问题集上比较结果。
验收与自测#
- 能给 AI 一个包含事实、约束、交付与未知项的任务。
- 能审查一次 diff,并指出至少一个需要独立验证的判断。
- 能接管 AI 生成但运行失败的代码,提出下一次有价值的实验。
问:AI 写了测试,是否还需要你审查测试? 答:需要,测试可能和实现共享同一个错误假设。
问:所有任务都应多代理并行吗? 答:只有边界独立、接口明确时才可能节约整体时间。
问:AI 的总结和终端结果冲突时相信哪个? 答:以实际运行证据为准,并调查差异。