把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥
系列导读 :本系列用一个可运行的 Spring Boot 秒杀 Demo,拆解"当 AI 成为我的全职下属"之后,研发工作流应该如何重建。五期内容从一次超卖事故开始,依次讨论并发正确性、上下文裁剪、防作弊 CI、Agent 友好任务单元和 Human-in-the-loop 上线门禁。主线不是证明 AI 能不能写代码,而是回答一个更实际的问题:当 Agent 已经能高吞吐地产出代码时,工程师如何用边界、验证和审批机制,把它变成可靠的执行者。
配套代码 :
demo/AutoEnterprise-Seckill/src/main/java/com/xiaoz/seckill/service
文章目录
- [把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥](#把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥)
-
- [1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统](#1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统)
- [2. 原因:大 Service 会放大 Agent 的误改面](#2. 原因:大 Service 会放大 Agent 的误改面)
- [3. 目标:把需求变成一个可交给 Agent 的任务单元](#3. 目标:把需求变成一个可交给 Agent 的任务单元)
- [4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元](#4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元)
- [5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收](#5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收)
- [6. Redisson 例子:锁边界也要写成 Agent 可执行规则](#6. Redisson 例子:锁边界也要写成 Agent 可执行规则)
-
- [6.1 启用并验证 Redisson 模式](#6.1 启用并验证 Redisson 模式)
- [7. SOLID 在这里的作用:不是原则展示,而是任务隔离](#7. SOLID 在这里的作用:不是原则展示,而是任务隔离)
-
- [7.1 单一职责:让 Agent 少读少改](#7.1 单一职责:让 Agent 少读少改)
- [7.2 开闭原则:新增策略,不覆盖旧案例](#7.2 开闭原则:新增策略,不覆盖旧案例)
- [7.3 依赖倒置:让测试替换基础设施](#7.3 依赖倒置:让测试替换基础设施)
- [7.4 接口隔离:减少 Agent 可误用能力](#7.4 接口隔离:减少 Agent 可误用能力)
- [8. 不要为了"Agent 友好"制造过度抽象](#8. 不要为了“Agent 友好”制造过度抽象)
- [9. 验收一次 Agent 修改:看文件、看命令、看不变量](#9. 验收一次 Agent 修改:看文件、看命令、看不变量)
- 实验环境
- 小结
-
- 适用边界与回退
- [测试 Demo 仓库](#测试 Demo 仓库)
1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统
你给 Agent 的任务是:"给秒杀接口加一个库存扣减策略,保持不超卖。"
结果它不仅改了库存逻辑,还顺手调整了订单创建、Redis 锁、Controller 返回值和测试夹具。PR 看起来很努力,但 Review 时很难回答三个问题:
- 这次修改到底影响了哪些路径?
- 哪些文件是完成任务必须改的,哪些是越界改动?
- 现有并发验证还能不能证明库存、订单和响应一致?
这不是单纯的"模型不够聪明"。更常见的原因是:我们把一个边界不清的大 Service 交给 Agent,让它在过大的操作面里自由搜索。它看到的上下文越混杂,越容易把"可修改"误判成"应该修改"。
本文不把重点放在"SOLID 原则有多优雅",而是讨论一个更实用的目标:如何把 Java 代码拆成 Agent 可安全执行、可审计、可验收的任务单元。
2. 原因:大 Service 会放大 Agent 的误改面
典型的"万能秒杀 Service"可能同时负责:
- 参数校验和用户身份解析。
- 查询商品、扣库存、创建订单。
- 获取 Redis 锁和处理重试。
- 记录日志、发送消息、降级补偿。
- 组装接口响应和异常文案。
人类可以凭经验跳读这些分支,Agent 却会把它们都纳入候选修改范围。对 Agent 来说,大 Service 至少带来四类额外风险:
| 风险 | 表现 | 后果 |
|---|---|---|
| 上下文噪声 | 无关 Controller、DTO、日志和配置混入任务包 | Agent 选错参考代码 |
| 权限模糊 | 没说哪些文件不能改 | Agent 顺手改测试或流水线 |
| 副作用隐藏 | 订单、库存、锁释放耦在一起 | Review 难以判断影响范围 |
| 验收不聚焦 | 只说"测试通过" | Agent 可能绕开真实业务不变量 |
所以第 4 期要解决的不是"怎样写一个更漂亮的架构",而是:怎样让 Agent 接到任务时,只能看见必要上下文,只能修改允许路径,只能通过真实验收。
3. 目标:把需求变成一个可交给 Agent 的任务单元
一个适合交给 Agent 的任务单元,至少要包含五部分:
text
agent-task/
├── issue.md # 问题、目标、成功标准
├── allowed-files.txt # 允许修改的文件
├── context.json # 进入上下文的文件、原因和规模
├── constraints.md # 禁止修改的路径、依赖和行为
└── verify.ps1 # 必须执行的验收命令
如果任务是"修改库存扣减策略",不要把整个仓库扔给 Agent。更合适的任务边界是:
text
允许阅读:
- AtomicSeckillService.java
- OrderCreator.java
- SeckillProductMapper.java
- SeckillResult.java
- 相关并发测试
允许修改:
- AtomicSeckillService.java
- SeckillProductMapper.java
- 必要时新增同目录下的小策略类
禁止修改:
- pipeline/
- ai_firm/cheat_detector.py
- Controller 返回协议
- 已有并发测试的断言语义
- pom.xml 中未说明的新依赖
这样做的价值是把"请帮我修一下"改成"你只能在这个边界内完成这个验收目标"。Agent 的自由度减少了,但成功率和可审计性会上升。
4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元
当前 Demo 把核心逻辑拆成四个角色:
| 组件 | 给 Agent 的含义 |
|---|---|
AtomicSeckillService |
任务入口:编排库存扣减和订单创建 |
SeckillProductMapper |
库存不变量:stock > 0 条件更新 |
OrderCreator |
订单副作用:只负责创建订单 |
SeckillResult |
响应契约:成功、售罄、模式和消息 |
AtomicSeckillService 的业务逻辑很短:
java
@Transactional
public SeckillResult execute(Long userId, Long productId) {
if (productMapper.deductStockIfAvailable(productId) != 1) {
return SeckillResult.soldOut("atomic");
}
SeckillOrder order = orderCreator.create(userId, productId, "atomic");
return SeckillResult.success(order.getId(), "atomic");
}
这段代码对 Agent 友好,不是因为它符合某个抽象口号,而是因为它让任务边界变得清楚:
- 要改库存条件,优先看
SeckillProductMapper。 - 要改订单字段,优先看
OrderCreator。 - 要改成功/失败响应,优先看
SeckillResult。 - 要调整编排顺序,才需要改
AtomicSeckillService。

上下文裁剪器在这个结构下只需带回目标 Service、Mapper、订单创建器和结果对象。它不是为了"少给 Token"而少给,而是为了让 Agent 明确知道:本次任务与 Web 层、管理接口、H2 Console、CI 脚本无关。
5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收
下面是一个可以直接给 Agent 的任务描述模板:
text
任务:调整 atomic 秒杀路径的库存扣减策略,保持不超卖。
允许修改:
- src/main/java/com/xiaoz/seckill/service/AtomicSeckillService.java
- src/main/java/com/xiaoz/seckill/mapper/SeckillProductMapper.java
禁止修改:
- pipeline/
- ai_firm/
- src/test/ 中已有断言语义
- Controller 响应结构
- pom.xml 中未说明的新依赖
验收标准:
- mvn.cmd test 通过
- python pipeline\verify_integrity.py 通过
- unsafe 模式允许复现超卖
- atomic 模式 database_orders <= stock
- atomic 模式 remaining_stock >= 0
- oversold 必须为 false
这个模板的重点不是格式,而是把 Agent 的目标从"让项目绿"改成"在指定文件内恢复业务不变量"。如果它修改了 pipeline/ 或跳过并发测试,即使最终 CI 是绿的,也应该判为失败。
6. Redisson 例子:锁边界也要写成 Agent 可执行规则
Redisson 模式是一个很好的边界案例。它只负责获取锁、调用原子业务、在 finally 中释放当前线程持有的锁:
java
RLock lock = redisson.getLock("seckill:product:" + productId);
boolean acquired = false;
try {
acquired = lock.tryLock(500, TimeUnit.MILLISECONDS);
if (!acquired) {
return new SeckillResult(false, null, "redisson", "系统繁忙,请重试");
}
SeckillResult result = atomicSeckillService.execute(userId, productId);
return new SeckillResult(result.success(), result.orderId(), "redisson", result.message());
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
给 Agent 修改这段代码时,任务约束应写得更具体:
text
必须保留:
- finally 中释放锁
- lock.isHeldByCurrentThread() 判断
- Redis 不可用时失败,而不是降级到 unsafe
- 数据库 stock > 0 条件更新作为最终防线
不得引入:
- catch Throwable 后吞异常
- 未说明的无限等待锁
- Redis 失败后自动调用 unsafe 模式
这里仍保留数据库 stock > 0 条件更新。原因是锁与数据库约束解决的问题不同:
- 锁降低同一资源的并发冲突。
- 数据库条件更新守住最终库存不变量。
只依赖 Redis 锁,一旦锁配置、网络或调用路径出现问题,数据库就失去最后防线。对 Agent 来说,这条规则必须写进 constraints.md,不能只靠它"理解架构意图"。
6.1 启用并验证 Redisson 模式
先将可访问的 Redis URI 与应用基础地址写入环境变量,再执行:
powershell
$env:REDIS_ADDRESS='<Redis URI>'
$env:SECKILL_BASE_URL='<应用基础地址>'
mvn.cmd spring-boot:run "-Dspring-boot.run.profiles=redis"
python pipeline\run_stress_test.py --base-url $env:SECKILL_BASE_URL --mode redisson --concurrency 100 --requests 500 --stock 100
预期 database_orders 不超过 100、remaining_stock 不小于 0、oversold 为 false。如果 Redis 不可用,接口应返回 HTTP 503,而不是静默降级为无锁执行。
本文当前环境没有运行 Redis,所以 Redisson 网络行为仍标注为待读者在 Redis 环境中复验;这不是缺陷隐藏,而是明确区分"已编译"和"已压测"。
7. SOLID 在这里的作用:不是原则展示,而是任务隔离
SOLID 可以保留,但它在本文里的角色应该是工具,不是主角。
7.1 单一职责:让 Agent 少读少改
一个类只处理一个变化原因,Agent 修改时就不必同时推理订单、库存、消息和锁。单一职责的直接收益是:上下文包更小,Review 面更窄。
7.2 开闭原则:新增策略,不覆盖旧案例
Demo 保留 unsafe、atomic、redisson 三条路径。这样读者可以对比行为,Agent 也不需要为了实现新策略删除旧案例。旧路径能继续作为回归参照。
7.3 依赖倒置:让测试替换基础设施
业务编排依赖 Mapper 和小组件,而不是在方法中自行创建连接、客户端和线程池。测试可以注入替代实现,生产配置也能独立变化。
7.4 接口隔离:减少 Agent 可误用能力
如果一个工具只需要扣库存,就不应同时暴露删除商品、批量改价等接口。对 Agent 而言,接口越大,可误用的能力越多。
8. 不要为了"Agent 友好"制造过度抽象
Agent 友好不等于每三行代码创建一个接口。判断是否需要拆分,可以问三个问题:
- 这个职责是否有独立变化原因?
- 是否存在第二种实现、测试替身或独立验收标准?
- 拆分后是否明显缩小 Agent 的阅读范围或修改范围?
如果三个答案都是"否",保留直接代码通常更清晰。过度抽象会制造新的坏处:文件更多、跳转更远、上下文裁剪更难解释。Agent 不是越多层越安全,而是边界越明确越安全。
9. 验收一次 Agent 修改:看文件、看命令、看不变量
一次 Agent 提交能否合并,不应只看"有没有通过测试"。建议至少检查这几项:
| 检查项 | 通过标准 |
|---|---|
| 修改范围 | 只改了任务允许的文件 |
| 路径边界 | 没有修改 pipeline/、审计脚本和无关 Controller |
| 依赖变化 | 没有新增未说明依赖 |
| 测试诚信 | 没有 @Disabled、assertTrue(true)、空 catch |
| 业务不变量 | 订单数、库存、成功响应三者一致 |
| 模式隔离 | unsafe、atomic、redisson 行为仍可区分 |
可执行命令可以写成固定清单:
powershell
mvn.cmd test
python pipeline\verify_integrity.py
python pipeline\run_stress_test.py --mode unsafe --concurrency 100 --requests 500 --stock 100
python pipeline\run_stress_test.py --mode atomic --concurrency 100 --requests 500 --stock 100
如果要运行 Redisson 模式,先启动 Redis 并设置 REDIS_ADDRESS,再补跑:
powershell
python pipeline\run_stress_test.py --base-url $env:SECKILL_BASE_URL --mode redisson --concurrency 100 --requests 500 --stock 100
这些命令把"我觉得它没乱改"变成可复核证据。Agent 可以写代码,但验收标准不能交给它自己临时解释。

实验环境
| 项目 | 版本或参数 |
|---|---|
| JDK | 17.0.15 |
| Spring Boot | 3.5.15 |
| MyBatis-Plus | 3.5.15 |
| Redisson | 4.5.0 API 编译通过 |
| 验证范围 | Maven 测试通过;本文运行环境未安装 Redis,分布式锁网络行为需读者启动 Redis 后验证 |
小结
第 4 期的重点不是证明 SOLID 仍然正确,而是把代码结构变成 Agent 可安全执行的工作边界。
当一个任务单元具备明确输入、允许修改范围、禁止路径和验收命令时,Agent 的能力才会从"自由发挥"变成"受控执行"。模块越清晰,Agent 需要的上下文越少;边界越稳定,自动生成代码的风险越可控。
最后一期将把前四期组合为一套 Human-in-the-loop 工作流:哪些步骤交给 Agent,哪些门禁必须由人类和自动化系统掌握。
适用边界与回退
- 单机应用优先考虑数据库原子更新,不要为了"分布式"强行引入 Redis。
- Redis 锁不可用时应失败或进入经过设计的降级路径,不能自动切换到
unsafe。 - Redisson Watchdog、等待时间和连接池参数需要结合业务耗时压测,Demo 参数不能直接复制到生产。
- 如果团队还没有稳定的自动化测试,先补验收脚本,再把修改任务大规模交给 Agent。
测试 Demo 仓库
配套 Demo 仓库地址:https://github.com/quan020406/xiaozhan-blog-column-demos
上一篇 :AI Agent 防作弊 CI 实战
下一篇:小z疯狂码字ing...
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
