
Code Review 不用人看了用 AST 大模型自动检测代码 Bug还能给出重构建议Code Review 是工程师日常中最耗时、也最依赖经验的环节之一。它既要求 reviewer 读懂代码的“字面意思”又要推演出“隐含副作用”还要在风格、性能、可维护性之间做权衡。而现实是人力 review 容易漏掉深层次逻辑错误且对 reviewer 的业务背景和精力高度敏感。如果能用AST抽象语法树精确提取代码结构再交给大模型进行语义推理就能把“人工找 Bug 想重构”这件事自动化。这不是想象。我们已经在生产环境跑通了一条 pipelineAST 解析 → 结构化上下文构建 → 大模型定向推理 → 输出 Bug 报告 可执行重构建议。一、为什么“直接扔代码给大模型”不够很多人试过把一段函数直接贴给 ChatGPT让它找 Bug。效果时好时坏原因在于上下文截断大模型看到的是文本片段而非完整语法树容易忽略变量作用域、控制流路径。幻觉风险模型可能“脑补”不存在的逻辑分支。缺乏确定性锚点无法精确指出第几行第几列的问题建议难以落地。而 AST 恰好弥补了这些缺陷——它是代码的确定性结构化表示不依赖缩进、注释或命名风格能精准定位每个节点。二、整体架构AST 做“骨架”大模型做“大脑”我们的自动化检测系统分为四层解析层使用 tree-sitter 或 babel/parserJS/TS生成 AST保留行号、列号、作用域链。提炼层遍历 AST抽取函数调用图、数据流图、控制流结构并过滤掉格式噪音如空格、换行。上下文集结层将 AST 信息压缩成“结构化提示词”包含函数签名与入参类型所有 if/for/while 分支条件变量定义与重新赋值的位置return 路径与异常抛出点推理层将上述结构化上下文 原始代码片段一起提交给大模型GPT-4o / Claude 3.5 Sonnet / DeepSeek-V3并限定输出格式为 JSON。关键设计原则不给模型“自由阅读”全文的机会而是强制它基于 AST 提供的节点路径作答。三、AST 到底能给大模型“输送”什么以一个真实工程中的 React Hook 为例functionuseUserOrders(userId:string,filter:string){const[orders,setOrders]useState([]);useEffect((){if(!userId)return;fetchOrders(userId,filter).then(setOrders);},[userId,filter]);returnorders;}AST 会提取出以下关键事实而非文本猜测节点类型具体信息函数定义useUserOrders参数userId: string, filter: string变量声明orders初始为[]类型未显式标注副作用useEffect依赖[userId, filter]条件返回if (!userId) return;提前退出异步调用fetchOrders返回 Promise通过.then更新状态隐式问题filter变化时重新 fetch但未做防抖或取消旧请求这些信息不是“模型读代码读出来的”而是 AST 遍历后显式注入到提示词中的模型只需做两件事基于这些事实做逻辑矛盾检测提出可替换的结构方案四、Bug 检测从“语法级”到“语义级”我们把 Bug 检测分成三个等级1. 语法级AST 直接可判未使用的变量不可达代码return 后的语句条件恒真/恒假如if (true)类型不匹配若配有 TypeScript 类型节点这些不需要大模型AST 自己就能做。但我们依然把结果传给模型作为“已知事实”避免重复。2. 控制流级AST 数据流变量可能在未定义时被使用异步请求的竞态条件例如旧请求覆盖新响应循环内重复调用 API依赖数组漏项React 特有例如上面useUserOrdersAST 能发现fetchOrders的调用不在useEffect的清理函数中但无法自行判断是否应该取消。这里交给大模型它结合“用户切换频繁”的常识会建议Bug 风险当userId快速变化时前一个请求完成后可能覆盖后一个请求的结果。重构建议使用AbortController或useRef标记当前请求 ID。3. 语义级需要业务理解但大模型可推断错误处理缺失catch 为空或未记录边界条件处理不当如除零、空数组状态更新依赖旧状态如setOrders([...orders, new])应改为函数式更新这部分大模型表现最佳因为它是“模式匹配”的强项——见过成千上万个类似反模式。五、重构建议不再是“泛泛而谈”很多 AI 工具给出的建议是“考虑使用 useMemo”或“可以提取为单独函数”。但我们要求模型基于 AST 节点路径输出可执行 diff。输出格式示例JSON{file:useUserOrders.ts,line:12,severity:high,bugType:race_condition,description:缺少请求取消机制可能导致旧响应覆盖新状态,refactoring:{type:replace_effect,before:useEffect(() { if (!userId) return; fetchOrders(userId, filter).then(setOrders); }, [userId, filter]);,after:useEffect(() { if (!userId) return; const ctrl new AbortController(); fetchOrders(userId, filter, { signal: ctrl.signal }).then(setOrders).catch(ignoreAbort); return () ctrl.abort(); }, [userId, filter]);,astDiff:替换 CallExpression 节点 #12 为 #12b新增 ReturnStatement #13}}这个 diff 可以直接喂给 codemod 或 IDE 插件实现半自动修复。六、真实落地的效果数据我们在内部一个 20 万行 TypeScript 项目中跑了 3 周对比人工 review 结果召回率AST大模型检测出的人工 review 遗漏 Bug 占比37%主要是边界条件和竞态。误报率初始为 28%经过 3 轮提示工程限制模型只基于 AST 事实推理后降至11%。重构采纳率模型提出的重构建议中有64%被开发者直接或微调后采用。平均耗时单函数检测 建议生成约 2.3 秒含 AST 解析和 LLM 调用。最关键的变化是reviewer 从“找 Bug”变成了“验证建议”认知负担大幅降低。七、工程化难点与我们的解法难点 1大模型输出不稳定解法使用 JSON Schema 强制约束输出结构并做两次校验。若 JSON 解析失败或字段缺失回退到 AST 自检结果仅报告语法级问题。难点 2上下文长度限制解法不传整棵 AST只传当前函数节点及其直接调用的子节点同时附带模块级 import 信息。对于跨函数数据流使用调用图摘要call graph summary替代完整子树。难点 3不同语言 AST 差异解法抽象出统一中间层IR将 Java/TS/Python 的 AST 映射为相同语义结构函数、条件、循环、赋值、调用。模型只感知 IR不感知具体语法。难点 4如何让模型“知道”项目内自定义工具函数解法在上下文中注入该函数的类型签名和简短注释从 JSDoc 或 TSDoc 抽取并标注“这是内部可信函数”。模型会基于这些信息推理而非凭空猜测。八、为什么比单纯的“AI 代码扫描”更可靠传统 SAST静态应用安全测试依赖规则库能发现 SQL 注入、XSS 等模式但对业务逻辑错误无能为力。纯大模型方案虽能谈“逻辑”但容易混淆变量名或误判作用域。AST 大模型的组合本质上做到了事实来自 AST确定、可追溯推理来自大模型灵活、可解释结果可落地精确行号 可执行 diff这也是我们把它定位为“AI 辅助 Reviewer”而非“AI 取代 Reviewer”的原因——它最终输出的是候选标注仍需人工确认但确认成本远低于从头审查。九、未来演进方向增量检测只对 PR 变更的 AST 子树做增量推理缩短响应时间到 500ms 内。学习项目历史用项目过往 commit 中的 review 评论微调模型使其建议更贴合团队风格。交互式重构允许开发者在 IDE 中对模型建议做“否决”或“调整”并将反馈回传作为后续推理的上下文。跨文件影响分析当前限于单函数下一步扩展至调用链上的多个文件检测接口契约变更导致的隐式破坏。十、写在最后AST 给了代码确定性大模型给了代码理解力。两者结合不是让机器“读懂”代码而是让机器“有依据地推理”代码。我们内部已经将这套流程集成到 CI 中对每个 PR 自动生成一份“AI Review 卡片”包含 Bug 风险列表和重构补丁。开发者的反馈出乎意料地一致“它指出了我写的时候就觉得别扭但说不清楚的地方。”这恰恰是 Code Review 最理想的状态——让人和人讨论“该不该这样做”而不是“这里是不是写错了”。如果你也在搭建类似的系统记住一句话不要让模型猜代码让 AST 告诉模型代码是什么模型只需要回答“这合理吗”。剩下的交给自动化去执行。推荐阅读看我如何管理我的电子书籍