AI生成的代码能跑但不敢上线?生产级代码的六道工程关口与评审实战 📅 发布时间:2026/9/16 7:29:57 👁 浏览次数: 我们先把话说在前面AI 写的代码我已经用了快两年从最早的“图一乐”到后来真的拿它顶生产环境的活儿踩坑踩到能写出一本《AI代码事故回忆录》。如果你现在问我要一句总结那就是——AI 生成的代码能跑是因为编译器只校验语法而上线要过的关卡是分布式、高并发、安全、可观测性、还有一堆系统跟系统之间约定俗成的“潜规则”。这篇文章我就把这些没法直接上线的真实原因掰开讲顺便把这两年攒下的让 AI 代码真正“够格上线”的工程化手段也一并交出来。1. “能跑”和“能上线”差的根本不是语法是边界先说个最典型的场景。你让 AI“写一个 Python 脚本把 A 表的数据同步到 B 表”它三十秒给你交出来本地一跑数据真过去了看起来完美。但你怎么验证它处理了以下这些情况A 表有 2000 万行跑不动怎么办、B 表里已经存在相同主键怎么办、同步到一半网络断了要不要回滚、源表字段出现 NULL 会不会让目标表的非空约束炸掉、有没有可能把测试环境的数据误同步到生产库AI 端到端跑通的那一次只是它构造的“理想输入”恰好通过。它不知道你的表里有什么脏数据不知道你的下游系统有什么约束更不知道你的网线在凌晨三点会抖动。这就是“能跑”和“能上线”之间最核心的鸿沟——能跑是处理了正常路径能上线是处理完所有非正常路径并且在异常来临时还能优雅地失败。我把这个差异拆成了一个我每次 review AI 代码都会拿出来对照的表格维度本地“能跑”生产“能上线”输入固定样例、构造数据脏数据、超长输入、非法格式、缺失字段运行环境单机、无并发多实例、限流、熔断、容器重启容量很少量数据生产级数据量内存/CPU/IO 有预算约束依赖可能装了没用的库依赖最小化、版本锁定、License 合规错误处理没有错误路径有重试、降级、告警、回滚方案可观测性print() 就够日志、指标、trace 三件套齐全安全本地无攻击面不泄露密钥、不注入、不越权变更记录没人看可审计、可追溯、有评审记录这个表格是我实际工作里检验“AI 写的这段代码能不能进 code review”的尺子。只要有一项标红哪怕它功能 100% 正确我也会退回让同事或 AI 自己继续改。原因很简单生产环境的本质就是一个持续出错的环境。硬件会坏、依赖会挂、上游会延迟、用户会乱点。能跑的代码只要没想过怎么应对这些事上线就大概率变成事故现场。2. 拆穿真相AI 代码生成器的三个先天性短板如果说上面的边界问题是所有生产代码的共同要求那 AI 生成的代码还有一些“爹妈给的缺陷”。这三条是我在几百次使用中总结出来的共性问题理解了它们你才能知道为什么 AI 写得又快又对但工程师一看到提交就倒吸一口凉气。2.1 只写 happy path错误分支全靠猜绝大多数情况下AI 生成代码的逻辑是“顺推”的你给我一个目标我找一条从输入到输出的最短路径这条路径上的一切假设都是成立的。但真实世界的代码里错误分支的数量往往是正常分支的 3 倍以上——文件打不开、接口超时、反序列化失败、数据库连接池耗尽、磁盘写满……我曾经让 AI 写过一个读取 Excel 并入库的脚本它能在数据完全干净的情况下正确入库但只要某个单元格有换行符、日期格式不对、或者有一行只填了一半脚本直接抛异常退出。你说它错了吗也没错。但它把“异常时如何降级”这个决定权完全交给了默认的异常栈而非业务设计。这不是 prompt 写得不够好这是生成模型的底层倾向。它见过太多教程代码而教程代码的默认姿势就是“假设输入合法分类讨论从简”。所以你 review AI 代码时第一个盯死的地方就是错误处理分支。我自己惯用的做法是把 AI 生成的代码里所有except、return null、throw后面的逻辑遮住然后问自己一句如果这里炸了这一段代码能告诉运维它为什么炸吗回答不了就打回重写。2.2 缺乏“系统世界观”集成细节全废AI 写一个孤立的函数往往有模有样但一旦牵扯到系统之间的集成问题就开始冒头。它不知道你公司内部的 RPC 框架有什么约定不知道别的团队的服务对调用方有什么隐性要求更不知道你的网关限流规则是 1 秒 20 次还是 1 秒 2000 次。举个真实例子。我让 AI 生成“调用内部用户中心接口获取手机号”的代码它能按照接口文档写对 URL 和参数但它不会在代码里自动处理这几个系统级问题手机号是否需要脱敏后返回、接口是否需要透传 traceId 方便全链路追踪、调用频率有没有触发对方的降级策略、接口返回里如果包含多个手机号该取哪一个。这些信息不在 API 文档里而在组织协作的上下文里。人是靠开会、看文档、问同事才能把这种事搞清楚的AI 没有这个渠道。所以在集成场景下AI 扮演的角色更接近“高效的桩代码生成器”——它帮你把费用了很大精力才能写出的骨骼一次性搭好但肌肉、血管、神经这些连接组织的细节还得靠对系统有完整认知的人来填充。2.3 资源管理懒惰连接、句柄、并发全是隐患如果说前两条是“想得不够深”这一条就是“写得太随意”。我发现 AI 非常不擅长主动管理资源生命周期。让它写一段操作数据库的代码它不会主动加连接池让它写文件处理它可能忘记考虑文件句柄释放让它写多线程任务它倾向于直接开一个裸线程或者套一个根本没设置超时的asyncio.wait。举一个我自己被坑过的例子。当时让 AI 写一个“定时扫描某个目录并上传到对象存储”的脚本生成的代码里有一个致命的资源问题每次上传都新建一个 HTTP 客户端上传完不关闭。本地跑三五次没感觉放到生产环境跑半小时文件描述符直接打满进程崩溃。AI 不是不知道 HTTP 客户端要复用而是它在生成这段代码时根本没有“这段代码会在一个长生命周期的进程里长时间运行”的意识。这种问题的隐蔽性很强因为本地测试环境通常跑一两次就退出资源不释放的后果几乎看不见。但生产环境里的进程是 7x24 小时跑的一个个被遗忘的连接和句柄就像水管里越积越厚的垢总有一天会堵死。所以我现在用 AI 写任何涉及资源协作的代码都会额外加一条要求把进程生命周期写进 prompt并且 review 时逐个核对连接、文件、内存和并发对象的创建与释放是否成对出现。3. 从能跑到能上线必须趟过的六道工程关口前面聊的是 AI 代码的“性格缺陷”这章讲更落地的东西。一条数据从 AI 的对话框走到生产服务器中间至少得过六道关卡每一道都可能把“运行完美”的代码拦在门外。逐个说。3.1 契约审查接口文档和真实返回值到底信谁的AI 特别喜欢做的事情就是根据方法名和参数名“推测”返回值的结构。你让它调用一个getUserInfo()它自然而然地认为返回值里有个userName字段。但如果真实接口返回的是nickname、或者整个返回对象被统一包了一层{ code: 0, data: { ... } }AI 写的解析代码就必然踩空。这事的危险之处在于本地联调的时候你可能连着一个 mock servermock 数据的字段是严格按照 AI 生成的代码来定义的所以怎么跑怎么通。等接上真实环境才发现字段名对不上一运行全是undefined或NoneType。我的经验是所有 AI 生成的客户端调用代码都必须对着真实接口文档逐字段核对一遍不能只看类型还要看嵌套层级、可选性、命名风格snake_case 还是 camelCase、时间格式时间戳还是 ISO 字符串。这个步骤没有捷径但可以做得很快——把 AI 生成代码里的所有.field提取出来跟基架团队提供的契约文件比对一遍五分钟就能扫完。3.2 安全硬约束密钥值千万别让 AI 替你写进代码AI 对安全的意识非常薄弱最典型的表现就是图方便。你让它生成“调用某 API 并带上鉴权”的代码它可能直接在代码里写Authorization: Bearer sk-xxxx而这个 token 是你在对话里发给它的示例值。如果你在沙箱环境用了真实密钥这条密钥就等于裸奔在代码仓库里。更隐蔽的还有注入问题。AI 非常喜欢用字符串拼接构建 SQL、拼接 HTML、拼接 shell 命令因为训练语料里的教学代码大多这样写。一旦拼接的内容来自用户输入SQL 注入和命令注入的洞就开了。安全这块我的规矩是死的任何 AI 生成的代码提交前必须先过一次自动扫描。密钥检测用 git-secrets 或者 trufflehog 扫一遍注入风险用 CodeQL 之类的工具做静态分析。不满足直接退回。AI 可以帮你高效完成业务逻辑但底线的安全审查我坚持用人和工具双重确认。3.3 性能盲区跑得动和扛得住是两种代码AI 写算法题、写小函数时间复杂度通常是合理的但一旦扩展到大数据量场景问题就藏不住了。最常见的问题是循环里做查询。让 AI 给一批用户 ID 批量补充信息它可能直接写出一个for id in ids: query_db(id)——本地几百个 ID 没问题生产环境几万个 ID 就是一场灾难。请记住AI 生成代码的视频教程里所有的例子都是小数据集它没有“这个操作会被放大 10000 倍”的意识。另一个我遇到过的性能坑是内存。AI 写文件处理代码时习惯一次性read()整个文件本地文件几 MB 的时候无所谓生产上的日志文件动辄几个 GB一跑就 OOM。性能优化不是要求 AI 写出最优代码而是在 review 阶段强制戴上“生产放大镜”。我的做法是把 AI 写出的代码里所有涉及遍历、查询、I/O 的片段圈出来然后问自己三句——这个操作的时间复杂度是多少这个操作会执行多少次这批数据最大可能有多大。任何一个问题答不上来就得再补一轮设计。3.4 可观测性缺失线上环境没有 printAI 代码的默认调试手段是print或者更懒一点什么都不打。这在本地开发时完全没问题但在生产环境里你不可能通过看标准输出去排查问题。日志、指标、链路追踪这三样缺一样出了问题你只能瞪眼。我印象很深的一次事故一个由 AI 生成的定时任务处理失败时只是return false既不记录失败原因也不打日志。等到系统里出现脏数据我们花了两个多小时才从任务调度日志里找到线索因为唯一的报错信息是“任务失败”。而它为什么失败、失败在哪一批数据上完全无迹可寻。现在我对 AI 代码的要求多了一条硬性的每一条错误分支必须写清楚“who、what、when、why”——谁触发了这个错误、正在做什么操作、什么时间发生的、推测的原因是什么。做不到的话哪怕功能跑得通也过不了 review。3.5 数据与状态AI 不知道你的生产数据有多脏你在 prompt 里告诉 AI“用户 ID 是一个字符串”AI 就按字符串处理但它不会想到生产环境里的用户 ID 可能是00123和123两种格式并存。你告诉 AI“取最大值”它不知道你的上游系统偶尔会写入负数或者 NULL。AI 的“干净世界假设”和“生产环境的脏乱现实”之间的矛盾是所有问题的根源之一。程序员拿到一段 AI 代码后真正花时间的不是让代码跑起来而是把代码扔到真实样本数据上、跑出各种边界情况、再反复修补。这个过程是任何生成式 AI 都无法替代的因为脏数据的情况每家公司都不一样而且会随时间累积变化。推荐一个实操技巧每次准备让 AI 处理数据任务时先从生产库里采样一小批100 行左右真实数据塞给 AI让它基于真实样本而不是想象的情况去生成代码。我试过几次生成的代码健壮性明显提升一个档次。因为 AI 的自回归能力会让它去适配你给的数据模式而不是自创一套干净模式。3.6 合规与审计代码生成的自动化责任边界最后这关很多人忽略但对企业和团队来说不可回避AI 生成的代码如果在生产环境里出了问题责任怎么界定、变更怎么审计、代码有没有可能引入了来路不明的第三方库合规层面有两件事必须做一是依赖来源审计。AI 经常为了省事在代码里使用来源不明的第三方包或者建议安装一个你完全不了解的库。这在企业内部是不可接受的每个依赖都要过安全扫描、License 审查、版本锁定。二是变更审批留痕。生产环境的任何变更都要有记录AI 参与了哪些部分、谁 review 的、做过怎样的测试都要有据可依。不要觉得这是流程主义真出了事故这堆记录能救你的命。4. 实测案例一段 AI 生成的“可用”代码如何一步步被评审打回光讲理论容易飘放一个真实走查案例上来。某次内部分享会我现场让 AI 写一个“批量导入用户信息到内部系统的函数”要求是接收一个 CSV 文件路径读取后逐行入库。AI 花 40 秒交出了完整的 Python 代码看起来有函数、有异常处理、甚至有类型注解现场不少人觉得“这直接能用”。但我把这段代码按上面的标准走了一遍一共发现 7 个问题第一SQL 注入。AI 用 f-string 拼接要入库的值如果 CSV 里有特殊字符SQL 语句会被改写。这在内部工具里可能“没那么容易出事”但一旦接入不可信来源的数据就是灾难。改成参数化查询即可。第二字段校验缺失。代码完全没有检查 CSV 里是否缺少必填列、手机号格式是否正确、邮箱是否为空。如果有脏数据它会把脏数据直接写进生产库。需要增加一个“校验阶段”和“错误行报告”而不是静默跳过。第三数据全量加载内存。AI 用的csv.reader(file)配合列表推导把所有行一次性读进内存。生产环境的 CSV 可能有几十万行内存会撑爆。正确的做法是分块迭代逐行处理并批量提交。第四无幂等处理。如果任务执行到一半失败重跑重复的手机号会被再次插入产生重复数据。需要先查重、加唯一索引或采用 upsert 逻辑。第五事务边界太大。AI 把读取和入库写在同一个事务里一行失败整个文件回滚这在数据量大时会造成极长的锁持有时间。应该按批次提交并且明确批大小。第六错误信息不可观测。它的异常处理是except Exception as e: print(e)没有记录是第几行、哪个字段、什么原因。运维拿到这个日志根本没法快速定位。需要结构化日志输出。第七缺少数据脱敏意识。代码直接处理了包含手机号、邮箱等个人信息的 CSV但没有任何脱敏展示、权限校验或审计日志。用真实数据测试时敏感信息直接从日志里打出来了。现场我把这 7 个问题逐一讲解后大家都沉默了——AI 生成的代码确实是“能跑的”甚至能做 demo 演示、能做一次性脚本但真要上生产、面对真实数据和复杂环境时还隔着很远的距离。这不是说 AI 不行而是说明了一个事实AI 生成代码的定位更像一个初稿而不是终稿。它真正的价值是帮你把 80% 的重复性骨架搭好剩下 20% 的“生产级打磨”工作必须由了解业务、了解系统、了解数据的工程师来完成。这 20% 可能只占代码量的四分之一却要花掉 80% 的上线时间——因为那些“边界情况”“异常分支”“安全约束”“可观测性”这些硬功夫没有任何捷径可走。5. 带着 AI 过评审让 AI 代码真正具备上线资格的实操经验前面踩了这么多坑说到底还是得给些能直接抄作业的方法。分享四条我这两年用得最顺手的实战经验每一条都经历了真实项目的验证。5.1 给 AI 补“上下文”而不是只给一句需求很多人抱怨 AI 写的代码不能用其实是 prompt 给得太抽象。你让它“写个导出功能”它只能默认你的场景是小文件、单机、几秒钟跑完。正确的做法是把生产环境的约束条件直接写进 prompt。我自己常用的模板包含这几项数据规模上限最多多少行、多少条、运行环境容器内存限制、CPU 核数、关键约束必须使用连接池、禁止明文密钥、禁止阻塞主线程、失败时应该如何处理重试策略、告警方式、日志内容。同样一个需求加了这些上下文之后AI 生成的代码质量会高出一大截甚至可能主动写出分批次处理、超时控制这类你还没来得及想的设计。我把它理解为“帮 AI 建立它缺失的系统世界观”——它是没有主动意识但如果你把世界的规则喂给它它能在规则范围内做得很好。5.2 把 AI 当实习生要求它给出自测方案这个观点对我帮助很大。你带实习生的时候不会让他在不了解系统的情况下直接改核心代码更不会在他说“代码写完了”之后不做 review 就让他上线。AI 也一样。我的习惯是在让 AI 生成代码后紧接着追问一轮“你这段代码有哪些可能失败的场景请列出来并且告诉我你在代码里怎么处理了它们”。这个追问会让 AI 主动补上很多边界分支。实测下来第二轮生成的代码里错误处理占比会从不到 5% 提升到 20% 以上。更进一步我会要求 AI 生成附带单元测试的代码并且把正常路径、边界路径、异常路径都覆盖到。这个要求一加等于把 AI 从“只交答案”逼到“同时交证明”它自己写出来的测试往往能暴露出代码里的问题你再去 review 就轻松很多。5.3 建立专属的《AI 代码 Review Checklist》前面列的所有关卡如果没有落成清单靠人脑记是记不全的。我给自己团队设计过一个AI Code Review Checklist每次提交 AI 生成的代码时逐项打勾。内容大致如下错误分支是否都有日志日志是否包含足够的定位信息所有外部依赖库、API、服务是否经过确认是否有 License 风险是否涉及硬编码密钥、token、密码输入校验是否覆盖类型、长度、格式、非法字符、空值大数据量场景是否存在全量加载、循环内查询、N1 问题连接、文件句柄、线程池等资源是否成对释放进程长期运行是否安全时间处理是否明确时区日期格式是否统一接口调用是否有超时和重试重试是否会造成重复提交是否考虑幂等性重复执行会不会产生脏数据日志中是否可能泄露敏感信息这份清单看起来简单但它把“能不能上线”从个人经验变成了团队流程。只要逐项打勾AI 代码的质量稳定性会显著提升评审效率也会大幅提高。5.4 让 AI 做局部重构好过让它从零生成整个模块最后这条可能是最反直觉的。我刚开始用 AI 时习惯让它从零生成一整个服务或一整个模块后来发现改造成本比手写还高。原因是模块级的代码有太多团队特有的架构约束——目录结构、依赖注入方式、统一异常处理、基类约定——AI 不懂这些生成出来的代码再漂亮也是“异乡人”。现在我更倾向于让 AI 做局部重构给一段现有代码告诉它“把这段函数里的 N1 查询优化掉”“给这个函数补充参数校验和错误日志”“把这段同步调用改成异步并设置超时”。因为改动范围小、上下文清晰AI 的产出质量非常高而且改动效果可以直接对比验证。这条经验的实际收益很大你不再是把 AI 当成系统架构师而是当成一个非常听话的结对程序员让它替你干那些熟练但繁琐的活儿。6. 说到底AI 代码能不能上线取决于谁在负责“最后 20%”两年多用下来我对“AI 生成的代码能跑为什么不能直接上线”这个问题的回答已经稳定了——不是因为 AI 写得不够好而是因为“上线”这件事的标准从来不是“能跑”而是“在不可控的环境里持续正确地跑”。这个标准里包含的健壮性、安全性、可观测性、可维护性每一项都得靠人的认知和工程流程来兜底。AI 真正改变的是什么是让我把精力从“怎么写代码”转移到了“怎么让代码在任何情况下都不出事”。它负责把初稿完成我负责把它变成真正经得起生产环境考验的软件。省下来的时间我用来做更深层的设计系统的瓶颈在哪、哪里最容易出故障、故障来了怎么快速恢复。这些事 AI 暂时还做不了也是我们工程师不可替代的价值所在。所以如果你现在还在为“AI 代码上线就出问题”头疼我的建议很简单别放弃 AI也别轻信 AI。把它当实习生把评审当必修课把那条 checklist 贴在工位上。做到这三点你就会发现 AI 写的代码不但能跑而且真的能上线——只是那个“能上线”的工程是握在你自己手里的。