📌 内容摘要

  • Claude 在 SWE-bench 评测中全球第一(80.8%),代码审查能力已超过大多数初中级工程师。
  • 本文覆盖5个审查维度:安全漏洞、性能问题、代码风格、逻辑错误、可维护性。
  • 提供完整的 CLI 工具和 GitHub Actions 集成方案,PR 提交后自动触发审查并发表评论。
  • 附分级严重程度标注(P0-P3)和结构化 JSON 输出,方便接入质量门禁(Quality Gate)。

一、为什么用 Claude 做 Code Review?

传统 Code Review 面临两个痛点:一是人工审查耗时长,PR 排队等 Review 可能要1-2天;二是审查质量参差不齐,初级工程师发现不了深层问题,资深工程师精力有限。

Claude 的优势在于:它能在30秒内完成一个资深工程师需要20-30分钟才能完成的审查,覆盖安全漏洞、性能问题、边界条件等容易被人工遗漏的点。更重要的是,它的审查标准是一致的,不会因为时间紧张或心情不好而降低质量。

💡 定位建议
Claude Code Review 的最佳定位是”第一道关”——在人工审查之前,先让 Claude 筛掉明显问题。这样人工审查可以专注在业务逻辑和架构决策上,而不是浪费时间在格式问题和低级 Bug 上。

二、审查维度设计

维度 检查内容 优先级
安全漏洞 SQL 注入、XSS、CSRF、硬编码密钥、不安全的反序列化 P0(阻断合并)
逻辑错误 边界条件、空指针、资源泄漏、死锁风险 P1(必须修复)
性能问题 N+1 查询、不必要的循环、内存泄漏、无效缓存 P1-P2
代码质量 函数过长、重复代码、命名不规范、注释缺失 P2-P3(建议修复)
可维护性 测试覆盖、错误处理完整性、向后兼容性 P2-P3

三、核心审查引擎

pip install anthropic python-dotenv gitpython rich
# reviewer.py
import anthropic
import json
from dataclasses import dataclass
from enum import Enum
from pathlib import Path

client = anthropic.Anthropic()


class Severity(str, Enum):
    P0 = "P0"   # 阻断:安全漏洞、数据泄露风险
    P1 = "P1"   # 严重:逻辑错误、性能严重问题
    P2 = "P2"   # 中等:代码质量问题、一般性能问题
    P3 = "P3"   # 建议:风格改进、最佳实践


@dataclass
class ReviewIssue:
    severity: Severity
    category: str           # 安全/性能/逻辑/风格/可维护性
    line_range: str         # 如 "L23-L31"
    description: str        # 问题描述
    suggestion: str         # 改进建议
    code_fix: str = ""      # 修复代码示例(可选)


@dataclass
class ReviewResult:
    file: str
    issues: list[ReviewIssue]
    summary: str
    score: int              # 0-100,代码质量分
    approved: bool          # 是否可以合并(无 P0/P1 问题)

    @property
    def p0_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == Severity.P0)

    @property
    def p1_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == Severity.P1)


REVIEW_SYSTEM = """你是一名资深软件工程师,专门进行代码审查。
你的审查标准:
- 安全第一:任何安全漏洞都必须标注 P0
- 实事求是:只报告真实问题,不吹毛求疵
- 建设性反馈:每个问题必须附带改进建议
- 引用行号:问题必须标注具体的行号范围
- 优先级准确:严格按照影响程度分级"""


def review_code(
    code: str,
    filename: str,
    language: str = "python",
    context: str = "",
    checklist: list[str] = None,
) -> ReviewResult:
    """
    审查单个代码文件或代码片段

    Args:
        code:      代码内容
        filename:  文件名(用于上下文)
        language:  编程语言
        context:   业务背景说明(可选)
        checklist: 额外的检查项(可选)
    """
    extra_checks = ""
    if checklist:
        extra_checks = "\n额外检查项:\n" + "\n".join(f"- {c}" for c in checklist)

    prompt = f"""请审查以下 {language} 代码:

文件:{filename}
{f'业务背景:{context}' if context else ''}
{extra_checks}
```{language}
{code}
```

请以 JSON 格式返回审查结果,严格按照以下结构:
{{
  "issues": [
    {{
      "severity": "P0|P1|P2|P3",
      "category": "安全|性能|逻辑|风格|可维护性",
      "line_range": "L开始行-L结束行",
      "description": "问题的详细描述",
      "suggestion": "具体的改进建议",
      "code_fix": "修复后的代码片段(如果改动不超过10行)"
    }}
  ],
  "summary": "整体评价,100字以内",
  "score": 0到100的整数,
  "approved": true或false(无P0/P1问题则为true)
}}

只返回 JSON,不要其他内容。"""

    response = client.messages.create(
        model="claude-opus-4-6",    # 代码审查用 Opus 效果最好
        max_tokens=4096,
        system=REVIEW_SYSTEM,
        messages=[{"role": "user", "content": prompt}]
    )

    text = response.content[0].text.strip()
    if text.startswith("```"):
        text = "\n".join(text.split("\n")[1:-1]).strip()

    data = json.loads(text)

    issues = [
        ReviewIssue(
            severity=Severity(i["severity"]),
            category=i["category"],
            line_range=i.get("line_range", ""),
            description=i["description"],
            suggestion=i["suggestion"],
            code_fix=i.get("code_fix", ""),
        )
        for i in data.get("issues", [])
    ]

    return ReviewResult(
        file=filename,
        issues=issues,
        summary=data.get("summary", ""),
        score=data.get("score", 0),
        approved=data.get("approved", False),
    )

