跳转到正文
文章目录

引言:一次「看起来没问题」的 PR

想象这样一个场景:

深夜,你的同事提交了一个 Pull Request,改动了 300 行代码,修复了一个线上 Bug。你快速扫了一眼逻辑,觉得没问题,点了「Approve」。第二天早上,生产告警响了——那个 PR 里有一处边界条件没覆盖到,测试也没有跑,直接合并进了主分支。

这不是个例,而是没有 CI/CD 守护的代码审查每天都在发生的故事。

本文将深入回答一个问题:代码审查(Code Review)为什么必须和 CI/CD 配合使用? 并给出一套可落地的实践方案。


一、代码审查的本质与局限

1.1 审查的目的

代码审查(Code Review)是指在代码合并到主分支之前,由一个或多个工程师对变更内容进行检查的过程。其核心目标包括:

  • 发现 Bug:逻辑错误、边界条件、并发问题
  • 保证质量:可读性、可维护性、设计合理性
  • 知识共享:团队成员互相了解代码变更
  • 规范统一:确保代码风格一致
  • 安全审计:发现潜在的安全漏洞

1.2 纯人工审查的天然缺陷

然而,人类审查者存在不可避免的局限性:

认知负荷问题

人脑一次能处理的信息有限。当一个 PR 包含 500+ 行改动时,审查者很难同时关注:

  • 变量命名是否规范
  • 函数是否超过 50 行
  • 是否存在 SQL 注入风险
  • 单元测试覆盖率是否达标
  • 依赖包版本是否有安全漏洞
  • 构建是否能通过

这些机械性、规则性的检查,人脑做得既慢又不可靠。

主观性与一致性问题

  • 同一段代码,A 觉得可以,B 觉得有问题
  • 不同时间段,同一个人的判断标准不一致
  • 团队新人不熟悉规范,老人审查时要花大量时间解释

遗漏问题

  • 审查者没有在本地运行测试,无法发现运行时错误
  • 看不到测试覆盖率的变化趋势
  • 无法感知性能回退(除非有基准测试)

时间压力问题

  • 审查积压时,工程师倾向于「快速通过」
  • 「LGTM(Looks Good To Me)」文化导致形式化审查

二、CI/CD 如何弥补人工审查的不足

CI/CD 的核心价值在于:把所有可以自动化验证的事情交给机器,让人类专注于机器无法判断的部分。

2.1 CI 在审查中扮演的角色

当开发者提交 PR 时,CI 系统立即介入,充当第一道自动化守门员:

</> PLAINTEXT
PR 创建/更新
     │
     ▼
┌─────────────────────────────────────┐
│           CI 自动检查层              │
│                                     │
│  ✅ 代码风格(ESLint/Prettier)      │
│  ✅ 类型检查(TypeScript/mypy)      │
│  ✅ 单元测试(Jest/pytest)          │
│  ✅ 测试覆盖率阈值                   │
│  ✅ 构建成功                         │
│  ✅ 安全漏洞扫描(npm audit)        │
│  ✅ 依赖许可证检查                   │
│  ✅ 代码复杂度分析                   │
└─────────────────────────────────────┘
     │
     ▼ CI 全部通过后
┌─────────────────────────────────────┐
│           人工审查层                 │
│                                     │
│  👤 业务逻辑是否正确?               │
│  👤 设计是否合理?                   │
│  👤 是否有更好的实现方式?            │
│  👤 是否符合产品需求?               │
│  👤 是否有潜在的架构问题?            │
└─────────────────────────────────────┘
     │
     ▼
   合并主分支

2.2 分工协作的哲学

检查类型 执行者 原因
代码格式、缩进 CI(机器) 有明确规则,人工检查浪费时间
测试是否通过 CI(机器) 需要实际运行,人眼无法判断
覆盖率是否达标 CI(机器) 需要统计计算
安全漏洞扫描 CI(机器) 需要对比漏洞数据库
性能回退检测 CI(机器) 需要基准测试数据对比
业务逻辑正确性 人工 需要理解上下文和需求
代码可读性 人工 涉及主观判断和团队文化
架构设计合理性 人工 需要全局视野和经验
安全设计审计 人工 涉及复杂的攻击面分析

