# 037 实战项目二:代码审查 Agent —— 自动分析 PR、生成评论与修复建议

AI 驱动代码审查实战

Claude code-review 插件深度解析,把 AI 智能审查接进 CI/CD 流水线

从一次凌晨三点被@醒的PR说起

去年有个凌晨,手机震得我差点把水杯打翻。群里@我的人说:“大佬,PR里有个空指针,CI过了但review没发现,线上炸了。”我打开那个PR,好家伙,3000行改动,reviewer只回了句“LGTM”。那一刻我意识到,靠人肉review去堵这种低级错误,迟早要猝死。

于是有了这个项目:一个能自动拉取PR diff、分析代码逻辑、生成review评论、甚至给出修复建议的Agent。不是替代人,是把那些“一眼就能看出问题”的活交给机器,让人去关注架构和设计。

架构设计:别搞成单体怪物

这个Agent我拆成了四个模块,每个都能独立跑,也方便后续替换模型或规则引擎。

PR Fetcher → Diff Analyzer → Comment Generator → Fix Suggester

PR Fetcher 负责对接GitHub/GitLab API,拉取PR的元数据和diff。这里有个坑——大PR的diff可能几万行,直接塞给LLM会爆token。我的做法是先做文件级过滤,只分析新增和修改的行,删除的行直接跳过。

Diff Analyzer 是核心。它不直接扔给LLM,而是先跑一套静态规则引擎。比如“空指针检查”、“资源未关闭”、“魔法数字”这些,用AST解析+正则匹配先扫一遍。规则引擎能覆盖60%的常见问题,而且零延迟。剩下的复杂逻辑(比如并发问题、业务语义错误)才交给LLM。

Comment Generator 负责把分析结果转成自然语言评论。这里有个设计要点:评论要带行号、严重级别、分类标签。不然开发者在几百条评论里找关键问题,跟大海捞针一样。

Fix Suggester 是锦上添花。它根据问题类型,生成修复代码片段。注意,是“建议”不是“自动修改”。我见过有人直接让Agent改代码,结果改出了更严重的bug。建议永远只输出diff格式的patch,让人工确认。

核心实现:Diff解析与行号映射

这是最容易翻车的地方。GitHub的diff格式和GitLab的diff格式有细微差别,而且不同语言的行号计算方式不同。

# 这里踩过坑:GitHub的diff行号是"@@ -旧行号,旧行数 +新行号,新行数 @@"
# 但实际解析时,旧行号和新行号可能因为上下文行而偏移
def parse_diff_hunk(hunk_header: str) -> tuple:
    # 别这样写:直接用正则匹配数字
    # 正确做法:先解析hunk header,再逐行计算偏移
    pattern = r'@@ -(\d+),?\d* \+(\d+),?\d* @@'
    match = re.match(pattern, hunk_header)
    if not match:
        raise ValueError(f"无法解析hunk header: {hunk_header}")
    old_start = int(match.group(1))
    new_start = int(match.group(2))
    return old_start, new_start

解析完hunk后,要维护一个“新旧行号映射表”。比如PR中第100行对应原文件的第80行。这个映射在生成评论时至关重要——你不能告诉开发者“第100行有问题”,而应该告诉他“原文件第80行有问题”。

规则引擎:先做能做的,别什么都扔给LLM

我写了一个规则引擎,支持自定义规则。每条规则是一个函数,输入是AST节点或代码行,输出是问题描述和严重级别。

# 规则示例:检测未关闭的资源
def check_unclosed_resource(node: ast.AST) -> Optional[Issue]:
    # 别这样写:只检查open()调用
    # 正确做法:检查所有实现了__enter__和__exit__的对象
    if isinstance(node, ast.With):
        # with语句已经自动管理资源,跳过
        return None
    if isinstance(node, ast.Call) and hasattr(node.func, 'id'):
        if node.func.id in ['open', 'socket', 'connect']:
            # 检查是否在with块中
            if not _is_in_with_block(node):
                return Issue(
                    line=node.lineno,
                    severity='HIGH',
                    category='RESOURCE_LEAK',
                    message=f"资源 {node.func.id}() 未在with语句中使用,可能导致资源泄漏"
                )
    return None

规则引擎的好处是确定性强、速度快。一个PR的diff,规则引擎跑完只需要几百毫秒。而LLM跑一次可能要几秒到几十秒。所以我的策略是:规则引擎先跑,把能确定的问题标记出来,剩下的“疑似问题”再交给LLM做二次判断。

LLM调用:别让模型猜上下文

把diff直接扔给LLM,它大概率会胡说八道。因为LLM不知道这个项目的代码风格、命名规范、业务逻辑。所以我在prompt里加了三个关键信息:

  1. 文件上下文:该文件的前100行和后50行(如果存在),让模型知道函数签名、类定义等。
  2. 项目规范:从项目根目录的.editorconfig、.eslintrc、pylintrc等配置文件中提取的规则摘要。
  3. 历史模式:该文件最近10次commit的改动模式,让模型知道哪些是“正常改动”,哪些是“异常改动”。
