AI修改接口代码时,有一种问题非常容易让人误判:

代码能编译,类型检查也通过,但真正运行起来以后,调用方却出现了异常。

比如只是调整一个接口返回值,结果出现:

  • 前端页面某个区域不显示;

  • 老版本客户端逻辑异常;

  • 下游服务判断条件失效;

  • 原本正常的默认行为突然改变;

  • 没有明显报错,但业务结果已经不一样。

这类问题的关键往往不是:

“代码有没有报错。”

而是:

“接口契约有没有被悄悄改变。”


一、接口契约不只是字段和类型

很多人理解接口时,首先想到的是:

字段名
字段类型
请求参数
返回结构

这些当然重要。

但真实项目里的接口契约还包括很多隐含规则。

例如一个字段:

avatar?: string

除了类型之外,调用方可能默认:

没有头像 → undefined

如果AI改成:

没有头像 → ""

类型看起来可能仍然没有问题。

但调用方如果写的是:

if (avatar === undefined)

行为就已经改变。

所以接口契约不仅是“长什么样”。

还包括:

不同值分别代表什么。


二、null、空字符串和字段缺失并不是一回事

例如接口原来返回:

{
  "nickname": null
}

AI为了“统一格式”,改成:

{
  "nickname": ""
}

从展示角度看,两者似乎都代表“没有昵称”。

但下游程序可能把它们理解成完全不同的状态:

null = 尚未设置
""   = 用户主动设置为空

甚至字段完全不存在:

{}

又可能代表:

当前版本不支持这个字段

如果AI只关注类型兼容,很容易忽略这些业务语义。


三、默认值变化尤其容易制造“静默问题”

假设以前分页接口默认:

pageSize = 20

AI重构后改成:

pageSize = 100

程序可能不会报错。

类型检查也完全正常。

但是系统行为已经变化:

  • 数据库查询量增加;

  • 页面加载变慢;

  • 网络返回变大;

  • 某些调用方一次处理更多数据。

这种问题最麻烦的地方就在于:

它不会立刻告诉你“这里错了”。

而是通过性能、业务结果或者边界场景慢慢表现出来。


四、可选字段变成必填字段,影响范围可能非常大

例如以前:

createUser({
  name
})

就可以创建用户。

后来AI修改接口,增加:

createUser({
  name,
  region
})

如果 region 变成必填,就不能只修改当前调用点。

项目里可能还有:

  • 后台脚本;

  • 单元测试;

  • 管理后台;

  • CLI工具;

  • 其他服务;

继续使用旧接口。

所以修改接口以后,不能只确认:

当前功能能不能跑。

还需要搜索:

谁还在调用这个接口。


五、返回结构不变,业务语义也可能已经改变

例如接口一直返回:

{
  "success": true
}

以前代表:

操作已经真正完成。

后来为了异步处理,修改成:

任务已经进入队列。

返回结构完全没有变化。

调用方也不会报错。

success: true 的实际含义已经从:

完成

变成:

已接受,等待处理

如果调用方仍然在收到 true 后立即刷新结果,就可能得到旧状态。

这就是典型的:

结构兼容,但语义不兼容。


六、状态码变化也属于接口契约

例如以前资源不存在时返回:

404

AI重构异常处理后变成:

200
{
  "data": null
}

从接口本身来看,也许仍然能表达“没有数据”。

但下游代码可能依赖:

status === 404

执行特殊逻辑。

一旦状态码改变,原来的判断就不会再执行。

所以修改API时还要检查:

  • HTTP状态码;

  • 错误码;

  • 异常类型;

  • 错误信息结构。

这些都属于接口的一部分。


七、为什么类型检查经常发现不了这种问题?

因为类型系统更擅长检查:

string是不是string
number是不是number
字段存不存在
参数能不能传进去

但它不一定知道:

0代表什么?
null代表什么?
空数组代表什么?
success=true代表什么?

这些属于业务语义。

比如:

status: string

从类型角度看:

"pending"
"done"
"unknown"

都是合法字符串。

但对业务来说,它们代表完全不同的状态。

所以:

类型正确,只能证明结构层面没有明显冲突。

不代表行为一定兼容。


八、AI修改接口前最好先找“消费者”

修改一个接口时,可以先搜索:

接口函数名
API路径
返回类型
DTO名称
客户端方法

例如要修改:

getUser()

不要只看:

user.service.ts

还应该检查:

前端页面
测试
其他Service
后台任务
SDK
CLI

因为真正决定接口能不能改的,并不只是提供接口的一方。

还有:

所有依赖它的消费者。


九、可以建立一个“接口变化清单”

让AI修改接口之前,可以先要求:

请先分析本次接口变化:

1. 请求参数有没有变化;
2. 返回字段有没有变化;
3. 默认值有没有变化;
4. null和空值行为有没有变化;
5. 状态码有没有变化;
6. 错误类型有没有变化;
7. 哪些调用方会受到影响。

确认这些内容以后,再开始写代码。

这样能明显降低:

接口改完以后才发现调用方行为变了

这种情况。


十、兼容性测试不能只看“能不能调用”

例如旧调用方:

getUser()

调用成功,并不代表兼容性没有问题。

更应该检查:

旧输入
↓
新接口
↓
是否仍然得到旧调用方预期的行为

也就是说测试不仅要覆盖:

请求有没有成功?

还应该覆盖:

返回结果的语义有没有改变?

特别是公共接口、SDK和多个模块共用的方法,这一步更加重要。


十一、接口修改可以按这个顺序排查

如果AI改完接口以后出现奇怪问题,可以依次确认:

第一步:看结构。

字段和类型有没有变化?

第二步:看默认值。

没有传参数时行为是否一样?

第三步:看空值。

nullundefined、空字符串、空数组是否发生变化?

第四步:看错误行为。

状态码和异常类型是否一致?

第五步:找调用方。

哪些地方依赖旧行为?

第六步:验证业务语义。

最终结果是否仍然符合原来的接口契约?


最后

AI修改接口以后,调用方没有直接报错,并不意味着这次修改一定安全。

真正容易被忽略的是:

接口的隐含契约。

它可能存在于:

默认值、空值、状态码、异常类型和业务语义之中。

所以判断一个接口修改是否正确,不能只问:

类型检查通过了吗?

还要继续问:

原来的调用方还能不能得到它原本预期的行为?

大型项目里,很多难发现的问题都不是“代码不能运行”。

而是:

代码仍然可以运行,但行为已经悄悄发生了变化。

这也是AI参与接口重构时,特别值得检查的一层。


持续更新 Codex、Claude Code 与大模型开发实战内容,更多深度内容和稳定订阅渠道欢迎搜索关注「孤狼GPT」。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