ChatGPT、Codex实战:一次改多个文件时,为什么每个Diff都合理,组合起来却可能出错?
最近用Codex改稍微大一点的真实项目时,我发现有一类问题特别有迷惑性。
不是某一行代码明显写错了。
也不是哪个文件一打开就能看出Bug。
恰恰相反。
你把Codex改过的文件一个个打开看:
Controller看起来合理。
Service看起来合理。
Schema也没问题。
测试文件甚至写得挺完整。
有时候连Build和单元测试都能正常通过。
可一旦把这些修改真正放在一起运行,问题就出来了:
接口参数对不上。
状态更新顺序乱了。
旧模块开始报错。
数据库字段变了,但消费者还在按旧结构读取。
甚至出现一种最让人头疼的情况:
每个Diff单独看都没毛病,组合起来却错了。
这类问题以后可能会越来越常见。
因为Codex、ChatGPT这类AI编程工具越来越擅长一次处理多个文件、多个模块。
真正的难点正在从:
“AI会不会修改一个文件?”
逐渐变成:
“AI能不能保证多个局部正确的修改,组合以后仍然保持系统一致?”
一、先看一个非常典型的场景
假设你让Codex修改一个用户资料接口。
需求是:
给用户增加一个display_name字段。
这个需求听起来不复杂。
但在真实项目里,它可能涉及:
数据库Model。
Schema。
Service。
API Response。
前端类型。
缓存。
测试。
Codex分析项目以后,一次修改了6个文件。
你开始看Diff。
Model里加字段。
没问题。
Schema里加入display_name。
也没问题。
Service里保存这个值。
逻辑合理。
接口Response也返回这个字段。
还是合理。
测试补了新增字段。
也通过了。
于是你很自然会觉得:
这次修改质量不错。
结果真正跑完整流程时,前端某个页面突然报错。
最后排查发现:
缓存层存储的还是旧对象结构。
更新用户资料以后,数据库里已经有display_name,接口的一部分请求却仍然从旧缓存读取。
这时候你再重新看前面的几个Diff,会发现一个很有意思的现象:
没有哪个文件明显写错。
真正的问题在于:
AI完成了多个局部修改,却没有完整维护这些局部之间的关系。
二、为什么会出现“局部都对,全局却错”?
这其实是软件工程里一个非常经典的问题:
局部正确,不等于全局一致。
代码文件不是独立存在的。
一个真实项目更像一张网络。
文件之间通过各种关系连接:
函数调用。
接口。
数据结构。
类型。
配置。
事件。
数据库。
缓存。
共享状态。
执行顺序。
所以你改一个文件的时候,本质上经常是在改变这张网络里的一个节点。
如果只改变一个节点,风险相对容易判断。
但如果一次改变五六个节点,就不能只问:
“这些节点各自是不是正确?”
还要问:
“节点之间原来的关系,现在还成立吗?”
这就是多文件AI修改真正难的地方。
三、最常见的问题:接口契约发生了漂移
多文件修改里最常见的一类错误,就是:
Contract Drift——接口契约漂移。
比如原来的函数:
getUser() -> User
修改以后变成:
getUser() -> User | null
单看这个函数,没有问题。
调用方A也被Codex一起修改了:
if (!user) {
return 404
}
看起来还是没问题。
但问题可能出在调用方B。
调用方B没有被这次任务读取到,仍然默认:
user.name
一定存在。
于是当前任务相关文件全部合理。
但系统里的契约已经发生了变化。
这就是为什么有时候AI修改一组文件以后:
新功能正常。
旧功能却突然坏掉。
不是新代码明显错误。
而是:
一个局部修改偷偷改变了全局契约。
四、第二类问题:数据结构改了,但传播没有完成
这在AI修改Schema、类型、数据库Model时特别常见。
例如原来:
User {
id
name
}
现在变成:
User {
id
name
displayName
}
这时候需要考虑的远远不只是Model。
还可能包括:
数据库迁移。
序列化。
反序列化。
缓存。
API Response。
前端Type。
消息队列Payload。
日志。
测试Fixture。
Mock数据。
只要其中一个地方仍然保持旧结构,系统就可能产生不一致。
而Codex完成当前任务时,很容易优先找到:
直接依赖当前代码的文件。
但真实项目里还有大量“间接消费者”。
例如:
某个定时任务。
某个内部脚本。
某个Event Consumer。
某个测试辅助函数。
它们平时不在主要调用链上,因此很容易被漏掉。
五、第三类问题:每一步逻辑都对,但执行顺序错了
这个问题更加隐蔽。
假设一个订单流程是:
写数据库 → 更新库存 → 发送事件。
Codex为了重构代码,把逻辑拆成几个更清晰的函数。
每个函数单独看都很漂亮。
但组合以后可能变成:
发送事件 → 写数据库 → 更新库存。
如果只是看每个函数内部:
没有任何明显错误。
问题发生在:
时间顺序。
真实系统里大量约束其实都是顺序约束:
先鉴权,再查询。
先写数据库,再更新缓存。
先锁定资源,再执行操作。
先Commit,再通知下游。
这些规则如果没有明确写进测试或者文档,AI很容易在重构时认为:
“只是重新组织代码。”
但对系统来说:
执行顺序本身就是业务逻辑。
六、第四类问题:共享状态在多个文件之间发生冲突
还有一类问题,经常出现在:
Store。
Context。
Cache。
Singleton。
全局配置。
Session。
数据库Transaction。
例如一个模块修改:
status = processing
另一个模块根据旧状态:
if status == pending
决定是否继续。
两个文件各自逻辑都合理。
但如果状态定义、更新时机或者生命周期发生变化,它们组合以后就会失效。
这也是为什么AI做多文件重构时,经常出现一种现象:
代码看起来比以前更干净了。
函数也拆得更漂亮了。
但某些边界流程开始变得不稳定。
因为真正被破坏的并不是代码风格。
而是:
共享状态之间原来隐藏着的协作关系。
七、为什么AI特别容易遇到这种问题?
这里不能简单归结成“AI上下文不够”。
更深层的原因是:
真实软件系统的很多关系,本来就没有被显式表达。
比如:
函数A调用函数B,这是明确的。
AI很容易看到。
但:
“接口C虽然没有直接引用配置D,但线上部署时必须保持一致。”
这种关系可能根本不会出现在静态代码依赖里。
再比如:
“修改这个Schema以后,记得同步更新那个内部系统。”
这个规则可能只存在于团队经验。
所以AI面对的实际上是两类关系:
第一类:显式依赖
Import。
函数调用。
类型引用。
接口定义。
这些比较容易发现。
第二类:隐式依赖
业务流程。
历史兼容。
运行时状态。
发布顺序。
环境约束。
跨系统约定。
真正危险的,通常是第二类。
八、为什么一次改的文件越多,风险往往不是线性增加?
很多人会直觉觉得:
改2个文件,比改1个文件风险大一点。
改10个文件,风险大10倍左右。
但真实情况经常不是这样。
因为多文件修改增加的不只是:
文件数量。
还增加了:
文件之间的关系数量。
例如只有两个模块:
A ↔ B
关系很简单。
如果变成五个模块:
A、B、C、D、E
它们可能存在:
A依赖B。
B影响C。
C共享D的数据。
E又依赖A输出。
这时候你需要验证的已经不只是5个文件。
而是多个文件之间形成的组合关系。
所以大型AI修改真正容易失控的地方是:
修改面扩大以后,验证复杂度增长得比代码行数更快。
九、一个很实用的方法:不要一上来就让Codex改,先让它画“影响面”
如果任务涉及多个模块,我现在更倾向于让AI先做一步:
Impact Map——影响面分析
比如先问:
这次修改会直接影响哪些文件?
有哪些间接调用方?
哪些接口契约可能变化?
哪些数据结构会传播?
哪些测试必须同步更新?
哪些模块虽然不用改,但需要验证?
这个步骤的价值非常大。
因为它把任务从:
“找到几个文件然后开始写。”
变成:
“先理解修改会穿过哪些系统边界。”
之后再真正让Codex执行修改,稳定性通常会高很多。
十、第二个办法:多文件任务尽量拆成“可验证批次”
比如一个需求需要修改:
Database。
Backend。
API。
Frontend。
不要一定要求Agent一次全部完成。
可以拆成:
第一阶段:
数据库和后端Model修改。
先确认数据结构。
第二阶段:
API契约调整。
确认Response。
第三阶段:
前端消费者调整。
第四阶段:
跑完整集成验证。
这样做的好处不是AI写得更慢。
而是:
每一步出了问题,你都知道是哪一层开始出现偏差。
如果一次修改15个文件以后再跑验证,一旦失败,定位成本会非常高。
十一、第三个办法:Review时不要按文件看,要按“关系”看
很多人Review AI代码时有一个习惯:
打开File A。
看完。
打开File B。
看完。
打开File C。
看完。
每个都没问题。
于是Approve。
但多文件修改更应该按“关系”Review。
例如:
Schema变了。
→ 谁生产这个Schema?
→ 谁消费这个Schema?
→ 缓存里是什么结构?
→ 测试数据是什么结构?
接口返回变了。
→ Backend改了吗?
→ Frontend类型同步了吗?
→ 老客户端还能不能接受?
状态流变了。
→ 谁写状态?
→ 谁读状态?
→ 顺序有没有改变?
真正需要Review的是:
依赖链。
而不是文件列表。
十二、第四个办法:让Codex主动寻找“没有修改但可能受影响的文件”
这个提示其实特别实用。
不要只问:
“你修改了什么?”
可以额外要求:
列出本次没有修改,但可能因为接口、类型、状态或调用链变化而受到影响的模块。
因为真正危险的地方,经常恰恰不是Diff里面的文件。
而是:
没出现在Diff里的消费者。
这一点对大型项目尤其重要。
十三、给自己测一个指标:跨文件耦合修改率
这篇我建议只用一个主指标:
跨文件耦合修改率
统计最近一段时间AI处理的任务。
看看其中有多少任务同时涉及:
3个以上存在强依赖关系的文件或模块。
例如最近20个Codex任务:
12个只是修改1—2个相对独立文件。
8个同时涉及API、Service、Schema、Database等多个强关联模块。
那么跨文件耦合修改率就是:
8 ÷ 20 = 40%。
低于20%
说明你交给AI的大部分还是:
局部Bug。
单文件修改。
小功能。
独立逻辑。
这种任务通常比较容易验证。
20%—40%
说明AI已经开始进入真正的项目级修改。
这时候建议开始固定使用:
影响面分析。
契约检查。
分批修改。
集成验证。
高于40%
如果大量任务本身就是跨模块、跨文件、跨状态的复杂修改,那么你的使用方式已经明显进入高复杂度阶段。
这时候真正决定效率的,不只是模型能不能改代码。
而是:
AI能否持续保持跨文件上下文和系统关系。
十四、Plus和Pro怎么判断?
如果你的跨文件耦合修改率还比较低:
日常主要是小Bug。
单文件逻辑。
简单功能。
偶尔让AI处理几个文件。
那么Plus通常已经能够覆盖大量开发工作。
因为这时候你的主要需求仍然是:
代码理解。
修改。
测试。
局部上下文。
没有必要因为偶尔一次大型任务就直接把使用强度拉到最高。
如果你的跨文件耦合修改率已经很高,但返工也很多:
一次经常改十几个文件。
任务结束以后频繁需要重新检查。
AI容易漏掉依赖。
那也不建议第一反应就是提高套餐。
这时候更重要的是先优化:
任务拆分。
影响面分析。
AGENTS.md。
测试体系。
接口契约。
验证流程。
因为工作流不稳定时,提高AI容量,很可能只是让你更快地产生更大的Diff。
真正更容易体现Pro价值的情况是:
你的跨文件任务已经比较成熟。
项目约束写得清楚。
测试体系完整。
大型修改的回退率也比较低。
但你开始长期遇到:
大项目上下文。
多个复杂任务连续执行。
大量代码读取。
长时间Agent任务。
高频项目级修改。
这时候更高的AI容量,才更容易转化成真实开发吞吐量。
所以判断顺序依然应该是:
先看Workflow能不能稳定消化复杂任务,再看AI容量够不够。
最后
以后用ChatGPT、Codex改真实项目,有一个观念可能越来越重要:
不要只判断每个Diff是不是合理。
因为软件系统真正运行的,从来不是一堆独立Diff。
而是:
这些修改组合以后形成的新系统状态。
一段代码可以正确。
一个文件可以正确。
甚至十个文件都可以分别正确。
但如果它们之间:
接口不一致。
状态不同步。
顺序被打乱。
隐含契约被破坏。
系统仍然可能出错。
所以当Codex下一次一次修改十几个文件,而你逐个看下来都觉得“好像没问题”时,真正应该再追问一句:
这些文件组合起来以后,它们之间原来的关系还成立吗?
这往往才是多文件AI编程里,最值得检查的地方。
持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了稳定的Plus/Pro会员订阅渠道,有需要可自取。
更多推荐



所有评论(0)