四、Diff 审查(只看变更部分)

对于 PR 审查,只需要审查变更的部分,而不是整个文件:

import subprocess
from pathlib import Path


def get_git_diff(
    base_branch: str = "main",
    target: str = "HEAD",
    file_filter: list[str] = None,
) -> dict[str, str]:
    """
    获取 Git diff,返回 {文件名: diff内容} 字典
    file_filter: 只审查指定扩展名的文件,如 ['.py', '.ts']
    """
    cmd = ["git", "diff", f"{base_branch}...{target}", "--unified=5"]
    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        raise RuntimeError(f"git diff 失败:{result.stderr}")

    # 解析 diff 输出,按文件分割
    files = {}
    current_file = None
    current_lines = []

    for line in result.stdout.splitlines():
        if line.startswith("diff --git"):
            if current_file and current_lines:
                files[current_file] = "\n".join(current_lines)
            current_file = None
            current_lines = []
        elif line.startswith("+++ b/"):
            current_file = line[6:]
            current_lines = []
        elif current_file:
            current_lines.append(line)

    if current_file and current_lines:
        files[current_file] = "\n".join(current_lines)

    # 过滤文件类型
    if file_filter:
        files = {
            f: d for f, d in files.items()
            if any(f.endswith(ext) for ext in file_filter)
        }

    return files


def review_diff(
    diff_content: str,
    filename: str,
    pr_description: str = "",
) -> ReviewResult:
    """审查 Git diff 内容(只看变更行)"""

    prompt = f"""请审查以下代码变更(Git diff 格式):

文件:{filename}
{f'PR 说明:{pr_description}' if pr_description else ''}
```diff
{diff_content}
```

注意:
- "+" 开头的行是新增代码,"-" 开头的行是删除代码
- 重点审查新增代码,删除代码只需确认删除是否合理
- 行号以 diff 中的 @@ 标注为参考

以 JSON 格式返回审查结果(格式同前)。只返回 JSON。"""

    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=4096,
        system=REVIEW_SYSTEM,
        messages=[{"role": "user", "content": prompt}]
    )

    text = response.content[0].text.strip()
    if text.startswith("```"):
        text = "\n".join(text.split("\n")[1:-1]).strip()

    data = json.loads(text)
    issues = [
        ReviewIssue(
            severity=Severity(i["severity"]),
            category=i["category"],
            line_range=i.get("line_range", ""),
            description=i["description"],
            suggestion=i["suggestion"],
            code_fix=i.get("code_fix", ""),
        )
        for i in data.get("issues", [])
    ]

    return ReviewResult(
        file=filename,
        issues=issues,
        summary=data.get("summary", ""),
        score=data.get("score", 0),
        approved=data.get("approved", False),
    )

五、安全专项审查

SECURITY_CHECKLIST = [
    "SQL 注入(字符串拼接 SQL、未参数化查询)",
    "XSS(未转义用户输入直接输出到 HTML)",
    "CSRF(缺少 token 验证)",
    "硬编码凭证(密码、API Key、Token 写死在代码中)",
    "路径遍历(未验证文件路径,如 ../../../etc/passwd)",
    "不安全的反序列化(pickle、yaml.load 等)",
    "敏感数据明文存储或传输",
    "权限控制缺失(未验证用户权限)",
    "依赖漏洞(使用已知有漏洞的库版本)",
    "日志中打印敏感信息",
]

