AI 生成代码的思考与实践
怎么让 AI 生成的代码真实可运行而不是"看起来对"?本文记录一套已落地的实现方案------包括架构设计、知识管理、沙箱验证、自动修复------以及在真实技术栈上反复踩坑后的思考。

核心问题:80% 和 20%
业界有一个务实判断:大模型做 80% 的脚手架和通用代码,剩下 20%(业务补偿、异常流)交给开发人员 Review 并手写补全。
这个判断基本成立,但那 80% 里还能再切两刀,且每刀的质量保障机制完全不同:
| 层次 | 占比 | 生成策略 | 质量保障 | 实测成熟度 |
|---|---|---|---|---|
| 架构层(脚手架) | ~40% | 最强模型一次性生成 ,人工 Review 后冻结为工程模板。后续新项目只复制这份固化模板,不再让 LLM 随意改动 | 冻结即保障:模板一旦验证通过就不再变 | 接近 100% |
| 通用业务代码 | ~40% | LLM 按服务契约并行生成 + 沙箱验证 + 自动修复 | 三道门禁:沙箱运行验证 + CI 静态质量门禁(SonarQube 覆盖率/圈复杂度)+ 人工 MR 审批 | 70~90% 命中率 |
| 业务逻辑 + 异常流 | ~20% | 人类定义验收标准,AI 执行 | 人工审批最终通过的 MR | 演进中 |
关键设计决策:
架构层不交给 Agent 循环。架构决策的变更频率极低(一年改不了几次),且每次变更都需要人工判断------这类代码的生命周期是"生成一次 → 人工确认 → 长期复用"。流程是"最强模型出初版 → 人工逐行 Review → 冻结为模板 → 后续直接复制"。只有技术栈升级或架构调整时,才重新走一次这个流程。
业务层可以放心交给 Agent 异步处理。Agent 修改的是业务实现类,即使质量不佳,CI 里的 SonarQube 质量门禁(覆盖率 >80%、圈复杂度 <10)也会阻断不达标代码合入。人工只需负责审批最终通过的 MR。
一个关键认知:那 20% 的重心不是"手写补全代码",而是"写清楚什么算对"。当人类把验收标准写成测试用例,AI 就能拿着标准自驱修复------这比人工补代码的杠杆大一个量级。
三道门禁的分工
erlang
第一道 · 沙箱运行验证(生成时)
构建 → 启动 → CRUD + 分页冒烟 → 前端 Vite 构建
回答的问题:代码能不能跑?
第二道 · CI 静态质量门禁(提交时)
SonarQube:覆盖率 >80%、圈复杂度 <10、代码异味扫描
回答的问题:代码质量够不够?
第三道 · 人工 MR 审批(合并前)
人类看业务逻辑正确性、架构合理性、安全风险
回答的问题:这个改动该不该上线?
三道门各管一段,互不替代:沙箱拦"跑不了的",SonarQube 拦"跑得了但质量差的",人类拦"质量好但业务错的"。
实现方案:一条完整管线
需求 → 技术栈锁定 → 检索+知识注入 → 模板+LLM 并行生成 → 沙箱运行验证 → 自动修复 → 交付
以下按管线各环节展开。
1. 技术栈锁定(给生成器确定性)
用户四级联动选栈(前端→语言→框架→持久层),确认后不可更改。锁定带来两个好处:
- 依赖版本可以钉死进提示词(LLM 不再写训练数据里的旧版本号)
- 服务契约可以预定义(方法签名、路由规则、返回结构全部固定)
实测中最大的一类失败------"LLM 写了旧版本 API"------靠版本钉子直接消除。比如 MyBatis-Plus 3.5.9+ 把分页插件挪进独立构件、移除了 ServiceImpl,这些信息钉进提示词后,相关问题不再出现。
2. 确定性模板 + LLM 并行生成(分层策略)
能确定的不交给 LLM:pom.xml、实体类、配置文件、建表 SQL------这些文件的"正确版本"完全由规则决定,用模板渲染,零幻觉零漂移。
需要智能的才交给 LLM:Controller、Service、前端页面------这些需要理解业务语义,由 LLM 按服务契约并行生成(3 路并发),互相独立不等待。
生成用快模型 (照契约填空不需要深度推理),修复用旗舰模型(从报错反推根因需要更强的推理能力)------成本和速度双优。
3. 沙箱验证(不信"看起来对")
生成的代码在一次性 Docker 容器里真实运行:
- 后端冒烟:构建 → 启动 → 增删改查 + 分页五个接口逐一验证
- 前端构建:Vite 离线构建,检查 import 一致性
- 隔离:无网络、限内存/CPU、超时强杀------LLM 产物当不可信代码对待
- 快速失败:进程死亡秒级判负(附带应用日志),只有"活着但没就绪"才等
这一步是整个系统的信任锚点:后续所有环节(修复、知识沉淀、模板升级)都以"沙箱通过"为唯一准入标准。
4. 自动修复(阶梯式)
冒烟失败后进入修复循环,最多三轮:
第 1 轮快修:关闭思考模式,旗舰模型,30-60 秒/文件
第 2 轮快修:换个角度再试
第 3 轮深思:开启思考模式,从报错深度推理根因
修复提示词注入全项目源码 (不只是出错文件),让模型能看到跨文件签名一致性;支持多文件输出,一次修复多个文件的联动问题。
两个实战教训:
- 测试脚本不作为修复对象------它是验证工具,不是被测代码
- 确定性模板禁止被修复重写------模板是已验证的正确实现,LLM 改了反而引入回归
5. 知识体系(越用越准)
三层知识,按加载时机和信任等级分层:
规则(每次都读):实体规范、REST 约定、分页协议------必须精简,防止上下文膨胀。判断标准:只有"每次生成都必须遵守"的才留在这里。
技能 (按栈加载):实战验证过的坑。frontmatter 声明适用栈和版本范围,按技术栈匹配注入。版本门控:每次生成前先查 Context7 / GitHub 获取当前版本,与技能记录比对------一致才启用,不一致回退官方文档重新验证。
问题库(按症状查):真实故障的症状→根因→修复,人工确认后入库。修复遇到新报错先查库,命中直接注入已知方案。
知识流动规则 :问题库反复命中 → 升级为技能(从"事后修"变"事前防");技能每次生效 → 越用越准。核心纪律:没过沙箱冒烟的代码不能进技能库。
6. 知识新鲜度:Context7 而不是自建
脚手架知识不需要自己维护------Context7 按需检索官方文档的最新版本和代码示例,比自建知识库更及时且零维护成本。
但 Context7 也有局限(topic 匹配偏模糊、偶尔给旧写法),所以优先级是:自验证技能 > Context7 示例。冲突时以经实战验证的为准。
7. 工程模式进技能:分布式事务、幂等、消息可靠
当系统复杂度从"单表 CRUD"升级到"多服务协同",会出现一类跨栈通用的工程模式------它们不是某个框架的 API 用法,而是"无论用 Spring Boot 还是 Gin 还是 FastAPI,都必须正确处理的架构约束"。
这类知识的正确归属是技能层:做成带触发条件的技能文件,agent 在生成相关代码时自动加载,在沙箱验证时作为检查规则。
分布式事务
LLM 的训练数据里充满"教科书版 2PC"和"过度简化的 Saga 示例",直接生成几乎必然不可用。需要把团队的实际决策固化成技能:
yaml
---
stack: global
stage: generating
when: "涉及跨服务写操作"
---
# 分布式事务模式
## 版本无关
- 单体应用内多表写:用本地事务(BEGIN/COMMIT),不引入 Saga
- 跨服务写:优先最终一致性(消息驱动 + 补偿),不用 2PC/XA
- Saga 编排:每个参与方必须实现「正向操作 + 补偿操作」两个接口
- 补偿必须幂等(网络重试会调多次)
- 编排状态持久化(重启不丢上下文)
沙箱验证规则(agent 生成后自查):
- 如果代码里有跨服务调用 + 写操作,检查是否声明了事务边界
- Saga 参与方必须有 compensate 方法
- 编排器的状态变更必须先写库再发消息(避免消息先到、状态未落)
幂等性兜底
LLM 生成的代码默认不幂等------同样请求发两次,创建两条记录。这在重试场景下是灾难。技能应该规定:
python
# 幂等模板(agent 生成写接口时自动套用)
def create_order(idempotency_key: str, payload: dict):
existing = db.query(IdempotentRecord, key=idempotency_key)
if existing:
return existing.result # 幂等返回
result = do_create(payload) # 正常执行
db.save(IdempotentRecord(key=idempotency_key, result=result))
return result
沙箱验证规则:
- 对每个写接口连续调两次相同 payload,断言返回一致且数据库只多一条记录
- 消息消费者必须处理"重复投递"(at-least-once 语义下必然发生)
消息队列重试与死信
这是 LLM 最容易写出"能编译但上线就出问题"代码的领域。需要把生产级约定固化:
| 场景 | LLM 默认写法(有问题) | 技能纠正后 |
|---|---|---|
| 消费失败 | 直接 throw,消息无限重投 | 指数退避重试 N 次 → 进死信队列 |
| 死信处理 | 没有死信消费者 | 死信落地 + 告警 + 人工介入入口 |
| 消费确认 | 处理前就 ACK | 处理成功后才 ACK(at-least-once) |
| 顺序消息 | 假设全局有序 | 按 partition/key 有序,消费者不依赖跨分区顺序 |
| 消息幂等 | 默认消息不重复 | 按 message_id 去重(配合幂等表) |
沙箱验证规则:
- 消费者代码里不能有"裸 throw"(必须 catch → retry → dead letter)
- ACK 必须在业务逻辑执行成功之后
- 死信队列必须有消费者(不能只生产不消费)
这类技能的共同特征
这三类模式有以下共性:
- 跨栈通用 :不依赖特定框架,Java/Go/Python 都适用 → 技能标记
stack: global - 有明确对错判据 :沙箱里可以写成自动化断言 → 不只是"提示词建议",是可执行规则
- LLM 默认写法是错的:训练数据偏学术示例 → 需要人工经验覆盖
- 出错代价高:不是编译报错,是上线后数据不一致 → 必须在生成阶段就拦住
所以这类知识不应该散落在提示词里,而应该结构化为"生成模板 + 验证断言"的成对技能------agent 生成时套模板,沙箱验证时跑断言,两端闭环。
8. 架构验证与安全部署:从"代码能跑"到"流量可切"
代码通过沙箱冒烟只是第一步------"能跑"和"能接生产流量"之间还有一整段工程要过。这段也应该整理成技能,由 agent 在生成和部署两个阶段自动执行。
架构级验证(CI 门禁的一部分)
| 检查项 | 工具 | 阻断级别 | 说明 |
|---|---|---|---|
| 循环依赖 | mvn dependency:analyze / jdeps |
Blocker | 循环依赖是架构腐化的第一步,必须在 CI 阶段就阻断 |
| 合约测试 | Pact / Spring Cloud Contract | Blocker | 消费方驱动的接口契约------接口改了但消费方不知道,比编译错误更致命 |
| 性能基准 | JMH / wrk 基线对比 | Warning → Blocker | 新版本吞吐量下降超过阈值(如 10%)时阻断合并 |
循环依赖检测的 Blocker 意味着:宁可让构建失败,也不让带循环依赖的代码进主干------因为修循环依赖的成本随时间指数增长。
老模块升级:绞杀者模式
对已有系统的模块升级,不用"一次性替换"(风险太大),而用绞杀者模式(Strangler Fig):
markdown
老模块(继续跑)────┐
├── 网关按路由规则分流 ──→ 新模块(逐步接流量)
│
新模块(并行开发)──┘
关键风险点是数据兼容性:新老模块并行期间共享同一个数据库,新模块写的表结构/字段必须向后兼容------老代码还在读。这意味着:
- 新增字段可以,删除/重命名字段不行(先双写过渡,确认老代码不再读后才删)
- 数据迁移脚本必须可回滚(切流量前留快照)
- 新老模块的 ORM 映射必须经过同一份 schema 契约测试
灰度发布:权重 + Header 双通道
新版本部署 1~2 个节点后,不直接全量切流量,而是通过网关层精细控制:
权重通道:新节点网关权重设为 1%(或 5%),生产流量按比例小规模试探。
Header 通道 :内部测试人员的请求强制携带 X-Canary: test,网关识别后强制路由到新节点------无论 IP 怎么变(手机切 Wi-Fi、VPN 重连),测试请求始终打到新版本。
双通道的核心优势:
- 如果新节点不可用,权重自动失效,流量秒级切回老节点(不需要人工干预)
- 测试人员的请求始终可控,不会因为 IP 变化导致路由漂移
- 测试人员可以随时"强制体验新版本",不受权重比例限制
观察期:两个阶段的硬指标
灰度不是"部署完等一等",而是盯具体指标跑够具体时长:
| 阶段 | 时长 | 关注指标 | 原因 |
|---|---|---|---|
| 功能期 | 30 分钟 | 业务错误码(如支付失败率) | 功能性问题暴露快------路由错了、参数解析错了、权限校验漏了 |
| 性能期 | 1 小时 | GC 频率和年轻代吞吐量 | 新代码的日志框架可能引入额外对象分配,GC 压力上来需要一个完整周期才看得到 |
两个阶段都通过 → 逐步提权重(5% → 25% → 50% → 100%);任一阶段指标异常 → 立即回切。
部署策略选型
| 场景 | 策略 | 说明 |
|---|---|---|
| 常规迭代(接口不变) | 滚动更新 | 逐个节点替换,零停机,资源利用率高 |
| 大版本更新(接口/存储变更) | 蓝绿更新 | 新旧两套完整环境并行,验证后切 DNS/网关,可秒级回切 |
选型逻辑:变更越大,回切速度要求越高。滚动更新的回切是"再滚一轮"(分钟级),蓝绿的回切是"切回旧环境"(秒级)------大版本变更时多花一倍资源买这个秒级回切,值得。
部署知识如何进体系
和分布式事务/幂等/消息可靠一样,这些部署知识也应该做成**"生成模板 + 验证断言"的成对技能**:
- 生成时:agent 生成的代码自动附带 Dockerfile(滚动更新用)或 docker-compose 双环境(蓝绿用);网关配置里预埋灰度权重规则和 Header 路由规则
- 验证时 :CI 门禁自动跑
dependency:analyze(循环依赖 Blocker)、合约测试、性能基准对比 - 部署时:灰度脚本自动执行两阶段观察(30min 功能 + 1h 性能),指��达标才提权重
最终效果:从"AI 生成代码"到"流量切换"整条链路都有自动化保障,人类只在两个地方介入------定义阈值(什么算异常)和按下最终的 100% 切换确认。
实测数据
| 技术栈组合 | 全流程通过 | 生成耗时(含修复) |
|---|---|---|
| SpringBoot + MyBatis-Plus + Vue | ✅ | ~3 分钟 |
| FastAPI + SQLAlchemy + Vue / React | ✅ | ~2 分钟 |
| Gin / Echo + GORM + Vue | ✅ | ~2 分钟 |
| Flask + SQLAlchemy + Vue | ✅ | ~2 分钟 |
| Django + Django ORM + Vue | ✅ | ~4 分钟 |
| SpringBoot + JPA + Vue | ✅ | ~5 分钟 |
每个组合都经过多轮真实踩坑才通过------比如 Java 栈历经六类问题(Maven 离线缓存身份、MyBatis-Plus 拆模块、冷启动超时、类型混用、私加依赖、schema 触发器),每一类修复后都沉淀进了技能或问题库。
一个重要发现:思考模式的成本
实测发现 qwen3.8 系列默认开启"思考模式"(先生成长篇推理再输出代码),单文件生成从秒级膨胀到 5-13 分钟。关闭思考模式 (enable_thinking: false)后:
- 生成本质是"照契约填空",不需要深度推理 → 关掉后速度提升 5-10 倍,正确率不变
- 修复需要"从报错推理根因" → 保留思考能力给旗舰修复模型
这个发现改变了性能优化的方向:瓶颈不在代码量,而在模型在不必要的推理上消耗的时间。
演进方向
终局判断:架构层靠"强模型一次生成+冻结"保证输出稳定一致,业务层靠"Agent 自由生成+三道门禁过滤"实现近自主,人类只审批最终通过的 MR 并按下发布键。
架构层(冻结制) 业务层(门禁制)
────────────────── ──────────────────
最强模型一次性生成 Agent 自由生成业务类
人工 Review 后冻结为模板 沙箱运行验证(能跑?)
后续只复制,LLM 不碰 CI 质量门禁(SonarQube)
模板升级才重启生成流程 人工 MR 审批(该上线?)
五条演进路径:
- 架构层冻结管线:新栈或版本升级时,最强模型生成完整工程 → 人工逐行 Review → 冻结为模板 → 后续只复制。模板是"经人审批的资产",不是"AI 的输出"
- CI 质量门禁接入:沙箱验证通过后自动提 MR → SonarQube 跑覆盖率/圈复杂度/代码异味 → 不达标自动打回 Agent 重改 → 达标后人工审批
- 业务测试用例接入沙箱:人类写"金额超限要拒绝"这类验收标准,沙箱同时跑 CRUD 冒烟和业务断言
- Spec 层相似度判定:新项目和已有项目比实体关系+操作语义相似度,高相似直接走填空模式
- 工具按需递增:先跑稳 5 工具单代理,再逐步接入向量检索、代码仓库阅读、外部系统
MR 审查的三层基础设施:从"前期人工多"到"未来全自动"
AI 生成的新代码,在提交 MR 合并前,未来一定是 AI 先扫有问题再人工。前期人工参与会多一些,但必须提前把三层基础设施搭好------缺一不可,它们是从"人工兜底"演进到"基本全自动"的唯一路径。
第一层 · 评估基准(离线评估与回放)------迭代的基石
没有这一层,无法判断 AI 的迭代方向是变强了还是退步了。
黄金评测集(Golden Dataset):从历史人工 Review 通过的代码中,抽取 100~200 个典型场景(如"复杂事务""缓存穿透防护""空指针防护"),人工写好"标准答案"(期望的代码片段或行为断言)。
回放比对(Replay) :每次升级大模型版本或调整提示词后,不直接上线,先在黄金集上跑一遍:
markdown
新模型/新提示词 → 黄金集回放 → 相似度/覆盖率/圈复杂度对比
│
指标不降级 → 允许发布新流程
指标降级 → 阻断,回滚到上一版
这就是"迭代框架"的技术锚点:任何改动(模型版本、提示词、技能文件)都必须先过黄金集回放,不降级才发布。没有这个锚点,调整提示词后感觉"似乎更好了",实际上可能引入了多个新的回归。
第二层 · 执行管道(AI 扫描 + 确定性门禁)------两段式
"AI 先扫"必须分两段,不能一开始就全交给大模型:
阶段一 · 硬规则门禁 :先跑 SonarQube / Checkstyle 等确定性工具。圈复杂度 >10、缺少单元测试、代码异味的,直接红牌阻断------不经过大模型。
markdown
MR 提交 → SonarQube 硬规则 ──不达标──→ ❌ 红牌(不进大模型)
│达标
↓
大模型语义扫描 ──发现问题──→ 💬 AI Comment(评论,不阻断)
│无问题 │
↓ ↓
✅ 进入人工审批 人工确认后修复合并
阶段二 · AI 语义门禁 :只有硬规则通过的代码,才交给大模型做"业务逻辑漏洞扫描"(比如检查是否漏掉了 @Transactional 回滚、是否存在缓存穿透风险)。
权限渐进策略:前期 AI 只提"评论(Comment)",不拥有"阻断(Block)"权限。运行 3 个月、误报率稳定在可接受范围后,才将权限升级为"阻断"。这避免了早期 AI 误判导致正常代码被卡住。
第三层 · 反馈闭环(人工修正记录)------从人工到全自动的唯一桥梁
前期人工参与多不是问题,问题是人工改完的代码没有沉淀。
强制反馈机制:开发者在合并 MR 时,必须勾选或填写"AI 误判/漏判原因"(比如"AI 没考虑到财务四舍五入规则""AI 漏了跨时区的时间处理")。
结构化存储:这些反馈不是写在评论里就完了,而是结构化存入数据库(关联 MR diff + AI 扫描结果 + 人工修正内容),形成"AI 错在哪 → 人怎么改的"配对记录。
回流机制:定时通过 RAG 向量化或微调(SFT)输入后续模型。每一轮人工修正,都让 AI 下次少犯一个错。
核心逻辑:早期代码质量靠人工,后期全自动靠的是持续积累的"人工修正记录"。没有第三层,将永远停留在"人工 Review"阶段,无法演进到"AI 自主审查"。
三层之间的关系
markdown
评估基准(第一层)──→ 保证迭代方向正确(不倒退)
│
执行管道(第二层)──→ 保证日常 MR 质量可控(渐进信任)
│
反馈闭环(第三层)──→ 保证人工经验不断回流(越用越准)
三层缺一:没有评估基准,迭代方向不可知;没有执行管道,审查完全依赖人工;没有反馈闭环,人工经验无法沉淀,无法从"人工多"过渡到"全自动"。
总结
AI 生成代码的核心不是"让 AI 写更多代码",而是建一套分层信任体系:
- 架构层靠冻结:最强模型出一次,人工 Review 后固化为模板------输出稳定一致,后续不经 LLM
- 业务层靠门禁:Agent 自由生成,沙箱拦"跑不了的",SonarQube 拦"质量差的",AI 语义扫描拦"质量好但逻辑错的",人工拦"AI 漏判的"
- 迭代靠三层基础设施:黄金集回放保证不倒退,两段式门禁渐进信任,人工反馈结构化回流
- 踩过的坑沉淀成知识(下次绕过)
- 人从"写代码"转向"审批 MR + 定义什么叫对 + 教会 AI 什么叫对"
这套体系的价值不在于替代程序员,而在于:架构层的确定性和业务层的自由度各得其所,人类从"写代码的人"变成"教 AI 写代码的人",最终只需在门禁末端做最终判断。