AI 代码生成质量管控实战:从 80% 生成率到四道防线,我只用了三个月 摘要三个月前我把 AI 当魔法用效率翻倍直到一次线上事故差点毁掉生产数据。如今我建起四道防线——风格、架构、行为、安全——让 AI 代码审查在合并前先过质检AI 代码质量与 AI 代码安全双双可控返工率骤降信任感回归。文章目录第一阶段放纵期效率有多爽隐患就有多大第二阶段事故期一次线上事故改变了我的判断第三阶段建管线期四道防线的搭建实录防线①风格静态检查先把乱止住防线②架构一致性检查把跨层调用拦下来防线③行为测试关键路径必须有测试防线④安全扫描把 AI 学到的老式写法揪出来四道防线的全景图沉淀下来的东西AI 代码质量评估矩阵这套管线不是万能的总结第一阶段放纵期效率有多爽隐患就有多大第 1 个月基本是放纵期。手里三个典型任务全交给 AI一个数据列表页的 CRUD一个带筛选和分页的查询接口还有一个数据库字段回填脚本。AI 处理这些任务的速度大概是我手写的一倍多。我那会儿在群里跟同事说感觉自己在用魔法。问题不是第一天就暴露的是攒出来的。代码风格先开始乱。同一个接口AI 一会儿生成getUser一会儿生成fetchUserData。类型定义里any越用越多——AI 面对不确定的类型最省事的选择就是any。等你发现不对劲的时候代码库里已经到处是any了。然后是架构层面的不一致。我们的项目约定是组件通过 store 拿数据但 AI 生成的组件经常直接 import service 层的方法绕过 store 一把梭。单看每一个文件都没问题拼在一起就拧巴了。我当时的判断是能跑就行先上线再说。反正后面有空再重构。这句话我后来才知道有多贵。第二阶段事故期一次线上事故改变了我的判断转折点发生在第 2 个月一次数据库脚本事故。一个 AI 生成的回填脚本在线上跑了一半崩了。具体过程不在这篇展开简单说就是脚本没做事务包裹崩的时候前面的数据已经改了后面的没改线上订单数据的折扣字段半新半旧。为了恢复数据又折腾了好几个小时。那是我第一次意识到AI 生成的代码和手写代码之间差的不是会不会写而是有没有人替它的假设负责。这里有个核心认知我觉得值得单独拎出来说AI 生成代码的合格线是能编译不是行为正确。LLM 的训练目标是预测下一个 token。它生成代码的时候是在这段代码最像人写的代码这个方向上做采样。它没有跑过你的测试没有看过你的数据库长什么样不知道你的历史数据里有脏数据也不理解你的业务规则。它知道的只是代码大概长这样。所以 AI 生成的代码会犯一类很典型的错语法永远是对的逻辑看起来也对但它的假设和你的现实对不上。那次事故之后我做了个决定AI 可以继续写代码但代码进主干之前必须过一套检查。第三阶段建管线期四道防线的搭建实录事故之后我做了决定第 3 个月就把这套检查搭了出来。一共四道防线每一道解决一层问题。防线①风格静态检查先把乱止住第一道最简单也最立竿见影上 ESLint 10 Prettier 3把代码格式和基础规范先钉死。为什么先做这个因为 AI 代码最大的问题不是逻辑是乱。命名不统一、any满天飞、魔法数字到处都是。这些不解决后面每一道防线的噪音都会很大——你没法在一堆any里找到真正的逻辑问题。配置上除了官方推荐规则我加了几条针对 AI 代码的no-explicit-any来自 typescript-eslintimport/order来自 eslint-plugin-import需要一并装插件// eslint.config.jsexportdefault[{files:[**/*.{ts,tsx}],rules:{// AI 面对不确定类型时最爱用 any直接禁掉typescript-eslint/no-explicit-any:error,// 魔法数字AI 喜欢把 86400000 直接写进代码可读性差// 业务常量请用命名常量替代不要靠 ignore 白名单no-magic-numbers:[error,{ignore:[0,1,-1,100]}],// import 顺序AI 生成的 import 顺序随机强制排序import/order:[error,{alphabetize:{order:asc}}]}}]跑一遍AI 生成的代码会被揪出一堆问题$ npx eslint src/components/UserList.tsx /Users/me/proj/src/components/UserList.tsx 3:1 error Unexpected any. Specify a different type typescript-eslint/no-explicit-any 7:5 error Avoid magic number: 86400000 no-magic-numbers 9:2 error Import order violated import/order ✖ 3 problems (3 errors, 0 warnings)跑一次的效果之前 AI 生成的代码单文件 lint error 经常十几个起步现在基本压到个位数以下。any的使用量下降了大概八成。通过标准很简单0 error有 error 就打回。不过要承认风格检查治标不治本。它抓的是乱不乱抓不到对不对。所以后面还有三道。防线②架构一致性检查把跨层调用拦下来第二道防线解决的是架构问题AI 不知道你的分层约定。我们的项目约定是组件 → store → service → API四层数据获取必须走 store。但 AI 生成的组件经常直接 import service绕过中间层。单看代码没问题时间长了整个项目的依赖关系就成一团浆糊。这一步用两个工具一个是 ESLint 的no-restricted-imports规则直接禁止跨层 import// eslint.config.js{rules:{no-restricted-imports:[error,{patterns:[// 组件层禁止直接 import service 层只能走 store{group:[/services/*],message:组件层不能直接调 service请通过 store 获取数据}]}]}}这规则配上/services/*的路径别名AI 生成的组件只要一碰 service 层就被拦$ npx eslint src/components/OrderCard.tsx /Users/me/proj/src/components/OrderCard.tsx 1:1 error ChainService should not be imported. 组件层不能直接调 service请通过 store 获取数据 no-restricted-imports ✖ 1 problem (1 error, 0 warnings)另一个是 madge 8检查循环依赖。AI 在生成互相引用的模块时很容易制造 A 依赖 B、B 又依赖 A 的循环运行时直接报错。npx madge--circular--extensionsts,tsx src/$ npx madge --circular --extensions ts,tsx src/ ✔ No circular dependency found!效果之前平均每两三个 PR 就会出现一次跨层调用现在基本在 CI 阶段就被拦住了。循环依赖的问题也少了很多。这里的局限是架构约束是约定不是事实。AI 每次生成新代码都可能打破约定所以这道检查必须持续跑不能跑一次就完事。防线③行为测试关键路径必须有测试第三道防线是测试。AI 生成的代码测试覆盖通常很低——它擅长写功能代码不擅长给自己写验证。更麻烦的是如果你让 AI 补测试它写出来的测试经常是测实现不测行为。比如// ❌ 错误写法测的是实现细节test(should call doSomething method,(){constspyvi.spyOn(instance,doSomething)instance.doSomething()expect(spy).toHaveBeenCalled()})// ✅ 正确写法测的是行为结果test(discount should be applied to total,(){constresultcalculateTotal({items,discountCode:SAVE10})expect(result.finalPrice).toBe(90)})第一种测试你把doSomething改名重构测试就挂了——它约束的是你调用了这个方法而不是这个功能行为对不对。第二种测试重构了也一样通过因为约束的是结果。跑起来的效果$ npx vitest run ✓ src/__tests__/calculateTotal.test.ts (2 tests) 2ms ✓ discount should be applied to total ✓ no discount code returns original price Test Files 1 passed (1) Tests 2 passed (2)我们的策略是不追求覆盖率数字只盯关键路径。支付、权限、数据写入这类核心链路必须有行为测试。工具函数和纯展示组件允许不测。通过标准是关键路径测试全绿。好处是测试数量不多维护成本可控但每次重构都有人兜底。说到底行为测试就是在替 AI 的假设负责——它假设 discount 会生效测试就验证 discount 真的生效了。防线④安全扫描把 AI 学到的老式写法揪出来最后一道防线是安全。这事挺有意思。AI 的训练语料里混着大量十几年前的老式写法——SQL 字符串拼接、eval()、硬编码密钥。AI 不知道这些写法已经过时了它只是觉得这段代码在训练集里很常见就给你生成出来。我们用 Semgrep1.172.0pip 安装做静态扫描加自定义规则# semgrep-rules/sql-injection.yamlrules:-id:no-sql-string-concatlanguages:[javascript,typescript]message:检测到 SQL 字符串拼接请使用参数化查询severity:ERRORpatterns:-pattern:|query($SQL $X)-pattern:|query(...${$X}...)-id:no-evallanguages:[javascript,typescript]message:检测到 eval()请使用 JSON.parse 或 Function 构造器替代severity:ERRORpatterns:-pattern:eval($X)$ semgrep --config semgrep-rules/ --lang js src/ ┌─────────────┐ │ Scan Summary │ └─────────────┘ 2 rules matched in 2 files. no-sql-string-concat src/utils/legacyQuery.ts no-eval src/utils/legacyParser.ts再配合npm audit查依赖漏洞。AI 生成代码时引用的第三方包版本经常乱来npm audit能补上这一层。效果高危问题在合并前被拦下SQL 拼接、eval()这类问题肉眼可见地减少了。通过标准是0 高危。局限也要说清楚SAST 工具误报率不低扫出来的结果需要人工确认。而且它抓的是已知模式抓不到业务逻辑层面的漏洞——比如这个接口谁能调这种权限问题工具是看不出来的。四道防线的全景图合并之前代码要过四道关卡AI 生成的代码 │ ▼ ① ESLint Prettier风格── 不通过 → 打回修改 │ ▼ ② no-restricted-imports madge架构── 不通过 → 打回修改 │ ▼ ③ Vitest 关键路径测试行为── 不通过 → 打回修改 │ ▼ ④ Semgrep npm audit安全── 不通过 → 打回修改 │ ▼ 合并进主干这套流程跑起来之后AI 生成代码的返工率明显下来了。更重要的是团队对AI 写的代码的信任感恢复了——我们知道它可能有错但至少四种最常见的错在合并前就被拦住了。下面是完整的流程图通过不通过通过不通过通过不通过通过不通过AI 生成的代码① 风格检查ESLint Prettier② 架构检查no-restricted-imports madge打回修改③ 行为测试Vitest 关键路径打回修改④ 安全扫描Semgrep npm audit打回修改合并进主干打回修改沉淀下来的东西AI 代码质量评估矩阵三个月下来我把经验整理成一张表新代码合并前对一遍AI 生成的和手写的都适用维度检查项工具通过标准AI 代码的常见翻车风格命名、类型、魔法数字ESLint 10 Prettier 30 errorany泛滥、命名不统一架构跨层调用、循环依赖no-restricted-imports madge 8无违规绕过 store 直连 service行为关键路径行为断言Vitest 4关键路径全绿测实现不测行为安全SQL 注入、敏感信息、依赖Semgrep 1.172 npm audit0 高危SQL 拼接、密钥硬编码这张表的用法不是所有代码都要过全部四关而是按代码的重要性分级。核心业务代码四关全过工具类代码可以只过①和④一次性脚本可以只过④。这套管线不是万能的聊到这得泼盆冷水。第一这套管线抓不到业务逻辑对不对。四道防线检查的都是代码本身有没有问题但这个折扣计算规则是否符合业务要求这种问题工具一件都管不了只能靠人 review。第二小项目、原型阶段不值得上全套。管线本身有维护成本配置规则、处理误报、维护测试都是时间。做 MVP 的时候能跑就行是合理的选择别为了流程而流程。第三别因为出了事故就否定 AI 写代码。样板代码、重复代码、工具函数、测试骨架AI 的效率依然是碾压级的。我到现在还是用 AI 写大部分代码只是多了一套检查兜底。说白了这套管线的本质就一句话把 AI 当实习生用。实习生写的代码要 reviewAI 写的当然也要。区别只是实习生会问这样做对吗AI 只会说好的马上生成。总结三个月前我在群里说感觉自己在用魔法三个月后我发现魔法确实存在但需要一套结界。AI 代码生成是杠杆它把写代码的边际成本压到极低。但杠杆的另一头是验证代码的成本——这部分一点没少反而因为代码量暴增而变高了。四道防线就是把验证成本摊到一个固定结构里让快和对能同时成立。另外那次差点搞坏生产库的数据库脚本事故完整复盘我写了另一篇从现象、排查到修复都有想看的可以等我发下一篇AI 生成代码翻车实录数据库回填脚本上线后数据错乱。