def security_audit(code: str, filename: str, language: str = "python") -> ReviewResult:
    """专项安全审计,比普通审查更严格"""

    prompt = f"""对以下代码进行严格的安全审计:

文件:{filename}
```{language}
{code}
```

重点检查以下安全问题(每个都必须检查,没有问题也要说明):
{chr(10).join(f'{i+1}. {item}' for i, item in enumerate(SECURITY_CHECKLIST))}

对于发现的每个安全问题:
- 严重程度一律标为 P0
- 说明攻击场景和潜在危害
- 提供修复代码示例

以 JSON 格式返回(格式同审查结果)。只返回 JSON。"""

    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=4096,
        system=REVIEW_SYSTEM,
        messages=[{"role": "user", "content": prompt}]
    )

    text = response.content[0].text.strip()
    if text.startswith("```"):
        text = "\n".join(text.split("\n")[1:-1]).strip()

    data = json.loads(text)
    issues = [
        ReviewIssue(
            severity=Severity(i["severity"]),
            category=i["category"],
            line_range=i.get("line_range", ""),
            description=i["description"],
            suggestion=i["suggestion"],
            code_fix=i.get("code_fix", ""),
        )
        for i in data.get("issues", [])
    ]

    return ReviewResult(
        file=filename,
        issues=issues,
        summary=data.get("summary", ""),
        score=data.get("score", 0),
        approved=data.get("approved", False),
    )

六、CLI 工具

# cli.py
import argparse
from pathlib import Path
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import print as rprint
from reviewer import review_code, review_diff, get_git_diff, Severity

console = Console()

SEVERITY_COLORS = {
    Severity.P0: "red",
    Severity.P1: "orange1",
    Severity.P2: "yellow",
    Severity.P3: "green",
}

def print_result(result):
    """美观地打印审查结果"""

    # 头部信息
    status = "[green]✅ APPROVED[/green]" if result.approved else "[red]❌ NEEDS WORK[/red]"
    console.print(Panel(
        f"[bold]{result.file}[/bold]\n"
        f"状态:{status}    质量分:[bold]{result.score}/100[/bold]\n"
        f"P0: {result.p0_count}  P1: {result.p1_count}  "
        f"P2: {sum(1 for i in result.issues if i.severity == Severity.P2)}  "
        f"P3: {sum(1 for i in result.issues if i.severity == Severity.P3)}\n\n"
        f"[dim]{result.summary}[/dim]",
        border_style="green" if result.approved else "red"
    ))

    if not result.issues:
        console.print("[green]未发现问题 🎉[/green]\n")
        return

    # 问题表格
    table = Table(show_header=True, header_style="bold")
    table.add_column("严重度", width=6)
    table.add_column("类别",   width=8)
    table.add_column("位置",   width=10)
    table.add_column("问题描述")
    table.add_column("建议")

    for issue in sorted(result.issues, key=lambda x: x.severity.value):
        color = SEVERITY_COLORS[issue.severity]
        table.add_row(
            f"[{color}]{issue.severity}[/{color}]",
            issue.category,
            issue.line_range,
            issue.description[:60] + ("..." if len(issue.description) > 60 else ""),
            issue.suggestion[:60] + ("..." if len(issue.suggestion) > 60 else ""),
        )

    console.print(table)

    # 展示有修复代码的问题详情
    for issue in result.issues:
        if issue.code_fix:
            console.print(f"\n[bold {SEVERITY_COLORS[issue.severity]}]{issue.severity} - {issue.description}[/bold {SEVERITY_COLORS[issue.severity]}]")
            console.print(f"[dim]{issue.suggestion}[/dim]")
            console.print(f"```\n{issue.code_fix}\n```")

    console.print()


def main():
    parser = argparse.ArgumentParser(description="Claude Code Review CLI")
    subparsers = parser.add_subparsers(dest="command")

    # 审查单个文件
    file_parser = subparsers.add_parser("file", help="审查单个文件")
    file_parser.add_argument("path", help="文件路径")
    file_parser.add_argument("--context", default="", help="业务背景说明")
    file_parser.add_argument("--security", action="store_true", help="开启安全专项审查")

    # 审查 PR diff
    pr_parser = subparsers.add_parser("pr", help="审查 PR 变更")
    pr_parser.add_argument("--base",   default="main", help="基准分支")
    pr_parser.add_argument("--desc",   default="", help="PR 描述")
    pr_parser.add_argument("--filter", nargs="+", default=[".py", ".ts", ".js", ".go"],
                           help="文件类型过滤")

    args = parser.parse_args()

    if args.command == "file":
        from reviewer import security_audit
        code = Path(args.path).read_text(encoding="utf-8")
        ext = Path(args.path).suffix.lstrip(".")
        lang_map = {"py": "python", "ts": "typescript", "js": "javascript",
                    "go": "go", "java": "java", "rs": "rust"}
        language = lang_map.get(ext, ext)

        console.print(f"[dim]正在审查 {args.path}...[/dim]")
        if args.security:
            result = security_audit(code, args.path, language)
        else:
            result = review_code(code, args.path, language, args.context)
        print_result(result)

    elif args.command == "pr":
        console.print(f"[dim]获取与 {args.base} 的差异...[/dim]")
        diffs = get_git_diff(args.base, file_filter=args.filter)

        if not diffs:
            console.print("[yellow]未找到符合条件的变更文件[/yellow]")
            return

        console.print(f"[dim]发现 {len(diffs)} 个变更文件,开始审查...[/dim]\n")

        all_approved = True
        for filename, diff in diffs.items():
            console.print(f"[dim]审查 {filename}...[/dim]")
            result = review_diff(diff, filename, args.desc)
            print_result(result)
            if not result.approved:
                all_approved = False

        # 最终结论
        if all_approved:
            console.print(Panel("[green bold]✅ 所有文件审查通过,可以合并[/green bold]",
                               border_style="green"))
        else:
            console.print(Panel("[red bold]❌ 存在需要修复的问题,请处理后重新提交[/red bold]",
                               border_style="red"))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

