AI写代码越改越错?问题出在协作模式

作者:公子世无双2026.11.07 02:51浏览量:0

简介:AI写代码出错后反复修改却依然出错,根源往往不在代码能力,而在协作模式:开发者提供给AI的是“症状级”信息而非“架构级”诊断,导致AI在错误的结构上缝补。本文从并发失效、测试假象两个真实场景出发,剖析AI编程的盲区机制,并给出从“贴报错”转向“给约束”的可落地协作方法,帮助开发者打破“越改越错”的闭环。

AI 写代码越改越错?问题出在协作模式

一、一个典型的“越改越错”现场

设想这样一个开发场景:让 AI 从零生成一个中等规模的 C 项目,约两千行代码。结果是——能编译,能运行,测试全部通过。表面上看,这是一次成功的生成。

随后进行性能验证时发现了一个异常:并发发起三个理论耗时约 300ms 的请求,预期总耗时应接近 300ms,实际却用了 905ms。三个请求被串行执行了。

把现象和报错信息反馈给 AI,它开始修复。第一版改完,仍然串行;第二版换了一种写法,还是串行;第三版直接编译报错。三轮修改,问题不但没有收敛,反而扩散了。

这就是典型的“越改越错”。但真正值得讨论的,不是 AI 改错了,而是为什么它每一轮都在“正确地改”,整体却依然错。

二、换一种输入,一句话就改对了

转折点出现在改变沟通方式之后。不再提供报错信息,而是给出一句架构层面的约束:

“fetch 不要自己管理事件循环。创建连接,注册到全局事件循环,返回一个 pending Promise。I/O 由全局事件循环统一驱动。”

AI 一次改对。涉及 9 个文件,耗时从 905ms 降到 302ms。

同一个模型、同一份代码库,前三轮改不对,后面一句话就改对了。差异不在模型能力,而在输入信息的层级。

  • 报错信息是症状,不是诊断。
  • 告诉 AI“这个函数返回值不对”,它就去改那个函数——但问题可能根本不在这个函数,而在整个代码的组织方式。
  • 在错误的架构上缝补,每一针本身都是对的,缝出来的整体仍然是错的。

这个案例里,AI 的单项能力其实很强:DNS 解析是对的,socket 创建是对的,HTTP 序列化是对的,Promise 包装也是对的。每一行代码都正确,但这些“正确”的代码被组织进了一个“错误”的结构。AI 缺的不是写代码的能力,而是判断这些代码应该如何组织在一起的能力。

三、更隐蔽的风险:实现与测试共享同一套盲区

并发问题修复后,继续做代码审查,发现了更严重的问题。

在基准测试模式下,每一次 HTTP 请求实际上都失败了——16576 次请求,全部失败。但测试报告显示:16576 次请求,0 错误,通过。

原因很直接:实现代码把每次调用无条件计入“成功”,而测试只校验这个计数。实现不检查错误,测试不验证错误。更关键的是,实现和测试由同一个模型生成,二者共享一致的认知盲区——它们完美地互相配合,制造出“一切正常”的假象。

把这个“一切正常”的测试结果再喂给 AI,它当然改不对:它无法从自己的输出中看出问题。从实现侧看不出来,从测试侧也看不出来,只有人从外部做独立审查,才能打破这个闭环。

四、根因分析:症状级输入 vs 架构级输入

把上述两个场景抽象一下,可以得到一张清晰的对照表:

维度 症状级协作 架构级协作
输入内容 报错文本、异常现象 结构性约束、职责边界
AI 动作 定位到报错位置做局部修补 重构代码组织方式
收敛性 可能反复震荡甚至恶化 一次到位或快速收敛
适用问题 局部逻辑错误 架构错位、职责混乱

“越改越错”的循环本质是一个信息层级错配问题:

  1. 开发者观察到的是症状(慢、报错、返回值异常);
  2. 症状被原样传递给 AI;
  3. AI 在症状指向的局部位置做修改;
  4. 根因在架构层未被触及,症状以另一种形式再现;
  5. 回到第 1 步。

循环的每一轮都“有进展”,但整体没有向正确方向移动。这不是 AI 笨,而是协作模式把 AI 锚定在了错误的抽象层级上。

五、可落地的协作方法

1. 先判断问题层级,再决定输入形式

遇到问题时先问一句:这是局部逻辑错误,还是结构组织错误?

  • 局部错误(边界条件、类型转换、空值处理):贴报错、贴堆栈是高效的。
  • 结构错误(并发模型、模块职责、数据流向、生命周期管理):贴报错几乎无效,应直接描述期望的结构。

判断信号包括:同一类问题反复出现、修改一处引发另一处、性能特征与预期模型不符、错误在多个模块间“漂移”。

2. 用“约束语言”替代“现象语言”

把“它串行了,帮我修”改写为对目标结构的陈述。例如:

  1. 不要这样描述:
  2. “三个请求总耗时 905ms,应该是 300ms,帮我修。”
  3. 而要这样描述:
  4. “I/O 调度权归全局事件循环所有;单个请求只负责
  5. 创建连接、注册回调、返回 pending Promise;
  6. 任何模块不得自行轮询或阻塞等待。”

约束语言的价值在于:它把 AI 的搜索空间从“无数种局部补丁”压缩到“符合该约束的少数结构”。

3. 让实现与测试由不同视角生成

针对“实现与测试共享盲区”的问题,可采取以下做法:

  • 先写测试规约,再写实现:测试用例基于外部需求描述生成,而不是基于实现代码反推。
  • 交叉审查:实现由 AI 生成时,测试由人编写关键断言;反之亦然。
  • 注入负向用例:显式要求测试覆盖失败路径,例如“构造一个必然失败的请求,断言错误计数递增”。
  • 校验计数器语义:对“成功计数”这类指标,必须同时存在“失败计数”与“总数校验”,三者应满足守恒关系。

一个最小化的负向测试示意:

  1. /* 断言:失败请求必须被计入错误计数 */
  2. ASSERT_EQ(stats.success, 0);
  3. ASSERT_EQ(stats.failed, N);
  4. ASSERT_EQ(stats.success + stats.failed, stats.total);

如果实现把所有调用都计入成功,上述断言会立即失败,假象无法维持。

4. 定期做“外部审查”,打破自洽闭环

AI 生成的全部产物——实现、测试、报告——构成一个自洽系统。自洽不等于正确。需要周期性引入外部视角:

  • 人工抽查关键路径的实现与断言是否真正对应需求;
  • 对性能指标做独立复测,不依赖生成代码自带的统计;
  • 对“全部通过”的结果保持怀疑,优先检查测试是否具备失败能力。

5. 把架构约束沉淀为可复用上下文

一次有效的架构级描述应当被记录下来,成为后续协作的固定上下文。包括:并发模型、模块职责边界、错误处理约定、资源生命周期规则。这些内容比任何单次报错都更有长期价值。

六、结论

AI 写代码出错后反复修改仍然出错,核心原因可以归结为三点:

  1. 信息层级错配:开发者持续提供症状级信息,而问题位于架构级,AI 被迫在错误的结构上做局部优化。
  2. 实现与测试共享盲区:同一模型生成的实现与测试互相“配合”,制造出通过率假象,使反馈信号本身失真。
  3. 缺乏外部审查:闭环内部无法自证错误,只有人从外部介入才能打破。

对应的改进方向同样清晰:先判断问题层级,用约束语言替代现象语言,让测试具备独立性与失败能力,并坚持外部审查。AI 的代码能力已经足够强,真正决定协作效果的,是开发者能否把问题描述在正确的抽象层级上。