用AI做代码审查

Webelight Solutions作为一家基于服务的软件公司运营,处理从小型MVP到复杂企业系统的项目。在大约100名员工中,超过90名是技术人员——编写React、Node.js、Python、Flutter应用程序的工程师,QA工程师测试它们,以及DevOps工程师部署它们。每个人都每天都在处理代码。

有了这么多双手在运作,我们看到每天有50多个PR。这并非夸大——那是我们在GitLab上的实际平均值。

让我们来分解一下这意味着什么:

  • 每个PR每天50次×每次审查15分钟 = 每天750分钟
  • 750分钟 = 每天12.5小时工程时间
  • 按保守估计每小时$10计算,那是每天$150消失在代码审查上。

但时间和金钱甚至不是最糟糕的部分。真正的问题是质量

当你在一天中在5–10个PR之间切换上下文时,你的大脑没有能力做到:

  • 追踪复杂的逻辑更改并发现边缘情况
  • 识别看似无害代码的性能含义
  • 注意隐藏在例程更新中的安全漏洞
  • 捕捉只有特定条件下才会出现的微妙Bug
  • 验证代码库中实际存在的导入
  • 确保新代码与现有模式正确集成

我们得到的是表面级审查。"LGTM"评论,意思是"这看起来合理",而不是"我已经彻底验证了这不会破坏任何东西。"

1、为什么我们之前的AI尝试失败了

这并非我们第一次使用AI代码审查。就像2023年和2024年初期的许多工程团队一样,我们多次尝试自动化这个过程。每次尝试都教给我们一些有价值的东西——即使那个教训是"还没准备好。"

尝试#1:上下文问题

我们尝试使用GPT-4来审查代码差异。结果令人失望。AI会标记"缺少导入"而这些导入已经在文件中。它会建议使用代码库中不存在的函数。它错过了集成问题,因为它对代码库的其余部分如何工作没有任何想法。

为什么?因为我们只向它提供diff——即更改的行——没有任何关于周围代码库的上下文。这就像要求某人批评他们从未读过的书中的一段。

尝试#2:成本问题

好吧,所以我们需要提供更多上下文。我们尝试包含整个文件、相关模块,甚至依赖图。AI变得更聪明了,但现在我们遇到了不同的墙:成本

2024年初,向Claude或GPT-4提供大量上下文意味着以惊人的速度消耗token。我们对10次审查进行了试点,累积了$47的API成本。将其推断为每天50次审查,我们将每天在AI推理上花费 $235——比手动流程花费更多

所以我们又搁置了。

2、Diwali突破

快进到2024年10月下旬,在Diwali假期期间。在调试一些无关内容时,我偶然发现了一个改变一切的公告:z.ai发布了GLM-4.6,一个新的专注于编码的模型。

基准测试立即引起了我的注意。GLM-4.6在编码任务上显示出令人印象深刻的结果——与Claude Sonnet 4.5不相上下,后者已成为代码理解的金标准。但真正让我停下来的原因是定价。

z.ai提供了一个"编码计划",包含第一个月无限额度,每月$6,之后每份代码检查$6。六美元。我脑子里算了笔账:如果我们能以每份审查$6的成本每天处理50次审查,那就是每份审查$0.004。对比每天手动审查的$150。

但有一个陷阱。我们还必须与另一个API集成,重建我们的工具……除非。

那个发现是第二个拼图的那一刻,就像被闪电击中一样:z.ai支持与Anthropic的Claude API相同的API接口。这意味着它可以与Claude Code CLI无缝配合——Anthropic的命令行工具,为AI模型提供完整的代码库上下文。

意识到:

  1. 使用Claude Code CLI向AI提供完整的代码库上下文
  2. 将其指向z.ai的GLM-4.6而不是昂贵的Claude API调用
  3. 实际上零边际成本进行代码审查

那一晚我快速测试了几次。AI的反馈确实令人印象深刻——它捕捉到了一些我和7年编程经验可能在忙碌的一天遗漏的问题。上下文意识是我们一直在缺失的一切。

终于,我们可以构建几个月前就设想的系统的那一天到了。

3、为规模化架构

随着核心技术经验证,我在Diwali晚上映射出了架构。这不是要成为快速脚本——我们需要一些生产就绪的东西,能够处理高卷、为管理层提供可见性,并且由团队维护。