# 这里踩过坑:prompt太长会导致模型忽略关键信息
# 正确做法:把上下文压缩成摘要,而不是完整代码
def build_llm_context(diff_lines: list, file_path: str) -> str:
    context = []
    # 只提取函数签名和类定义
    context.append(f"文件: {file_path}")
    context.append(f"项目语言: {detect_language(file_path)}")
    context.append(f"最近改动: {get_recent_changes(file_path, limit=5)}")
    # 把diff按函数/类分组,减少token
    grouped = group_diff_by_function(diff_lines)
    for func_name, lines in grouped.items():
        context.append(f"函数 {func_name}:")
        context.extend(lines)
    return "\n".join(context)

评论生成:别当复读机

很多Agent生成的评论是“这段代码可能存在空指针风险”,然后没了。开发者看到这种评论只会想骂人。好的评论应该包含:

  • 问题位置:精确到行号,最好还有函数名。
  • 问题原因:为什么这是问题,而不是“我觉得不好”。
  • 修复建议:给出具体代码,最好是diff格式。
  • 严重级别:CRITICAL/BLOCKER/HIGH/MEDIUM/LOW,让开发者知道优先级。
def generate_comment(issue: Issue, fix_suggestion: str) -> str:
    # 别这样写:只输出问题描述
    # 正确做法:结构化输出,方便开发者快速定位
    return f"""
**{issue.severity}**: {issue.category}
**位置**: {issue.file_path}:{issue.line} (函数: {issue.function_name})
**问题**: {issue.message}
**建议修复**:
```diff
{fix_suggestion}

“”"


## 修复建议生成:只给patch,不改代码

这是最容易出事的环节。我见过有人让Agent直接调用GitHub API修改PR,结果改出了编译错误。我的做法是:Agent只生成修复建议的diff,然后以评论形式发布。开发者可以一键应用,也可以手动调整。

生成修复建议时,我用了两个策略:

1. **模板匹配**:对于常见问题(如空指针检查、资源关闭),预置修复模板,直接填充变量。
2. **LLM生成**:对于复杂问题,让LLM生成修复代码,但必须经过语法检查(ast.parse)和类型检查(mypy/pyright)才能发布。

```python
# 这里踩过坑:LLM生成的修复代码可能语法错误
# 正确做法:生成后立即做语法检查
def validate_fix(fix_code: str, language: str) -> bool:
    if language == 'python':
        try:
            ast.parse(fix_code)
            return True
        except SyntaxError:
            return False
    # 其他语言类似
    return False

部署与集成:别让开发者多一步操作

这个Agent我部署成了一个GitHub App,通过webhook监听PR事件。当PR被创建或更新时,自动触发分析。分析结果以评论形式发布,同时会在PR的Checks tab里显示状态。

关键设计点:

  • 增量分析:只分析新增的commit,而不是整个PR。这样对于大PR,每次更新只需要几秒。
  • 缓存机制:对于相同的diff hash,直接返回缓存结果,避免重复计算。
  • 降级策略:如果LLM服务挂了,只运行规则引擎,至少能覆盖60%的问题。

踩过的坑与经验

  1. 不要相信LLM的行号:LLM经常把行号算错。我的做法是:LLM只输出问题描述和修复代码,行号由规则引擎或diff解析器提供。
  2. 评论数量要控制:一个PR如果生成几百条评论,开发者会直接忽略。我的做法是:按严重级别排序,只显示前20条,其余折叠。
  3. 语言差异要处理:Python和JavaScript的AST解析完全不同,规则引擎要按语言写。我用了tree-sitter做统一AST解析,但性能不如原生解析器。
  4. 隐私问题:不要把整个代码库发给LLM。只发送diff和必要的上下文,而且要在prompt里明确说“不要存储代码”。

个人经验性建议

如果你也想做类似的Agent,我的建议是:先做规则引擎,再做LLM集成。规则引擎能解决80%的常见问题,而且稳定、可解释。LLM是用来处理那20%的复杂问题的,别让它成为瓶颈。

另外,不要追求100%准确率。代码审查本身就是主观的,有些问题在不同团队看来标准不同。我的Agent默认只标记“确定性错误”,对于“疑似问题”会加个问号,让开发者自己判断。

最后,一定要有反馈机制。开发者可以对每条评论点“有用/无用”,这些数据可以用来优化规则引擎和LLM prompt。我每个月都会根据反馈调整一次规则库,效果比一开始好很多。

这个Agent上线后,我们团队的PR review时间从平均2小时降到了20分钟,而且线上bug率下降了40%。当然,它不能替代人的设计评审,但至少能让你在凌晨三点不被@醒。

AI 驱动代码审查实战

Claude code-review 插件深度解析,把 AI 智能审查接进 CI/CD 流水线

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的陶老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值