三、审查流程中 CI/CD 的具体应用场景

3.1 状态检查(Status Checks)与分支保护

现代代码托管平台(GitHub、GitLab)都支持将 CI 检查结果设置为必须通过才能合并。

GitHub 分支保护规则配置:

</> PLAINTEXT
Repository Settings
└── Branches
    └── Branch protection rules
        └── main
            ├── ✅ Require status checks to pass before merging
            │   ├── ci/lint
            │   ├── ci/test
            │   ├── ci/build
            │   └── ci/security-scan
            ├── ✅ Require branches to be up to date before merging
            ├── ✅ Require pull request reviews before merging
            │   └── Required approving reviews: 2
            └── ✅ Restrict who can push to matching branches

这样配置后,即使 PR 获得了所有人工审批,只要 CI 有一项失败,就无法合并。

3.2 自动化代码质量报告

CI 可以在每个 PR 上自动发布质量报告,让审查者一眼看到关键指标:

SonarCloud / CodeClimate 集成示例:

</> YAML
# .github/workflows/quality.yml
name: Code Quality

on: [pull_request]

jobs:
  sonarcloud:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # 需要完整历史用于分析

      - name: 运行测试并生成覆盖率
        run: npm test -- --coverage --coverageReporters=lcov

      - name: SonarCloud 扫描
        uses: SonarSource/sonarcloud-github-action@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

PR 页面会自动显示:

  • 新增代码的覆盖率
  • 代码异味(Code Smells)数量
  • 重复代码比例
  • 技术债务评估

3.3 预览环境(Preview Deployments)

这是 CI/CD 与审查结合最强大的场景之一:每个 PR 自动部署一个独立的预览环境。

</> PLAINTEXT
PR #42: feat/new-checkout-flow
├── CI 检查: ✅ 全部通过
├── 预览环境: https://pr-42.preview.myapp.com  ← 自动生成
└── 审查者可以直接点击链接,在真实环境中验证功能

Vercel / Netlify 天然支持此功能。自建方案示例:

</> YAML
# .github/workflows/preview.yml
name: Deploy Preview

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  deploy-preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 构建项目
        run: npm run build

      - name: 部署到预览环境
        id: deploy
        run: |
          # 使用 PR 号作为子域名
          PREVIEW_URL="https://pr-${{ github.event.number }}.preview.example.com"
          ./scripts/deploy-preview.sh ${{ github.event.number }}
          echo "url=$PREVIEW_URL" >> $GITHUB_OUTPUT

      - name: 在 PR 发布评论
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## 🚀 预览环境已就绪\n\n访问地址:${{ steps.deploy.outputs.url }}\n\n> 此预览环境将在 PR 关闭后自动销毁`
            })

3.4 自动化安全审查

安全漏洞扫描必须依赖 CI,人工无法做到:

</> YAML
# .github/workflows/security.yml
name: Security Scan

on: [pull_request]

jobs:
  dependency-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: npm 依赖漏洞扫描
        run: npm audit --audit-level=high

      - name: SAST 静态应用安全测试
        uses: github/codeql-action/analyze@v3
        with:
          languages: javascript, typescript

      - name: 密钥泄露检测
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: ${{ github.event.repository.default_branch }}
          head: HEAD

      - name: Docker 镜像漏洞扫描
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif

      - name: 上传安全报告
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

3.5 性能回退检测

</> YAML
# .github/workflows/benchmark.yml
name: Performance Benchmark

on: [pull_request]

jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 运行基准测试
        run: npm run benchmark -- --output benchmark-results.json

      - name: 对比基准(与 main 分支比较)
        uses: benchmark-action/github-action-benchmark@v1
        with:
          tool: 'jest'
          output-file-path: benchmark-results.json
          github-token: ${{ secrets.GITHUB_TOKEN }}
          alert-threshold: '130%'         # 性能下降超过 30% 时告警
          comment-on-alert: true          # 在 PR 上评论告警
          fail-on-alert: true             # 性能回退则 CI 失败

