AI写代码越改越错?病根在架构而非报错
作者:菠萝爱吃肉2026.11.06 20:02浏览量:0简介:AI辅助编程中,“贴报错→改代码→再报错”的循环常让修复越陷越深。本文基于一个真实C项目案例,剖析症状级反馈为何失效、一句架构级指导为何能一次修复,并揭示AI实现与AI测试的同源盲区风险,给出一套人机分工的可落地协作实践。
一、一个典型案例:两千行代码“行行都对”,整体却是错的
先讲一个真实经历。让AI从零实现一个约两千行的C项目:能编译、能运行、自带测试全部通过,表面上无可挑剔。但压测时出现了反直觉的现象——用并发原语同时发起三个理论耗时300毫秒的请求,实际总耗时却是905毫秒:请求被完全串行化了。
把现象和报错描述交给AI,它开始修。第一版,依然串行;第二版,换了一种实现思路,还是串行;第三版,直接编译失败。这就是典型的“越改越错”:每一轮修改都在上一轮的补丁之上继续打补丁,问题不仅没有收敛,反而在不断扩散。
转折点出现在换一种沟通方式之后。不再提供报错信息,只给出一句架构约束:
“fetch 不要自己管理事件循环。创建连接,注册到全局事件循环,返回一个 pending 状态的 Promise,所有 I/O 由全局事件循环统一驱动。”
AI一次性改对了。涉及9个文件的调整,总耗时从905毫秒降到302毫秒,达到理论并发水平。
同一个AI,为什么三轮“修Bug”改不对,一句“讲架构”就改对?差异不在模型能力,而在输入信息所处的抽象层级。
二、报错是症状,不是诊断:信息层级错位
报错信息、异常堆栈、错误现象,本质上都是症状。当你说“这个函数返回值不对”,AI的最优策略就是在该函数内部寻找局部修改——重排语句、调整返回逻辑、修补边界条件。每一步局部推理都合理,但前提本身已经错了:问题可能根本不在那个函数,而在整个模块的组织方式。此时AI是在错误的架构上“缝补”,每一针都缝对了位置,缝出来的衣服却仍然是错的。
值得注意的是,AI的单点能力其实很强:DNS解析正确、socket创建正确、HTTP报文序列化正确、Promise语义包装正确。两千行代码里几乎没有“错误的行”,只有“错误的结构”。它缺的不是写代码的能力,而是判断“这些正确的代码应当如何组织在一起”的能力——而这恰恰是报错信息无法传递的。
一句话总结:症状级信息只能触发局部修复,架构级信息才能触发全局重组。这就是三轮失败与一次成功之间的全部区别。
三、为什么一句架构约束能“一次改对”
回过头分析那句成功的关键指令,它实际上同时传递了三类设计决策:
- 职责边界:I/O驱动权归属全局事件循环,单个请求函数无权自建循环;
- 接口契约:fetch返回pending状态的Promise,而非等待完成后的结果;
- 控制流模型:通过回调注册实现事件驱动,而非轮询或阻塞等待。
用示意代码对比两种组织方式:
错误组织——每个请求自己驱动I/O:
Promise fetch_async(const char *url) {conn = create_connection(url);while (!io_ready(conn)) {poll(conn, timeout); // 自己轮询,串行化的根源}return promise_resolve(parse(conn));}
正确组织——注册到全局事件循环:
Promise fetch_async(const char *url) {conn = create_connection(url);Promise p = promise_new_pending();event_loop_register(loop, conn, on_io_ready, p);return p; // 立即返回,I/O由循环统一驱动}
前者在单线程环境中天然串行:三个请求各自占用主线程轮询,互相排队等待。后者把“等待”这件事统一上交给事件循环,单线程也能借助非阻塞I/O实现并发。这不是某个函数写错了,而是控制流的所有权分错了——所以任何函数级别的修补都注定无效。
这给了一个重要启示:与AI协作时,最有价值的信息往往不是“哪里错了”,而是“应当如何组织”。约束解空间,比描述错误更有效。
四、更隐蔽的风险:AI实现与AI测试的同源盲区
并发问题修好之后,继续人工review代码,又发现一个更令人后怕的问题:bench模式下每一次HTTP请求都失败了——16576次请求,全部失败。而测试报告却显示:16576次请求,0错误,全部通过。
原因很简单,也很致命:实现代码把每次调用无条件计入“成功”计数器,而测试只校验这个计数器是否等于总调用次数。
// 有缺陷的验证逻辑for (int i = 0; i < N; i++) {result = http_request(target);success_count++; // 无条件计数,错误被吞掉}assert(success_count == N); // 永远通过
// 正确的验证逻辑for (int i = 0; i < N; i++) {result = http_request(target);if (result.error_code != 0) {fail_count++;log_failure(i, result.error_code);} else {assert(response_valid(&result));success_count++;}}assert(fail_count == 0 && success_count == N);
这里暴露的是AI辅助开发中最危险的结构性风险:实现和测试由同一个模型生成,它们会拥有一致的盲区。实现不检查错误,测试就不会验证错误;测试恰好只断言实现“恰好做对”的那部分。两者完美配合,制造出“一切正常”的假象。
更麻烦的是闭环的自我强化:你把这个“全部通过”的测试结果反馈给AI,它从自己的输出里看不出任何异常,自然无从修复。无论从实现侧还是测试侧观察,系统都是“绿色”的——只有人从外部引入独立视角,才能打破这个闭环。
五、正确的协作模式:人守架构与验收,AI守实现与执行
由此可以回答开篇的问题:AI写错后改来改去还是错,通常不是因为AI能力不足,而是协作模式不对。
错误模式:AI写→报错→把报错贴给AI→AI改→再报错→再贴→循环。这个循环的根本缺陷在于,你始终在症状层面提供信息,而问题出在架构层面。
更好的模式是把架构决策前置,在动手之前就明确约束:
- 并发模型:约定事件驱动、线程池或协程,明确I/O驱动权的归属;
- 错误处理契约:明确错误码语义、失败是否计入成功、异常传播的边界;
- 资源所有权:谁创建、谁释放,超时与重试策略如何定义;
- 理解确认:要求AI在实现之前先陈述它对上述约束的理解,再开始写代码;
- 测试独立性:测试用例从规格推导而非从实现推导,强制包含失败路径与故障注入场景;
- 外部验收:人在外部做行为级检查,看真实耗时、真实错误率、真实资源曲线,而不是只看测试绿灯。
六、几点可落地的实践建议
1. 把“改Bug”升级为“给约束”。下次AI反复修不对时,先问自己:我提供的是症状,还是设计决策?多数情况下,一句话的架构约束,胜过十轮报错粘贴。
2. 永远不要让实现者给自己出考卷。无论执行者是人是AI,谁写实现,验证就应尽量独立:独立编写验收口径、独立设计负向用例、必要时引入故障注入,让“全部通过”这件事本身变得不那么容易。
3. 用外部可观测指标兜底。测试通过只是必要条件。真实耗时分布、错误率、内存与连接句柄曲线等外部观测数据,才是打破“自说自话”闭环的关键手段。
4. 把review的重点从“这行代码对不对”转向“这些代码的组织方式对不对”。在AI辅助开发的时代,逐行检查语法的价值在下降,判断结构与契约的价值在上升。
结语
AI不是不会改代码,而是需要正确层级的信息。报错告诉它“哪里疼”,架构约束告诉它“系统应当如何运转”——前者引发缝补,后者引发重组。同时要警惕实现与测试同源带来的闭环假象:机器可以高效生成代码与用例,但架构决策与最终验收,仍应由人来掌舵。把人放在信息层级更高的位置上,AI的强大执行能力才能真正兑现为可靠的工程产出。

登录后可评论,请前往 登录 或 注册