1. 为什么我坚持让AI写完代码先过我这道人工闸门
团队里最近有个挺有意思的变化:以前是我追着开发要代码审查,现在反过来了,AI助手写完一坨代码,主动往我这儿一丢,说"你审吧"。标题里那句"AI的代码交给我审",说的就是这个场景。听着像是玩笑,但真落到日常协作里,它其实是一套挺严肃的工程流程——AI负责产出,人负责把关,中间靠几条硬性清单卡住质量。
先把话说清楚:这篇不是教你"怎么让AI写代码",那玩意儿现在谁都会。我要聊的是AI产出代码之后,人工审查这一环到底该怎么审、审什么、卡在哪几条线上。关键词里提到的代码审查、Claude Code、沙箱、权限,基本就是我这套流程的四个支点。适合谁看?适合已经在用AI辅助编码、但发现"AI写得挺快、坑埋得挺深"的开发者,也适合团队里负责代码质量、被AI产出搞得审查压力陡增的技术负责人。
我自己的体感是,AI写的代码有个很鲜明的特点:表面工整,暗处随意。命名规范、注释齐全、结构清晰,第一眼看上去比很多人类写的还漂亮。但你只要往深里挖两层,就会发现它在边界条件、权限处理、异常路径、资源释放这些"不显眼但要命"的地方,经常给你埋雷。所以审查AI代码,不能按审人类代码那套"看逻辑对不对"来,得换一套更偏"防御性"的思路。
标题里"四条清单,两次打回"不是修辞。四条清单是我实际在用的四类检查项,两次打回是这套流程跑下来最常见的节奏——第一轮打回基本都栽在权限和沙箱上,第二轮打回多半是边界和资源问题。下面我把这套东西完整拆开讲,包括为什么这么设计、每条清单具体查什么、以及那些只有真审过才懂的坑。
2. 四条清单的由来:AI代码的失效模式决定了审查重点
2.1 先搞清楚AI代码到底容易在哪翻车
要设计审查清单,得先知道审查对象会怎么坏。我前后审了大概两三百个AI产出的代码片段和模块,把翻车点归了归类,发现它们高度集中在四个区域,而且这四个区域有个共同特征——都是"运行时才暴露、静态看很难发现"的问题。
第一个区域是权限与访问控制。AI特别爱写"直接访问"的代码,比如直接读某个路径、直接调某个系统接口、直接假设当前进程有管理员权限。它不会主动去想"如果这个操作没权限会怎样"。第二个区域是沙箱与隔离边界。AI生成的代码经常默认自己跑在一个"什么都能干"的环境里,对容器边界、文件系统隔离、网络隔离毫无敬畏。第三个区域是边界条件与异常路径。空值、超长输入、并发竞争、超时重试,这些它要么不处理,要么处理得极其敷衍。第四个区域是资源生命周期。文件句柄、数据库连接、锁、临时文件,开了不关是常态。
你看,这四个区域恰好对应我标题里的四条清单。这不是我硬凑的,是失效模式倒推出来的审查结构。审查清单的本质,是把"AI最可能忽略的东西"变成"人必须逐条确认的东西"。
2.2 四条清单分别是什么,为什么是这四条
我把四条清单命名为:权限清单、沙箱清单、边界清单、资源清单。每一条都对应一类具体的检查动作,而不是笼统的"检查安全性"。
权限清单查的是"这段代码假设自己有什么权限,这个假设成立吗"。沙箱清单查的是"这段代码在隔离环境里还能不能跑,会不会越界"。边界清单查的是"输入极端、并发极端、时间极端时会发生什么"。资源清单查的是"开出去的东西有没有收回来"。
为什么偏偏是这四条,而不是五条六条?因为审查是有成本的。清单太长,审查者会疲劳,最后变成走过场。四条是我实测下来"覆盖了绝大多数致命问题、又不至于让人审到崩溃"的平衡点。每条清单下面大概三到五个具体检查项,加起来十几项,一个中等规模的模块审下来大概二十到四十分钟,这个成本团队能接受。
提示:清单不是越多越好。我早期试过搞八条清单,结果审查者前三条认真看,后面全靠感觉。砍到四条之后,反而每条都能落到实处。
2.3 "两次打回"是怎么形成的
"两次打回"不是我定的规矩,是流程跑顺之后自然形成的节奏。第一次打回,几乎必然发生在权限和沙箱这两块。因为AI产出的代码在"能不能跑起来"这个层面经常就有问题——它假设的权限环境和你实际的运行环境对不上,或者它在沙箱里访问了不该访问的东西。这一轮打回,改的是"能不能安全地跑"。
第二次打回,通常发生在边界和资源这两块。第一轮改完,代码能跑了,但一上压力测试或者跑长时间,边界问题和资源泄漏就冒出来了。这一轮打回,改的是"能不能稳定地跑"。
我一开始还想着能不能一轮改完,后来发现不行。因为权限和沙箱的问题不解决,你根本测不到边界和资源问题——代码在第一步就崩了。所以这个"两次打回"其实是被问题的依赖关系逼出来的,先解决"跑得起来",再解决"跑得稳",顺序不能反。
3. 权限清单:AI最爱假设自己有权限,这是第一打回区
3.1 AI代码里的权限假设有多离谱
我审过一段AI写的文件处理代码,它上来就是一句删除某个系统临时目录下的文件。逻辑没问题,但它默认当前进程对这个目录有写权限。实际部署的时候,这个进程跑在一个受限账户下,直接报权限不足。AI写的时候压根没考虑这茬,因为在它的"训练经验"里,删个临时文件天经地义。
这类问题在AI代码里极其普遍。它倾向于写"理想路径"——假设所有资源都可访问、所有操作都被允许。而真实系统里,权限是一层一层卡出来的。你可能会遇到"你需要来自administrators的权限才能删除"这种提示,也可能遇到更隐蔽的"应用程序特定权限设置并未向在应用程序容器中运行的地址开放"这类问题。AI不会主动帮你处理这些,它甚至不知道这些存在。
所以权限清单的第一条就是:逐行找出代码里所有"需要特权"的操作,然后问一句"这个特权在当前运行环境下有吗"。需要特权的操作包括但不限于:写系统目录、改注册表、访问其他用户的文件、绑定低位端口、加载内核模块、修改系统时间。
3.2 权限清单的具体检查项
我把权限清单落成了五个具体检查项,审查时逐条过:
| 检查项 | 具体动作 | 常见问题 |
|---|---|---|
| 特权操作识别 | 标出所有需要 elevated 权限的调用 | AI默认有管理员权限 |
| 运行账户确认 | 确认代码实际以什么账户运行 | 开发用管理员,生产用受限账户 |
| 降级路径 | 无权限时是否有优雅降级 | AI通常直接抛异常 |
| 权限申请时机 | 是否在真正需要时才申请 | AI爱在启动时一次性申请全部 |
| 错误信息 | 权限失败时提示是否可操作 | AI的报错信息对用户毫无帮助 |
这里重点说"降级路径"。AI写的代码在遇到权限不足时,基本就两种反应:要么直接崩,要么抛一个用户看不懂的异常。但好的工程代码应该有降级方案——比如写不了系统目录就写到用户目录,改不了注册表就存配置文件。这个降级逻辑AI几乎不会主动写,必须人工补。
还有"权限申请时机"这一项,很多人会忽略。AI喜欢在程序启动时就把所有可能用到的权限一次性申请了,图省事。但这在安全审查里是大忌——权限应该按需申请、用完即释放,而不是一上来就要一大把。你审AI代码时,看到启动阶段一堆权限申请,基本可以判定这块要打回重写。
3.3 一个真实的权限打回案例
说个具体的。有次AI写了个配置管理模块,需要在程序启动时读取一个系统级的配置文件,如果不存在就创建一个。代码逻辑很顺,但它创建文件时用的是绝对路径,指向系统配置目录。在开发机上跑没问题,因为开发账户权限高。一上测试环境,进程以服务账户运行,创建文件直接失败,整个模块起不来。
第一次打回,我给的修改意见是三条:第一,读取系统配置失败时,回退到用户级配置目录;第二,创建文件前先检查目录可写性,不可写就明确报错并给出替代方案;第三,把"需要写系统目录"这个假设从代码里彻底拿掉,改成"优先系统目录、回退用户目录"的双路径策略。
改完之后,这个模块在受限账户下也能正常跑,只是配置存到了用户目录。这就是权限清单的价值——它逼着你去质疑AI那些"想当然"的假设。
4. 沙箱清单:代码在隔离环境里还活不活得下去
4.1 沙箱边界是AI最没有概念的东西
如果说权限问题是AI"假设自己有权限",那沙箱问题就是AI"假设自己在一个没有边界的世界里"。它写的代码经常默认可以随意访问文件系统、随意发起网络请求、随意调用系统命令。但在现代部署环境里,代码大概率跑在某种沙箱或容器里,文件系统是隔离的、网络是受限的、系统调用是白名单的。
我审过一段AI写的代码,它需要调用一个外部命令来处理数据。代码里直接用了系统调用去执行那个命令。逻辑上没错,但它没考虑这个命令在沙箱里可能根本不存在,或者沙箱禁止了进程创建。结果就是代码在本地跑得好好的,一进容器就挂。
沙箱清单的核心问题就一个:这段代码依赖的所有外部资源,在目标沙箱环境里都存在且可访问吗?外部资源包括:文件路径、环境变量、网络端点、系统命令、设备节点、共享内存。
4.2 沙箱清单要查的四件事
第一件,文件系统假设。AI代码里出现的每一个路径,都要确认在沙箱里是否可访问。特别注意那些硬编码的绝对路径,比如/tmp、/var/log、C:\Windows\Temp这类。沙箱里的临时目录往往是映射过的,跟宿主机不是一回事。
第二件,网络假设。AI代码如果发起了网络请求,要确认沙箱是否允许出网、允许访问哪些地址。很多沙箱默认禁止所有出网,或者只允许白名单。AI不会管这些,它只管把请求发出去。
第三件,进程与命令假设。代码里如果有执行外部命令、创建子进程的操作,要确认沙箱是否允许。容器环境里,进程创建经常是被限制的。
第四件,环境变量与配置假设。AI代码可能依赖某些环境变量,但沙箱里的环境变量跟宿主机完全不同。这个坑特别隐蔽,因为代码在本地跑的时候环境变量都在,一进沙箱就全没了。
注意:沙箱问题最坑的地方在于,它在开发环境里几乎不会暴露。开发机权限全开、网络全通、命令齐全,AI代码跑得飞起。只有进了真正的隔离环境,问题才集中爆发。所以沙箱清单必须在"接近生产的环境"里验证,本地跑通不算数。
4.3 沙箱打回的典型场景
有个模块,AI写的时候用了一个第三方命令行工具做格式转换。代码里直接subprocess调用那个工具。本地测试全过。部署到容器里,容器镜像里根本没装那个工具,直接报"命令未找到"。
这是第一次打回。我给的方案是:要么把工具打进镜像,要么改用纯代码库实现转换逻辑。团队选了后者,因为把外部工具打进镜像会增大镜像体积、增加攻击面。改完之后,代码不再依赖任何外部命令,沙箱里跑得很稳。
这个案例说明一个原则:AI代码对外部环境的依赖越少越好。每多一个外部依赖,就多一个沙箱里可能不存在的风险点。审查时看到AI引入的外部依赖,第一反应应该是"这个能不能去掉"。
5. 边界清单:极端输入和并发才是真正的照妖镜
5.1 AI处理边界条件的方式就是"不处理"
前两条清单解决的是"能不能跑",从边界清单开始,解决的是"跑得对不对、稳不稳"。AI在边界条件上的表现,用一句话概括就是:它只处理它想到的情况,想不到的一律不管。
空输入、超长输入、非法格式、并发访问、超时、重试,这些在AI代码里要么完全没有处理,要么处理得极其表面。比如一个解析函数,AI会写正常的解析逻辑,但不会写"如果输入是空字符串怎么办""如果输入超长怎么办""如果输入包含特殊字符怎么办"。它默认输入永远是"正常"的。
但真实世界里,输入永远不正常。用户会输入空值,网络会超时,并发会撞车。边界清单就是要把这些"不正常"一个个拎出来,逼着代码给出明确行为。
5.2 边界清单的检查维度
我把边界分成四个维度来查:
- 输入边界:空值、超长、非法字符、类型错误、编码问题
- 时间边界:超时、时钟回拨、时区、长时间运行后的状态漂移
- 并发边界:竞态条件、死锁、资源争抢、顺序依赖
- 容量边界:内存上限、磁盘上限、连接数上限、队列长度上限
每个维度下,审查时要问的是"这个边界上代码的行为是什么"。注意,不是问"代码有没有处理",而是问"行为是什么"。因为有些代码"没处理"本身就是一种行为——比如空输入时直接崩溃,这也是一种行为,只是不可接受。
我审AI代码时有个习惯动作:给每个函数都脑补一个"最坏输入",然后看代码会怎么反应。这个习惯帮我抓出了大量边界问题。AI写的函数,十个里有六七个在最坏输入下会崩或者给出错误结果。
5.3 并发问题是AI代码的重灾区
边界清单里,并发问题最值得单独拎出来说,因为AI在这块栽得最狠。AI写的代码经常有共享状态,但它不会加锁,或者加了锁但锁的粒度不对。更麻烦的是,AI写的并发问题往往在单线程测试里完全看不出来,一上并发就炸。
我审过一个缓存模块,AI写的,逻辑是"先查缓存,没有就计算,然后写缓存"。单线程跑完美。多线程一跑,同样的 key 被计算了好几次,因为两个线程同时发现缓存没有,同时开始计算。这是典型的 check-then-act 竞态。AI完全没意识到这个问题。
修复方案是加锁或者用原子操作。但这里有个细节:加锁的粒度要控制好,锁太大会影响性能,锁太小又保护不住。AI如果被要求加锁,它倾向于加一把大锁把整个函数锁住,简单粗暴但性能差。人工审查时要根据实际并发压力调整锁的粒度。
5.4 边界打回:从"能跑"到"跑得对"
第二次打回基本都发生在边界这一块。第一轮权限和沙箱改完,代码能跑了,测试一上强度,边界问题就冒出来。我印象最深的一次,一个数据处理模块,正常数据跑得好好的,一上生产,遇到一条超长记录,直接内存溢出。AI写的代码里,读取记录时没有长度上限检查,默认记录不会太长。
打回意见是加长度校验和分块处理。改完之后,超长记录会被截断或分块,不再撑爆内存。这个改动不大,但如果没有边界清单,这个问题会一直潜伏到生产环境才爆发,那时候代价就大了。
6. 资源清单:开了不关是AI的肌肉记忆
6.1 资源泄漏为什么在AI代码里这么常见
资源清单是四条里最"朴素"的一条,查的就是文件、连接、锁、内存这些资源有没有正确释放。但就是这么朴素的一条,AI代码的通过率低得惊人。原因是AI写代码时,注意力全在"主逻辑"上,资源释放这种"收尾工作"它经常忘。
文件句柄开了不关、数据库连接用了不还、锁加了不解、临时文件创建了不删,这些在AI代码里是常态。更麻烦的是,资源泄漏在短时间测试里看不出来,只有长时间运行或者高并发时才暴露。所以它特别适合放在最后一轮审查,因为前面几轮的问题不解决,你根本跑不到能暴露资源泄漏的阶段。
6.2 资源清单的检查方法
资源清单的检查方法很直接:找出代码里所有"获取资源"的操作,然后确认每个获取都有对应的释放,且释放路径覆盖所有分支。
具体来说,要查这几类资源:
| 资源类型 | 获取方式 | 释放方式 | AI常见问题 |
|---|---|---|---|
| 文件句柄 | open | close | 异常路径不关闭 |
| 数据库连接 | connect | close/归还连接池 | 忘记归还 |
| 锁 | lock/acquire | unlock/release | 异常时死锁 |
| 临时文件 | create | delete | 从不删除 |
| 内存 | alloc | free/GC | 循环引用 |
重点看"异常路径"。AI写的代码,正常路径下的资源释放往往是对的,但一旦中间抛异常,释放逻辑就被跳过了。这就是为什么资源清单要特别关注 try-finally 或者类似的结构——资源释放必须放在 finally 里,保证无论是否异常都能执行。
6.3 资源打回与"两次打回"的收尾
资源问题通常是第二次打回的一部分。第一轮改完权限和沙箱,第二轮改边界时,资源问题往往一起暴露。我一般会把边界和资源放在同一轮审查里,因为它们经常交织在一起——比如并发场景下资源没释放,既是边界问题也是资源问题。
有个连接池模块,AI写的,获取连接后如果业务逻辑抛异常,连接就不归还了。跑一段时间连接池就耗尽。这是典型的资源泄漏。打回意见是:所有连接获取必须配 try-finally,finally 里归还连接。改完之后,连接池再也没耗尽过。
到这里,"两次打回"的完整节奏就清楚了:第一次打回解决"能不能安全地跑"(权限+沙箱),第二次打回解决"能不能稳定地跑"(边界+资源)。这个节奏不是硬性规定,但它符合问题的依赖关系,所以实践中反复出现。
7. 审查AI代码时我踩过的坑和攒下的经验
7.1 别被AI代码的"表面工整"骗了
这是我踩过的第一个坑,也是最贵的一个。刚开始审AI代码时,我看它命名规范、注释齐全、结构清晰,下意识就觉得"这代码质量不错",审查时放松了警惕。结果上线后一堆问题。后来我才明白,AI代码的工整是"表演性"的,它把表面功夫做足,恰恰掩盖了深层的随意。
所以现在我审AI代码,第一件事就是"无视它的外表",直接跳到权限、沙箱、边界、资源这四个维度去查。外表再漂亮,这四个维度不过关,一律打回。这个心态转变很重要,不然你很容易被AI的"礼貌"迷惑。
7.2 审查要带着"恶意"去审
审人类代码时,我们通常假设作者是善意的、逻辑是通的,审查重点是找疏漏。但审AI代码,我建议换个心态:假设这段代码在每一个可能的点上都会出错,然后逐个验证它没出错。这种"有罪推定"式的审查,听起来累,但对AI代码特别有效,因为AI代码的失效模式就是"到处都可能出错"。
具体做法是,对每个函数、每个分支、每个外部调用,都问一句"这里如果出问题,会是什么问题"。这个习惯养成之后,审查效率反而提高了,因为你不再纠结"作者为什么这么写",而是直接验证"这么写会不会坏"。
7.3 清单要落地成可勾选的形式
四条清单如果只是记在脑子里,审查时很容易漏。我的做法是把每条清单做成一个可勾选的检查表,审查时逐项打勾。这个检查表不用很复杂,就是每个检查项一行,审完打勾。团队里现在审AI代码,都是对着检查表过的。
这个做法还有个好处:它让审查结果可追溯。打回的时候,我能明确指出"你这段代码在权限清单第3项没过",而不是笼统地说"权限有问题"。AI(或者用AI的人)拿到具体条目,改起来也更有方向。
7.4 打回意见要具体到"改哪里、怎么改"
打回不是目的,改对才是。我早期打回时,经常只写"权限处理有问题,改一下",结果对方(或者AI)改出来的东西还是不对,因为"有问题"太笼统了。后来我改成具体意见:指出具体行、说明具体问题、给出具体改法。比如"第42行创建文件前没有检查目录可写性,改成先检查、不可写则回退到用户目录"。
这种具体意见,AI拿到之后基本能一次改对。笼统意见则要来回好几轮。所以打回的成本,很大程度上取决于意见的具体程度。
7.5 不是所有AI代码都值得审
最后说个反直觉的经验:有些AI代码不值得审,直接重写更快。如果一段AI代码在权限和沙箱这两条清单上大面积不过关,说明它的基础假设就是错的,修修补补不如推倒重来。我现在的判断标准是:如果第一次打回的意见超过五条,或者涉及架构层面的调整,那就别改了,让AI按新的约束重写一遍。
这个判断帮我省了大量时间。早期我总想着"能改就改",结果在一个错误的架构上反复修补,越修越乱。后来学会"该重写就重写",效率反而高了。AI重写一段代码的成本很低,没必要在错误的代码上死磕。
8. 把这套流程跑顺之后,我的真实体会
这套"四条清单、两次打回"的流程,我跑了大概半年,最大的体会是:它把"审AI代码"从一件凭感觉的事,变成了一件有章法的事。以前审AI代码,我心里没底,不知道重点在哪,审完也不确定有没有漏。现在有了四条清单,审查有了明确的靶子,审完心里踏实。
另一个体会是,这套流程反过来也提升了AI产出的质量。因为我知道会被这四条清单卡,所以在让AI写代码时,会提前把这些约束写进提示里——"注意权限降级""注意沙箱兼容""注意边界处理""注意资源释放"。AI带着这些约束写出来的代码,第一次打回率明显下降。审查清单不只是审查工具,它还是给AI的"需求说明书"。
当然,这套流程不是万能的。它覆盖的是AI代码最常见的失效模式,但不可能覆盖所有问题。业务逻辑的正确性、算法的合理性,这些还是得靠人来看。清单解决的是"工程健壮性",不是"业务正确性"。这两者要分开。
最后分享一个我最近在用的技巧:把四条清单直接喂给AI,让它自己先审一遍自己的代码。具体做法是,AI写完代码后,我让它对照这四条清单逐条自查,把不过关的地方标出来。实测下来,AI自查能抓出大概一半的问题,剩下的一半还是得人来。但这一半已经省了不少事。这个技巧的关键是,清单要写得足够具体,AI才能对照着查。笼统的"检查安全性"它查不出东西,具体的"检查所有文件操作是否有权限降级路径"它就能查。
这套东西还在迭代。最近在考虑加第五条清单,专门查"依赖管理"——AI引入的第三方库、外部服务、系统命令,这些依赖的版本、可用性、安全性。但还没想好怎么把它做得足够轻,不至于让审查成本失控。等跑顺了再分享。