四、完整的 PR 工作流设计

4.1 理想的 PR 生命周期

</> PLAINTEXT
1. 开发者在 feature 分支完成开发
          │
          ▼
2. 本地运行 pre-commit hooks(快速检查)
   - 代码格式化
   - 简单 lint
   - 提交信息格式验证
          │
          ▼
3. 推送分支,创建 PR(草稿状态)
          │
          ▼
4. CI 自动触发(完整检查)
   - 全量测试
   - 构建验证
   - 安全扫描
   - 预览环境部署
          │
          ▼
5. CI 全部通过 → PR 标记为「Ready for Review」
          │
          ▼
6. 自动指派审查者(CODEOWNERS 规则)
          │
          ▼
7. 人工审查(专注于逻辑和设计)
          │
          ▼
8. 审查通过 + CI 通过 → 允许合并
          │
          ▼
9. 合并到 main → 触发 CD 流水线
          │
          ▼
10. 自动部署到生产 / 等待人工审批发布

4.2 CODEOWNERS 自动指派审查

</> BASH
# .github/CODEOWNERS

# 默认:所有文件需要核心团队审查
*                    @org/core-team

# 前端代码:前端团队
/src/components/     @org/frontend-team
/src/pages/          @org/frontend-team

# 后端 API:后端团队
/src/api/            @org/backend-team
/src/services/       @org/backend-team

# 数据库 migration:DBA 必须审查
/migrations/         @org/dba-team

# CI/CD 配置:DevOps 团队
/.github/            @org/devops-team
/Dockerfile          @org/devops-team
/docker-compose.yml  @org/devops-team

# 安全相关:安全团队
/src/auth/           @org/security-team
/src/crypto/         @org/security-team

4.3 Pre-commit Hooks 与 CI 的分层设计

不是所有检查都需要在 CI 运行,合理分层可以大幅提升效率:

</> PLAINTEXT
┌──────────────────────────────────────┐
│ Pre-commit(本地,毫秒级)            │
│  - 代码格式化(Prettier/Black)       │
│  - 基础 Lint 错误                    │
│  - Commit message 格式               │
└──────────────────────────────────────┘
              │ git push
              ▼
┌──────────────────────────────────────┐
│ CI - 快速检查 Job(2分钟内)          │
│  - 完整 Lint + 类型检查              │
│  - 单元测试(不含集成测试)           │
└──────────────────────────────────────┘
              │ 并行
              ▼
┌──────────────────────────────────────┐
│ CI - 完整检查 Job(5-15分钟)         │
│  - 集成测试                          │
│  - E2E 测试                          │
│  - 构建 + 安全扫描                   │
│  - 预览环境部署                      │
└──────────────────────────────────────┘

pre-commit 配置示例:

</> YAML
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: check-merge-conflict

  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.57.0
    hooks:
      - id: eslint
        files: \.[jt]sx?$
        args: ['--fix']

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        files: \.(js|ts|jsx|tsx|css|md|json|yaml)$

  - repo: https://github.com/commitizen-tools/commitizen
    rev: v3.13.0
    hooks:
      - id: commitizen

五、真实团队案例:没有 CI 的审查 vs 有 CI 的审查

案例对比

场景: 一个 5 人团队,每天合并 8-10 个 PR,项目为 TypeScript + React 前端应用。

❌ 没有 CI 的状态

</> PLAINTEXT
平均 PR 审查时间:45 分钟
其中:
  - 审查者本地拉取代码运行:10 分钟
  - 人工检查格式问题:8 分钟
  - 测试是否跑过(靠作者自觉):无法保证
  - 实际逻辑审查:27 分钟

每周因审查遗漏导致的 Bug:3-4 个
生产事故(由合并引入):每月 2-3 次
审查积压(>24小时未审查的 PR):日常