我脑海中还有另一个目标:我们需要一个分析仪表板,让技术主管和团队负责人能够审查统计数据,跟踪代码质量趋势,并了解AI在捕捉什么。

需求

  • 自动触发:一旦创建或更新GitLab MR就应该开始审查
  • 后台处理:不能阻塞webhook;审查必须异步运行
  • 完整上下文:必须克隆仓库并向AI提供完整的代码库访问
  • 综合分析:检测关键问题、安全顾虑、bug、性能问题
  • 成本跟踪:监控每次审查的token使用和成本
  • 详细日志:每次审查期间发生的事情的完全可见性
  • 分析仪表板:实时统计数据和历史数据
  • 可扩展性:在不破坏的情况下处理50多个并发审查
  • 成本计算:计算精确成本,包括上下文缓存读取
  • 详细日志:完全透明到审查过程中发生了什么

4、技术栈决策

我选择了我知道可以处理生产工作负载并能让我快速行动的工具:

前端:React + Tailwind CSS + shadcn/ui我们需要一些现代、响应式且令人愉快使用的东西。shadcn的组件库为我们提供了开箱即用的专业UI,而Tailwind让我们无需编写自定义CSS即可快速迭代设计。

后端:Python FastAPIFastAPI非常适合这个任务:快速、原生异步、出色的自动API文档,并且易于部署。此外,Python的生态系统非常适合Git操作和HTTP请求。

后台任务:Celery + Redis代码审查可能需要30–60秒。我们需要适当的工作队列、重试逻辑以及扩展worker的能力。Celery + Redis为我们提供了久经考验的可靠性。

数据库:PostgreSQL用于存储审查记录、日志、统计信息和仓库元数据。Postgres的JSON支持非常适合存储AI的结构化分析输出。

AI集成:Claude Code CLI + z.ai这是展示的明星。Claude Code CLI处理繁重的工作,通过提供代码库上下文,而z.ai的GLM-4.6以极小的成本提供强大的分析。

有了清晰的架构,我启动了使用Claude Sonnet 4.5的Cursor——我现在的编码伴侣——并开始构建。

5、实施冲刺

接下来是那些罕见的开发会议之一,一切都只是点击。八小时的专注、坚定的努力。没有会议。没有中断。只是架构直接转化为代码。

5.1 核心流程

以下是我们系统中代码审查的工作方式:

  1. Webhook接收:GitLab在创建或更新PR时发送webhook
  2. 数据库记录:我们立即创建审查记录并返回
  3. 后台任务:Celery获取审查任务
  4. 仓库克隆:克隆仓库并检出源分支
  5. AI分析:运行Claude CLI,提供完整的代码库上下文
  6. 结果解析:提取结构化分析(问题、分数、建议)
  7. GitLab评论:将格式化结果发布回MR
  8. 仪表板更新:存储指标用于分析

让我给你展示有趣的部分:

5.2 使用完整的代码库上下文运行Claude CLI

这是奇迹发生的地方。以下是实际调用Claude并提供完整代码库上下文的代码:

def run_claude_cli(self, prompt: str, max_retries: int = None) -> Tuple[bool, str, Dict, str]:
    """执行Claude CLI并返回结果及重试逻辑。"""
    # 使用 --output-format json 获取结构化响应及使用统计
    result = subprocess.run(
        [self.claude_path, "chat", "--output-format", "json"],
        input=prompt,
        capture_output=True,
        text=True,
        timeout=self.timeout,
        cwd=str(self.project_dir),  # 这是克隆仓库目录
        env=env
    )
    # 从Claude CLI的JSON包装中解析
    # 格式:{"type":"result","result":"<analysis>","usage":{...}}
    cli_response = json.loads(result.stdout)
    actual_response = cli_response['result']
    usage_data = cli_response['usage']
    # 提取token使用量以计算成本
    input_tokens = usage_data.get('input_tokens', 0)
    output_tokens = usage_data.get('output_tokens', 0)
    cache_read_tokens = usage_data.get('cache_read_input_tokens', 0)

关键洞察:通过在克隆仓库目录内(cwd=str(self.project_dir))运行Claude CLI,AI会自动获得搜索文件、阅读代码、理解项目结构以及验证导入和依赖项是否实际存在的权限。

5.3 使用Celery进行后台任务处理

以下是我们使用Celery异步处理代码审查的方式:

