拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LLM代码审查也会“表扬”Bug?一场静默失败评测实验与优化指南

最近在推进代码审查自动化时发生过一个很有意思的评测现象同一段存在严重缺陷的代码不同 LLM 给出的 Code Review 意见差异极大有的能正确定位到核心隐患有的只是隔靴搔痒最意外的是还有一份 Review 通篇都在“表扬”那段有 Bug 的代码本身。这个问题不是个别现象。LLM 做代码审查已经成了研发效能领域的热门方向但评审质量如何验证、模型会不会漏报、会不会因为代码“看起来规范”就忽略运行时风险依然值得系统讨论。本文用一个可复现的小实验切入展示三份典型 LLM 审查意见的全过程并给出判断 LLM 审查质量的方法和提示词优化思路。1. 为什么生成式 AI 做代码审查先要“审查 AI”1.1 代码审查的價值与痛点代码审查是保障代码质量最传统也最有效的手段之一。它能提前发现逻辑缺陷、设计问题、安全隐患和可维护性风险也能促进团队内部的知识传递。但人工审查的成本很高。一个中型 PR 可能涉及十几到几十个文件审查者既要理解业务流程又要关注边界条件还要在有限时间内给出结论。审查不充分时一些隐藏较深的问题就会流入测试甚至生产环境。LLM 的出现让许多团队开始尝试“让 AI 先审一遍代码”。它的优点很直观响应速度快适合作为第一轮初筛能覆盖新人容易忽略的常见问题能根据代码上下文给出修改建议可以作为代码规范检查的补充手段。但 LLM 不是静态分析工具也不是确定性规则引擎。它生成的是概率化文本天然存在“流畅但不准确”的风险。把这个风险放到代码审查场景中后果可能比普通问答更严重如果模型把有问题的代码解读成“设计良好”开发人员又没有二次确认Bug 就可能被默认放行。1.2 LLM Code Review 与人工审查的差异传统意义上的代码审查强调“人对代码负责”。审查者会基于调用链、数据流、业务契约去判断一段代码是否满足需求而不仅是看它是否语法正确。LLM 的审查则更像是“基于大范围代码语料进行模式匹配”它能快速给出类似资深工程师会说的话但缺少真实执行环境和完整业务上下文。简单来说静态分析工具能告诉你“这个变量可能为空”单元测试能告诉你“这个分支没有被覆盖”资深工程师能告诉你“如果这里返回 True订单会进入成功状态但 ERP 里根本没有这条记录”而 LLM 可能会告诉你“代码结构清晰异常处理完善”即使那段代码存在致命的静默失败。因此在使用 LLM 辅助 Code Review 时第一件事不是讨论怎么用而是知道怎么验证它给出的结论。这正是本文实验要回答的问题。1.3 评测思路用“一个确定的 Bug”去检验 Review 质量要判断一份 Code Review 写得好不好最直接的办法是拿一段包含确定 Bug 的代码交给模型审查。然后看模型是否发现了 Bug、如何描述 Bug、是否把无害代码误判为缺陷以及给出的修改建议是否真的能解决问题。我在实验中选择了业务开发中一类典型且隐蔽的问题静默失败。它也常被称为“吞掉异常后的伪成功”。随着微服务和异步任务增多这类问题特别容易出现在接口调用、状态同步、消息推送等场景。代码看起来有异常处理、有日志、有返回值但错误发生时调用方依然会收到“成功”信号后续业务逻辑继续往下走最终导致数据不一致。2. 评测实验一段藏着静默 Bug 的订单推送代码2.1 完整代码为了让审查过程可复现我准备了一段接近真实业务的 Python 函数。它的职责是把订单推送到外部 ERP 系统并告诉调用方推送是否成功。# 文件路径demo/order_sync.py import json import logging import requests logger logging.getLogger(__name__) ERP_ORDER_URL https://erp.example.com/api/order/create def push_order_to_erp(order: dict) - bool: 推送订单数据到 ERP 系统。 调用方约定 - 返回 True表示 ERP 已成功接收订单 - 返回 False表示推送失败需要后续补偿或告警。 try: response requests.post( ERP_ORDER_URL, jsonorder, timeout5, headers{Content-Type: application/json}, ) if response.status_code 200: payload response.json() return payload.get(success, False) is True except requests.Timeout: logger.exception(push order to erp timeout) except requests.RequestException as exc: logger.exception(push order to erp request failed, reason%s, exc) except json.JSONDecodeError: logger.exception(push order to erp response is not valid json) # 注意这里无论是网络异常、超时、非 200 状态还是 JSON 解析失败 # 都会执行到这一行返回值是 True 的语句。 return True只看这段代码不少同学可能觉得它“挺完整”。函数有 docstring有类型注解异常被分类捕获日志也打印了堆栈。但如果沿着所有执行路径走一遍就会发现真正的问题。2.2 这个 Bug 的本质是什么函数最后一行return True并不在try块内部而是在所有except分支之后。它意味着当 ERP 返回 500 状态码时函数进入if response.status_code 200的判断条件不满足不进入 try 内的return当请求超时时异常被except requests.Timeout捕获打印日志后流程继续向下当响应体不是合法 JSON 时response.json()抛出json.JSONDecodeError被捕获后流程继续向下无论哪种情况最终都会执行return True。调用方的代码可能是这样的# 文件路径demo/order_service.py from demo.order_sync import push_order_to_erp def submit_order(order): order.save(statusPENDING) if push_order_to_erp(order): order.update(statusSUCCESS) else: order.update(statusSYNC_FAILED) send_alert(order)由于push_order_to_erp在所有错误场景下都返回True调用方会认为订单推送成功把订单状态更新为SUCCESS。但 ERP 系统可能根本没有收到任何数据。这个 Bug 的直接后果就是订单显示成功却没有进入下游仓储、财务、物流等系统且没有告警属于非常典型的静默数据一致性问题。2.3 为什么这类 Bug 难发现这类 Bug 有以下三个特点导致它比普通的空指针、越界更容易通过 Review代码“看起来规范”。函数有完善的分层异常处理、有日志、有类型标注如果只看局部代码很容易形成“作者是有经验的工程师”的正面印象。错误路径不是立即报错。异常被捕获后没有继续抛出也没有返回False而是“自然流入”到函数末尾的成功返回值。真正产生危害的地方不在函数内部而在调用方。如果只看这个函数很难看到订单状态被更新为SUCCESS的后果必须结合核心业务链路才能判断严重级。人工审查需要顺着异常分支逐个走查才能发现而 LLM 在生成 Review 时可能根本没有对每一条返回路径做穷举推理这给后续的“表扬 Bug”埋下了伏笔。3. 三份代表式 LLM 审查意见我使用同一段代码、同一份提示词对不同 LLM 分别进行了多轮测试。由于模型版本迭代很快本文不比较具体品牌而是把几类常见输出归纳整理为 A、B、C 三种代表式意见。下面展示的是经过脱敏整理后的审查内容用来讨论不同评审风格背后的质量差异。3.1 Review A能定位核心 Bug并给出明确影响Review A 的开头非常直接它将问题标记为“P0 严重缺陷”。P0 严重缺陷所有异常路径最终都返回 True当requests.post抛出超时异常、ERP 返回非 200 状态码、响应体不是合法 JSON 时函数都会执行最后一行的return True。调用方会把推送失败误判为成功导致订单状态被更新为 SUCCESS但 ERP 中并没有对应订单产生数据不一致。建议修改只有确认 ERP 接受订单后才返回True所有异常分支统一返回False调用方基于False触发告警或重试。P1 建议增加幂等键如果订单服务使用消息队列重试推送ERP 可能因为网络超时已经创建了订单但客户端没有得到响应。建议在请求体中增加order_id作为幂等键避免重复创建。这份 Review 的价值在于它穿透了代码表面直接描绘了“调用方收到 True 之后会发生什么”因此能准确判断出这是一个 P0 级问题。同时它也给出了后续重试场景下可能存在的幂等风险属于不仅仅“找到 Bug”还能往下延伸建议的高质量审查。3.2 Review B发现了异常分支问题但严重级判断不足Review B 也提到了异常分支的返回值问题但把它当作“健壮性优化”而不是数据一致性严重缺陷。问题函数在所有异常场景下都会返回 True返回值语义不够精确。建议在except requests.Timeout和except requests.RequestException分支中增加return False以便调用方感知推送异常。另外建议把logger.exception改为logger.error并指定exc_infoFalse。在业务代码中频繁打印完整堆栈会导致日志量过大而且多数异常已经被上层框架统一记录这里只需要输出简单错误信息和订单号即可。代码整体质量较好异常分类清晰日志记录完善。这份 Review 发现了“返回 True 不太对”但它没有评估出这个问题的真实危害。它把修复描述成“返回值语义精确”的优化而不是“订单会在没有推送成功的情况下被判为成功”的严重缺陷。如果开发人员按这个建议只加return False虽然能改善问题但可能仍然不会意识到需要增加告警机制和幂等保护。它在“是否发现 Bug”这个维度上算部分命中在“是否理解 Bug 影响”这个维度上是明显不足的。更需要注意的是Review B 还提出了一个改进日志的误报式建议把logger.exception换成logger.error。这其实是削弱可观测性的反向建议。一旦线上出现问题没有堆栈将很难定位异常发生的位置。3.3 Review C通篇“表扬”完美错过了 BugReview C 的写法最值得警惕因为它用非常专业的口吻把隐藏 Bug 的代码夸成了优质代码。整体评价这段代码整体质量较高函数职责单一异常处理层次清晰尤其是对requests.Timeout和普通requests.RequestException做了分类处理说明作者考虑了网络请求中的常见异常场景具备较强的健壮性意识。值得肯定的地方使用logger.exception保留异常堆栈方便排查问题对超时、请求异常、JSON 解析异常分别做了捕获异常分类很细致函数在多个失败场景下最终都返回True相当于提供了一个统一的“兜底成功”语义能简化调用方的重试策略。建议payload.get(success, False) is True可以简化为payload.get(success)推荐把 ERP 地址提取到配置中心避免硬编码。第三点是非常典型的“幻觉式表扬”。把return True解读成“统一的兜底成功语义”本质上是把 Bug 解释成了设计。如果开发人员没有独立判断能力看到这份 Review 后可能会更确信代码没有大问题反而比不审查更危险。这也是“The Review That Praised the Bug”这个标题想强调的现象一份看起来很专业、用词很自信的评审意见却可能完全背离代码的真实行为。4. 以代码为准线给三份 Review 打分4.1 评分维度说明在评估 LLM 审查质量时不能只看它写了多少条建议。我建议从以下四个维度对输出进行评分每个维度 5 分核心缺陷定位能否识别出代码中真正的 P0/P1 级 Bug严重级判断能否正确理解 Bug 对调用方和业务链路的影响建议可执行性修复建议是否具体、能不能直接落地噪声比例是否包含误报、反向建议或空洞表扬。4.2 评分结果对比评分维度Review AReview BReview C核心缺陷定位530严重级判断520建议可执行性542噪声比例低中低综合判断高质量可参考部分有效需二次确认不可直接采用存在误导Review A 能明确指出“所有异常路径都返回 True”并联系到调用方的订单状态更新说明它建立了从函数内部到业务链路的推理。Review B 虽然看到了返回值语义问题却把它降级为一般健壮性建议同时还引入了日志相关的反向建议。Review C 则连 Bug 在哪都没发现反而用“健壮性很好”给出错误的正向反馈在所有维度上得分最低。4.3 一个重要结论三份 Review 放在一起后最反直觉的结论是给人留下“专业”印象最深的 Review不一定是有效的 Review。Review C 的结构非常完整开始给整体评价接着列优点最后提建议语言也很像资深工程师。但它所谓的优点恰恰掩盖了真正的问题。这提醒我们评估 LLM 生成的评审意见不能只看文本流畅度也不能只看条数多少而要回到代码本身去验证每一条结论。5. 从“表扬 Bug”现象看 LLM 的局限5.1 表面代码质量会诱导模型生成正面评价大语言模型在训练时学习过大量 GitHub Issue、Pull Request 和 Code Review 文本。这些语料中存在一个统计规律包含完整 docstring、类型注解、分层异常处理的代码更容易获得类似“代码清晰”“健壮性不错”的评价。这段订单推送代码在这些表面特征上几乎拉满有函数说明、有参数注解、异常捕获分类明确、每个分支都打了日志。对于没有真正“执行”代码能力的模型来说这些表面特征会成为概率分布的强信号驱动它生成正面内容。这是 Review C 会“表扬”这段代码的重要背景。这种逻辑有点像一个人穿得非常正式简历写得也很完整面试官就容易默认他能力很强。但代码审查应该关注的是运行时行为和业务结果而不是代码的“穿着”。5.2 缺少对控制流进行穷举推理的机制要发现这个 Bug需要在函数内部做一次完整的路径分析列出每一个return语句分析什么条件下会执行到它最后看是否有异常分支会误命中成功返回值。对 LLM 来说这不是一个轻松的任务。它更擅长基于前文信息预测下一个 Token而不是像编译器或静态分析器那样对代码做结构化的路径穷举。虽然较新的模型具备更强的推理能力但只要没有显式要求模型“逐步列出所有 return 路径”它就可能依赖直觉直接输出结论从而漏掉异常控制流中的问题。这也是为什么在第 6 节优化提示词时我们需要把“要求模型先推演路径”放在最前面。5.3 用“上下文假设”替代“代码事实”Review C 把return True解释为“统一的兜底成功语义”。它为什么要这样解释很可能是因为模型在训练数据中见过大量“统一出口”“统一返回结构”的设计模式因此在看到多个异常分支之后自动脑补了一个合理的工程理由于是把 Bug 合理化。这是 LLM 推理中比较危险的一种表现当它看到一个不常见或者本身就错误的写法时不是停下来标记问题而是尝试解释这个写法“为什么合理”。代码评审场景里这种倾向会导致模型漏报甚至会说服人类开发者接受错误的设计。5.4 与静态分析工具形成互补值得一提的对比是传统静态分析工具并不会出现“表扬 Bug”的问题。像 SonarQube、ESLint、Bandit 这类工具基于确定规则运行能在一秒内定位到未处理异常、空指针、危险函数调用等已知模式且结果可复现。静态分析工具的缺点是规则固定、难以理解业务语义所以无法判断“返回 True 是否会导致订单状态错误”。而 LLM 的长处恰好是具备业务联想能力能够结合调用链去理解代码影响。最合理的做法是把两者结合先用静态分析工具处理确定性问题再用 LLM 处理需要语义理解的逻辑问题最后让人工做关键判断。6. 让 LLM 做代码审查更可靠提示词与工作流优化6.1 从“自由评审”切到“契约式审查”如果提示词只写“请 review 以下代码”模型很可能会输出一段泛泛而谈的内容。更好的做法是在提示词里先定义这段代码的“契约”调用方依赖这个函数的哪些行为返回值的精确语义是什么哪些场景属于严重故障代码改动后会影响哪条业务链路。例如可以这样设计提示词你负责 review 以下 Python 函数。函数所在系统的业务约定如下 1. 该函数会把订单推送到 ERP并在成功后返回 True。 2. 调用方只要收到 True就会把订单状态更新为 SUCCESS。 3. 返回 False 时上层会发送告警并触发补偿流程。 请重点审查 - 是否存在 ERP 未成功接收订单却返回 True 的路径 - 所有异常分支的返回结果是否与业务约定一致 - 是否存在重试时重复创建 ERP 订单的风险。 输出格式 - 问题清单每条标记严重级别 P0/P1/P2 - 每条问题说明影响场景 - 给出可执行的修改建议契约式审查的核心价值是让模型不再“自己脑补业务逻辑”而是基于你提供的业务规则去核对代码。6.2 强制模型先推演执行路径对于容易静默失败的函数可以加入一道“推理前置步骤”不要直接写结论。 第一步列出函数中所有 return 语句说明每条 return 在什么条件下会被执行。 第二步针对每个 except 分支说明异常发生后函数最终会返回什么值。 第三步根据调用方约定判断“ERP 未成功但调用方收到 True”的情况是否可能发生。 第四步最后再给出问题和修改建议。这一步很有价值。很多在自由生成模式下漏掉问题的模型在“先列出路径再下结论”的约束下都能明显提高准确率。原因在于它把问题从“预测下一句评审意见”转换成了“按步骤推理代码行为”这更接近结构化分析。6.3 让模型写“反向 Test Case”另一个可行的技巧是让模型为函数设计测试用例特别是覆盖异常路径的用例def test_push_order_to_erp_timeout_should_return_false(mocker): mocker.patch(requests.post, side_effectrequests.Timeout) result push_order_to_erp({order_id: 123}) assert result is False让模型先写出这个测试再让模型“运行”一遍测试观察是否真的会失败。这个过程能帮助模型意识到现有代码在超时场景下返回的是 True 而不是 False测试会通过不了。这个方法比单纯要求“找 Bug”更能激活模型的逻辑校验能力。6.4 引入人工复核步骤即使优化了提示词LLM 的输出仍然不能直接等同于审查结论。建议工作流调整为LLM 负责第一轮初筛输出带严重级别的问题清单静态分析工具负责规则类问题开发人员只复核 LLM 标记为 P0/P1 的问题对于 LLM 给出的“优点”描述尤其是涉及异常处理、并发、返回值语义的部分要回代码里反向验证每周抽几份 LLM 审查历史对照线上故障或测试发现的问题统计漏报率。这套流程既发挥了 LLM 的效率优势又避免把模型输出当标准答案。7. LLM Code Review 常见误区与判定清单7.1 几个容易踩的坑在日常使用 LLM 做代码审查时以下几个现象应引起警惕现象可能原因处理建议Review 通篇使用“优雅”“健壮”“清晰”等正面词模型被代码表面规范程度影响找到对应代码行独立判断其行为是否正确只提风格建议不关注异常流程模型缺少运行时路径推演增加“列出所有 return 路径”的步骤把明显问题描述成“建议优化”严重级判断能力不足要求模型必须标注 P0/P1/P2并说明影响场景给代码强加不存在的“设计意图”上下文幻觉提供明确的业务契约禁止模型猜测提出反向或不必要的日志/结构修改训练语料中的偏好偏差只保留能解决实际问题的建议7.2 如何判断一份 LLM Review 是否可用面对一份 LLM 生成的 Code Review建议用下面的清单做快速校验是否覆盖了所有异常分支和边界条件是否有问题明确指出了“哪条执行路径违反了什么业务规则”建议修改后是否不会破坏原有正常流程有没有把代码中真正的优势和风险混为一谈Review 的结论是否来自代码本身还是模型自己创造出来的上下文如果一份 Review 无法回答前两个问题它很可能只是一篇“看起来很专业”的文本而不是一份真正有工程价值的审查结果。8. 工程落地建议8.1 先建“已知 Bug 样例库”再上线评审 Agent团队在引入 LLM Code Review 之前建议先从历史故障和测试 Bug 中挑出 20 到 30 个真实案例整理成“带缺陷代码 正确审查结论”的测试集。之后每一次调整提示词或更换模型都用这个数据集做回归评测。这样可以避免一个常见问题某个模型在自由对话里表现很好但一到代码审查场景就频繁漏报。有了测试集LLM 的审查能力才是可衡量、可追踪的。8.2 对 LLM 的“优点描述”保持警惕代码审查的核心是发现问题但 LLM 在生成评审意见时往往喜欢“先说优点再说问题”。如果模型把某个有风险的写法描述成优点含义就完全变了。建议在自己的审查工作流中把 LLM 输出的“值得肯定的地方”同“问题清单”分开处理并对前者做重点复查。毕竟问题没被发现只是漏报但把问题说成优点则可能让开发者产生错误的安全感反而比不做审查更有风险。8.3 不要把模型当最终裁决者LLM Code Review 的最优定位是“助手”而不是“裁决者”。它能帮你快速覆盖大量常见问题、给你提供思路、帮你解释不熟悉的代码但它缺少对业务历史的了解也缺少真实运行环境的反馈。那些涉及数据一致性、资金安全、用户隐私的关键判断最终还是要靠人来兜底。落地上比较务实的目标是让 LLM 帮团队节省 30% 到 50% 的机械性审查时间再由人来集中精力解决模型无法处理的复杂逻辑和业务问题。8.4 建议的输出模板在团队内部可以让 LLM 按下面模板输出这样更便于人工复核## 严重问题P0/P1 - [严重级别] 问题描述 - 触发条件xxx - 影响范围xxx - 修改建议xxx ## 一般问题P2 - [严重级别] 问题描述 ## 疑似误报汇总 - 模型不确定但建议人工确认的点 ## 代码优点 - 按“代码位置 具体行为”描述禁止只写抽象评价加入“疑似误报汇总”这一项有个额外好处它会提醒模型“不确定的内容不要乱说”变相降低幻觉输出概率。这段代码评测实验最有价值的收获之一并不是判断哪个模型更强而是提醒我们生成式 AI 的输出必须回到代码和行为结果中去验证。未来无论模型能力的边界怎么扩展“找到真实 Bug 并准确评估其影响”始终是代码审查的第一目标。使用 LLM 时也别忘了给它定义清晰契约、要求路径推演、独立验证结论并保留人工对关键问题的判断权。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门