我正在读一段 AI 生成的代码。这不是一次普通的代码审查,因为我发现,AI code 最危险的地方,不是它写错了,而是它看起来太正常了:变量命名规范,注释完整,函数的职责边界也很清楚。可就在我准备把它合入主干的前一分钟,我看到回调函数里有一行日志,会把一个外部服务的访问密钥直接打到日志系统里。
如果这段代码上了生产,密钥可能通过日志泄露给第三方日志平台,影响面会比“代码写错”更大。
这件事让我想通了一个问题:AI 编程工具大幅提高了代码产出速度,但真正决定工程质量上限的,已经不再是“写代码的能力”,而是“读代码的能力”。如果你不能认真阅读一段 AI 生成的代码,你就没有资格把它放进你的项目里。
这篇文章想聊的,不是怎么安装某个 AI 编程助手,也不是怎么让 AI 写得更好,而是围绕一个看起来像宣言的句子展开:I do read AI code。我愿意把 AI 生成的代码当成真正需要被审查、被理解、被验证的工程产物,而不是“工具吐出来的答案”。
1. 为什么“读 AI 代码”正在成为一项基础能力
1.1 代码来源改变了,默认信任模型也该改变
过去很长一段时间,我们读代码的动机来自“我要维护这段代码”。你可能是接手别人的模块,需要改 bug;也可能是同事提交了功能分支,你需要做 code review。那时代码的主要来源是人类,人类会犯错误,但错误模式基本可预测:命名不好、逻辑短路、边界遗漏、并发问题。
现在不一样了。AI 编程工具成了越来越常见的代码来源。你输入一段需求描述,它能在几十秒内生成一个函数、一个组件,甚至一整套项目脚手架。Cursor、Claude Code、GitHub Copilot、通义灵码、OpenCode 这类工具,正在把“从空白文件开始写代码”这件事变成“从一段生成结果开始改代码”。
这个转变带来一个很隐蔽的后果:你不再是代码的“作者”,而是代码的“审核者”。但大部分人的审核能力还没有跟上。
如果你在拿到 AI 生成代码后,选择直接复制粘贴然后运行,实质上你是在用“它看起来对”作为信任依据。可 AI 生成代码时,并不像人类开发者那样拥有“我在什么项目里、有哪些依赖、核心业务规则是什么、哪些路径不能走”的全局上下文。它更像一个记忆力很强、但缺乏项目常识的外包工程师,你给它一个局部任务,它就可能基于统计上的常见模式补全出答案。
所以,默认信任模型必须改:凡是 AI 生成的代码,一律先当作不可信输入,直到你读明白、验证过。
1.2 “看起来能跑”和“真正应该跑”是两回事
我在本地见过很多次这样的现象:一段 AI 生成的 Python 脚本,在 Jupyter Notebook 里能跑,数据也能打印出来,但把它接到生产接口上,就会报错。原因是生成代码时,它假设输入是一个已经清洗过的 DataFrame,而生产接口进来的数据可能包含空值、重复字段、类型不一致。
这种“看起来能跑”的错觉,正是最危险的地方。
传统代码写错了,往往报错信息明显,代码根本跑不起来。但 AI 生成代码经常会在“刚好能运行”和“正确解决问题”之间留一条很宽的缝。它可能使用了过时的 API、拼错了参数名但恰好被关键字参数兜住、或者调用了某个已经废弃的 SDK 方法,只是老版本兼容所以没报错。
更麻烦的是,AI 生成的代码往往“面向测试样例优化”,而不是“面向长期维护优化”。你如果只拿一两个 happy path 去验证,它很容易通过;但你换一批异常输入、换一个运行环境、把并发量提上去,问题就会一个一个暴露出来。
“能跑”通常只能证明流程没有断,不能证明逻辑是对的。这是我在读 AI 代码时最深的一个体感。
1.3 不读 AI 代码,最贵的是后续维护成本
很多团队引入 AI 编程工具的初期,都会觉得“效率提升巨大”。这种提升是真实的,但它隐藏了另一个数字:代码库中不可理解的代码占比正在上升。
如果 AI 生成了一段代码,团队里没有一个人认真读过、理解过,那么这段代码就变成了“遗产代码”。三个月后,当业务需求发生变化,你需要修改它的行为时,你会发现你根本不知道它是怎么工作的,也不知道哪里能动、哪里不能动。
这时候,你面前只有两条路:要么花更长时间重新逆向理解这段代码,要么直接删掉重写。而很多团队在压力下会选择“再加一层胶水代码”,把问题包得更厚。
所以,我不把“读 AI 代码”理解成一个道德呼吁,而是一个成本问题:你现在不读,将来一定会在调试、维护、交接时把这段时间连本带利补回来。早读,其实就是省钱。
2. 先看懂 AI 生成代码的几类典型问题
读 AI 代码之前,最好建立一个风险认知模型。AI 生成代码的问题不是“单一错误”,而是分布在几个不同层面的系统性偏差。
2.1 幻觉型错误:表面完整,实则调用了不存在的接口
最常见的一类问题,是 AI 基于训练数据中的模式,生成了“看起来正确但实际不存在”的接口调用。
例如,你让它调用某个第三方 SDK 的图片压缩接口,它可能直接生成一个client.compress_image()方法。可当你去查 SDK 文档时,发现这个版本里根本没有compress_image,只有一个process_image,参数还完全不一样。
这类错误在代码审查里最难发现,因为:
- 函数名符合直觉
- 参数类型看着合理
- IDE 的静态检查未必能捕捉,特别是动态语言
- 必须运行到对应分支才会报错
我的处理方式很笨但很有效:凡是 AI 代码里出现的第三方 API、SDK 方法、远程接口,我会逐个去官方文档里确认名称和参数。这个方法慢,但它能挡住绝大多数幻觉型错误。
2.2 依赖幻觉:安装了包,项目却越来越不干净
AI 还会“发明”一些不存在的依赖包。它可能觉得某个功能应该有对应的库,于是生成import my_toolkit,但 PyPI 或 npm 上根本没有这个包;更有意思的是,有些攻击者会提前注册一些常见但空白的包名,如果你在安装依赖时没有仔细核对,就可能在不知不觉中拉入一个来历不明的包。
这类问题的排查链路通常是:
- 先看报错:
ModuleNotFoundError或Cannot find module - 再检查依赖文件:是不是 AI 自动加了一个你没见过的包
- 去包管理平台确认包名、维护者、下载量
- 确认包版本范围内没有恶意提交
- 最后才是安装并锁定版本
所以,我不建议让 AI 自动帮你安装依赖。让它生成代码可以,安装依赖前,一定要人工确认包名和来源。
2.3 环境假设错配:本地能跑,换台机器就崩
AI 生成代码时,经常会默认环境里已经有某些配置。比如它假设:
- 系统已经设置好环境变量
- 当前目录下有某个配置文件
- 容器里已经安装了特定系统库
- 数据库连接字符串存在于默认位置
- 文件路径用的是 Linux 绝对路径
这些假设在生成环境里可能全部不成立。
我见过一个案例:AI 生成的定时任务脚本,在开发者的 Mac 上运行完全正常,但部署到 Linux 服务器后,由于路径分隔符和默认 shell 不同,脚本第一个命令就失败。不是 AI 不懂跨平台,而是它没有足够信息判断应该使用哪种路径风格,所以它选择了一个最常见的写法。
读这类代码时,要特别关注“环境相关”的隐式假设。凡是涉及文件路径、环境变量、系统命令、权限、时区、编码的地方,都要额外确认。
2.4 安全与合规风险:最容易被包装成“正常代码”
AI 生成代码还有一个让人头疼的问题:它可以把不安全的行为藏得很好。
比如,它可能因为业务需要而生成一段“把用户上传文件保存到本地”的代码,但没有对文件名做任何过滤,导致路径穿越;也可能生成了一个网络请求,但没对目标地址做校验;还可能为了调试方便,把敏感信息直接打印到日志里。
互联网上有过一个非常经典的提醒:不要把你理解不了的代码粘贴到浏览器 DevTools 控制台里执行。这个提醒同样适用于 AI 编程场景。当你让 AI 帮你“修复一下页面脚本”,它给出的结果里可能包含一段外部请求代码。如果你没有读就直接执行,等于把一个未知脚本引入了你的运行环境。
安全审查不是安全工程师一个人的事。作为使用 AI 代码的人,你至少要检查这几点:
- 代码里有没有网络请求,请求发往哪里
- 有没有读取环境变量、密钥、token
- 有没有文件写入,写入路径是否可控
- 有没有执行系统命令,命令参数能否被外部输入影响
- 有没有把日志输出到第三方系统
我经常用一张表格来快速评估一段 AI 代码的风险面。
| 风险类型 | 常见表现 | 阅读时重点 |
|---|---|---|
| 幻觉 API | 调用了不存在的函数、方法、参数 | 逐个核对官方文档 |
| 依赖注入 | import 了不存在的包,或包名可疑 | 检查依赖来源和包名 |
| 环境错配 | 依赖特定路径、环境变量、系统命令 | 明确运行环境并做跨环境测试 |
| 安全隐患 | 密钥暴露、路径穿越、任意命令执行 | 检查网络、文件、环境变量、命令调用 |
| 逻辑偏差 | 边界条件错误、业务规则不符合 | 结合需求回归测试 |
| 长期维护 | 命名混乱、结构不清晰、没有测试 | 关注可读性和测试覆盖 |
3. 我读一段 AI 代码时,具体在看什么
读 AI 代码,不是从头到尾逐行读,而是带着问题读。我一般会读三遍。
3.1 第一遍:不看实现,先看它打算怎么改变系统
第一遍先不进入细节。我会把 AI 生成的代码当作一个“提案”,先回答几个系统级问题:
- 这段代码会被谁调用?
- 它读哪些输入,写哪些输出?
- 它会改动数据库吗?会调用外部接口吗?
- 如果失败,它会抛出异常、返回空值,还是静默吞掉错误?
- 它会不会改变全局状态、缓存、配置文件?
这一遍的目标不是找 bug,而是建立“这段代码在系统里的位置感”。没有位置感,后面所有细节阅读都可能走偏。
3.2 第二遍:追输入、输出和异常分支
第二遍开始贴近代码,但只看数据流和异常流。
对每个函数,我会问:
- 输入参数有没有做校验?空字符串、None、超长、非法格式怎么处理?
- 返回值是否符合调用方的预期?类型稳定吗?
- 中间有没有改变原始输入对象?
- 出现异常时,是向上抛,还是内部捕获?捕获后有没有记录上下文?
- 有没有过度设计,为了“结构好看”而引入不必要的抽象?
AI 代码经常在 happy path 上表现得很好,但异常分支往往只有一个笼统的try...except,甚至把异常信息丢弃。这种代码最明显的问题不是“会挂”,而是“挂了之后你不知道为什么挂”。
3.3 第三遍:找外部调用和副作用
第三遍是读代码里最容易产生安全问题和运行事故的部分:外部调用和副作用。
我一般会用搜索功能把这几类关键词扫一遍:
http://、https://fetch(、requests.、axios.、curlexec(、system(、subprocess、eval(、Function(fs.write、open(..., "w")、os.removeprocess.env、os.environ、getenvlogger.、console.log
扫描完之后,逐条确认三个问题:这个调用的目的是什么?它使用的参数来自哪里?如果参数被篡改,会发生什么?
这一步不需要自动化工具,日常用 IDE 的全局搜索就够了。
3.4 用最小验证代替肉眼信任
读完之后,还是要验证。但验证不应该是“把整段代码跑一遍”,而是做最小验证。
我会先构造一个最小输入,只覆盖这段代码最核心的路径;然后再构造一个明显越界的输入,看它能不能正确处理。如果代码里有外部依赖,我会用 mock 或者测试桩替换掉,确保验证过程不依赖真实网络、真实数据库或真实密钥。
这里有一个很实用的原则:不要把 AI 生成代码第一次运行的输出当作正确结果。你至少要让它“错误一次”,才能确认错误处理路径是真正有效的。
3.5 一份可复用的 AI 代码审查清单
我把上面这些整理成一个“四问”框架,每次读 AI 代码前,先走一遍:
- 它在做什么?可以用一句业务语言描述出来吗?
- 它凭什么能工作?依赖哪些外部条件、接口、数据?
- 它失败时会怎样?有没有日志、异常、兜底方案?
- 如果三个月后是我来维护它,我会怎么改?
如果这四个问题中有一个回答不上来,就不要急着合入。先问 AI,或者自己去看文档、写测试,直到能回答为止。
4. 工具和流程怎么配合“读代码”
读 AI 代码不能只靠个人意志,还得靠工具和流程提供支持。
4.1 命令行的 AI 是新的写作入口,但不是唯一的判断入口
现在很多 AI 编程工具从 IDE 插件扩展到了命令行,比如 Claude Code、OpenCode、Cursor 的命令行模式。为什么这些工具受欢迎?因为它们把“和 AI 对话”变成了一种更自然的编程交互:你可以在终端里描述任务,AI 直接修改文件、执行命令、查看结果。
这类工具确实提升了效率,但它也带来一个诱惑:把终端里的 AI 当成“权威”,它说什么就是什么。
我建议你换一个心态:命令行的 AI 是帮你生成草稿的入口,不是最终判断人。生成完代码后,你仍然要打开文件,用正常的 code review 流程过一遍。不要因为它在终端里直接执行成功了,就跳过阅读。
4.2 常见认证和安装报错:先看密钥、再看区域、再看来源
很多人第一次配置 AI 命令行工具时,会遇到几种典型的报错。
第一种是401 unauthorized,有些服务会返回类似{"code":"api_key_required","message":"..."}。这个通常说明 API Key 没有配置,或者配置的位置不对。排查顺序一般是:
- 检查环境变量名是否和工具文档一致
- 检查 API Key 是否写入到了正确的配置文件
- 检查 Key 是否有效、是否过期
- 检查当前终端会话有没有重新加载环境变量
第二种是区域限制报错,比如{"error":{"code":"unsupported_country_region_territory", ...}}。遇到这种结果,不要试图用非常规方式绕过。先去看官方服务条款和支持范围,确认当前账号或服务区域是否在支持列表里。如果你处于不支持的区域,合规的做法是选用合规的服务或等待官方开放。
第三种是“来路不明的安装脚本”。网上会有一些博主为了方便,提供“一键安装”命令,但这类命令经常会把一堆环境变量、代理配置、第三方脚本一并带进你的系统。我的建议是:始终使用官方文档里的安装指令,不要执行任何你不理解的安装脚本。
注意:无论什么时候,都不要把你不理解用途的代码粘贴到终端、控制台或 DevTools 里执行。这个习惯比任何安全工具都重要。
4.3 把代码审查固化到 IDE、Git 和 CI 里
阅读 AI 代码这件事,如果只靠“自觉”,大概率会在 deadline 前失效。更好的做法是把审查流程固化到工具链里。
在 IDE 层面,开启 AI 生成代码的 diff 预览;没有 diff 预览,就不要直接生成。把 AI 生成前后的差异当成第一次 review 的最小单元。
在 Git 层面,所有 AI 生成代码的改动,都应该走正常的 Merge Request 或 Pull Request 流程,不要使用--no-verify跳过检查。提交信息里最好标明这部分代码是 AI 生成的,方便后续回溯。
在 CI 层面,至少要加这些自动化检查:
- 单元测试和测试覆盖率
- 依赖锁定检查
- 密钥扫描,防止把 token、密码提交到仓库
- 静态代码扫描,检查危险函数调用
- 构建和跨环境运行测试
自动化不能替代人读代码,但它可以在人读之前先挡掉一批低级问题。
4.4 善用“解释代码”代替“重新生成代码”
遇到一段看不懂的 AI 代码时,很多人的第一反应是“让它重写”。但我更建议先问它:“请解释这段代码为什么要这样写。”
这个动作有两点价值:
- 它强迫你把注意力放在逻辑上,而不是急着得到一个新答案
- 它能帮你识别 AI 是不是真的理解需求,还是在模式匹配
如果 AI 的解释含糊其辞,或者只翻来覆去说“这样做更安全”“这是常见做法”,那这段代码大概率需要重写。如果它能清楚说出权衡和边界,你再决定是保留还是调整。
5. 信任但要验证:一套可持续的 AI 编程工作流
最后,我想把整个思路收束成一套可以放进日常开发里的工作流。
5.1 先把 AI 当实习生,而不是专家
最影响 AI 代码质量的,往往不是模型能力,而是你怎么使用它。
如果你把 AI 当专家,给它一句模糊的需求,它就会给你一个听起来很有道理、但实际上漏洞百出的方案。如果你把 AI 当实习生,你会:
- 讲清楚背景和目标
- 给出约束和边界
- 要求它先说思路,再动手
- 对结果做 review
- 让它在不理解的地方直接问
这个心态转化,比任何提示词技巧都重要。你越把它当实习生,越会自然地去读、去验证、去追问它给出的代码;你越把它当专家,越容易在关键时刻放弃判断。
5.2 推荐工作流:约束 -> 生成 -> 阅读 -> 测试 -> 灰度 -> 合入
我目前比较推荐的工作流是这样的:
- 约束:在让 AI 生成前,先用自然语言写出需求边界、输入输出格式、不允许做的操作。
- 生成:让 AI 生成一个最小实现,避免一次生成一个巨大模块。
- 阅读:按前面说的“读三遍”方法,阅读生成代码,确认自己理解了它的行为。
- 测试:补上最小用例,至少覆盖 happy path 和一个异常分支。
- 灰度:把改动放到 staging 或小流量环境,观察日志、错误率和性能数据。
- 合入:走正常的 code review 和 CI,再合入主干。
这套流程看起来不像“效率拉满”,但它保证了一件事:AI 生成代码被真正消化成项目资产,而不是留在代码库里的定时炸弹。
5.3 长期维护:代码可读性比“一次性跑通”更重要
AI 编程还有一个容易忽视的问题:代码生成很便宜,但代码阅读很贵。
如果你依赖 AI 反复生成一次性代码,短周期内可能觉得很爽;但当多个模块堆叠起来,你会发现代码风格不一致、抽象层级混乱、没有一个地方像“同一批人写出来的”。这会让后续的维护成本指数级上升。
我在项目里会特别强调:AI 生成的代码,同样要遵守项目既有的代码风格和结构。不要因为它是 AI 生成的,就默认接受不同的命名风格、返回类型和错误处理方式。如果一段代码就算跑通了,但可读性很差,我会倾向于让 AI 重写,而不是改造。
5.4 适用边界:什么场景该用,什么场景要谨慎
不是说所有 AI 生成代码都不能用,而是要分清场景。
适合用 AI 生成并快速落地的场景包括:
- 样板代码和脚手架
- 测试数据生成器
- 与核心业务无关的脚本
- 用于学习和探索的原型代码
- 常见数据结构转换、格式化、工具函数
需要更谨慎的场景包括:
- 核心交易逻辑
- 涉及资金、权限、鉴权、隐私的模块
- 安全敏感的文件读写和命令执行
- 现有系统的复杂变更,特别是测试覆盖不足的模块
- 无人 review 的“小改动”
判断标准可以很简单:这段代码如果坏了,影响多大?如果影响大,就必须读、必须测、必须 review。
回到开头那句话。I do read AI code,不是一句炫耀,也不是一种保守主义。它是在 AI 生成代码越来越普遍之后,一个开发者对自己写进项目的每一段代码负责的方式。
AI 可以帮我们省下大量键入代码的时间,但它不能替我们理解业务、判断边界、承担线上故障的责任。真正可靠的工程,从来不是“AI 写得更多”,而是“人理解得更多”。
下一次,当你准备把 AI 生成的代码直接提交时,先停下来,多读一遍。多读的那一遍,可能不会让代码跑得更快,但会让它活得更久。