资讯动态

AI 代码 Review 实战:从看代码对不对到看 AI 懂不懂,我踩了一个坑之后调整了 review 方式

发布时间:2026/8/26 19:56:14 来源:尧图企业网站定制
摘要AI 写代码越来越快但 review 方式不能照搬人代码那套——否则你会漏掉最致命的一类 bug。本文从一次搜索缓存键粒度不够的翻车出发总结了 AI 代码的两种 bug 类型“写错了和不知道”给出了三档 review 策略A 档 review 测试链、B 档 review 上下文、C 档逐行 review以及一套可操作的三步法——列出假设、验证假设、修复假设。文末附带可直接复用的 review 检查清单和决策表。文章目录一个搜索缓存翻车了为什么这个翻车在 review 时被漏掉了AI 代码的 bug 有两种三档 review 策略A 档不 review 代码review 测试链是否完整B 档review 上下文不 review 代码C 档逐行 review 验证上下文理解三步法怎么review 上下文步骤 1列出 AI 代码里的所有假设步骤 2验证每个假设是否成立步骤 3对不成立的假设补代码或补注释Review 决策表这套策略的边界总结一下一个搜索缓存翻车了前阵子给一个搜索功能加缓存。需求不复杂——用户搜商品按关键词和分类筛选把结果缓存起来减少数据库查询。AI 写的代码我看了一遍觉得没问题就 merge 了。上线第二天用户投诉说搜’手机-苹果’和’手机-小米’出来一样的结果。排查过程挺直接的。先看调用方传的参数——第一次搜手机-苹果keyword手机, category苹果第二次搜手机-小米keyword手机, category小米。参数没问题。再看缓存代码。AI 的实现是这样的// 为什么要看这段缓存逻辑本身是对的问题出在缓存键的设计上functionsearchProducts(keyword:string,category?:string,minPrice?:number){constcacheKeysearch:keywordconstcachedcache.get(cacheKey)if(cached)returncachedconstresultsawaitdb.query(SELECT * FROM products WHERE name LIKE ? AND category ? AND price ?,[%${keyword}%,category,minPrice])cache.set(cacheKey,results,{ttl:300})returnresults}// 翻车时的运行结果 用户A: searchProducts(手机, 苹果) → 缓存未命中查数据库写入缓存 keysearch:手机 → 返回 [iPhone15, 小米14, 华为Mate60...] 用户B: searchProducts(手机, 小米) → 缓存命中 keysearch:手机 → 返回 [iPhone15, 小米14, 华为Mate60...] ← 本应只返回小米14问题找到了——缓存键只用了 keyword没把 category 纳进去。用户搜手机-苹果时缓存写入了 key‘search:手机’。用户搜手机-小米时缓存命中了同一个 key返回了手机-苹果的结果。但有意思的是AI 的代码逻辑本身没问题——缓存读写、过期时间、清理策略都是对的。问题出在缓存键的粒度不够。修复后我意识到不是 AI 写错了是我 review 时没问对问题。这直接催生了后面的三步法。为什么这个翻车在 review 时被漏掉了我复盘了一下当时 review 的心理活动。我看的是缓存读写逻辑对不对√过期时间设置合理不合理√有没有做缓存穿透防护√异常处理有没有√这些全是缓存逻辑层面的东西。我没去问一个问题“AI 对缓存键的粒度的假设在我们这个场景下成立吗”这个问题的本质是我的 review 注意力在代码对不对而不是AI 知不知道。这不是我粗心。我 review 人代码也是这个习惯——看逻辑对不对、边界处理了没、异常捕获了没。这套方法 review 人代码很有效因为人代码的 bug 就出在这几个地方。但到了 AI 代码这套方法就不够了。AI 代码的 bug 有两种我后来想明白一件事AI 代码的 bug 跟人代码的 bug不是一个维度的东西。bug 类型人代码AI 代码逻辑错误写错分支条件、算错索引、变量名混淆同样会出现但频率比人低边界遗漏没处理 null、没考虑空数组、没做类型校验偏中等训练数据里边界场景覆盖率决定上下文缺失很少——人知道自己在做什么项目最常见——AI 不知道业务规则、不知道数据分布、不知道外部系统限制假设偏差同事会问这个假设合理吗AI 不会问它直接按最可能的路径写人代码的 bug 集中在写错了这个象限。AI 代码的 bug 分两种——“写错了和不知道”。写错了类API 调错、语法错误、逻辑写反跟人代码一样按老方法 review 就能发现。但不知道类是 AI 特有的——AI 不知道 null 会传进来、不知道数据不满足类型定义、不知道这个 API 有调用频率限制、不知道缓存键要区分用户。大多数人 review AI 代码时只做了第一步查写错了没做第二步查不知道。一个简单的判断方法如果这段代码换个有经验的同事来写会不会写出同样的逻辑会 → 大概率是写错了不会 → 大概率是不知道。不过这个观点主要适用于日常的业务逻辑代码B 档。对于支付、权限这类代码C 档AI 的写错了风险也不低该逐行 review 还是得逐行。三档 review 策略第二弹的信任分级已经定了哪些代码敢放权第三弹的测试策略定了怎么测这一弹补上放权之后怎么 review。三档不是新发明的是在信任分级的三档框架下补充每个档位 review 的具体方法。A 档不 review 代码review 测试链是否完整A 档包括纯函数、工具函数、常量定义、脚手架代码。信任分级里说 A 档自动 merge但前提是测试链完整。什么叫测试链完整类型检查TypeScript strict mode snapshot 测试到位。如果项目没有类型检查或者没有 snapshot 测试那 A 档也不能不 review因为没人兜底。我自己的做法是review A 档代码时不看代码本身看两件事——这个文件有没有被类型检查覆盖项目里有没有 tsconfig 的 include 漏掉的文件这个文件的 snapshot 测试是不是稳定的频繁变动的 snapshot 测试失去意义如果这两件事都 OK直接 merge不花时间看代码。B 档review 上下文不 review 代码B 档包括业务逻辑、API 封装、数据转换。这是 AI 写代码的主力档位也是不知道类 bug 最常见的地方。review 方法不看代码语法和逻辑看 AI 对上下文的假设。具体操作就是下一节的三步法。这里先给一个直觉——你在 B 档 review 时把自己当成这个项目的新人而不是reviewer。新人会问的问题就是你 review 要回答的问题“这个字段一定有值吗”“这个 API 一定有数据返回吗”“这个缓存的调用方是谁”C 档逐行 review 验证上下文理解C 档包括支付、权限、事务边界、数据回填。出事成本高review 不能偷懒。逐行看代码逻辑跟 review 人代码一样。额外加一步验证 AI 有没有遗漏关键场景。比如 AI 写了个支付回调处理函数逐行看完了逻辑还要问一句“有没有什么场景是 AI 没考虑的”——比如重复回调、超时回滚、部分成功。三步法怎么review 上下文这是我从那次翻车之后沉淀的方法目前用下来没再漏过类似的不知道类 bug。传统的 review 是看代码的视角三步法切到看假设的视角区别大概是这样的传统review: 看代码逻辑 → 看边界处理 → 看异常捕获 → merge ↓ AI review三步法: 列出假设 → 验证假设 → 修复假设 ↓ 核心差异你review的不是代码是AI的理解步骤 1列出 AI 代码里的所有假设逐行看代码标注出AI 默认了但没写在代码里的东西。还是用翻车那个缓存键的例子// AI 写的代码逐行标注假设functionsearchProducts(keyword:string,category?:string,minPrice?:number){// 假设1keyword 一定有值 ← 参数有默认值吗调用方会不会传空字符串constcacheKeysearch:keyword// 假设2只用 keyword 做缓存键就够了 ← category 和 minPrice 不影响查询结果constcachedcache.get(cacheKey)if(cached)returncached// 假设3缓存里有数据就一定是对的 ← 缓存过期时间够吗// 假设4数据库一定能查到数据 ← 查不到怎么办constresultsawaitdb.query(...)cache.set(cacheKey,results,{ttl:300})returnresults}// 标注出来的假设列表 假设1keyword 一定有值 假设2只用 keyword 做缓存键就够了 假设3缓存里有数据就一定是对的 假设4数据库一定能查到数据步骤 2验证每个假设是否成立对每个假设问三个问题问调用方这个假设在我们这个场景下成立吗查数据实际数据满足这个假设吗看文档有没有文档说这个假设不成立的情况如果某个假设不成立但暂时无法修复比如依赖外部系统至少要在代码里加注释标明避免后续维护的人踩同一个坑。回到翻车案例假设验证结果结论keyword 一定有值调用方是前端搜索框空字符串会被前端拦截✅ 成立只用 keyword 做缓存键就够了调用方会传 category 和 minPrice不同分类返回不同数据❌不成立缓存里有数据就一定是对的数据不频繁变更300 秒 TTL 够用✅ 成立数据库一定能查到数据查不到返回空数组不是异常✅ 成立步骤 3对不成立的假设补代码或补注释找到不成立的假设后修复代码或补注释说明。// 修复后的代码把 category 和 minPrice 纳入缓存键functionsearchProducts(keyword:string,category?:string,minPrice?:number){// 构建缓存键时把所有影响查询结果的参数都包含进去constcacheKeysearch:${keyword}:${category??all}:${minPrice??0}constcachedcache.get(cacheKey)if(cached)returncachedconstresultsawaitdb.query(SELECT * FROM products WHERE name LIKE ? AND category ? AND price ?,[%${keyword}%,category,minPrice])cache.set(cacheKey,results,{ttl:300})returnresults}// 修复后运行结果 用户A: searchProducts(手机, 苹果) → 缓存未命中查数据库写入缓存 keysearch:手机:苹果:0 → 返回 [iPhone15, 华为Mate60...] 用户B: searchProducts(手机, 小米) → 缓存未命中查数据库写入缓存 keysearch:手机:小米:0 → 返回 [小米14, 红米Note13...] 两个结果不再互相覆盖 ✅三步法说穿了就是逐行标注假设 → 验证假设 → 修复假设。但这三步做下来比直接看代码逻辑多花 5-10 分钟能把你从代码对不对的惯性里拽出来切换到AI 懂不懂的视角。Review 决策表这是我现在用的 review 决策表写文章时改了改去掉了项目敏感信息档位典型代码review 什么不 review 什么耗时典型翻车案例A工具函数、常量、类型定义、脚手架测试链是否完整类型检查snapshot代码逻辑本身2-3 分钟snapshot 测试没覆盖到新文件类型错误漏过B业务逻辑、API 封装、数据转换、缓存上下文假设三步法语法、逻辑、代码风格5-10 分钟缓存键粒度不够本文案例C支付、权限、事务边界、数据回填逐行看代码 验证上下文理解跳过任何东西15-30 分钟支付回调没处理重复通知Review 检查清单review 前扫一遍这个代码的信任等级是什么A/B/C→ 决定 review 深度如果是 A 档测试链完整吗→ 完整就 merge如果是 B 档AI 的代码里有没有假设了但没告诉它的东西→ 三步法走一遍如果是 C 档逐行看完了吗→ 额外验证场景遗漏这套策略的边界不是所有场景都适用。遗留系统没有测试基础设施。A 档不 review 的前提是类型检查和 snapshot 测试到位。如果项目没有 TypeScript strict mode没有 Jest 配置A 档也得逐行 review。说白了这套策略依赖前面的防线——没有防线review 就得加码。纯 UI 组件不适用。我前面说的不 review 代码是针对逻辑代码的。UI 组件的 review 是另一套——看布局、看交互、看状态管理不能套用三步法。“不 review 代码不是不 review”。A 档和 B 档不看代码语法和逻辑细节但还是要看代码结构——变量命名、函数拆分、模块划分。这些是代码的可维护性跟 AI 会不会写错没关系。总结一下回头看这个系列的四篇文章其实都在回答同一个问题你的验证能力决定了你能放权多少。防线告诉你怎么兜底信任分级告诉你哪些代码敢放测试策略告诉你写完了怎么测review 告诉你你怎么审。你的验证能力越强你能放权给 AI 的就越多。这个逻辑跟 AI 本身没关系——跟人的工程能力有关系。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价