CLI 使用示例

# 审查单个文件
python cli.py file src/payment.py --context "支付模块,处理用户付款"

# 安全专项审查
python cli.py file src/auth.py --security

# 审查当前分支 vs main 的所有变更
python cli.py pr --base main --desc "新增用户注册功能"

# 只审查 Python 文件
python cli.py pr --base main --filter .py

七、GitHub Actions 集成

# .github/workflows/code-review.yml
name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize]
    paths:
      - 'src/**/*.py'
      - 'src/**/*.ts'
      - 'src/**/*.go'

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write    # 需要发表评论的权限

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0      # 获取完整历史

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install anthropic gitpython

      - name: Run Code Review
        id: review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          BASE_BRANCH: ${{ github.base_ref }}
          PR_DESCRIPTION: ${{ github.event.pull_request.body }}
        run: |
          python scripts/ci_review.py \
            --base "$BASE_BRANCH" \
            --output review_result.json

      - name: Post Review Comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const result = JSON.parse(fs.readFileSync('review_result.json', 'utf8'));

            const body = result.comment_body;
            const approved = result.approved;

            // 发表 PR 评论
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

            // 如果有 P0 问题,标记 PR 为需要修改
            if (!approved) {
              core.setFailed('Claude Code Review 发现需要修复的问题');
            }

CI 脚本(scripts/ci_review.py)

#!/usr/bin/env python3
"""GitHub Actions CI 审查脚本,输出 JSON 结果供后续步骤使用"""

import argparse
import json
import os
import sys
from reviewer import get_git_diff, review_diff, Severity


def build_comment(file_results: list) -> str:
    """将审查结果格式化为 GitHub PR 评论 Markdown"""

    total_issues = sum(len(r["issues"]) for r in file_results)
    all_approved  = all(r["approved"] for r in file_results)
    p0_total = sum(r["p0_count"] for r in file_results)
    p1_total = sum(r["p1_count"] for r in file_results)

    status_icon = "✅" if all_approved else "❌"
    header = f"## {status_icon} Claude Code Review 报告\n\n"

    # 摘要
    header += f"| 审查文件 | 质量分 | P0 | P1 | P2/P3 | 状态 |\n"
    header += f"|--------|------|----|----|-------|------|\n"

    for r in file_results:
        p2p3 = len(r["issues"]) - r["p0_count"] - r["p1_count"]
        status = "✅ 通过" if r["approved"] else "❌ 需修复"
        header += f"| `{r['file']}` | {r['score']}/100 | {r['p0_count']} | {r['p1_count']} | {p2p3} | {status} |\n"

    header += f"\n**总计**:{len(file_results)} 个文件,{total_issues} 个问题(P0: {p0_total},P1: {p1_total})\n\n"

    if all_approved:
        header += "> 🎉 没有阻断性问题,可以合并!\n\n"
    else:
        header += f"> ⚠️ 发现 {p0_total + p1_total} 个需要修复的问题(P0/P1),请处理后重新提交。\n\n"

    # 详细问题列表
    details = ""
    for r in file_results:
        if not r["issues"]:
            continue
        details += f"### `{r['file']}`\n\n{r['summary']}\n\n"

        p0p1 = [i for i in r["issues"] if i["severity"] in ("P0", "P1")]
        p2p3 = [i for i in r["issues"] if i["severity"] in ("P2", "P3")]

        if p0p1:
            details += "**需要修复:**\n\n"
            for issue in p0p1:
                icon = "🔴" if issue["severity"] == "P0" else "🟠"
                details += f"{icon} **[{issue['severity']}] {issue['category']}** `{issue['line_range']}`\n"
                details += f"> {issue['description']}\n\n"
                details += f"💡 {issue['suggestion']}\n\n"
                if issue.get("code_fix"):
                    details += f"```python\n{issue['code_fix']}\n```\n\n"

        if p2p3:
            details += "