@celery_app.task(name="process_code_review", bind=True, max_retries=2)
def process_code_review(self, review_id: int, gitlab_token: str):
    """后台处理代码审查。"""
    async def _process():
        # 从数据库获取审查
        review = await session.execute(
            select(Review).where(Review.id == review_id)
        )
        # 更新状态为进行中
        review.status = ReviewStatus.IN_PROGRESS
        review.started_at = datetime.utcnow()
        await session.commit()
        # 克隆仓库
        clone_dir = Path(settings.GIT_CLONE_DIR) / f"review_{review_id}"
        gitlab.clone_repository(
            repository.clone_url,
            review.source_branch,
            clone_dir
        )
        # 运行AI审查
        reviewer = AIReviewer(str(clone_dir))
        success, error, analysis, raw_output = reviewer.run_claude_cli(prompt)
        # 计算成本
        cost_info = reviewer.calculate_cost(analysis, raw_output)
        review.estimated_cost = cost_info.get('estimated_cost', 0.0)
        # 发布到GitLab
        comment = reviewer.format_review_comment(analysis)
        gitlab.post_merge_request_comment(
            repository.gitlab_project_id,
            review.mr_iid,
            comment
        )

Celery的美妙之处在于,如果出现故障——网络超时、Git克隆问题、任何错误——它会自动以指数退避重试。我们已经将其配置为最多重试2次,可以捕获瞬时故障而不会冲击系统。

5.4 实践中的成本计算

需求之一是准确的成本跟踪。以下是我们如何从Claude CLI的响应中提取实际token使用量:

def calculate_cost(self, analysis: Dict, raw_output: str = "") -> Dict:
    """从Claude CLI响应中计算token使用量和成本。"""
    # 从分析JSON中提取(来自 --output-format json)
    if 'usage' in analysis:
        usage = analysis['usage']
        input_tokens = usage.get('input_tokens', 0)
        output_tokens = usage.get('output_tokens', 0)
        cache_read_tokens = usage.get('cache_read_input_tokens', 0)
    # 使用配置的定价计算成本
    input_cost = (input_tokens / 1_000_000) * settings.COST_INPUT_TOKEN
    output_cost = (output_tokens / 1_000_000) * settings.COST_OUTPUT_TOKEN
    cache_cost = (cache_read_tokens / 1_000_000) * settings.COST_CACHE_READ_TOKEN
    estimated_cost = input_cost + output_cost + cache_cost
    return {
        "input_tokens": input_tokens,
        "output_tokens": output_tokens,
        "cache_read_tokens": cache_read_tokens,
        "estimated_cost": round(estimated_cost, 6)
    }

这给了我们每次审查的精确可见度。定价按每百万token配置:输入$3,输出$15,缓存读取$0.30(尽管使用z.ai的无限制计划,实际成本实际上接近零)。

5.5 仪表板

前端出奇快,多亏了shadcn的组件。以下仪表板显示的内容:

仪表板提供实时分析:审查次数、成功率、执行时间、成本和问题摘要每个指标都直接从PostgreSQL拉取,为管理层提供代码质量趋势的即时可见性

我们跟踪:

  • 总审查和成功/失败计数
  • 成本指标:总花费、每次平均审查
  • 性能:平均执行时间(目前41.3秒)
  • 问题发现:关键问题、安全顾虑、bug、性能说明
  • 基于时间的分析:今天、本周、本月*

每条指标都直接从PostgreSQL拉取,为管理层提供代码质量趋势的即时可见性。

5.6 审查详情页面

当你点击特定审查时,你会得到完整的细分:

单个审查的详细视图,包括总体评分、执行的指标、分类的问题和变更文件、执行日志和token使用量的标签页每次审查都显示:MR详情:标题、分支、摘要 总体评分:AI对10分的评估* 执行指标:花费的时间、成本、分析的文件* 分类问题:关键、安全、bug、性能、质量、积极方面* 标签页:完整分析、更改文件、执行日志、token使用量*

日志选项卡特别用于调试:

执行日志为审查过程的每一步提供完全透明度每一步都记录了时间戳,所以如果出现问题,我们确切知道在哪里。

6、JIRA对齐博弈改变者

我们在构建这个系统时意识到有机会解决另一个痛点:确保PR实际解决了JIRA工单中规定的要求

