最近同事用 AI 写了一段看起来非常合理的代码,上线后直接把线上的多语言翻译全干没了。
逻辑很简单:每次发版时,先从远端拉取最新的翻译 JSON,和本地的合并,然后一起提交上去。这样就能保证新增的翻译和已有的翻译共存。伪代码大概是这样:
async function updateTranslation(local) {
const remote = await fetchRemoteJSON();
const merged = { ...remote, ...local };
await upload(merged);
}
看起来没毛病对吧?AI 写的也很利索,一把过。
但问题出在 fetchRemoteJSON 失败的时候。AI 很”贴心”地加了兜底逻辑:请求失败就返回空对象,保证流程不中断。
async function fetchRemoteJSON() {
try {
const res = await fetch(REMOTE_URL);
return await res.json();
} catch (e) {
console.warn('拉取远端翻译失败,使用空对象兜底');
return {};
}
}
于是某次发版时远端接口刚好抖了一下,remote 拿到了 {},合并后只剩本次新增的几条翻译,然后被完整地提交上去——之前积累的上千条翻译,没了。
正确的做法是拉取失败时直接中断流程,而不是吞掉错误继续跑。拉不到最新数据,就不该继续合并和提交。让流程报错、让人介入,远比”优雅降级”后悄悄覆盖数据要安全得多。
AI 在写代码时有一个明显的倾向:让代码跑通。它会本能地给每个可能出错的地方加 try-catch,给每个可能为空的值加默认值。这在很多场景下是合理的,但在某些场景下,这种”兜底”本身就是 bug。像这个案例,空对象不是一个安全的兜底值——它意味着”没有任何历史翻译”,和”拉取失败”是两码事。AI 没有区分这两种语义,只是机械地保证了函数不会抛错。
review AI 代码时,多问一句:这个 catch 里的兜底值,在业务上意味着什么?它真的安全吗? 不是所有的错误都该被吞掉,有时候报错才是正确的行为。