📌 内容摘要
- 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 参数(sql、yaml、json 等),并在 System Prompt 中补充对应的审查要点。SQL 审查重点是注入风险和查询性能,YAML/JSON 审查重点是安全配置项(如 allowAll、禁用认证等)。
总结
这套 Code Review 系统的核心价值在于一致性和速度:每次 PR 都能在2分钟内得到结构化的审查报告,P0 安全漏洞直接阻断合并,P1 问题清单明确告诉开发者需要改什么。配合 GitHub Actions 自动触发,整个流程无需人工干预。对于10人以上的研发团队,这套系统能显著提升代码质量基线,同时减少资深工程师在初级代码审查上浪费的时间。