有多少次,你审查一个PR,批准它,合并它,只发现后来它并没有真正实现工单中的一个要求?或者它添加了一堆没有被请求的功能?

我们将JIRA工单分析直接集成到审查流程中:

# 从MR描述中提取JIRA工单
if settings.JIRA_ENABLED:
    jira_service = JiraService(
        jira_url=settings.JIRA_URL,
        email=settings.JIRA_EMAIL,
        api_token=settings.JIRA_API_TOKEN
    )
    # 从MR描述中提取工单
    mr_description = mr_details.get('description', '')
    jira_tickets, jira_ticket_urls = jira_service.extract_jira_tickets(mr_description)
    if jira_tickets:
        # 获取工单信息
        tickets_info = jira_service.fetch_multiple_tickets(jira_tickets)
        jira_context = jira_service.format_tickets_for_prompt(tickets_info)

当开发人员在其MR描述中包含JIRA工单链接时,我们的bot会:

  • 提取工单ID:从JIRA获取详细信息(摘要、描述、验收标准)
  • 将该上下文包含在AI提示中
  • 要求AI验证对齐情况

AI随后在其审查中生成"JIRA对齐"部分:

AI的审查评论直接出现在GitLab MR上,并包含JIRA对齐分析AI会分析PR变更是否满足了JIRA工单中描述的所有标准这已经捕获到多次开发人员忘记实现部分需求的情况,或者添加了本应是单独工单的功能

它报告:

  • 状态:完全对齐/部分对齐/未对齐
  • 工单类型:功能/错误/任务
  • 是否匹配描述:变更是否与工单描述相符
  • 是否匹配验收标准:是否满足了所有要求
  • 分析:变更的详细解释
  • 缺失的需求:工单中有但PR中没有的内容
  • 额外变更:PR中有但工单中没有的内容

仅这一个功能就捕获到了许多次开发人员忘记实现部分需求或添加了本应是单独工单的功能的情况。验证代码实际实现了工单要求捕捉到了一大类代码审查遗漏的问题。

7、超出预期的结果

我们于周四下午部署到生产环境(是的,我们就是那些人)。系统在下午6:47处理了第一次真实审查。到周五结束时,我们已经处理了36次审查。数字看起来是这样的:

7.1 时间和成本节省

  • 手动流程:750分钟/天×每小时$10 = 每天$150
  • 自动化流程:36次审查×$0.33总计 = 每份审查$0.009
  • 时间节省:~每天12.5小时工程时间回归生产力
  • 成本降低99.4%减少

平均审查需要41.3秒才能完成——比人类甚至通读差异都要快,让他们能够彻底分析。

但AI也强调了:

  • 21个关键问题需要立即关注
  • 22个安全问题(暴露的API密钥、未验证的输入等)
  • 51个潜在bug(null检查、边缘情况、逻辑错误)
  • 性能问题(N+1次不必要的查询、不必要的重新渲染、内存泄漏)
  • 代码质量问题(不一致的模式、缺少错误处理)

但这也有积极的方面:

  • 良好的实现、聪明的解决方案、正确的模式

这种平衡的反馈使它真正有用,而不仅仅是抱怨机器人。

7.2 语言和框架支持

因为Claude(和GLM-4.6)都在广泛的代码库上训练,我们的机器人可以处理任何东西。我们向它抛出:

  • React & Next.js前端
  • Node.js后端
  • PythonAPI和脚本
  • Flutter移动应用
  • TypeScriptJavaScriptDart——你懂的

AI了解特定于框架的模式。它知道React hooks不应是条件性的。它会捕捉Flutter小部件生命周期问题。它会发现Python异步/await误用。它能发现代码库中不存在的导入。

7.3 团队采用

从团队的采用情况来看,压倒性地积极:

  • 审查在打开PR时立即发生——他们得到详细、可操作的反馈
  • AI捕捉到了人类审查者可能会遗漏的事情——它能发现复杂逻辑更改、边缘情况和隐藏的安全漏洞
  • 他们可以在要求人类审查之前迭代代码——这节省了时间并提高了质量
  • 技术主管喜欢——他们可以看到跨项目的质量趋势,发现关键问题并专注于架构关注点,而不是狩猎bug*

技术主管非常喜欢:

  • 他们可以看到跨项目的质量趋势
  • 关键问题被立即标记
  • 他们有数据来支持代码质量讨论
  • 他们可以专注于架构关注点而不是发现bug

