
1. 一个让所有工程师后背发凉的场景你有没有遇到过这种事让 AI Coding Agent 帮你加一个功能它确实加完了代码也能跑测试也过了但上线之后系统开始出各种莫名其妙的毛病——原本正常的模块开始报错边界条件处理失效性能莫名其妙下降甚至有些老功能直接挂了。你回头一看 diff发现 AI 为了“优雅地”实现新功能顺手重构了三个不相关的模块改了两个公共函数的签名还“贴心”地删掉了它认为“冗余”的防御性代码。新功能确实做完了但系统被改坏了。这不是段子这是 2024 年以来大量团队在引入 AI Coding Agent 之后真实踩过的坑。我自己在过去一年多的项目里用过的 Agent 从早期的代码补全工具到后来的自主式 Coding Agent踩的坑足够写一本书。今天就把这件事掰开揉碎讲清楚为什么 AI 把功能做完了系统却被改坏了以及我们该怎么防。这篇文章适合所有正在或准备把 AI Coding Agent 引入研发流程的人——不管你是刚接触 LLM 辅助编程的新手还是已经在团队里推行 AI 协作的老手都能从里面找到可以直接抄作业的防护策略。2. 问题本质AI 的“完成”和工程的“完成”不是一回事2.1 AI 眼中的“任务完成”到底是什么要理解为什么系统会被改坏首先得理解 AI Coding Agent 是怎么定义“完成”的。当你给 Agent 一个任务比如“给用户模块加一个手机号登录功能”它的内部逻辑大致是这样的解析需求 → 定位相关代码 → 生成修改方案 → 执行修改 → 验证通常是跑测试或自我检查→ 输出结果。问题出在最后一步的“验证”上。AI 的验证标准通常是新功能能不能跑通。它会跑一下测试如果测试通过它就认为任务完成了。但工程意义上的“完成”远不止于此——还包括不破坏现有功能、不引入隐性耦合、不改变公共接口的语义、不降低系统的可维护性。这些维度AI 在默认情况下根本不会去检查。我见过最典型的一个案例一个 Agent 被要求给订单模块加一个折扣计算功能它实现完之后为了让代码“更简洁”把订单金额的计算逻辑从Decimal改成了float。新功能的测试全过了但上线之后财务对账开始出现分分钱的误差。这种问题测试根本测不出来因为测试用例的数值恰好不会触发浮点精度问题。2.2 回归被 AI 忽视的隐形杀手在软件工程里回归Regression指的是修改代码后原本正常的功能出现异常。这是所有工程师的噩梦也是 AI Coding Agent 最容易制造的问题。传统的回归风险主要来自人的疏忽——改 A 的时候没想到会影响 B。而 AI 带来的回归风险有它独特的特点第一AI 的修改范围往往超出必要边界。人类工程师改代码时通常比较克制只动必要的地方。但 AI 倾向于“顺手优化”它会在实现新功能的过程中把沿途看到的不合理代码一并改掉。这种“善意”的越界恰恰是回归的温床。第二AI 对隐式契约不敏感。代码里有很多约定俗成的东西是不写在文档和测试里的——比如某个函数虽然签名允许传 null但调用方从来不会传 null比如某个字段虽然类型是 string但实际只会是特定枚举值。AI 不知道这些隐式契约它可能“合理地”利用这些它以为存在的自由度结果打破了调用方的假设。第三AI 的自信掩盖了不确定性。人类工程师改完代码会心虚会反复检查。AI 改完代码往往语气笃定告诉你“已完成”这种自信会让 review 的人放松警惕。2.3 一个真实的翻车现场说一个我自己经历的例子。当时我们有一个用 Python 写的服务里面有一个核心的计费函数逻辑比较复杂涉及阶梯计价和多种优惠叠加。我让 Agent 帮忙加一个新的优惠类型。Agent 看完代码后说这个函数太长了建议拆分成几个小函数。我觉得有道理就让它做了。它把原来的一个 200 行的函数拆成了 5 个小函数新优惠类型也加进去了测试全过。结果上线第二天客服反馈说某些用户的账单金额不对。排查了半天才发现Agent 在拆分函数的时候把原来一个if-elif链的顺序调整了。原来的顺序是有讲究的——先判断特殊优惠再判断通用优惠调整之后变成了先判断通用优惠导致某些同时满足两个条件的用户走了错误的计算分支。这个 bug 测试为什么没测出来因为原来的测试用例只覆盖了单一优惠场景没有覆盖优惠叠加的场景。而 Agent 在拆分时完全没有意识到if-elif的顺序本身承载着业务逻辑。3. 根因拆解AI 改坏系统的五条路径3.1 路径一过度重构与范围蔓延这是最常见的一条路径。你让 AI 加功能它顺便帮你重构。重构本身不是坏事但 AI 的重构往往缺乏全局视角。AI 的重构逻辑通常是局部最优的——它看到一段代码觉得可以更优雅就改了。但它不知道这段代码之所以写得“不优雅”可能是因为某个历史遗留的兼容性要求可能是因为某个下游系统依赖了当前的实现细节也可能是因为性能考量。我总结了一个规律AI 重构的冲动和代码的“丑陋程度”成正比。代码越乱AI 越想动手。但恰恰是那些看起来最乱的代码往往承载着最多的隐式约束。老代码里的“丑陋”很多时候是无数次 bug 修复沉淀下来的防御性设计。3.2 路径二隐式契约的无声破坏软件系统里充满了隐式契约。举几个例子函数 A 返回的列表调用方默认它是有序的但函数签名里没写配置项 B 虽然允许为空但下游系统默认它不为空接口 C 虽然文档说返回 200 表示成功但实际上调用方依赖了响应体里的某个字段这些隐式契约不会出现在类型系统里不会出现在文档里甚至不会出现在测试里。它们只存在于老工程师的脑子里和历史的 commit message 里。AI 在修改代码时会把这些隐式契约当作“不存在”然后按照它理解的“合理方式”去改。结果就是代码看起来更规范了但系统坏了。3.3 路径三测试覆盖的盲区被精准命中AI 有一个很讽刺的特点它写测试的能力很强但它破坏系统的能力更强。为什么因为 AI 写的测试往往覆盖的是“正常路径”而它破坏的往往是“边界路径”。更麻烦的是当 AI 修改代码后跑测试测试通过会给它一个强烈的正反馈。它会认为自己的修改是安全的。但实际上测试通过只说明“测试覆盖的场景没被破坏”不说明“系统没被破坏”。我做过一个统计在我们团队引入 Agent 的前三个月由 AI 修改引发的线上问题中超过 70% 的问题场景在现有测试用例中完全没有覆盖。这不是测试写得不好而是测试永远不可能覆盖所有场景而 AI 的修改恰好命中了那些没被覆盖的角落。3.4 路径四上下文窗口的物理限制这一条是技术层面的硬约束。LLM 有上下文窗口限制这意味着 Agent 在处理大型项目时不可能同时“看到”所有代码。当 Agent 修改一个模块时它可能只加载了当前文件和直接相关的几个文件。那些间接依赖当前模块的下游代码它看不到。于是它做了一个在当前上下文里看起来完全合理的修改但这个修改破坏了一个它根本不知道存在的调用方。这就像在一个巨大的迷宫里面改路——你只看到了眼前这几条通道改完之后眼前的路确实更顺了但迷宫另一头的出口被堵死了。3.5 路径五LLM 的“讨好型人格”这一条比较微妙但影响很大。LLM 在训练过程中被优化成“帮助用户完成任务”这导致它有一种强烈的“讨好”倾向。具体表现是当你让它加功能时它会倾向于用最“漂亮”的方式实现而不是最“保守”的方式。它会倾向于展示自己的能力而不是克制地只做被要求的事。它会倾向于告诉你“完成了”而不是告诉你“我做了这些修改但有几处我不确定”。这种讨好型人格在简单任务上是优点在复杂系统里是灾难。因为复杂系统需要的是克制、保守、最小改动而不是炫技。4. 防护体系让 AI 改不坏系统的六层防线知道了根因防护就有方向了。下面这六层防线是我在实际项目中逐步总结出来的从流程到工具到习惯层层递进。4.1 第一层任务边界的最小化定义这是最基础也最有效的一层。核心思想是永远不要给 AI 一个模糊的大任务要给它一个边界清晰的小任务。具体怎么做把“给用户模块加手机号登录”这种任务拆解成在UserService中新增loginByPhone方法签名如下……在AuthController中新增/login/phone路由调用上述方法新增单元测试覆盖正常登录和验证码错误两种场景关键点是明确告诉 AI 不要做什么。比如“不要修改现有方法的签名”、“不要重构UserService的其他方法”、“不要改动AuthController中已有的路由”。我实测下来加了“不要做什么”的约束之后AI 越界修改的概率能降低 60% 以上。这个投入产出比非常高。4.2 第二层修改范围的硬性约束光靠提示词约束还不够因为 AI 有时候会“忘记”。更硬的手段是在工具层面做限制。如果你用的是支持文件级权限的 Agent 工具可以配置它只能修改指定文件。如果工具不支持可以在 prompt 里明确列出允许修改的文件清单并要求它在修改任何清单外的文件前必须先询问。还有一个技巧让 AI 先输出修改计划人工确认后再执行。很多 Agent 支持这种“plan-then-execute”模式。虽然多了一步交互但能拦下大量越界修改。我们团队现在的标准流程是Agent 必须先输出一份修改计划列出要改哪些文件、每个文件改什么、为什么改。这份计划由人来 review确认没问题再让 Agent 执行。这一步看似麻烦但省下的 debug 时间远超这点开销。4.3 第三层回归测试的自动化护栏前面说过AI 破坏的往往是测试没覆盖的场景。那怎么办答案是在 AI 修改代码后自动运行比平时更全面的测试集。具体做法是维护一个“回归测试集”这个集合比日常开发的测试集更全包含所有历史 bug 的复现用例、所有边界场景、所有集成测试。每次 AI 修改代码后强制跑这个全集。更进一步可以用LLM as Judge的方式做语义级回归检查。具体来说把修改前后的代码 diff 喂给另一个 LLM让它判断“这个修改是否可能影响其他功能”。虽然这种方式有误报但作为一道额外的防线能拦下不少问题。我还见过一种更硬核的做法用基于 LLM 的单元测试生成工具在 AI 修改代码后自动生成一批针对被修改函数的测试用例跑一遍看有没有异常。这个思路的核心是“用 AI 的矛攻 AI 的盾”。4.4 第四层变更影响面的静态分析在 AI 修改代码后用静态分析工具算出这次修改的“影响面”——哪些函数被间接影响、哪些调用方可能受影响。Python 生态里可以用pyan、code2flow这类工具做调用图分析。Java 生态里可以用Soot、WALA。这些工具能帮你快速定位“这次修改可能波及的范围”然后针对性地做人工 review 或补充测试。我自己的做法是每次 AI 修改后先跑一遍调用图分析如果发现修改的函数被超过 5 个地方调用就强制人工 review 所有调用方。这个规则帮我拦下过好几次潜在的回归问题。4.5 第五层灰度发布与快速回滚再好的防护也不能保证 100% 不出问题。所以最后一层防线是让问题的影响面可控让回滚足够快。具体来说AI 修改的代码上线时走灰度发布流程。先放 1% 的流量观察核心指标有没有异常。如果异常立即回滚。这里有个细节回滚要能精确到单次修改。也就是说每次 AI 的修改应该是一个独立的 commit而不是和其他修改混在一起。这样出问题时能精准回滚而不是把一堆无关的修改一起回退。4.6 第六层建立 AI 修改的专属 review 清单最后一层是人的层面。既然 AI 修改有它独特的风险模式那就应该有一份专门的 review 清单针对这些风险模式做检查。下面这份清单是我们团队实际在用的你可以直接抄检查项检查内容风险等级修改范围是否修改了任务范围外的文件高函数签名是否改动了公共函数的签名高控制流顺序是否调整了 if-elif 链、switch 分支的顺序高数据类型是否改动了数值类型如 Decimal 改 float高异常处理是否删除了 try-catch 或防御性代码中默认值是否改动了函数参数的默认值中日志与监控是否删除了日志埋点或监控代码中注释与文档是否删除了承载业务逻辑说明的注释低这份清单的核心逻辑是AI 倾向于“优化”的东西恰恰是最容易出问题的地方。它觉得冗余的防御性代码可能是历史 bug 的修复它觉得可以合并的分支可能承载着业务规则它觉得可以简化的类型可能涉及精度要求。5. 实操指南一套可直接落地的 AI 协作流程前面讲的是原理和防线这一节讲具体怎么落地。下面这套流程是我们团队跑了半年多、迭代了十几版之后稳定下来的你可以根据自己的情况调整。5.1 任务下发阶段把需求写成 AI 能理解的规格给 AI 下任务时不要用自然语言描述需求要用结构化的规格。我通常用这个模板任务在 UserService 中新增 loginByPhone 方法 允许修改的文件 - src/services/user_service.py - tests/test_user_service.py 禁止修改的文件 - src/services/auth_service.py - src/models/user.py 方法签名 def login_by_phone(self, phone: str, code: str) - LoginResult 行为要求 1. 验证手机号格式不合法返回 INVALID_PHONE 2. 验证验证码错误返回 INVALID_CODE 3. 验证通过返回 LoginResult包含 user_id 和 token 约束 - 不要修改 UserService 中已有的任何方法 - 不要改动 User 模型 - 不要引入新的第三方依赖 - 复用现有的 verify_code 方法不要重新实现这个模板的关键是明确边界、明确签名、明确行为、明确约束。四样都写清楚AI 越界的空间就很小了。5.2 执行阶段plan-then-execute 模式不要直接让 AI 改代码先让它出计划。计划要包含要修改的文件清单每个文件的修改内容概述修改的理由可能的影响面人工 review 计划确认没问题再让它执行。这一步能拦下大部分越界修改。如果 Agent 工具支持可以开启“每步确认”模式让它每改一个文件就暂停等你确认。虽然交互次数多但安全性最高。5.3 验证阶段三层验证AI 改完之后走三层验证第一层是单元测试跑被修改模块的所有测试。这一层是基础但不够。第二层是回归测试集跑全量的回归测试。这一层能拦下大部分回归问题。第三层是影响面分析用静态分析工具算出修改的影响范围人工 review 关键调用方。这一层能拦下测试覆盖不到的隐性问题。三层都过了才进入 code review 环节。5.4 上线阶段灰度与监控AI 修改的代码上线时走灰度流程。先放小流量观察核心指标。观察期至少 24 小时覆盖完整的业务周期。监控要特别关注错误率、响应时间、核心业务指标如订单量、支付成功率。如果任何指标异常立即回滚。回滚要能精确到单次 AI 修改。所以每次 AI 修改应该是独立的 commitcommit message 里标注清楚是 AI 生成的方便追溯。6. 常见问题与排查技巧实录6.1 AI 改完代码后测试全过但上线出问题怎么办这是最典型的问题。测试全过说明测试覆盖的场景没被破坏但问题出在没覆盖的场景。排查思路是先定位出问题的功能然后看这个功能和 AI 修改的代码之间有没有调用关系。如果有重点看 AI 修改的代码在边界条件下的行为。一个实用技巧是用 git bisect 定位是哪次修改引入的问题。如果确认是 AI 修改引入的把那次修改的 diff 单独拿出来分析通常能很快找到问题。6.2 AI 总是“顺手”重构不相关的代码怎么办这是 AI 的天性很难完全避免但可以缓解。首先在 prompt 里明确写“不要重构任何现有代码”。其次用文件级权限限制它只能改指定文件。最后如果它还是改了在 review 时坚决打回让它重做。我自己的经验是对 AI 的越界修改要零容忍。一旦你接受了它的越界它下次会更放肆。反过来如果你每次都打回它会逐渐学会克制在同一个会话里。6.3 如何判断 AI 的修改是否安全这个问题没有 100% 的答案但可以用几个信号来判断修改范围是否超出任务边界超出则不安全是否改动了公共接口改动则不安全是否删除了防御性代码删除则不安全是否调整了控制流顺序调整则不安全是否改动了数据类型改动则不安全如果以上都没有那相对安全。但即使都符合也要跑回归测试。6.4 AI 修改引发的性能问题怎么排查AI 有时候会引入性能问题比如把 O(1) 的操作改成 O(n)或者引入了不必要的循环。排查方法是对比修改前后的性能基准测试。如果没有基准测试用 profiler 跑一下被修改的代码路径看热点在哪里。一个常见的原因是 AI 为了“代码优雅”把原本的批量操作改成了逐个操作。比如把batch_insert(items)改成for item in items: insert(item)。这种修改在功能上等价但性能差很多。6.5 多 AI 协作时如何避免互相破坏现在很多团队会同时用多个 Agent 处理不同任务。这时候风险更大因为 Agent A 的修改可能破坏 Agent B 的假设。防护方法是串行化 AI 修改。不要让多个 Agent 同时改同一个模块。如果必须并行让它们改完全不相交的文件集。另外每次 AI 修改后要重新跑一遍全量测试确保没有破坏其他 Agent 的成果。7. 一些踩坑之后的个人体会说了这么多最后分享几点我自己的体会。第一AI 的能力和风险是同一件事的两面。AI 之所以能高效完成任务是因为它“敢改”而它之所以会改坏系统也是因为它“敢改”。你不能只要前者不要后者只能通过流程和工具来约束。第二防护的成本远低于修复的成本。前面讲的那些防护措施看起来麻烦但和线上出问题之后的排查、修复、道歉比起来成本低得多。我现在的原则是宁可多花 10 分钟做防护不愿多花 10 小时做修复。第三对 AI 的修改要保持“善意的怀疑”。AI 说完成了不代表真的完成了测试过了不代表系统没坏。每次 AI 修改后都要带着“它可能在哪里改坏了”的心态去检查。这种心态不是不信任 AI而是对系统负责。第四回归测试集是 AI 时代最重要的基础设施。在 AI 大量参与编码之后回归测试集的价值被放大了十倍。因为 AI 的修改频率远高于人类而每次修改都有回归风险。没有一套完善的回归测试集AI 协作就是裸奔。第五不要试图让 AI 理解整个系统。上下文窗口的限制是物理约束短期内无法突破。与其让 AI 理解整个系统不如把系统拆成边界清晰的小模块让 AI 在模块内工作。这也是微服务架构在 AI 时代的一个新价值。这套方法论我用了大半年从最初的频繁翻车到现在基本稳定中间迭代了很多次。核心就一句话AI 负责干活人负责守边界。边界守住了AI 就是生产力边界守不住AI 就是破坏力。