最近用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会员订阅渠道,有需要可自取。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