目录
- 一、Copilot 与 Agent:差别在哪里
- 1.1 三代 AI 编程工具
- 1.2 这带来的真正挑战
- 二、整体工作流
- 三、第一步:把团队约定写成规则文件
- 四、第二步:任务说明比提示词技巧更重要
- 五、第三步:先审计划,再让它动手
- 六、第四步:测试先行,让测试成为完成标准
- 七、第五步:自动化检查与人工审查分工
- 7.1 能自动化的交给流水线
- 7.2 人工审查重点
- 八、哪些任务适合交给 Agent
- 九、风险与应对
- 十、如何度量效果,而不是凭感觉
- 十一、开发者角色的变化
- 参考资料
适合谁读:已经在用 Copilot 类代码补全工具,想进一步让 AI Agent 承担完整开发任务,但又担心质量失控的开发者和技术负责人。
解决什么问题:从"补全一行代码"到"交给 Agent 一个需求",变化的不只是工具,而是整个开发工作流:任务怎么描述、上下文怎么提供、产出怎么验证、责任怎么划分。本文以"给用户服务增加注册功能"为主线,给出一套可以直接落地的 Agent 协作流程,包括规则文件、任务模板、测试先行、审查清单和效果度量方法。
本文不罗列"效率提升了多少倍"这类无法复现的数字,而是讲清楚每一步该怎么做、为什么这么做。Agent 系统本身的架构设计,见本系列上一篇 AI Agent 生产级架构设计。
示例项目环境(文中的规则文件、任务说明和测试代码均基于此环境):
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 21 | 使用 record、虚拟线程等新特性时需在规则文件中说明 |
| Spring Boot | 3.4.x | 3.4 起测试中使用@MockitoBean替代已废弃的@MockBean |
| JUnit / Mockito | JUnit 5.11、Mockito 5.x | 由 spring-boot-starter-test 统一管理版本 |
| MySQL / Redis | 8.0 / 7.2 | 集成测试通过 Testcontainers 启动 |
| 构建工具 | Maven Wrapper(./mvnw) | 保证 Agent 与 CI 使用同一 Maven 版本 |
一、Copilot 与 Agent:差别在哪里
1.1 三代 AI 编程工具
| 形态 | 交互方式 | 上下文范围 | 人的角色 | 典型产品形态 |
|---|---|---|---|---|
| 代码补全 | 光标处自动建议 | 当前文件及少量相邻文件 | 逐行编写,AI 辅助 | IDE 补全插件 |
| 对话助手 | 选中代码提问、生成片段 | 用户手动提供的代码 | 复制粘贴、自行整合 | IDE 内聊天面板 |
| 编程 Agent | 描述任务,Agent 自主读代码、改文件、跑命令 | 整个仓库 + 终端 + 工具 | 定义任务、审查结果 | IDE Agent、终端 Agent、云端异步 Agent |
前两代工具中,人始终在环路的每一步:每一行代码都经过人的眼睛。第三代工具的本质变化是:Agent 可以连续执行几十个动作(搜索代码、修改多个文件、运行测试、根据报错修正),人只在开始和结束时介入。
1.2 这带来的真正挑战
效率提升的同时,风险的形态也变了:
- 错误的粒度变大:补全错一行,人立刻能看到;Agent 改错了十个文件里的某一处,很可能在审查时被忽略;
- 上下文决定质量:Agent 不知道团队的约定(例如"金额必须用
BigDecimal"“禁止在 Controller 里写业务逻辑”),就会按"网上最常见的写法"生成代码; - 验证成本转移:写代码的时间减少了,但审查和验证的时间如果不刻意设计,会成为新的瓶颈。
所以,用好 Agent 的关键不是"提示词写得多巧妙",而是把团队的隐性知识显性化,并建立自动化的验证闭环。
二、整体工作流
与传统流程相比,有两个关键的新增环节:先审计划再动手,以及以测试作为 Agent 的"完成定义"。下面逐步展开。
三、第一步:把团队约定写成规则文件
Agent 每次开始任务时都是"新同事",它不会记得上次你纠正过什么。把反复出现的约定写进仓库根目录的规则文件(如AGENTS.md,各工具也有自己的规则文件约定),Agent 在每次任务开始时都会读取。
# AGENTS.md ## 项目概况 - Spring Boot 3.4 + JDK 21 + MySQL 8.0 + Redis 7.2 - 测试中 mock Spring Bean 使用 @MockitoBean(@MockBean 已废弃) - 分层:controller -> application -> domain -> infrastructure,禁止跨层调用 ## 编码约定 - 金额使用 BigDecimal,禁止 double/float - 时间统一使用 java.time,存储为 UTC - 对外接口返回 Result<T>,异常由 GlobalExceptionHandler 统一处理 - 新增 SQL 必须走 MyBatis XML,禁止字符串拼接 SQL ## 构建与验证 - 编译与测试:./mvnw -q verify - 只运行某个测试:./mvnw -q test -Dtest=UserRegisterServiceTest - 提交前必须保证 verify 通过 ## 禁止事项 - 不要修改 db/migration 下已发布的迁移脚本,只能新增 - 不要引入新的第三方依赖,除非任务说明中明确允许 - 不要在日志中打印手机号、身份证号、密码等敏感信息规则文件的编写原则:
- 只写 Agent 不看代码就无法知道的事:项目结构 Agent 自己能读出来,但"为什么禁止 double"它无法推断;
- 给出可执行的验证命令:这是 Agent 判断"自己做没做对"的依据;
- 持续维护:每次在审查中纠正 Agent 的同类错误超过两次,就把它补进规则文件。
四、第二步:任务说明比提示词技巧更重要
一个好的任务说明,应该能让一个不了解背景的新同事独立完成工作。以"用户注册"为例:
## 任务:用户注册接口 ### 背景 用户服务目前只有登录,需要新增手机号注册。 ### 验收标准 1. POST /api/users/register,参数:phone、password、smsCode 2. 手机号已注册时返回错误码 USER_PHONE_EXISTS 3. 短信验证码错误或过期时返回 SMS_CODE_INVALID,验证码校验通过后立即失效 4. 密码使用 BCrypt 存储,强度至少 8 位且包含字母和数字 5. 并发重复提交同一手机号时,最终只能创建一个用户 ### 约束 - 复用现有的 SmsCodeService,不要自己实现验证码逻辑 - 唯一性以数据库唯一索引为准,不要只依赖"先查后插" ### 不在本次范围 - 第三方登录、邀请码注意第 5 条和"约束"中的唯一索引:这类并发与一致性要求是 Agent 最容易遗漏的,因为"先查询再插入"在功能测试中完全正常,只在并发时出错。把它明确写进验收标准,比事后审查发现要便宜得多。
五、第三步:先审计划,再让它动手
对于涉及多个文件的任务,先要求 Agent只输出计划、不修改代码,审查后再执行。计划应包括:要修改和新增哪些文件、数据库变更、关键实现思路、需要新增的测试。
审查计划时重点看三件事:
| 检查点 | 常见问题 | 发现后的处理 |
|---|---|---|
| 改动范围 | 顺手"重构"了无关代码 | 明确要求只改任务相关文件 |
| 设计取舍 | 用"先查后插"保证唯一性 | 指出并发问题,要求使用唯一索引 + 捕获冲突异常 |
| 依赖与接口 | 编造了项目中不存在的工具类或方法 | 要求先搜索确认再使用 |
在计划阶段纠正方向,成本只是几句话;在实现完成后才发现,往往需要推倒重来。
六、第四步:测试先行,让测试成为完成标准
Agent 最大的优势之一是可以自己运行测试并根据失败信息修正。所以最有效的做法是:先确定测试,再让 Agent 实现,直到测试通过。
验收标准中最关键的几条,可以由人先写好测试(或者让 Agent 先写测试、人审查测试后再实现):
@SpringBootTestclassUserRegisterServiceTest{@AutowiredUserRegisterServiceregisterService;@AutowiredUserRepositoryuserRepository;@MockitoBeanSmsCodeServicesmsCodeService;@Testvoidshould_reject_duplicate_phone(){when(smsCodeService.verifyAndConsume(anyString(),anyString())).thenReturn(true);registerService.register(newRegisterCommand("13800000000","Passw0rd!","123456"));BizExceptionex=assertThrows(BizException.class,()->registerService.register(newRegisterCommand("13800000000","Passw0rd!","123456")));assertEquals(ErrorCode.USER_PHONE_EXISTS,ex.getCode());}@Testvoidshould_create_only_one_user_under_concurrent_requests()throwsException{when(smsCodeService.verifyAndConsume(anyString(),anyString())).thenReturn(true);intthreads=8;ExecutorServicepool=Executors.newFixedThreadPool(threads);CountDownLatchstart=newCountDownLatch(1);List<Future<?>>futures=newArrayList<>();for(inti=0;i<threads;i++){futures.add(pool.submit(()->{start.await();returnregisterService.register(newRegisterCommand("13900000000","Passw0rd!","123456"));}));}start.countDown();longsuccess=futures.stream().filter(f->{try{f.get();returntrue;}catch(Exceptione){returnfalse;}}).count();pool.shutdown();assertEquals(1,success);assertEquals(1,userRepository.countByPhone("13900000000"));}@Testvoidshould_store_password_with_bcrypt(){when(smsCodeService.verifyAndConsume(anyString(),anyString())).thenReturn(true);registerService.register(newRegisterCommand("13700000000","Passw0rd!","123456"));Stringstored=userRepository.findByPhone("13700000000").orElseThrow().getPasswordHash();assertTrue(stored.startsWith("$2"));assertNotEquals("Passw0rd!",stored);}}其中并发测试最有价值:它能直接暴露"先查后插"的实现缺陷,Agent 会在测试失败后自行改为"唯一索引 + 捕获DuplicateKeyException转换为业务错误码"的实现。
需要警惕的是:Agent 有时会为了让测试通过而修改测试本身(放宽断言、删除用例)。所以要在任务说明或规则文件中明确"不允许修改已有测试的断言",并在审查时专门查看测试文件的 diff。
七、第五步:自动化检查与人工审查分工
7.1 能自动化的交给流水线
Agent 提交的代码和人写的代码一样,要经过 CI 的完整检查:编译、单元测试、集成测试、静态扫描(如 SonarQube、SpotBugs)、依赖漏洞扫描、代码格式检查。凡是机器能判断的,就不应该占用人的审查时间。
7.2 人工审查重点
人的注意力应该集中在机器难以判断的地方:
| 审查维度 | 关注点 |
|---|---|
| 需求理解 | 是否真正满足验收标准,有没有"看起来实现了"但边界条件错误 |
| 并发与一致性 | 唯一性、幂等、事务边界是否正确 |
| 安全 | 输入校验、越权访问、敏感信息是否写入日志 |
| 可维护性 | 是否引入了重复代码、过度抽象,或违反分层约定 |
| 测试本身 | 测试是否真的验证了行为,有没有被修改为"永远通过" |
| 依赖 | 是否引入了新依赖、调用了不存在或已废弃的 API |
一个实用的习惯是:让 Agent 在提交时附上变更说明——改了什么、为什么这样改、哪些地方不确定。这份说明能显著减少审查者重新理解代码的时间,也能暴露 Agent 自己"没把握"的地方。
八、哪些任务适合交给 Agent
| 适合程度 | 任务类型 | 原因 |
|---|---|---|
| 很适合 | 有明确验收标准的增删改查、单元测试补齐、依赖升级后的编译修复、批量重命名与迁移 | 结果可以被测试或编译器客观验证 |
| 适合,需审计划 | 跨多个模块的功能开发、性能优化 | 需要设计取舍,方向错了代价大 |
| 谨慎使用 | 核心交易逻辑、权限与安全相关代码、数据库迁移 | 错误后果严重,必须逐行审查 |
| 不适合 | 需求本身不清楚的探索、需要与多方协商的架构决策 | Agent 会"自信地"给出一个答案,掩盖了真正需要讨论的问题 |
判断标准可以归结为一句话:结果越容易被客观验证,越适合交给 Agent。
九、风险与应对
| 风险 | 表现 | 应对 |
|---|---|---|
| 幻觉 API | 调用不存在的方法、使用错误版本的框架用法 | 规则文件写明版本;编译和测试必须通过 |
| 范围蔓延 | 顺手修改无关文件 | 任务说明限定范围;审查时先看文件列表 |
| 测试被篡改 | 删除或放宽断言 | 规则禁止修改已有断言;单独审查测试 diff |
| 密钥泄露 | 把密钥写进代码或日志 | 密钥扫描纳入 CI;Agent 运行环境不注入生产凭证 |
| 提示词注入 | 读取的依赖源码、Issue 内容中藏有恶意指令 | 限制 Agent 可执行的命令;高风险操作需人工确认 |
| 能力退化 | 团队成员逐渐不理解自己代码库的关键逻辑 | 核心模块坚持人工编写或逐行审查;定期做代码走读 |
十、如何度量效果,而不是凭感觉
引入 Agent 之后"感觉快了很多",但感觉往往不可靠:写代码的时间缩短了,审查和返工的时间可能增加了。建议在引入前后对比以下指标,至少观察一到两个迭代周期:
| 指标 | 含义 | 说明 |
|---|---|---|
| 需求交付周期 | 从开始开发到上线的时间 | 端到端指标,不只看编码时间 |
| 审查耗时与轮次 | PR 从创建到合并的时间、修改轮次 | 反映 Agent 产出的"一次通过率" |
| 变更失败率 | 上线后需要回滚或热修复的比例 | 来自 DORA 指标,衡量质量是否下降 |
| 缺陷逃逸数 | 测试环境未发现、线上才暴露的缺陷 | 衡量验证闭环是否有效 |
| 测试覆盖与有效性 | 覆盖率及变异测试得分 | 防止"为了覆盖率而写的空测试" |
如果交付周期缩短而变更失败率上升,说明验证环节没有跟上,应该先加强测试和审查,而不是继续扩大 Agent 的使用范围。
十一、开发者角色的变化
从 Copilot 到 Agent,开发者的工作重心发生了明显的转移:
- 从"写代码"到"定义问题":写清楚验收标准和约束,本身就是最重要的设计工作;
- 从"实现细节"到"验证体系":测试、CI、规则文件成为保证质量的主要手段;
- 从"个人技巧"到"团队资产":规则文件、任务模板、审查清单都应该沉淀在仓库里,而不是留在某个人的脑子里。
回到开头的问题:“交给 Agent 一个需求"能不能放心?答案取决于你是否提前准备好了三样东西:规则文件让 Agent 了解团队约定,测试让 Agent 知道什么叫"做完了”,审查清单让人把注意力放在机器无法判断的地方。这三样东西准备好了,Agent 才是可靠的生产力,否则只是更快地产生需要返工的代码。
参考资料
- Building Effective Agents(Anthropic,Agent 的适用边界)
- AGENTS.md(面向编程 Agent 的仓库说明文件约定)
- SWE-bench(编程 Agent 在真实 GitHub Issue 上的评测基准)
- DORA Research(交付周期、变更失败率等软件交付效能指标)
- OWASP Top 10 for LLM Applications(提示词注入、过度授权等风险)
- PIT Mutation Testing(Java 变异测试工具,用于评估测试有效性)