上个月我在 code review 里看到一段生成得特别漂亮的 Go 代码结构清晰、命名讲究几乎挑不出毛病。我差点就点了 Approve。后来随手查了一下它调用的那个 API 签名发现函数名和实际包里的完全对不上——这是个根本不存在的函数但编译居然没报错因为参数被一层边界条件巧妙地糊弄过去了。那一刻我意识到AI 生成的代码比人写的更危险因为它比你更像一个“写过代码的人”。这段经历让我开始认真思考一个问题AI 帮我们写代码已经是大势所趋但它产出的代码到底能不能直接推到生产环境我的答案是不能至少不能不做检查就上。这篇文章就围绕我一直在用的“上线前四道检查”展开从代码走查、静态分析到安全扫描、依赖审计再到测试覆盖、性能验证最后是灰度发布和回滚预案。如果你也在用 AI 写代码、并承担着把它合入主分支的责任这篇文章应该是你 merge 之前最需要过一遍的清单。1. 为什么 AI 生成的代码不能“能跑”就上生产1.1 AI 代码的六种典型“翻车姿势”先说一个大家都认同的前提AI 生成的代码在“表面的正确性”上做得相当好它知道缩进、命名、注释规范甚至能写出不错的单元测试案例。但生产环境从来不是看表面而是看边界条件和异常链路下的表现。我总结了一下AI 生成的代码最容易在六个方向上翻车。第一是幻觉 API。这是最常见、也最隐蔽的问题。AI 在生成代码时会一本正经地调用一个不存在的库函数、一个早已废弃的接口签名或者一个参数顺序完全不对的方法。就像我开头说的那个例子IDE 不一定会报错因为 AI 会用类型转换把它伪装过去。第二是边界条件缺失。AI 几乎不会主动处理空指针、超时、并发冲突、重复调用这些“正常流程之外”的场景。你拿一份正常数据去测完全没问题一上生产就被极端情况击穿。第三是安全疏漏。AI 生成的代码可能会拼接 SQL、拼接 HTML、把敏感信息打进日志、或者直接用用户输入拼 URL 请求外部资源这些在 demo 里跑得通在生产里就是事故现场。第四是依赖错配。AI 倾向于使用“看起来最新”的依赖版本或者把一个本来只用于内部开发的包当成了公共依赖导致供应链风险。第五是性能盲区。AI 生成的正则表达式、多层嵌套循环、没加索引的数据库查询在数据量小的时候毫无感知数据量一上来就直接把 CPU 或者数据库连接池打爆。第六是幂等缺失。AI 生成的接口通常不考虑重复请求的问题一旦上游重试就会出现重复扣款、重复下单、重复写入这类数据错乱。这些翻车姿势单独拿出来任何一个都够一次线上 P0 事故。所以“能跑”之于生产环境就像“能点火”之于飞机——起飞之前你还需要检查发动机、油路、仪表和应急预案。1.2 “能跑”和“能上生产”是两件事为什么很多同学会对 AI 生成的代码放松警惕我觉得核心原因是他们把“功能正确”当成了“生产就绪”。功能正确只说明输入和输出符合预期而生产就绪要求的是幂等性、可观测性、可回滚性和安全性。举一个我身边的真实例子有人让 AI 写了一个订单创建接口单测、集成测试都过了上线第二天业务方反馈出现重复订单。排查下来发现AI 生成的代码没有做幂等性设计接口收到上游重试请求后直接又往订单表插了一行。这个场景任何人手工写代码都会下意识加一个订单号唯一索引或者幂等表但 AI 不会替你想到这一步它只负责把“功能逻辑”翻译成代码。所以把 AI 生成的代码合入主干之前至少要做一轮“生产环境视角”的审视。下面这张表是我日常工作的总览四道检查各管一段互相独立又互为补充。检查次序核心目标主要手段解决的典型风险第一道代码逻辑正确、质量合格人工 code review 静态分析 类型检查幻觉 API、逻辑遗漏、并发错误第二道安全合规、依赖可信密钥扫描、漏洞审计、SAST注入、越权、供应链风险第三道行为可验证、性能达标单元测试、集成测试、压测功能回归、性能劣化、数据错乱第四道发布可控、回滚可执行灰度放量、指标监控、预案演练全量故障、数据不可逆这四道检查的行动顺序我是刻意安排的先靠人眼解决“逻辑对不对”再用工具解决“漏洞有没有”接着用测试解决“行为稳不稳”最后用发布策略解决“出事了能不能回来”。接下来我按这个顺序把每一道检查的关键动作和踩坑经验展开讲。2. 第一道检查代码走查与静态分析别被表面漂亮糊弄过去2.1 人工走查时我会重点看这四个位置很多人觉得 AI 代码写得已经很规范了人工 review 就是走个形式。我的体会是恰恰相反越规范的代码越需要带着“找茬”的心态去过因为它太容易让你放松警惕。我给自己列了一个强制 check 的清单无论 AI 生成的代码看起来多完美这四处位置必须亲眼检查。第一个位置是 API 签名与外部契约。AI 生成代码时会编造函数签名、结构体字段和方法名因此我会对着官方文档或者本地依赖包里的真实定义逐个核对。重点看方法名、参数顺序、返回类型以及是否存在 deprecated 标记。第二个位置是异常处理与资源释放。AI 喜欢在 happy path 上写满逻辑但在异常分支上经常忘记关闭文件、数据库连接、HTTP resp.Body或者没有设置 context timeout、调用超时。这类代码初期没问题运行久了就是连接池泄漏和 goroutine 泄漏。第三个位置是并发安全与状态共享。它会很自然地用全局 map 存数据、不做锁、不处理 channel 关闭或者直接在一个结构体上开多个 goroutine 改同一个字段没有任何同步机制。第四个位置是数据操作与事务边界。AI 生成的 SQL 可能没有事务包裹或者事务只覆盖了一条 insert 而不是一批关联操作导致失败时写入一半的数据这种问题在数据库层面非常致命。每次人工走查时我会把上面几个位置作为“必答题”而不是“加分题”。没有异常处理我会直接打回没有做幂等我会追问没有事务边界我必然挂一个 P1 评论。这样看起来苛刻但挡掉过好几次潜在事故。2.2 静态分析与类型检查怎么配才不鸡肋人工走查主要解决业务逻辑问题但有些坏味道靠眼睛看效率太低必须交给静态分析和类型检查。我的原则是静态分析必须比 IDE 默认提示更严格类型检查必须开启严格模式否则这个环节形同虚设。以 Go 项目为例我至少会开 golangci-lint 里的这几个 lintergovet、errcheck、ineffassign、staticcheck、gosec。配置好之后在 CI 里跑一句golangci-lint run --timeout 5m --enable govet,errcheck,ineffassign,staticcheck,gosecerrcheck 会强制你处理所有错误返回值这能很有效地避免 AI 代码里常见的“只调函数不判断 error”的问题。TypeScript 项目我会把 tsconfig 里的 strict 设为 true并启用 eslint 的 typescript-eslint 推荐规则集。Python 项目我推荐 ruff bandit mypy各自负责风格、安全基线和类型标注。命令可以这样组合ruff check . bandit -r app/ mypy app/ --strict在配置静态分析时有一个经验值得分享不要对整个仓库全量跑历史代码不然你会被存量问题淹没根本看不出这次 AI 生成代码新引入了什么问题。我的做法是只针对本次 diff 做增量扫描让问题在 code review 阶段就暴露而不是淹没在海量历史告警里。2.3 工具不是万能的这三类问题必须靠人静态分析和类型检查能解决很多“错别字”级别的问题但有三类问题工具帮不上忙必须靠人的业务判断。第一类是业务语义错误比如把“金额向上取整”写成了“向下取整”工具无法知道你的业务应该舍还是入第二类是隐含的契约假设例如一个函数看似支持并发调用但内部依赖了某个不可重入的单例资源这在代码层面不一定能扫描出来第三类是性能策略选择AI 可能选择了功能等价但复杂度很差的实现比如在循环里重复查询数据库这类问题工具不会报错只能靠 review 时的嗅觉。我自己就吃过这个亏。有一回 AI 生成的代码在静态检查和类型检查里全绿却在一组热点接口上出现了 N1 查询数据量小时毫无感知上了生产后被流量放大数据库连接池瞬间耗尽。从那以后我给自己定了一条规矩工具全绿只是通过第一道检查的“最低门槛”真正拍板合入之前必须人工把上面四个位置逐一确认过。3. 第二道检查安全扫描与依赖审计别只看 CVE 列表3.1 依赖锁定与来源审计供应链风险最容易被忽视AI 生成代码时经常顺手引入新的依赖或者建议你把某个依赖升级到最新版本。这里有两层风险第一层是依赖版本本身存在已知漏洞第二层是依赖来源不可信。很多人只关注第一层Snyk 或 npm audit 一跑没漏洞就觉得安全了但第二层风险往往更致命。只要项目里引入了第三方依赖就要确保 lockfile 被提交到仓库。npm 看 package-lock.jsonPython 看 poetry.lock 或 requirements.txt 的 hashGo 看 go.sum。lockfile 的意义不只是统一版本它能保证所有人都安装完全一致的依赖树避免“我本地编译没问题、服务上构建就崩”的经典事故。锁定之后再对新增依赖做一遍来源审计这个包是不是来自官方 registry有没有被同名包仿冒维护者最近有没有异常动作。审计命令我常用这几个它们覆盖不同语言和维度npm audit --omitdev pip-audit osv-scanner -r . trivy fs --security-checks vuln,config .如果项目里用的是 npm 或 Python 生态我建议至少把 npm audit 和 pip-audit 加上如果服务是部署在容器里的可以再用 Trivy 扫一遍镜像。还有一点要特别提醒AI 可能会使用一个冷门小包来“节省自己写代码的时间”这些小包往往是供应链攻击的重点目标。每引入一个新依赖我都会在 review 记录里要求别人说明这个包解决了什么问题、有没有更主流的选择。3.2 密钥泄露扫描警告出现就得当真AI 生成代码时特别喜欢做一件事从网上的示例代码里复制一段配置而示例代码里往往带着真实的 API Key、AccessKey、数据库密码。这类密钥如果跟着代码进了仓库哪怕只是短暂存在也会被自动化爬虫扫到并滥用。所以我要求每轮 AI 代码合入之前都必须跑一遍密钥扫描工具。我常用的工具是 Gitleaks 和 TruffleHog两个都支持本地扫描和 CI 集成。Gitleaks 的用法很简单gitleaks detect --source . --report-format json --report-path gitleaks-report.json --redact这条命令会扫出仓库里所有疑似密钥的内容包括硬编码的 token、私钥、云厂商凭证。我要强调的一个经验是看到工具警告“这是一个示例密钥”不要因为“示例”就忽略。很多 AI 模型在训练阶段见过大量真实密钥它们生成出来的“示例”可能就是脱敏不彻底的旧密钥一旦有人拿去撞库照样会有风险。我宁可多花十分钟换一个密钥也绝不赌它没有泄露到外部。3.3 SAST 静态安全测试给代码装上“安检门”密钥扫描和依赖审计解决的是“仓库及依赖层面”的安全问题代码本身的注入、越权、SSRF 等漏洞则需要 SAST 工具进一步排查。这个环节我最常用的是 Semgrep 和 CodeQL。Semgrep 的好处是规则可定制且上手快可以直接内置一大堆安全规则还能针对企业私有逻辑写专属规则。我在项目里会跑这样一句semgrep scan --configauto --configp/owasp-top-ten --severityERROR这句命令会把 OWASP Top 10 相关的风险直接提升为 ERROR 级别阻断合并请求。如果团队用的是 GitHub 企业版可以再配上 CodeQL它对 Java、Go、Python、JavaScript 等主流语言的污点分析非常成熟能自动追踪用户输入是否流入了 SQL 查询、命令执行等危险 sink。比如 AI 生成了一段用字符串拼接方式构造 SQL 的代码CodeQL 大概率会直接标红提醒你改用参数化查询。有人说 SAST 误报率高我承认这一点所以我在配置时只把 ERROR 级别的规则接入 CI 强制拦截其他的留给开发人员手动确认。但无论如何安全扫描这一道必须排在测试之前因为代码一旦进了测试阶段说明你已经花了很多精力再返工成本很高。提前挡住不安全代码后面的测试才能跑得有价值。4. 第三道检查测试覆盖与性能验证用机器证明行为4.1 别让 AI 替你决定“有没有测试”很多 AI 编程助手会顺便生成测试代码但我必须泼一盆冷水AI 生成的测试用例绝大多数只覆盖正常的 happy path断言也写得非常宽松比如只断言“没有报错”、断言“返回值非空”却不验证具体值。这种测试存在的意义非常有限它更像是为了“看起来有测试”而写的样板。我的做法是三个补补核心分支、补错误路径、补边界条件。核心分支指的是业务里真正复杂的状态流转比如订单状态从“已支付”到“已发货”之间AI 有没有考虑到中间态的非法转换错误路径指的是网络超时、依赖服务异常、数据库写失败等场景要看代码在异常下能不能正确回滚、释放资源、返回友好错误边界条件则是空数组、超长字符串、并发重复请求这些极端输入。你可以在 AI 生成的单测基础上把这三类用例补进去然后观察覆盖率变化。集成测试这块我建议尽量用真实的基础设施。现在 testcontainers 已经很好用了Redis、MySQL、Kafka 都能在测试环境临时拉起一个真实实例来跑依赖测试不用把希望全部寄托在 mock 上。我自己在写 AI 相关流程的集成测试时至少会验证一个完整的“调用-落库-查库”往返链路确认 AI 生成的代码拿到的数据结构和真实存储的一致而不是仅仅在 mock 层自娱自乐。4.2 压测和性能分析必须设定 SLO 而不是“试一下”性能验证这块我踩过一次深刻的坑所以现在要求团队对所有改动量较大的 AI 生成代码做压测并设定明确的 SLO。SLO 怎么定我的建议是取线上实际流量的 P99 延迟和错误率作为基准再定义一个目标线。比如当前接口 P99 是 150ms那我要求 AI 改动后压测结果 P99 不超过 200ms错误率不超过 0.1%如果超了就打回优化而不是“看着差不多就行”。压测工具我常用 wrk 和 k6。wrk 适合快速验证单一接口的极限吞吐k6 则适合构造复杂的压测场景。一个简单的 k6 场景可以这样写import http from k6/http; import { check, sleep } from k6; export const options { scenarios: { ramp: { executor: ramping-vus, stages: [ { duration: 1m, target: 100 }, { duration: 2m, target: 300 }, { duration: 1m, target: 0 }, ], }, }, }; export default function () { const res http.get(http://localhost:8080/api/orders); check(res, { status is 200: (r) r.status 200, latency 200ms: (r) r.timings.duration 200, }); sleep(1); }压测的同时记得开内存和 CPU 监控观察是否存在泄漏趋势。Go 项目可以用 pprof 抓取堆栈Java 项目可以用 JFR 或 ArthasPython 项目用 tracemalloc。很多时候性能问题不是单纯“慢”而是“越跑越慢”这几乎都是资源泄漏或缓存无限增长导致的。除了压测数据库查询的 EXPLAIN 也必须看。AI 生成代码容易写出查询条件不走索引的 SQL数据量小的时候没事一旦主表有百万行数据查询马上变成全表扫描数据库 CPU 直接被打满。我见过一个典型案例AI 为一个用户列表页写了一个“SELECT * FROM users WHERE email LIKE %xxx%”的查询看起来没毛病但线上库里上百万用户这条 SQL 直接把数据库查挂了。4.3 数据一致性与幂等AI 代码最容易犯的“看不见的错”测试和压测跑完还有一个非常容易被忽略的环节数据一致性与幂等验证。AI 生成的代码往往只考虑“正常调用一次”的行为不考虑“重复调用”和“部分失败”的状态而这恰恰是线上故障的高发地带。以订单系统为例如果 AI 生成的接口没做幂等处理上游超时后重试就会产生两笔订单。我的解法很朴素要求所有写接口带上幂等键唯一的请求号作为主键或唯一索引重复请求直接返回第一次处理的结果。这个设计不需要多复杂但一定要有。另外事务边界必须人工核对涉及多张表更新的操作必须在一个事务里失败要回滚不能出现 A 表插入成功、B 表插入失败的中间状态。我会让 AI 生成代码之后自己画一遍“事务覆盖范围”的草图确认每一步读写操作都被正确包裹。数据一致性验证还有一个实用技巧构造一份“脏数据测试集”里面包含重复记录、空主键、超长字段、非法枚举值然后用这份测试集跑一遍业务处理逻辑观察 AI 生成的代码能否正确过滤或报错。没有这份测试集的 AI 代码就像没经过暴雨测试的防水手机看起来严丝合缝实际一泡就漏。5. 第四道检查灰度发布与回滚预案最后一道防线5.1 渐进式放量千万别一键切换所有流量前面三道检查都过了并不代表代码一定能安全上线。生产环境里的问题有很大一部分是由流量特征、用户行为、数据分布等因素触发的这些在测试环境里很难完整模拟。所以我坚持对 AI 生成代码的发布采用渐进式放量坚决反对一键全量切换。渐进式放量有三种常见实现方式。第一种是按比例切流量负载均衡层面把 1% 的流量引到新版本比如 Nginx upstream 的 weight、Kubernetes 的金丝雀 deployment、或者注册中心按权重选取实例第二种是按用户特征划分比如只对内部员工、只对白名单用户、只对某个灰度标签的用户开放第三种是特性开关用 feature flag 在代码里动态控制新旧逻辑的分支。三种方式可以在不同阶段组合使用比如先用内部用户做功能验证再逐步放大流量比例。我自己的放量节奏一般是 1% 观察一小时、5% 观察两小时、20% 观察半天、50% 观察一个完整业务周期最后才放开到 100%。这里的“观察窗口”不能只看监控面板刷新了几次而是要看一个完整业务周期有没有覆盖到。比如你的业务高峰期是晚上 8 点到 10 点那就必须在高峰时段全程观测过才算真正验证了生产环境的表现。5.2 灰度期间盯什么指标链路可观测是前提灰度放量之后很多人只是刷新一下“服务是否存活”的监控页面这远远不够。我盯的是 RED 指标Rate请求量、Errors错误量或错误率、Duration延迟分布。这三个指标缺一不可因为只看请求成功率高可能掩盖错误总量上升只看平均延迟又会被极端长尾掩盖 P99 劣化。告警规则我倾向于分成两个级别错误率超过 1% 就触发警告超过 5% 自动熔断或暂停放量P99 延迟超过 SLO 阈值且持续 5 分钟触发性能告警并回滚。这些指标要能正确解读前提是链路已接入全链路追踪。如果你的服务还没有 trace_id 贯穿日志那灰度期间排查问题基本靠猜。每次请求进来生成一个全局唯一的 trace_id日志系统把所有微服务打印的日志按 trace_id 串联才能快速定位是网关层超时还是业务逻辑异常。没有这个基础设施之前我不建议你放任何大改动上生产因为出了事你连“哪一环出问题”都判断不了。5.3 回滚预案重点不是代码回滚而是数据回滚最后一个环节是回滚预案。很多人觉得回滚就是“把上一版镜像重新部署一下”但那只适用于代码层面的回滚。如果 AI 生成的代码同时带了数据库变更比如加了新表、删了列、改了字段长度那么单纯回滚代码无法复原数据状态。这里我有一条非常强硬的规则合入 AI 生成的数据库迁移脚本之前必须确认迁移是可逆的。可逆的标准是存在一个明确的降级脚本能让旧代码在迁移后的数据结构上正常运行。比如想删除一张表正确的做法是先停掉对它的读写再改成“保留表结构但写入新表”最后才做物理删除。如果 AI 给你写了“DROP TABLE xxx”之类的迁移脚本我会直接判定为危险操作要求改写为“新增字段并双写”再走一个完整的发布周期。数据库迁移之前备份和恢复演练也不能省至少做一次“从备份恢复到指定时间点”的演练确保真的出事了能拉起来而不是靠碰运气。我在团队里会准备一张回滚检查表每次发布 AI 相关代码前逐项打钩新版代码的回滚步骤是否已给出、数据库迁移是否有降级路径、依赖服务的兼容性是否验证过、灰度期间的关键指标是否已确认、告警负责人和值班人是否已经在群里。这张表看起来琐碎但真到凌晨三点线上告警的时候你会发现它比任何经验都靠得住。写在最后我经常打一个比方AI 编程助手像一个极其高效但偶尔“脑补事实”的实习生。它能在十分钟内给你写出一版像模像样的代码但你绝不能让它独自承担上生产的责任。四道检查就是我给这个“实习生”配的导师团走查负责教它懂业务静态分析和安全扫描负责监督它别闯祸测试和压测负责验证它手脚协调灰度发布则保证即使它偶尔犯浑也只会摔倒在自己家里而不是大街上。我自己在实际操作中的体会是四道检查不能靠人肉逐次手动执行那样坚持不了多久。更好的做法是把它们固化成仓库里的脚本和 CI 流水线。静态分析、依赖审计、密钥扫描、SAST 这些环节从提交代码那一刻就自动跑人工走查和性能压测则按改动量分级触发改动大就全量走一遍改动小就摘重点。这样坚持一个月之后你会发现自己对 AI 生成代码的信任度明显提高了——因为“信任”不再是一句口号而是一套流程。如果你正在用 AI 写代码我的建议非常简单先别急着追求生成速度和代码量花一个下午把这四道检查的脚本搭起来然后把你最近一次 AI 生成的代码重新走一遍这个流程。大概率你会和我一样在第一次执行时就看到几个平时根本注意不到的隐患。省下的那一次线上事故比 AI 帮你多写一百行代码都值。