✅ 接入 CI/CD 后

</> PLAINTEXT
平均 PR 审查时间:22 分钟
其中:
  - 等待 CI 结果(并行,不占审查者时间):6 分钟
  - 浏览 CI 报告(覆盖率、质量分):2 分钟
  - 实际逻辑审查:20 分钟(质量更高)

每周因审查遗漏导致的 Bug:0-1 个
生产事故:每季度 0-1 次
审查积压:几乎消除(CI 不通过不需要人审)

六、进阶:AI 辅助代码审查 + CI/CD

近年来,AI 代码审查工具(如 GitHub Copilot Code Review、CodeRabbit、Qodo Merge)开始嵌入 PR 流程,形成三层审查体系:

</> PLAINTEXT
第一层:CI 自动化检查(规则驱动)
      ↓ 通过后
第二层:AI 代码审查(语义理解)
  - 分析逻辑潜在问题
  - 提出改进建议
  - 检测常见反模式
      ↓ AI 无异议后
第三层:人工审查(上下文理解)
  - 业务合理性
  - 架构决策
  - 团队知识共享

CodeRabbit 集成示例:

</> YAML
# .coderabbit.yaml
language: "zh-CN"
reviews:
  profile: "chill"
  request_changes_workflow: false
  high_level_summary: true
  poem: false
  review_status: true
  auto_review:
    enabled: true
    drafts: false
    base_branches:
      - main
      - develop
chat:
  auto_reply: true

接入后,每个 PR 会自动收到 AI 的逐行分析评论,人工审查者只需重点关注 AI 无法判断的部分。


七、常见问题与解答

Q1:CI 跑得太慢,影响审查效率怎么办?

优化方向:

  1. 测试并行化:将测试分片,多个 Job 同时运行
</> YAML
jobs:
  test:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]   # 4 个并行分片
    steps:
      - run: npm test -- --shard=${{ matrix.shard }}/4
  1. 依赖缓存:缓存 node_modules,避免每次重新安装
</> YAML
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
  1. 变更检测:只对有改动的模块运行测试(Monorepo 场景)
</> YAML
- name: 检测变更模块
  id: changes
  uses: dorny/paths-filter@v3
  with:
    filters: |
      frontend:
        - 'packages/frontend/**'
      backend:
        - 'packages/backend/**'

- name: 只测试变更的模块
  if: steps.changes.outputs.frontend == 'true'
  run: npm run test --workspace=packages/frontend

Q2:CI 通过了但人工审查发现严重问题,流程是否失效?

不,这恰恰说明分工合理。CI 负责可验证的客观问题,人工负责需要判断的主观问题。两者缺一不可,CI 通过不代表代码完美,只代表基线质量达标。

Q3:小团队有必要搭建 CI/CD 吗?

有,且成本极低。 GitHub Actions 对公开仓库完全免费,私有仓库每月有 2000 分钟免费额度。一个基础的 lint + test 流水线,配置时间不超过 2 小时,但带来的收益是持续的。


八、总结

代码审查与 CI/CD 的关系,本质上是人机协作的最佳实践:

维度 纯人工审查 CI/CD + 人工审查
格式规范 依赖个人习惯,不稳定 100% 自动强制执行
测试验证 靠作者自觉 强制通过才能合并
安全扫描 几乎缺失 每次自动执行
审查效率 低,夹杂大量机械工作 高,专注核心逻辑
审查质量 受疲劳、压力影响大 稳定,有数据支撑
知识门槛 依赖资深工程师全覆盖 初级工程师也能有效审查
事故率 高 显著降低

核心结论:

没有 CI/CD 的代码审查,是让人类去做机器该做的事;
没有代码审查的 CI/CD,是让机器去判断人类才能理解的事。
两者结合,才是现代软件工程的正确姿势。

从今天开始,哪怕只是加一个「自动跑测试」的 CI 检查,也能让你的代码审查质量上一个台阶。


📚 延伸阅读:

评论

搜索站内内容

输入关键词开始搜索