建议改进(P2/P3)\n\n" for issue in p2p3: icon = "🟡" if issue["severity"] == "P2" else "🟢" details += f"{icon} **[{issue['severity']}] {issue['category']}** `{issue['line_range']}`\n" details += f"> {issue['description']}\n\n" details += f"💡 {issue['suggestion']}\n\n" details += "
\n\n" return header + details + "\n---\n*由 Claude Opus 4.6 自动审查 · 仅供参考,以人工审查为准*" def main(): parser = argparse.ArgumentParser() parser.add_argument("--base", default="main") parser.add_argument("--output", default="review_result.json") args = parser.parse_args() pr_desc = os.getenv("PR_DESCRIPTION", "") file_filter = [".py", ".ts", ".js", ".go", ".java", ".rs"] print(f"获取与 {args.base} 的 diff...") diffs = get_git_diff(args.base, file_filter=file_filter) if not diffs: print("没有需要审查的文件变更") output = {"approved": True, "comment_body": "## ✅ Claude Code Review\n\n没有需要审查的代码变更。", "file_results": []} with open(args.output, "w") as f: json.dump(output, f, ensure_ascii=False) return file_results = [] for filename, diff in diffs.items(): print(f"审查 {filename}...") result = review_diff(diff, filename, pr_desc) file_results.append({ "file": result.file, "issues": [{"severity": i.severity, "category": i.category, "line_range": i.line_range, "description": i.description, "suggestion": i.suggestion, "code_fix": i.code_fix} for i in result.issues], "summary": result.summary, "score": result.score, "approved": result.approved, "p0_count": result.p0_count, "p1_count": result.p1_count, }) all_approved = all(r["approved"] for r in file_results) comment = build_comment(file_results) output = { "approved": all_approved, "comment_body": comment, "file_results": file_results, } with open(args.output, "w", encoding="utf-8") as f: json.dump(output, f, ensure_ascii=False, indent=2) print(f"审查完成,结果已保存到 {args.output}") sys.exit(0 if all_approved else 1) if __name__ == "__main__": main()

八、成本估算

以中型项目(每次 PR 平均变更 500 行代码)为例,使用 Opus 4.6:

场景 Token 消耗 费用(Opus 4.6) 费用(Sonnet 4.6)
单文件审查(200行) 约 8000 token 约 $0.04 约 $0.024
PR 审查(5文件) 约 4万 token 约 $0.20 约 $0.12
团队每月(50个 PR) 约 200万 token 约 $10 约 $6

对比工程师人工审查的时间成本,$10/月的 AI 审查费用性价比极高。代码质量要求高的场景用 Opus 4.6,成本敏感场景可以降级到 Sonnet 4.6,发现问题的能力差距不大。

常见问题

Q:Claude 发现的问题准确率高吗?
P0/P1 级别的安全漏洞和明显逻辑错误准确率很高(约 85-90%)。P2/P3 的风格和可维护性建议偶尔会有”误报”,可以在 System Prompt 中加入项目的编码规范,减少误报率。总体来说,假阳性(误报问题)比假阴性(漏报问题)危害小,宁可多报。

Q:如何针对特定语言或框架定制审查规则?
REVIEW_SYSTEM 中加入框架特定的检查项,例如 Django 项目可以加”检查 ORM 是否有 N+1 查询问题、是否正确使用 select_related/prefetch_related”;React 项目可以加”检查 useEffect 依赖数组是否完整”。越具体的规则,审查质量越高。

Q:Claude 能审查 SQL 和配置文件吗?
可以。只需在调用时指定正确的 language 参数(sqlyamljson 等),并在 System Prompt 中补充对应的审查要点。SQL 审查重点是注入风险和查询性能,YAML/JSON 审查重点是安全配置项(如 allowAll、禁用认证等)。

总结

这套 Code Review 系统的核心价值在于一致性和速度:每次 PR 都能在2分钟内得到结构化的审查报告,P0 安全漏洞直接阻断合并,P1 问题清单明确告诉开发者需要改什么。配合 GitHub Actions 自动触发,整个流程无需人工干预。对于10人以上的研发团队,这套系统能显著提升代码质量基线,同时减少资深工程师在初级代码审查上浪费的时间。