尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AI修Bug为何会把正确代码改错?实战防治指南

发布时间:2026/9/26 6:55:56

资讯中心
01
ARTICLE

AI修Bug为何会把正确代码改错?实战防治指南

AI修Bug为何会把正确代码改错?实战防治指南
先说明一下我今天聊的不是“AI 能不能写代码”这种老话题而是更扎心的一个问题AI 明明帮你找到了 Bug结果它把 Bug 修完的同时把旁边原本正确的代码也给改了。标题里那句话就是我最近踩坑的真实写照——AI 写代码失败不是因为没找到 Bug而是因为它“修着修着把对的又改错了”。如果你也在用 AI 编程助手修 Bug尤其是修那种并发、状态、边界条件的疑难杂症这篇文章值得你花十分钟看完。我不能保证你看完就再也不踩坑但至少能让你下次让 AI 动手前先给它套上“缰绳”。1. 现象拆解AI 是怎么“修着修着把对的改错”的1.1 一个真实的“修好一个、改坏一个”案例先说我最近的一个经历。项目里有个基于滑动窗口的限流器核心逻辑是每个用户每分钟最多请求 60 次窗口内计数用 Redis 的 INCR 和 EXPIRE 实现。线上偶发报错某些用户明明没超频却被限流了。我让 AI 帮忙排查。它的第一轮诊断其实挺准的——它发现代码里检查窗口边界时少了一个条件导致窗口刚过期时计数器还没来得及重置老计数被错误地算进了新窗口。它给出的修复是// 修复前 if (counter limit) { return false; // 触发限流 }// 修复后 if (counter limit !isWindowExpired(windowKey)) { return false; }这一步是对的问题也确实是这个。但 AI 在同一个 patch 里顺手干了一件事它把 Redis 操作的过期时间从expire(60)改成了expire(65)理由是“为了避免边界情况下 key 提前过期导致计数丢失”。看这段代码单看这个改动你能说它绝对错吗不能。因为确实存在一种极端情况请求恰好在第 59.9 秒到达然后窗口判断时 key 过期了计数器丢了一部分。但问题在于这个“修复”改变了系统的行为约定——产品定义的窗口就是 60 秒65 秒会让限流阈值在极端情况下偏离预期而且它根本没有对应需求。更麻烦的是测试还全过了。因为单测里根本没覆盖“窗口过期后计数器残留”的时序也没有断言 Redis 的过期时间。于是这个改动带着“修复 A”和“误伤 B”一起上了线。上线后两天线上监控报出超时时间与预期不符的告警我才意识到 AI 干了什么。这个案例特别典型AI 找到了真 Bug也修好了它但又把原本正确的行为约定给破坏了。这就是标题说的——“修着修着把对的又改错了”。1.2 为什么 AI 会秀这种操作三个技术层面的分析很多人以为 AI 修代码像人一样拿着整个项目在脑子里跑一遍然后精准地只动那一行。实际上大语言模型的工作方式完全不是这样它是在“按概率生成下一个 token”。具体来说有四个因素叠加导致 AI 特别容易“超范围修改”。第一注意力机制的局部性。模型在生成补丁时主要注意力集中在上下文窗口里的最近几千个 token 上。它看到的“相关代码”是局部片段很难像人一样建立完整的全局心智模型。当它读到expire(60)这行代码时它只是在概率上觉得“60 秒可能不够安全”但不知道这个 60 秒是产品需求还是技术实现。于是它很容易对“看起来可疑”但不是 Bug 的地方也施加“修复”。第二缺少执行反馈。人类修 Bug 时会反复跑测试、打断点、看日志让代码的行为反馈给大脑修正下一步动作。AI 在没有执行环境的情况下只能依据训练数据里的统计规律来想象“什么是对的”。训练数据里充满了“加个边界判断”“延长超时时间更稳妥”这类模式它就倾向于把这些模式套到代码上。第三上下文窗口截断。让 AI 看一个完整的项目是不现实的尤其是大型工程。实际使用中我们往往只把相关文件或相关函数丢给它。这会导致它不知道改动的影响面也不知道其他模块对这段代码的隐含假设。它在信息不完整的前提下做“看起来合理”的修改出错是必然的不犯错才奇怪。第四数据分布偏见。训练语料里存在大量“修复模式”的样例比如给 if 加空指针判断、给时间操作加偏移、给并发操作加锁。这些模式在通用代码里是合理的但放到你的业务代码里可能根本不适用。AI 不会问“这个系统设计时是不是故意留了这个边界”它只会按统计规律“顺手修了”。1.3 和人工修 Bug 相比差在哪里差距的核心不在“找 Bug 的能力”而在于“变更控制”意识。人工修 Bug哪怕是个初级工程师也知道一个原则修复 Bug 的首要约束是“最小变更”。即只改导致 Bug 的那一行、那一个条件、那一个函数其他一律不动。因为改动范围越大引入新问题或破坏既有行为的概率就越高。这是软件工程里用无数线上事故换来的原则。AI 不一样。它没有“项目的既有行为约定”这个概念它的目标函数是“生成一段看起来合理的补丁”。在这个目标下多改几行、顺手重构、调整参数都不算“错”甚至在概率上还会让它显得更“完整”。所以你会看到这样的对比维度人工修复AI 修复全局上下文有但可能不全面基本没有依赖局部片段变更控制意识强默认最小变更弱倾向“顺手完善”执行反馈可反复运行测试验证经常无执行环境靠想象风格判断遵循团队约定按训练数据偏好改写对业务语义的感知能问需求、看文档只会猜猜错了照改修改范围通常一行到几行常常扩散到多个位置这个对比不是贬低 AI而是帮我们看清AI 擅长“定位”但“守住边界”这件事目前仍然得靠人。认清这一点之后我们才能设计流程来遏制它的缺陷。2. AI 修 Bug 的四种“典型破坏模式”长期用 AI 修代码你会发现它的“破坏”不是随机的而是集中在几种固定的模式上。提前认识这些模式你在 review 时就能更快发现异常。2.1 把风格差异当 Bug 来“纠正”AI 训练数据里大部分代码是规范、现代、类型明确的写法。当它看到你项目里的老代码用了var、用了非空断言、用了魔法数字它会下意识觉得“这段代码有问题”然后帮你“规范化”。举个简单例子。项目里原本有var limit 60; // 限流阈值配置项默认值AI 改成了int limit 60; // 限流阈值配置项默认值你说这个改动有错吗没有。但它跟 Bug 毫无关系。我见过更夸张的AI 修一个空指针的 Bug 时顺手把整个函数从for循环改成stream().forEach()还美其名曰“提升可读性”。这种改动不仅增加 review 负担还可能因为闭包捕获的变量语义不同引入新的并发问题。2.2 修 Bug 的同时“顺势重构”这种模式最常见。AI 定位到问题函数后会给出一版“重写”。比如它发现你的登录接口有并发重复提交的 Bug它给出的补丁不仅加了一个分布式锁还顺手把密码校验逻辑从if嵌套改成guard clause把两个私有方法合并成一个甚至把 API 的返回结构从Map改成自定义 DTO。单看每一处改动都比原来“优雅”。但问题是你让 AI 修的是 Bug不是重构。合并后的方法可能改变异常处理顺序新 DTO 的字段序列化可能坑掉了下游调用方。这种“超范围完善”让人特别难受你要驳回吧里面确实有它修 Bug 的对的部分你全收吧无异于做了一次没有评审的重构。2.3 连环修复导致“修复漂移”这是最隐蔽的一种。AI 修 A 时发现 B 跟 A 相关就顺手改了 B改完 B 又觉得 C 也可以优化就把 C 也动了。每一处改动单看都合理但合起来补丁偏离了原始 Bug 的修复目标到了后期AI 甚至已经不是在“修 Bug”了而是在“按它的偏好优化代码”。最典型的场景是你让 AI 修一个缓存穿透的问题AI 发现缓存 key 生成逻辑不严谨就改了 key 的拼接方式改完发现 key 变了会导致旧缓存失效于是又加了版本号加完版本号又觉得序列化工具不统一顺手把 Jackson 换成了 Gson。最后你收到一个大规模换序列化库的补丁完全忘了最初只是要加一层缓存兜底。这种“修复漂移”特别危险因为每一小步都有它自己的“局部理由”但在全局上它没有尊重原系统的演进历史和既有约定。2.4 破坏语义不变量和隐含约定代码里经常有一些不写在注释里、但所有人都遵守的“隐含约定”。比如某个字段在写入时必须保持非负某个方法在调用前必须持有锁某个状态枚举的取值顺序不能随意调整。AI 没有能力识别这些不变量它只看字面。比如if (status Status.CLOSED) { return; }AI 认为“如果状态不是 CLOSED 就继续执行这不是很清晰吗”于是改成if (status ! Status.CLOSED) { // 执行后续逻辑 }看起来逻辑等价但如果中间有 return、有异常抛出的分支顺序依赖改写后行为就变了。更常见的是AI 把equals从Objects.equals(a, b)改成a.equals(b)一旦a为 null 直接 NPE。这些都属于“字面正确、语义错误”的改动。3. 为什么测试和 Code Review 都拿不住这种问题3.1 测试通过 ≠ 行为正确当 AI 改坏正确代码时测试经常依然是绿的。这不是说测试没用而是说测试的设计原则和 AI 的破坏模式之间存在盲区。大多数单元测试断言的是“输入-输出”对测的是函数在给定参数下返回什么值。但 AI 的破坏往往发生在“时序、状态、配置、副作用”这些维度上——比如改了超时时间、改变了缓存过期策略、调整了锁的粒度。这些点恰好是单测最不爱覆盖的部分因为写这类测试成本高、不稳定、还要 mock 一堆东西。我自己的经验数据让 AI 修并发类 Bug 时大约 60% 的“误伤改动”发生在测试覆盖不到的代码路径上比如配置常量、sleep 时间、重试次数、超时上限。这些值在业务上可能直接关系到 SLA但单测根本不会断言它们。3.2 Code Review 为什么也看不清按理说有 diff有 reviewAI 多改了什么应该一眼看出来。但实际上AI 生成的补丁风格非常“完整”它会按照代码格式规范、注释规范、命名规范来输出。这迷惑性极大——你看到的是一个格式漂亮、命名规范、注释齐全的 diff潜意识里会觉得“这么工整的代码应该没问题”。另一个问题是人类的注意力聚焦效应。当你知道 AI 修的是 A 点时你的目光会集中在 A 附近而 AI 改动的位置可能分散在文件末尾、另一个函数内部甚至是另一个文件里。review 时扫一眼 diff 没发现问题就很容易合进去。还有一个隐藏问题AI 补丁的“行数膨胀”。人工修复一个 Bugavg diff 可能在 5~20 行左右。AI 生成的补丁动辄 50~100 行因为它在补丁里加入了格式化、注释、重构。你的 review 能力有限diff 量越大漏看概率越高误伤的代码就越容易通过审查。3.3 快速识别“改坏了”的信号清单基于上面这些坑我总结了一套快速识别 AI 是否“越界”的信号补丁里出现了与 Bug 描述完全无关的文件或函数diff 中同时包含“逻辑修改”和“格式/命名/注释修改”常量、配置、超时时间、重试次数等“魔法数字”发生了变化变量被重命名即使名字看起来更“规范”出现了比原始代码“更高级”的写法比如把循环改成 stream、把 if 改成 switch注释被大规模增删尤其是原有注释解释过“为什么这样做”的地方单个补丁超过 40 行但 Bug 本身预计只要 3~5 行就能修完你只要在 review 时发现其中两条以上大概率 AI 已经“修着修着把对的改错了”。这时候不要收补丁先停下来做变更范围收敛。4. 怎么防把“最小变更”变成 AI 的硬约束前面聊了这么多问题接下来说点能落地的。我现在的做法是不指望 AI 自觉而是从提示词、版本控制、测试、评审四个环节同时给它“套缰绳”。4.1 提示词里显式声明“禁止无关修改”很多人让 AI 修 Bug 时提示词就一句话“帮我看看为什么这里抛异常”。这种开放式指令等于告诉 AI“你自由发挥吧”。我的建议是把指令模板化明确写出约束。我常用的模板长这样下面是代码片段。请定位导致【现象描述】的根因并只修改根因相关代码。 要求 1. 不修改任何与根因无关的逻辑 2. 不改变代码风格、命名方式、注释除非注释本身错误 3. 不重构代码结构、不提取函数、不改变方法签名 4. 不修改任何常量、配置、超时时间、重试次数等数值 5. 输出格式先列出根因分析再逐条列出修改点和修改理由最后列出你刻意没改但你觉得可疑的地方 代码 【代码片段】你可能会觉得这种提示词啰嗦但实测下来加了这套约束之后AI 越界修改的比例下降了非常明显——从大约七成的补丁带无关改动降到三成左右。原因是模型对“用户指令”的遵循能力越来越强尤其是在你明确说“不要做什么”的时候。4.2 让 AI 先复现再修复再验证AI 最大的问题是没有执行环境。所以你可以在流程里补上“复现”环节让 AI 先根据 Bug 现象写一个最小复现用例你本地跑通后再让 AI 基于这个用例去修代码。这个流程最大的好处是AI 写的修复至少要能解释“为什么这个复现用例会失败”。如果它给出的补丁和复现用例没有直接关系那基本可以判断它在“自由发挥”。而且这个最小复现用例在修完之后还可以直接留作回归测试兼顾验收。我现在的做法是凡是要求 AI 修 Bug先让它出复现步骤或复现代码如果它给不出来我宁愿自己先排查也不让 AI 直接改生产代码。因为让它基于“猜测”去改改坏的概率太高。4.3 版本控制兜底每次修复前先打基线这套操作是成本最低但最有效的防线。让 AI 动手改代码之前先把当前状态提交一次或者打一个 tag。AI 改完以后立刻用git diff查看它改了哪些文件、哪些行。关键技巧用git diff --word-diff看单词级别的变化有时候 AI 偷偷改了一个常量值整行 diff 里很难一眼看到但 word-diff 会高亮那个数字的变化。另一个技巧是git diff --stat先看整体规模——如果某个文件只是改一个函数diff 却显示 100 行增删直接怀疑它动了大手术。git add -A git commit -m fix(rate-limiter): before AI patch git diff --word-diff --stat你还可以在验收时逐个审视git diff块凡是与 Bug 修复无关的变化一律git checkout还原。这个操作比任何“让 AI 自觉”都可靠因为它从工程层面强制做了变更收敛。4.4 强制 AI 逐条列出改动理由我给 AI 的修复任务输出格式加了一个硬性要求必须逐条列出每一处修改及修改理由。没有理由的改动默认不接受理由不充分的改动也默认不接受。这里有一个细节要区分“AI 的辩护理由”和“真实的因果关系”。AI 很擅长编出听起来合理的理由比如“为了避免边界问题将过期时间从 60 秒改为 65 秒”——理由本身在技术上成立但它没有回答“这是产品要求吗”。所以我会追加一个提问“这段代码的改动是否影响外部可观察行为如果是请说明是修复了 Bug 还是改变了行为约定。”如果 AI 答不上来直接驳回这处改动。在 App 端写代码尤其要注意这一点因为很多行为是 API 契约的一部分改变契约比修 Bug 的负面影响大得多。4.5 用测试矩阵和回归基线做最后闸门如果你只是让 AI 改一个函数跑一下该函数的单测是不够的。建议做一个简单的测试矩阵测试层级覆盖内容执行时机针对性测试Bug 相关的最小复现用例、新增回归用例AI 改完后立即跑模块级测试修改涉及模块的全部单测合并前集成测试涉及上下游接口的关键链路合并前全量冒烟项目核心流程快速走一遍合并前并发/时序类测试如果是并发 Bug额外跑竞态检测或压测合并前这个矩阵不用自动化得很重但至少要有“针对性测试 模块测试 全量冒烟”三层。尤其是全量冒烟它能在你很容易忽略的关联模块里抓住“改了常量导致下游超时”这种问题。5. 实操全流程从提需求到验收改对聊完原则我完整演示一遍我现在处理“AI 修 Bug”的标准操作流程。5.1 修复前复现、记录、打基线第一步绝不直接打开 AI 对话框贴代码。先自己把 Bug 复现——用日志、用最小用例、用线上工单里的请求参数尽量搞清楚“输入是什么、期望输出是什么、实际输出是什么”。这些信息是后续让 AI 收敛范围的锚点。第二步把当前行为记录下来尤其要记录那些“看起来没问题但确实存在的约定”。比如你项目里约定限流窗口 60 秒那就把它写进提示词的背景里“注意窗口大小 60 秒是产品约定不允许修改。”第三步git 打基线。哪怕只是本地仓库 commit 一下也会让你后面有后悔药。这一步花 30 秒但能救你一天。5.2 给 AI 的修复提示词完整示例这里给一个我现在实际在用的完整模板你可以直接抄走改一改背景我有一个 Java 服务用 Redis INCREXPIRE 实现限流器。窗口大小固定 60 秒这是产品约定不允许修改。 Bug 现象某些用户未超频却被限流怀疑是窗口过期后计数残留导致的误判。 期望行为同一用户 60 秒窗口内请求数 60 时不限流 60 时限流窗口过期后计数从 0 开始。 约束 - 只修改根因相关代码 - 不要改变任何常量、超时时间、过期时间、重试次数 - 不要重命名变量或方法 - 不要重构循环、异常处理结构 - 不要增删注释 输出格式 1. 根因分析不超过 100 字 2. 逐条列出修改位置、修改内容、修改理由 3. 列出你没有修改但你认为可疑的代码位置 代码 【相关代码片段】这个模板里的“期望行为”非常重要。它给 AI 定义了“什么是正确的输出”让它更容易判断自己的改动是否越界。没有这个锚点时AI 容易自己定义正确性。5.3 验收五步法收到 AI 的补丁后我按五步走缺一不可。第一步看 diff 范围。用git diff --stat看整体规模如果超出心理预期先怀疑再相信。第二步逐条审视 diff对照 AI 的修改理由。与“根因分析”对不上的改动直接问它为什么改答不上来就还原。第三步只保留与 Bug 修复相关的改动手动还原所有“无关修改”。还原时用git checkout -- file或者git apply -R都行。第四步跑测试矩阵。先跑最小复现用例再跑模块测试再跑全量冒烟。如果存在并发时序类 Bug额外跑一轮简单压测把并发线程数调到略高于生产峰值。第五步有条件的话把最终补丁丢给另一个 AI 做“逆向评审”——让它只找出“与修复目标无关的改动”。两个 AI 的偏见可能不同交叉检查能多抓一层问题。5.4 这套流程的实测效果我用这套流程跑了大概两个月、四十多次“AI 修 Bug”任务统计下来的感受是在“修复本身正确”这个维度AI 本来表现就不差大约有一半以上能直接改对问题主要集中在误伤正确代码上。加了最小变更约束和逐条理由之后误伤率大幅降低一次性通过率从四成提升到接近七成。剩下三成仍然失败原因集中在两类一是 AI 对业务约定的理解实在无能为力二是测试矩阵覆盖不到的系统级副作用在线上才暴露。所以我对 AI 修 Bug 的态度是可以用但不能放养。把 AI 当成一个“手速极快、但不知道边界在哪的实习生”你要做的就是给它画清楚边界然后仔仔细细验收。6. 常见问题与避坑技巧6.1 AI 说“我只改了 Bug”但 diff 里多出 10 处改动怎么办先别急着骂 AI。用 4.3 节的方式把 diff 拉出来逐个改动块判断是否与根因相关。大多数时候你会发现问题出在你在提示词里没有明确讲“不要改风格、不要重构”。下次补上约束就行。当前这一次手动还原所有无关块这是最优解。6.2 修完 A结果 B 挂了怎么快速定位先确认 B 的代码是否碰过。如果 AI 做了扩散修改问题大概率出在它“顺手改”的位置上直接用git diff对比基线即可定位。如果 B 的代码 AI 没碰那要考虑是否命中场景依赖——比如修改了常量导致下游行为变化、修改了接口顺序导致调用链变化。这时候最快的办法是回到基线版本逐步把 AI 的改动注回来用二分法找到罪魁祸首。6.3 如何让 AI 养成“只修不破坏”的习惯AI 没有习惯只有提示词约束。每轮任务都带上第 5.2 节那条约束模板久而久之你会发现它的默认行为在收敛。另外你可以把“禁止无关修改”写进团队内部的 AI 使用规范文档里很多人一起用 AI 时约定打不出来后面 review 成本就是灾难。我自己的团队里定了一个规矩任何 AI 生成的补丁必须满足“删掉注释、格式化、重命名、魔法数字变化”中不超过一项超过一项一律打回重做。这个简单的阈值过滤器已经帮我们挡住了不少“看着工整、实际越界”的补丁。6.4 什么时候不该让 AI 修 Bug不是所有 Bug 都适合丢给 AI。我总结了几类高风险场景这些地方我宁可自己慢慢排也不让 AI 动涉及资金计算、支付金额、对账逻辑的代码涉及加解密、签名、鉴权流程的代码并发竞态条件尤其是锁顺序、死锁、活锁问题涉及崩溃恢复、数据迁移、持久化一致性的代码系统间契约定义比如 API 路径、请求/响应字段、序列化格式这些场景的共同点是错误成本极高、业务语义极强、外部系统依赖深。AI 的一次“顺手修改”可能造成资金损失或安全风险。在这些代码上AI 最合适的定位是“解释代码逻辑、帮你梳理流程”而不是直接出补丁。6.5 团队协作层面的其他建议在 CI 里加补丁规模限制比如单文件 diff 超过 100 行必须人工确认或者 PR 里有与 issue 描述无关的文件自动 block。让 AI 补丁和 issue 绑定要求 AI 的修改描述里写清楚“对应哪个问题”没有对应关系的改动不允许合入。定期做“AI 修改中毒复盘”把“AI 改坏正确代码”的案例收集起来做成团队内的反面教材。这个看似没有直接产出但能帮团队形成对“AI 越界修改”的高度警惕比任何流程都管用。最后分享一点我自己的实测体会用 AI 写代码、修 Bug 这件事我的核心感受是AI 的最强项是“快速给出一个看起来合理的方案”最弱项是“守护系统的既定行为”。它不是不会修 Bug而是它会不知道边界在哪。每次让 AI 动手前我都逼自己先想清楚系统里哪些行为是绝对不能变的。把这个“不能变”的边界跟 Bug 现象一起告诉它比让它猜要高效得多。代码合入前我还会再打开git diff从头到尾看一遍脑子里只问一个问题“这一行改了之后会不会影响我原本正确的逻辑”如果答案不确定就问 AI 要理由理由说不通就还原。这个过程看起来笨但它能救下很多改坏的正确代码。下次你让 AI 修 Bug 的时候如果它又“修着修着把对的改错了”不用沮丧——这说明你对正确逻辑是有判断力的只是还没给 AI 戴上它真正需要的缰绳。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。