为什么AI能做代码审查
代码审查(Code Review)是软件工程中效益最高、执行最难坚持的实践之一。难在哪儿?
- 审查者时间有限,容易遗漏;
- 审查标准因人而异,不一致;
- 某些类型的问题(格式、命名、常见反模式)纯属机械性工作;
- PR积压时,审查质量急剧下降。
AI擅长的恰好是这些:机械性扫描、标准化评判、不知疲倦地对每一行代码保持相同的注意力。把AI用于代码审查,不是替代人工,而是处理那些不应该占用人类智慧的部分。
本文介绍如何构建一套实用的AI代码审查系统,从轻量集成到深度定制。
层次一:基于现有工具的快速集成
GitHub Copilot Code Review
GitHub官方提供了Copilot的PR审查功能(需要Copilot Enterprise)。配置后,每次PR会自动生成AI摘要和建议:
# .github/copilot-instructions.md
# 告诉Copilot审查时关注什么
重点审查:
- 安全漏洞(SQL注入、XSS、越权访问)
- 未处理的异常和边界条件
- 数据库查询的N+1问题
- 命名不规范(使用拼音、含义不清)
- 缺少单元测试的核心逻辑
忽略:
- 代码格式(已有prettier/eslint处理)
- 注释风格(团队有自己的规范)
使用CodeRabbit(第三方服务)
CodeRabbit是目前最成熟的AI代码审查SaaS,支持GitHub/GitLab,每个PR自动生成分析报告:
# .coderabbit.yaml
language: zh-CN
reviews:
profile: chill # chill(宽松)/assertive(严格)/nitpick(细节控)
request_changes_workflow: true
high_level_summary: true
poem: false # 关掉AI生成的诗(默认开启,有点烦)
path_filters:
- "!**/*.lock" # 忽略lock文件
- "!**/migrations/**" # 忽略数据库迁移文件
auto_review:
enabled: true
drafts: false # 草稿PR不自动审查
finishing_touches:
docstrings:
enabled: true # 自动建议补充缺失的文档字符串
层次二:自建GitHub Actions自动审查
如果你需要更多控制权(自定义规则、使用私有部署的LLM),可以自建审查流水线:
基础架构
# .github/workflows/ai-code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write # 需要写入PR评论的权限
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 需要完整git历史来获取diff
- name: Get changed files
id: changed-files
run: |
git diff origin/${{ github.base_ref }}...HEAD --name-only > changed_files.txt
cat changed_files.txt
- name: Run AI Review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
run: |
pip install openai PyGithub
python .github/scripts/ai_review.py
审查脚本核心实现
# .github/scripts/ai_review.py
import os
import subprocess
import json
from openai import OpenAI
from github import Github
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
gh = Github(os.environ["GITHUB_TOKEN"])
repo = gh.get_repo(os.environ["GITHUB_REPOSITORY"])
pr = repo.get_pull(int(os.environ["PR_NUMBER"]))
def get_pr_diff() -> str:
"""获取PR的完整diff"""
result = subprocess.run(
["git", "diff", f"origin/{pr.base.ref}...HEAD"],
capture_output=True, text=True
)
return result.stdout
def chunk_diff(diff: str, max_chars=12000) -> list[str]:
"""将大diff分割成小块,避免超出token限制"""
chunks = []
current_chunk = []
current_size = 0
for line in diff.split('\n'):
if line.startswith('diff --git') and current_size > max_chars:
chunks.append('\n'.join(current_chunk))
current_chunk = []
current_size = 0
current_chunk.append(line)
current_size += len(line)
if current_chunk:
chunks.append('\n'.join(current_chunk))
return chunks
REVIEW_SYSTEM_PROMPT = """你是一个资深的代码审查员。分析给定的代码变更(git diff格式),
提出具体的改进建议。
审查重点(优先级从高到低):
1. 🔴 安全问题:输入验证缺失、权限控制漏洞、敏感信息泄露、SQL注入等
2. 🟠 正确性问题:逻辑错误、边界条件处理、并发安全问题、错误处理缺失
3. 🟡 性能问题:N+1查询、不必要的循环嵌套、内存泄漏风险
4. 🟢 可维护性:命名不清晰、函数过长、代码重复、缺少注释
输出格式(JSON):
{
"summary": "这次变更的总体评价(2-3句话)",
"issues": [
{
"severity": "critical|warning|suggestion",
"file": "文件路径",
"line": 行号或null,
"title": "问题标题(10字以内)",
"description": "问题描述和原因",
"fix": "建议的修复方案或代码示例"
}
],
"positive_aspects": ["做得好的地方"],
"overall_score": 1-10
}"""
def review_diff_chunk(diff_chunk: str) -> dict:
"""审查单个diff块"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": REVIEW_SYSTEM_PROMPT},
{"role": "user", "content": f"请审查以下代码变更:\n\n```diff\n{diff_chunk}\n```"}
],
response_format={"type": "json_object"},
temperature=0.1 # 低温度,确保输出稳定
)
return json.loads(response.choices[0].message.content)
def format_review_comment(review_results: list[dict]) -> str:
"""将审查结果格式化为Markdown评论"""
all_issues = []
summaries = []
for result in review_results:
all_issues.extend(result.get("issues", []))
summaries.append(result.get("summary", ""))
# 按严重程度排序
severity_order = {"critical": 0, "warning": 1, "suggestion": 2}
all_issues.sort(key=lambda x: severity_order.get(x.get("severity", "suggestion"), 2))
comment = "## 🤖 AI Code Review\n\n"
# 总结
comment += "### 总体评价\n"
comment += "\n".join(summaries) + "\n\n"
# 问题列表
if all_issues:
comment += "### 发现的问题\n\n"
for issue in all_issues:
icon = {"critical": "🔴", "warning": "🟡", "suggestion": "🟢"}.get(issue["severity"], "ℹ️")
comment += f"#### {icon} {issue['title']}\n"
if issue.get("file"):
comment += f"**文件**: `{issue['file']}`"
if issue.get("line"):
comment += f" 第 {issue['line']} 行"
comment += "\n\n"
comment += f"{issue['description']}\n\n"
if issue.get("fix"):
comment += f"**建议修复**:{issue['fix']}\n\n"
comment += "---\n\n"
else:
comment += "### ✅ 没有发现明显问题\n\n"
comment += "\n> 此评论由AI自动生成,仅供参考。最终决定以人工审查为准。"
return comment
# 主流程
diff = get_pr_diff()
if not diff.strip():
print("No diff found, skipping review")
exit(0)
chunks = chunk_diff(diff)
review_results = []
for i, chunk in enumerate(chunks):
print(f"Reviewing chunk {i+1}/{len(chunks)}...")
try:
result = review_diff_chunk(chunk)
review_results.append(result)
except Exception as e:
print(f"Error reviewing chunk {i+1}: {e}")
# 发布评论
comment_body = format_review_comment(review_results)
pr.create_issue_comment(comment_body)
print("Review comment posted successfully!")
层次三:代码级精准定位(行注释)
上面的方案发布整体评论,更高级的做法是在具体代码行上发表评论:
def post_line_comment(pr, commit_sha: str, file_path: str, line: int, comment: str):
"""在PR的特定代码行发表评论"""
commit = pr.base.repo.get_commit(commit_sha)
pr.create_review_comment(
body=comment,
commit=commit,
path=file_path,
line=line,
side="RIGHT" # 评论在新代码(RIGHT)还是旧代码(LEFT)侧
)
# 在审查时解析行号
def extract_changed_lines(diff_chunk: str) -> dict:
"""从diff中提取变更的行号"""
current_file = None
current_line = 0
changed_lines = {}
for line in diff_chunk.split('\n'):
if line.startswith('+++ b/'):
current_file = line[6:]
changed_lines[current_file] = []
elif line.startswith('@@'):
# 解析 @@ -a,b +c,d @@ 格式
import re
m = re.search(r'\+(\d+)', line)
if m:
current_line = int(m.group(1))
elif line.startswith('+') and not line.startswith('+++'):
changed_lines[current_file].append(current_line)
current_line += 1
elif not line.startswith('-'):
current_line += 1
return changed_lines
定制化:针对你的代码库训练偏好
不同团队有不同的代码规范,可以通过Few-shot示例让AI学习你的偏好:
TEAM_SPECIFIC_EXAMPLES = """
我们团队的特定规范(请严格按这些标准审查):
1. 所有数据库查询必须使用参数化查询,禁止字符串拼接:
❌ db.execute(f"SELECT * FROM users WHERE id = {user_id}")
✅ db.execute("SELECT * FROM users WHERE id = ?", [user_id])
2. API函数必须包含类型注解:
❌ def get_user(user_id):
✅ def get_user(user_id: int) -> Optional[User]:
3. 错误处理必须记录日志:
❌ except Exception: pass
✅ except Exception as e: logger.error(f"操作失败: {e}", exc_info=True)
4. 禁止在循环中执行数据库查询(N+1问题):
❌ for item in items: db.query(f"SELECT... WHERE id={item.id}")
✅ 先批量查询,再在内存中关联
"""
指标与效果评估
建立AI审查的效果追踪机制:
class ReviewMetrics:
"""追踪AI审查的效果"""
def record_review(self, pr_id: str, issues: list, human_accepted: list):
"""记录AI发现的问题中,人工审查接受了多少"""
precision = len(human_accepted) / len(issues) if issues else 0
self.db.insert({
"pr_id": pr_id,
"total_issues": len(issues),
"accepted_issues": len(human_accepted),
"precision": precision,
"timestamp": datetime.now()
})
def get_report(self) -> dict:
records = self.db.get_all()
return {
"avg_issues_per_pr": sum(r["total_issues"] for r in records) / len(records),
"avg_precision": sum(r["precision"] for r in records) / len(records),
"most_common_issue_types": self._analyze_issue_types(records)
}
关键指标:
- 精确率(Precision):AI发现的问题中,有多少是真正有价值的
- 误报率:AI标记了但实际上没有问题的比例
- 发现率(Recall):真实Bug中,AI发现了多少(需要人工标注历史数据)
- 审查时间节省:引入AI后人工审查时间的变化
实施建议
阶段一(第1个月):
- 引入第三方服务(CodeRabbit/GitHub Copilot)试水
- 收集团队对AI评论质量的反馈
- 建立"接受/拒绝AI建议"的文化
阶段二(第2-3个月):
- 针对高频问题类型定制Prompt
- 将AI审查与CI/CD流水线深度集成
- 对安全类问题设置强制人工复核
阶段三(长期):
- 收集被接受的AI建议作为训练数据
- 探索fine-tuning专用的代码审查模型
- 将AI审查数据纳入工程效率Dashboard
结语
AI代码审查不是"AI替代码审",而是把工程师的注意力从机械性扫描中解放出来,专注在业务逻辑和架构设计的判断上。
从今天开始,最简单的第一步:在你的GitHub Repo里安装CodeRabbit(免费计划支持公开仓库),看看AI会发现什么。你会惊讶于它能抓到多少本应被发现但没被发现的问题。