8、经验教训:奏效的和出乎意料的

在8小时内构建这一过程令人兴奋,但教给了我们几个宝贵的经验。

8.1 架构规划是值得的

我在第一个小时只是将系统映射在文本文件中(好吧,在文本文件中)。那规划时间节省了后续的数小时重构时间。当你确切知道你在构建什么时,代码会自动生成。

8.2 AI辅助开发是真实的

使用Cursor配合Claude Sonnet 4.5*来构建AI代码审查机器人,是令人愉悦的元体验。Cursor处理样板代码、建议模式、捕捉我的错别字,并让我专注于架构而不是语法。这本来要花费16–20小时而无需AI辅助。

8.3 上下文问题已解决

多年来,使用AI代码审查的瓶颈一直是因为缺乏上下文。你无法向AI提供足够的知识而不会花一大笔钱。Claude Code CLI改变了这个等式。AI可以从字面意义上搜索代码库、阅读文件、理解导入——它知道项目。

8.4 实践中的成本计算

要求之一是精确的成本跟踪。以下是我们如何从Claude CLI的响应中提取实际token使用量:

def calculate_cost(self, analysis: Dict, raw_output: str = "") -> Dict:
    """从Claude CLI响应中计算token使用量和成本。"""
    # 从分析JSON中提取(来自 --output-format json)
    if 'usage' in analysis:
        usage = analysis['usage']
        input_tokens = usage.get('input_tokens', 0)
        output_tokens = usage.get('output_tokens', 0)
        cache_read_tokens = usage.get('cache_read_input_tokens', 0)
    # 使用配置的定价计算成本
    input_cost = (input_tokens / 1_000_000) * settings.COST_INPUT_TOKEN
    output_cost = (output_tokens / 1_000_000) * settings.COST_OUTPUT_TOKEN
    cache_cost = (cache_read_tokens / 1_000_000) * settings.COST_CACHE_READ_TOKEN
    estimated_cost = input_cost + output_cost + cache_cost
    return {
        "input_tokens": input_tokens,
        "output_tokens": output_tokens,
        "cache_read_tokens": cache_read_tokens,
        "estimated_cost": round(estimated_cost, 6)
    }

这给了我们每次审查的精确可见度。定价按每百万token配置:输入$3,输出$15,缓存读取$0.30(尽管使用z.ai的无限制计划,实际成本实际上接近零)。

8.5 提示工程仍然重要

让AI输出可靠、结构化的JSON花了一些迭代。我们必须非常明确地提示:

⚠️⚠️⚠️ 绝对关键说明 ⚠️⚠️⚠️
不得在JSON之前或之后写入任何文本。
不得在markdown代码块中包装JSON。
你的整个响应必须仅是原始JSON对象。

8.6 日志是不可谈判的

当生产环境中出现问题时(并且它将会出现),详细的日志是"我们宕机一小时,同时进行调试"和"5分钟内修复"之间的区别。

每一步都记录了时间戳,所以如果出现问题,我们确切知道在哪里。

8.7 JIRA集成是隐藏的宝石

我们几乎为了更快发货而跳过此功能。很高兴我们没有。它已成为系统中最受赞誉的部分之一。验证代码实际上实现了工单要求捕捉到了一大类代码审查遗漏的问题。

9、下一步:这只是第一阶段

我们对我们构建的内容感到兴奋,但我们知道这仅仅是开始。

我们的第二阶段路线图包括:

  • 自动修复建议:为常见问题生成实际的代码补丁
  • 历史分析:跟踪随时间变化的每个开发者/团队的代码质量趋势
  • 自定义规则:让团队定义项目特定的审查标准
  • IDE集成:将反馈直接引入VS Code/Cursor
  • PR风险评分:预测哪些PR最可能导致问题
  • 测试覆盖率分析:与覆盖率工具集成以识别缺口
  • 测试覆盖率分析:与覆盖率工具集成以识别缺口

我们的愿景是进一步减少人工干预,同时持续提高代码质量。AI不是在取代人类代码审查人员——它是在放大他们。

老实地说,那个未来已经在这里了。


原文链接: How We Built an AI Code Review Bot That Saves 750 Minutes Daily (And Cut Costs by 99%)

汇智网翻译整理,转载请标明出处