首页 / 资讯中心 / 文章详情

AI 写代码总改坏系统?拆解根因与安全实操方案

AI 写代码总改坏系统?拆解根因与安全实操方案 ★ FEATURED ARTICLE
1. 一个让所有用 AI 写代码的人都踩过的坑你有没有遇到过这种情况让 AI 帮你给项目加一个功能它三下五除二把代码写完了你一看功能确实能跑逻辑也没问题心里还挺美。结果一提交CI 红了或者更惨——上线之后发现另一个八竿子打不着的模块挂了。你回头一查发现 AI 在实现新功能的时候顺手“优化”了一段它觉得写得不好的旧代码或者改了一个它认为“多余”的判断条件。功能是做完了但系统被改坏了。这个问题不是某一个人的遭遇而是当下所有把 AI Coding Agent 引入日常开发流程的团队都在面对的普遍现象。我自己在过去大半年里深度使用过各种 LLM 驱动的编码工具从简单的代码补全到完整的 Agent 式自动改代码踩过的坑可以说能写一本书。今天这篇文章我就想把这个问题的根因拆开揉碎讲清楚同时给出我自己实测有效的应对方案。这篇文章适合谁看如果你是一个正在用或者准备用 AI 辅助编码的开发者如果你是一个技术团队的负责人正在考虑引入 AI 编码工具如果你是一个对 LLM 在软件工程中应用感兴趣的技术人那这篇文章里的经验应该能帮你少走不少弯路。我会从问题根因、核心机制、实操方案、排查技巧几个维度展开尽量做到既有原理层面的解释也有可以直接抄作业的操作步骤。2. 为什么 AI 会把“做完功能”和“改坏系统”同时发生2.1 局部最优与全局盲区LLM 的天然局限要理解这个问题首先得理解 LLM 在写代码时的基本工作模式。当前主流的大语言模型无论是 GPT 系列、Claude 系列还是各种开源模型它们的核心能力都是基于上下文做概率性的 token 预测。当你给它一个任务比如“给用户模块加一个导出 CSV 的功能”它会在自己的上下文窗口内根据你提供的代码片段、文件结构、历史对话生成它认为最合理的代码。这里的关键词是“上下文窗口内”。LLM 没有真正的全局视野它看到的只是你喂给它的那部分信息。如果你只给它看了用户模块的几个文件它就不会知道订单模块里有一个同名的工具函数也不会知道数据库连接池的配置在另一个文件里被特殊处理过。它做出的每一个决策都是在有限信息下的局部最优解。这就好比你把一个装修任务交给一个只看了客厅照片的工人他可能会为了客厅的美观把承重墙敲了——因为他不知道整栋楼的结构。AI 改代码也是同样的道理。它看到一段它认为“冗余”的判空逻辑可能就直接删了但它不知道那个判空是为了防御上游某个接口在特定情况下返回 null 而加的。2.2 Agent 模式放大了风险从“建议”到“执行”如果只是代码补全风险其实还好因为最终敲下回车的是你自己。但现在的趋势是 Coding Agent也就是让 AI 自主地读文件、改文件、跑测试、提交代码。这个模式把 AI 从“顾问”变成了“执行者”风险就指数级上升了。我自己的观察是Agent 模式下的问题通常出现在三个环节。第一个环节是文件读取范围Agent 为了理解任务会自己去读它认为相关的文件但它的判断标准是基于文件名和目录结构的语义相似度很容易漏掉那些名字不相关但逻辑上强耦合的文件。第二个环节是修改决策Agent 在实现功能时会顺带做它认为合理的重构比如统一代码风格、提取公共函数、删除它认为的死代码。第三个环节是验证闭环很多 Agent 跑完测试就认为任务完成了但测试覆盖率本身可能就不够或者测试用例没有覆盖到被改动的边缘逻辑。这三个环节叠加起来就导致了“功能做完了系统改坏了”的典型症状。而且最麻烦的是这种破坏往往是隐性的不会立刻报错而是在某个特定条件下才触发等到发现的时候已经过去好几天了。2.3 回归问题的本质隐式契约被打破从软件工程的角度看这类问题的本质是隐式契约被打破了。什么是隐式契约就是那些没有写在接口文档里、没有类型系统约束、但实际存在的依赖关系。比如某个函数的返回值在特定入参下一定是非空的比如某个模块的初始化顺序不能变比如某个全局变量的生命周期依赖于另一个模块的调用时机。这些隐式契约在人类开发者的大脑里是存在的因为人类在长期维护中积累了这些知识。但 LLM 没有这种积累它只能从代码文本中推断。如果代码本身没有把这些契约显式化AI 就很容易在改动中破坏它们。更糟糕的是AI 破坏契约的方式往往很隐蔽它可能只是改了一个条件的顺序或者把同步调用改成了异步表面上看逻辑等价实际上打破了时序依赖。3. 核心机制拆解AI 改代码时到底发生了什么3.1 上下文构建AI 看到的和你以为它看到的要解决问题先得理解 AI 是怎么“看”你的代码的。当你给 Coding Agent 一个任务时它首先会做的是构建上下文。这个过程通常包括几个步骤解析你的指令提取关键词根据关键词在代码库中搜索相关文件读取这些文件的内容可能还会读取一些配置文件、测试文件、文档。问题就出在“根据关键词搜索”这一步。当前的代码检索技术无论是基于关键词匹配还是基于向量嵌入的语义搜索都有明显的局限性。关键词匹配容易漏掉那些命名不规范的代码比如一个处理用户数据的函数叫processData你搜“user”就搜不到。语义搜索虽然好一些但在面对大型代码库时召回率和准确率的平衡很难做好要么搜出来一堆不相关的要么漏掉关键的。我实测下来的经验是对于一个中等规模的代码库比如十万行左右Agent 在一次任务中能正确识别出的相关文件通常只有实际需要关注的文件的六成到七成。剩下的三四成就是风险的来源。3.2 修改策略AI 的“顺手优化”为什么危险Agent 在修改代码时通常会遵循一些它自己总结的“最佳实践”。比如它会倾向于消除重复代码把相似的逻辑提取成公共函数它会倾向于简化条件判断把嵌套的 if 改成提前返回它会倾向于统一命名风格把getUserInfo和fetchUserData改成一样的。这些优化单独看都是对的放在代码审查里也是加分项。但问题在于AI 做这些优化时不理解背后的业务约束。我遇到过一个真实的案例AI 在实现一个新功能时发现有两个地方都在做类似的参数校验就把它们提取成了一个公共函数。听起来很合理对吧但问题是这两个地方的校验规则虽然看起来一样实际上一个是用于创建场景一个是用于更新场景更新场景下某个字段是允许为空的而创建场景下不允许。AI 提取公共函数时把这个差异抹掉了导致更新场景下原本合法的请求被拒绝。这个案例的教训是代码的重复有时候是有意为之的它反映的是不同业务场景下的不同约束。AI 没有业务知识它只能看到代码表面的相似性。3.3 验证盲区测试通过不等于没问题很多团队引入 AI 编码工具后会把“测试通过”作为验收标准。这个标准本身没错但问题是测试本身的质量。我见过太多项目测试覆盖率看着有七八十但仔细一看大部分测试都是 happy path边缘情况、异常分支、并发场景基本没覆盖。AI 改完代码后跑一遍测试全绿它就认为任务完成了。但实际上被它改动的那些边缘逻辑可能根本就没有对应的测试用例。更隐蔽的是有些测试本身写得很脆弱比如依赖了特定的执行顺序或者 mock 了不该 mock 的东西AI 的改动导致测试的执行环境变了测试虽然还过但已经失去了验证意义。还有一种情况是AI 为了让测试通过会去改测试代码。这个行为本身就很危险因为测试是验证逻辑的基准如果基准被改了那验证就失效了。我个人的原则是AI 可以改业务代码但改测试代码必须人工审查而且要有充分的理由。4. 实操方案如何让 AI 安全地改代码4.1 前置约束把隐式契约显式化最有效的方案是在 AI 动手之前把那些隐式契约变成显式的约束。具体怎么做我总结了几条实操经验。第一条完善类型定义和接口文档。如果项目用的是 TypeScript把类型定义写严格避免any和unknown的滥用。如果用的是 Python加上 type hints并且用 mypy 之类的工具做静态检查。类型系统是最便宜的契约表达方式AI 在改代码时会参考类型信息类型越严格它犯错的概率越低。第二条把关键约束写成注释。注意不是那种“这个函数用来处理用户数据”的废话注释而是“这个函数的返回值在 userId 为负数时返回 null调用方必须判空”这种约束性注释。AI 在读取代码时会读到这些注释虽然不能保证它一定遵守但至少给了它信息。第三条维护一份架构决策记录。把那些“为什么这么设计”的背景写下来放在代码库的显眼位置。比如“订单模块和支付模块之间通过消息队列解耦不要改成直接调用”。这份记录不需要很正式一个 Markdown 文件就行但要让 AI 在构建上下文时能读到。4.2 过程控制限制 AI 的修改范围前置约束做好了接下来是过程控制。核心思路是限制 AI 的修改范围让它只改该改的地方。我自己的做法是在给 AI 下任务时明确指定它可以修改的文件列表。比如“只允许修改src/user/export.py和src/user/models.py其他文件一律不动”。这个约束看起来简单但效果非常明显。它把 AI 的决策空间从整个代码库缩小到了几个文件大大降低了误伤的概率。如果任务确实需要跨文件修改那就分步骤做。先让 AI 改一个文件验证通过后再改下一个。不要一次性给它太大的范围。我试过让 AI 一次性重构五个文件结果它改出了三个 bug排查花的时间比我自己写还长。还有一个技巧是用 git 分支隔离 AI 的改动。每次让 AI 干活之前先开一个新分支改完之后用git diff仔细审查每一处改动。这个习惯救过我很多次因为有些改动 AI 不觉得有问题但我一看 diff 就发现不对劲。4.3 后置验证构建多层防御体系AI 改完代码后验证环节至关重要。我的经验是不能只依赖单元测试要构建多层次的验证体系。第一层是静态检查。跑 lint、类型检查、代码格式检查。这一层能拦住大部分低级错误比如语法问题、类型不匹配、未使用的变量。第二层是单元测试。但要注意单元测试的覆盖率要够而且要包含边缘情况。如果项目本身的测试质量不高建议先补测试再让 AI 改代码。补测试这件事本身也可以让 AI 做但补完的测试要人工审查。第三层是集成测试。很多问题在单元测试层面是发现不了的比如模块间的交互问题、数据库事务问题、并发问题。集成测试的环境要尽量接近生产环境这样发现的问题才有参考价值。第四层是人工审查 diff。这一层不能省。AI 改的代码哪怕测试全过也要人工看一遍 diff。重点看几个地方有没有删除看起来“多余”但实际上有防御作用的代码有没有改变函数的调用时序有没有修改配置文件的默认值有没有动测试代码。第五层是灰度发布。如果条件允许把改动先发布到小流量的环境观察一段时间再全量。这一层能拦住那些测试覆盖不到的问题。4.4 工具链配置让 AI 在护栏内工作除了流程上的控制工具链的配置也很重要。我推荐几个实操中觉得有用的配置。第一个是权限控制。如果用的是 Agent 模式给它配置只读权限的文件列表让它不能改那些核心的、稳定的模块。比如数据库迁移脚本、认证授权模块、支付相关的代码这些地方不要让 AI 碰。第二个是预提交钩子。在 git 的 pre-commit 钩子里加上 lint、类型检查、单元测试任何一项不通过就拒绝提交。这样即使 AI 改出了问题也进不了代码库。第三个是变更影响分析。有些工具可以分析代码改动的影响范围比如改了某个函数哪些调用方会受影响。把这个分析结果作为 AI 改代码的参考能帮它避免一些明显的误伤。第四个是回滚预案。每次 AI 改代码之前确保有快速回滚的方案。比如用 feature flag 控制新功能的开关出问题直接关掉不用回滚代码。5. 常见问题与排查技巧实录5.1 典型问题速查表问题现象可能原因排查思路解决方案功能正常但其他模块报错AI 改了公共函数或工具类对比 diff重点看被多个模块引用的文件限制 AI 修改公共模块或改完后跑全量集成测试测试通过但线上出问题测试覆盖不足边缘情况未覆盖检查被改动代码的测试覆盖率看是否有未覆盖的分支补边缘测试用例增加灰度发布环节AI 反复改同一个地方上下文理解有误或任务描述不清检查给 AI 的任务描述是否明确上下文是否完整细化任务描述提供更多背景信息改动后性能下降AI 引入了低效的实现或改了索引/缓存逻辑对比改动前后的性能指标定位瓶颈对性能敏感代码加约束改完后跑性能测试AI 改了测试代码Agent 为了让测试通过而修改测试审查 diff 中测试文件的改动禁止 AI 修改测试代码或改后必须人工审查代码风格不一致AI 用了自己的风格没遵循项目规范检查项目是否有明确的代码规范配置配置 lint 规则让 AI 读取规范文件5.2 独家避坑技巧第一个技巧是给 AI 写“负面清单”。除了告诉它要做什么还要明确告诉它不要做什么。比如“不要修改任何以_test.py结尾的文件”“不要改动config/目录下的任何文件”“不要删除任何带有# IMPORTANT注释的代码”。这个负面清单要放在项目的显眼位置让 AI 每次都能读到。第二个技巧是用“小步快跑”代替“大步跃进”。不要指望 AI 一次性能完成一个大功能把它拆成多个小任务每个任务只改一两个文件改完验证通过再进入下一个。这样虽然看起来慢但总体效率更高因为排查问题的成本大大降低了。第三个技巧是建立“AI 改动日志”。每次 AI 改完代码记录一下改了哪些文件、改了什么、为什么改、验证结果如何。这个日志积累起来能帮你发现 AI 的常见错误模式后续就能有针对性地预防。第四个技巧是定期做“AI 代码审查”。每隔一段时间把 AI 改过的代码集中审查一遍看看有没有潜在的问题。有些问题不会立刻暴露但积累久了就会变成技术债。5.3 一个真实的排查案例说一个我自己遇到的案例。有一次我让 AI 给一个数据处理管道加一个字段它改完之后测试全过功能也正常。但上线第二天下游的报表系统数据对不上了。排查了半天发现 AI 在加字段的时候顺手把上游的一个数据过滤条件改了。原来那个过滤条件是为了排除测试数据AI 觉得那个条件“冗余”就删了。结果测试数据混进了生产数据导致报表异常。这个问题的根因是AI 不理解那个过滤条件的业务含义。它只看到代码逻辑上“多余”但不知道那是业务上的必要约束。后来我的做法是给这类关键逻辑加上明确的注释并且在 AI 的任务描述里明确说“不要改动数据过滤相关的逻辑”。这个案例的教训是AI 的“代码洁癖”在业务系统里往往是危险的。代码的“优雅”和业务的“正确”之间有时候需要取舍。AI 不懂这个取舍所以需要人来把关。6. 从工具到流程构建 AI 友好的开发环境6.1 代码库的可读性改造想让 AI 少犯错代码库本身的可读性很重要。这里说的可读性不只是给人看的也是给 AI 看的。我做了几件事来提升代码库的“AI 友好度”。第一件是统一命名规范。函数名、变量名、文件名尽量做到见名知意避免缩写和拼音。AI 在搜索相关文件时命名规范能大大提高召回率。第二件是模块边界清晰。每个模块的职责单一依赖关系明确避免循环依赖。这样 AI 在改一个模块时影响范围可控。第三件是关键逻辑加注释。前面提过了这里再强调一下。注释要写“为什么”而不是“是什么”把业务约束、边界条件、异常处理的原因写清楚。第四件是维护一份项目地图。用一个 Markdown 文件描述项目的整体结构、各模块的职责、关键的数据流。这份地图放在根目录AI 每次构建上下文时都能读到能帮它快速建立全局认知。6.2 测试策略的调整传统的测试策略是为人类开发者设计的引入 AI 后需要做一些调整。首先提高边缘情况的测试覆盖率。AI 最容易在边缘逻辑上犯错所以边缘情况的测试要特别充分。我通常会针对每个被 AI 改动的函数检查它的边界条件、异常分支、空值处理是否有对应的测试。其次增加契约测试。契约测试验证的是模块间的接口约定比如请求和响应的格式、错误码的定义、超时行为。这类测试能有效拦住 AI 破坏隐式契约的问题。再次引入变异测试。变异测试是通过故意修改代码来验证测试的有效性。如果修改了代码但测试还是过说明测试有问题。这个技术能帮你发现那些“看起来在测实际上没测到”的用例。最后测试代码和业务代码分开管理。不要让 AI 同时改业务代码和测试代码最好是把测试代码的修改权限收起来只让 AI 读不让它写。6.3 团队协作的适配AI 编码工具引入后团队的协作方式也需要调整。代码审查环节要特别关注 AI 改动的部分。我建议在 PR 描述里明确标注哪些是 AI 生成的哪些是人工写的这样审查的人能更有针对性。任务分配环节要把适合 AI 的任务和不适合的分开。适合 AI 的任务通常是范围明确、影响面小、有充分测试覆盖的。不适合的是涉及核心架构、影响面大、测试覆盖不足的。知识沉淀环节要把 AI 犯过的错误记录下来形成团队的“避坑指南”。这份指南可以喂给 AI 作为上下文帮它避免重复犯错。7. 我个人的一些体会用了大半年 AI 编码工具我最大的体会是AI 是一个能力很强但需要护栏的同事。它能把你的想法快速变成代码效率确实高但它没有业务 sense没有全局视野也没有对系统稳定性的敬畏。你不能指望它自己把握好分寸你得给它划好边界。另一个体会是测试的重要性被 AI 放大了。以前测试是质量保障的一部分现在测试是 AI 安全改代码的前提。没有好的测试你就不敢让 AI 动代码。所以如果你打算引入 AI 编码先把测试补起来这个投入是值得的。还有一个体会是人工审查不能省。我知道很多人希望 AI 改完直接提交省去审查的环节。但根据我的经验AI 改的代码里大概有百分之十到二十是需要人工调整的。这个比例看起来不高但如果不审查这些问题就会积累成技术债后面还起来更痛苦。最后分享一个小技巧我习惯在让 AI 改代码之前先让它复述一遍任务确认它理解对了再动手。这个步骤花不了多少时间但能避免很多因为理解偏差导致的误改。有时候 AI 复述的内容和我想要的完全不是一回事这时候就能及时纠正省得改完再返工。这个领域还在快速演进工具在变最佳实践也在变。我上面说的这些是基于我当前的经验不一定适用于所有场景。但核心思路应该是不变的给 AI 足够的上下文限制它的修改范围构建多层次的验证体系保持人工审查。做到这几点AI 就能成为你的得力助手而不是系统的破坏者。
阅读完成 · 觉得有帮助?
咨询建站