让 AI 写代码,最怕的不是错,而是看起来对
补档说明:本文属于「AI 工程落地周记」系列,计划发布时间为 2025-07-02 09:10。当前先保留为草稿,后续补充真实案例、代码片段和复盘细节后再发布。
我越来越觉得,AI 写代码最大的风险不是它会明显报错,而是它经常会生成一段“看起来特别像对的东西”。这种代码危险的地方就在于,它不会像语法错误那样立刻被发现,反而更容易一路混进 review、测试,最后在边角场景里才炸开。
我印象最深的一次,是一个缓存键生成函数。AI 给出的实现非常自然,变量名也不错,测试样例甚至都能过。但真正上线后我们才发现,它把两个不同参数集在某些顺序下算成了同一个 key,导致缓存串了。
这类问题最麻烦的,不是它难修,而是它在第一次看代码时太像“经验丰富的人写出来的东西”了。
为什么“看起来对”比“明显错”更危险
明显错误其实很好处理:
- 编译不过
- 测试挂掉
- linter 报警
团队会立刻停下来修。
真正危险的是下面这种情况:
- 代码能跑
- 名字看起来合理
- 路径样例也通过
- 只有在隐藏边界条件下才会露馅
这时候人最容易放松,因为系统没有给出任何强提醒。
一个典型例子
当时那段缓存键代码大概是这种味道:
function buildCacheKey(params: Record<string, string>) {
return Object.values(params).join(':')
}
看起来非常简洁,甚至很“优雅”。问题是,它只拼值,不拼字段名,也不保证字段顺序。于是:
{ city: 'beijing', type: 'weather' }{ type: 'beijing', city: 'weather' }
在某些情况下就可能撞出奇怪的结果。
正确实现当然也不复杂:
function buildCacheKey(params: Record<string, string>) {
return Object.entries(params)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join('&')
}
但重点不是“正确答案长什么样”,而是第一版错误实现太像正确答案,导致大家一开始都没警觉。
为什么 AI 特别容易生成这种代码
因为 AI 擅长的是:
- 局部模式
- 表面合理性
- 常见写法拼装
它不天然擅长的是:
- 你这个业务里真正危险的边界条件
- 哪个约束是项目特有的
- 哪个地方一旦错了会造成系统级副作用
所以它特别容易生成“局部逻辑顺、系统边界弱”的代码。
我后来怎么降低这种风险
1. 先让 AI 写测试点,不急着写实现
很多时候让 AI 先列“这段代码最容易在哪些边界条件下出错”,比直接让它写实现更有价值。
2. 人审时不只看代码可读性,还要问边界
我现在看 AI 代码时,会强迫自己额外问:
- 这段代码在重复输入下稳定吗?
- 在顺序变化下稳定吗?
- 在缺字段或空值时稳定吗?
- 在副作用场景里会不会重复执行?
3. 对“过于简洁”的实现更警惕
AI 特别喜欢给出很顺手、很短、很像最佳实践的代码。但越是这种一眼看上去很漂亮的实现,我现在反而越会多看两遍。
一个更有用的 review 清单
只要是 AI 生成的逻辑代码,我现在至少会过这 5 个问题:
- 它是不是只对示例成立,而不是对边界成立?
- 它有没有隐含依赖输入顺序?
- 它是否忽略了空值、重复值或异常值?
- 如果涉及副作用,是否考虑了幂等?
- 它是不是只是“像正确答案”,而不是真的满足业务约束?
总结
让 AI 写代码,最怕的不是明显错误,而是那种足够像正确实现、足够整洁、足够能通过浅层验证的“伪正确代码”。
真正有效的防线,不是单纯更信任或更不信任 AI,而是把 review 的重点从“写法顺不顺”转移到“边界和约束有没有真的被满足”。只有这样,AI 代码才更像助力,而不是隐形风险放大器。
