Skip to content

Code Review

Code Review(代码评审)是软件工程中最具杠杆效应的质量保障手段之一。它不仅仅是"检查代码有没有 Bug",更是团队知识流动、工程文化塑造、架构治理的核心环节。

本文将从 价值与文化 → 流程设计 → Reviewer 技巧 → Author 技巧 → 反模式 → 工具链 → 面试高频题 七个维度,全面深入地剖析 Code Review。

Code Review 在研发流程中的位置

开发者本地编码


┌──────────────────────────────┐
│        Self-Review           │
│    开发者自审代码              │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│        提交 PR / MR          │
│    附带描述、截图、测试        │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│      CI 自动化检查            │
│  Lint / Test / Build / Type  │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│       Code Review            │
│  Reviewer 人工审查代码        │
│  - 正确性                    │
│  - 设计合理性                │
│  - 可维护性                  │
│  - 安全性                    │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│       修改 & 再次 Review     │
│    根据反馈迭代修改           │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│        Approve & Merge       │
│    合入主干分支               │
└──────────────────────────────┘

一、Code Review 的价值

1.1 为什么需要 Code Review

很多开发者认为 Code Review 会拖慢开发速度,但事实恰恰相反——Code Review 是一种前置投资,它在早期发现的问题成本远低于线上修复。

缺陷发现成本曲线

成本

  │                                          ×  线上事故
  │                                     ×
  │                                ×
  │                           ×
  │                      ×
  │                 ×
  │            ×
  │       ×
  │  ×
  │× Code Review
  └──────────────────────────────────────────── 阶段
    编码  Review  测试  预发布  生产  线上事故

越早发现问题,修复成本越低。
Code Review 处于成本曲线最低点附近。

Code Review 的核心价值可以从四个维度来理解:

第一,提高代码质量。 Reviewer 站在不同视角审视代码,能发现 Author 由于"隧道视野"而忽略的问题。研究表明,Code Review 可以发现 60-90% 的缺陷(取决于 Review 深度),而单元测试通常只能覆盖 25-50%。两者结合使用效果最佳。

第二,知识共享与传播。 当一个资深工程师 Review 新人的代码时,通过评论和建议传递最佳实践;当一个新人 Review 另一个模块的代码时,快速了解系统的其他部分。这种双向的知识流动是团队成长的催化剂。

第三,降低 Bus Factor。 Bus Factor(巴士因子)指的是"如果团队中有 N 个人被巴士撞了,项目是否还能继续"。Code Review 确保每段代码至少有两个人理解它,显著提高了团队的抗风险能力。

第四,统一编码风格与架构决策。 通过 Review 过程中的讨论,团队逐渐形成统一的编码风格、架构风格和技术选型偏好,减少后续的技术债务。

1.2 Code Review 的投入产出比

指标没有 Code Review有 Code Review改善幅度
线上 Bug 率基准值降低 40-60%显著
新人上手时间3-6 个月1-3 个月50%
代码可维护性参差不齐趋于统一质变
知识孤岛严重大幅缓解质变
单次迭代速度略快略慢-10-15%
长期交付效率递减(技术债累积)稳定/递增显著

从表格中可以看出,Code Review 在短期内会略微降低迭代速度(约 10-15%),但在长期维度上,通过降低 Bug 率、减少技术债、加速知识共享,总体交付效率反而更高。

1.3 Code Review 的文化

Code Review 不只是一个技术活动,更是一个文化活动。一个健康的 Code Review 文化应该包含以下要素:

建设性反馈: 评论应该聚焦于代码本身,而非写代码的人。"这段逻辑有竞态风险"比"你怎么连竞态条件都不知道"好一百倍。

尊重与同理心: 理解每个人的背景和经验不同。对于新手,耐心解释原因;对于资深工程师,尊重其设计决策,有不同意见时用数据和论据说服。

学习心态: 无论是 Author 还是 Reviewer,都应该抱着学习的心态参与。Author 从反馈中学习,Reviewer 从阅读他人代码中学习。

及时响应: Review 不应该成为阻塞瓶颈。Google 的工程实践建议,Review 应该在 24 小时内响应(不一定完成,但至少给出初步反馈)。

1.4 业界 Code Review 实践

Google: Google 是 Code Review 文化的标杆。每一行代码在合入前都必须经过 Review。Google 内部使用 Critique 工具,要求至少一个 Reviewer 的 LGTM(Looks Good To Me)。Google 的工程实践指南明确指出:Reviewer 应该在收到 Review 请求后的一个工作日内响应。Google 还强调,代码库属于整个团队而非个人,任何人都可以 Review 任何代码。

Microsoft: 微软在 2000 年代中期开始推行 Code Review 文化。研究显示,在引入强制 Code Review 后,Windows 代码库的 Bug 密度下降了 20.9%。微软的 CodeFlow 工具支持内联评论和多轮迭代 Review。

Meta(Facebook): Meta 使用 Phabricator(后来迁移到自研工具)进行 Code Review。Meta 的特点是 Review 速度极快,鼓励小 PR 和快速迭代。他们的"Move Fast"文化要求 Review 不能成为交付的瓶颈。

开源社区: Linux 内核的 Code Review 以严格著称,Linus Torvalds 本人会亲自 Review 关键代码。Kubernetes、React 等开源项目通过 GitHub PR Review 进行社区协作式的 Code Review。


二、Code Review 流程

2.1 PR/MR 工作流

PR(Pull Request,GitHub 术语)和 MR(Merge Request,GitLab 术语)本质上是同一个概念:请求将一个分支的代码合并到另一个分支。

PR/MR 完整工作流

         创建特性分支


    ┌───────────────────┐
    │    本地开发编码     │
    │                   │
    │  - 编写功能代码    │
    │  - 编写单元测试    │
    │  - 本地运行测试    │
    └────────┬──────────┘


    ┌───────────────────┐
    │    自审(Self-Review)│
    │                   │
    │  - 检查 diff      │
    │  - 删除调试代码    │
    │  - 确认测试通过    │
    └────────┬──────────┘


    ┌───────────────────┐
    │    提交 PR/MR     │
    │                   │
    │  - 填写描述模板    │
    │  - 关联 Issue     │
    │  - 指定 Reviewer  │
    │  - 添加标签       │
    └────────┬──────────┘


    ┌───────────────────┐
    │    CI 自动化检查   │
    │                   │
    │  - Lint 检查      │
    │  - 类型检查       │
    │  - 单元测试       │
    │  - 构建检查       │
    └────────┬──────────┘

         CI 是否通过?
        ╱           ╲
      否              是
      │               │
      ▼               ▼
   修复 CI ──→  Reviewer 开始 Review

                  有修改意见?
                 ╱          ╲
               是             否
               │              │
               ▼              ▼
          Author 修改    Reviewer Approve
               │              │
               └──→ 再次 Review │

                      Squash & Merge


                        删除特性分支

2.2 Review 角色

在一次 Code Review 中,通常涉及以下角色:

角色职责要求
Author代码的编写者,负责提交 PR 并回应 Review 意见提供清晰的 PR 描述,及时响应评论
Reviewer审查代码的人,提出修改建议理解业务背景,认真审查每一行变更
Approver有权限批准合并的人,可以与 Reviewer 是同一人对代码变更负最终责任
Code Owner特定目录/模块的负责人,PR 涉及其负责区域时自动被请求 Review深入理解负责模块的架构和历史

在中小团队中,Reviewer 和 Approver 通常是同一个人。在大型团队或关键项目中,可能需要多个 Reviewer 的同意才能合并。

2.3 Review 时机

什么时候应该 Review:

  • CI 全部通过后再开始人工 Review(避免浪费时间在 Lint 错误上)
  • Author 明确标记 PR 为"Ready for Review"时(有些 PR 是 Draft 状态)
  • 优先 Review 阻塞其他人工作的 PR

Review 时长建议:

PR 大小建议 Review 时长说明
XS(< 50 行)10-15 分钟快速 Review,通常是小修复
S(50-200 行)15-30 分钟常规 Feature 或 Bug Fix
M(200-400 行)30-60 分钟较大 Feature,需要仔细审查
L(400-1000 行)分多次 Review建议拆分 PR
XL(> 1000 行)要求拆分除非是自动生成的代码或大规模重构

研究表明,Review 超过 60-90 分钟后,Reviewer 的注意力会显著下降,发现缺陷的能力急剧降低。因此,建议将 Review 拆分为多个短时间的 Session。

Review 效率与时间的关系

发现缺陷
的能力

  │  ×
  │    ×
  │      ×  ×
  │            ×
  │               ×
  │                  ×   ×   ×   ×   ×   ×

  └────────────────────────────────────────── 时间
   0   15   30   45   60   75   90  分钟

60 分钟后效率大幅下降。
单次 Review 建议控制在 60 分钟以内。

2.4 自动化检查:CI 先行

Code Review 中最常见的浪费是人工检查应该被自动化覆盖的问题。以下检查应该在 CI 中完成,而非依赖人工 Review:

检查类型工具说明
代码格式Prettier格式问题不应该出现在 Review 评论中
代码规范ESLint编码规范由工具保障
类型检查TypeScript Compiler类型错误应该在编译阶段发现
单元测试Jest / Vitest功能回归应该由测试保障
构建检查Webpack / Vite确保代码可以正常构建
CSS 规范Stylelint样式规范由工具保障
依赖安全npm audit / Snyk依赖漏洞自动扫描
Bundle Sizebundlesize / size-limit包体积变化自动检测
提交规范commitlintCommit Message 格式检查

核心原则:凡是能自动化的检查,都不应该依赖人工 Review。 人工 Review 应该聚焦在机器无法判断的问题上:设计合理性、业务逻辑正确性、可维护性等。


三、如何做好 Code Review(Reviewer 视角)

3.1 Review 关注点优先级

一个好的 Reviewer 不会眉毛胡子一把抓。Code Review 的关注点应该有明确的优先级:

Review 关注点优先级金字塔

              ╱╲
             ╱  ╲
            ╱ 正 ╲
           ╱ 确性 ╲         ← 最高优先级
          ╱────────╲
         ╱   设计   ╲
        ╱   合理性    ╲      ← 高优先级
       ╱──────────────╲
      ╱    可读性 &    ╲
     ╱    可维护性      ╲    ← 中优先级
    ╱────────────────────╲
   ╱       性能           ╲
  ╱                        ╲  ← 较低优先级
 ╱──────────────────────────╲
╱         代码风格           ╲
╲                            ╱ ← 最低(应由工具保障)
 ╲──────────────────────────╱

正确性(Correctness): 代码是否正确实现了需求?是否处理了边界情况?是否有逻辑错误?这是最重要的检查维度。

设计合理性(Design): 代码结构是否合理?抽象层次是否恰当?是否遵循了现有的架构模式?是否引入了不必要的复杂度?

可读性与可维护性(Readability & Maintainability): 变量命名是否清晰?函数是否过长?逻辑是否容易理解?其他开发者能否轻松接手?

性能(Performance): 是否有明显的性能问题?是否有不必要的重渲染?是否有 N+1 查询?大多数场景下,正确性和可读性优先于性能优化。

代码风格(Style): 缩进、命名约定、空格等。这些应该由 Prettier 和 ESLint 自动保障,不应该出现在 Review 评论中。

3.2 常见 Review 检查清单

3.2.1 通用检查项

通用 Code Review 检查清单

├── 正确性
│   ├── 功能是否正确实现了需求?
│   ├── 边界条件是否处理?(空值、空数组、超长字符串)
│   ├── 错误处理是否完善?(try-catch、错误边界)
│   ├── 并发/竞态条件是否考虑?
│   └── 数据类型是否正确?(特别注意 string/number 隐式转换)

├── 设计
│   ├── 是否遵循现有架构模式?
│   ├── 抽象层次是否合理?(不过度设计,也不过度冗余)
│   ├── 模块边界是否清晰?
│   ├── 是否遵循 SOLID 原则?
│   └── 是否引入了循环依赖?

├── 可读性
│   ├── 变量/函数命名是否清晰、有意义?
│   ├── 函数是否过长?(建议 < 50 行)
│   ├── 嵌套层级是否过深?(建议 < 4 层)
│   ├── 复杂逻辑是否有必要的注释?
│   └── 代码是否"自文档化"?

├── 测试
│   ├── 是否有对应的测试用例?
│   ├── 测试是否覆盖了核心路径?
│   ├── 测试是否覆盖了边界情况?
│   └── 测试命名是否清晰描述了测试场景?

├── 安全
│   ├── 是否有 XSS 风险?(dangerouslySetInnerHTML、v-html)
│   ├── 是否有敏感信息泄露?(密钥、Token 硬编码)
│   ├── 用户输入是否经过验证和转义?
│   └── API 调用是否有权限校验?

└── 其他
    ├── 是否有 TODO/FIXME 需要跟踪?
    ├── 是否有被遗忘的 console.log?
    ├── 是否有未使用的代码/导入?
    └── 文档/变更日志是否需要更新?

3.2.2 前端专项检查

前端 Code Review 除了通用检查项外,还有一些领域特有的关注点:

组件设计:

检查点好的实践不好的实践
组件职责单一职责,一个组件做一件事一个组件包含列表、详情、编辑三种模式
组件大小< 300 行,超过则拆分单文件组件 1000+ 行
Props 设计明确的类型定义,合理的默认值any 类型,缺少默认值
组件复用通过 Props 和 Slots/Children 实现灵活性通过大量 if-else 判断不同场景
组件命名PascalCase,语义清晰(UserProfile)模糊命名(Comp1、MyComponent)

状态管理:

检查点好的实践不好的实践
状态位置状态就近原则,能局部就不全局所有状态都放全局 Store
派生状态使用 computed / useMemo 计算手动同步多个相关状态
状态更新不可变更新(immutable update)直接修改对象/数组
异步状态处理 loading / error / success 三种状态只处理 success

性能相关:

检查点说明
不必要的重渲染React: 检查是否需要 memo / useMemo / useCallback
列表渲染 key使用稳定且唯一的 key,不要用 index
大列表优化超过 1000 条数据考虑虚拟滚动
图片优化是否使用懒加载、是否指定宽高、是否使用合适的格式
Bundle Size是否有不必要的大依赖引入
内存泄漏useEffect 清理函数、事件监听器移除、定时器清除

可访问性(Accessibility):

检查点说明
语义化 HTML使用 button 而非 div+onClick
ARIA 属性动态内容是否有 aria-live
键盘导航交互元素是否支持键盘操作
颜色对比度文字与背景的对比度是否达到 WCAG 标准
图片替代文本img 标签是否有 alt 属性

3.3 如何写好 Review 评论

Review 评论的质量直接影响 Code Review 的效果。一条好的 Review 评论应该遵循"三段式"结构:

Review 评论三段式

┌──────────────────────────────────────────┐
│  1. 描述问题(What)                      │
│     指出你发现的问题是什么                  │
├──────────────────────────────────────────┤
│  2. 解释原因(Why)                       │
│     说明为什么这是一个问题                  │
├──────────────────────────────────────────┤
│  3. 给出建议(How)                       │
│     提供具体的修改方向或示例代码             │
└──────────────────────────────────────────┘

不好的评论:

这里有问题,改一下。
不要这样写。

好的评论:

这里在 useEffect 中订阅了 WebSocket 事件,但没有在清理函数中
取消订阅。当组件卸载后如果收到消息,会尝试更新已卸载组件的
状态,导致内存泄漏。

建议在 useEffect 的返回函数中调用 unsubscribe:

useEffect(() => {
  const subscription = ws.subscribe(handler);
  return () => subscription.unsubscribe();
}, []);

另一个好的评论:

这个列表渲染使用了数组 index 作为 key。当列表发生增删或重排
时,React 会错误地复用 DOM 节点,可能导致状态错乱(比如
输入框的内容出现在错误的行上)。

建议使用 item.id 作为 key,确保每个元素有稳定且唯一的标识。

3.4 评论分级

不是所有的 Review 评论都一样重要。给评论分级可以帮助 Author 区分优先级,高效地处理反馈:

级别标记含义Author 的处理
Must Fix[Must Fix]🔴必须修复,不改不能合并必须修改
Should Fix[Should Fix]🟡强烈建议修复,但不阻塞合并建议修改,可以创建后续 Issue
Nit[Nit]nit:小问题,无关紧要的细节可选修改
Question[Question]我不理解这里,能解释一下吗回复解释即可
Praise[Praise]👍写得好!值得表扬开心收下

实际评论示例:

[Must Fix] 这里直接把用户输入拼接到 SQL 查询中,存在 SQL
注入风险。请使用参数化查询。

[Nit] 这个变量名 d 不够清晰,建议改成 deliveryDate。

[Question] 这里为什么选择了 setTimeout 而不是 requestAnimationFrame?
是有特定的考虑吗?

[Praise] 这个自定义 Hook 的抽象做得很漂亮,把复杂的分页逻辑
封装得非常干净。

3.5 前端 Code Review 实战案例

案例一:React 组件中的闭包陷阱

Author 提交了以下代码:

tsx
function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return <div>{count}</div>;
}

Reviewer 评论:

[Must Fix] 这里存在闭包陷阱。useEffect 的依赖数组为空,
所以回调函数中捕获的 count 永远是初始值 0。setCount(count + 1)
实际上永远是 setCount(0 + 1),计数器会卡在 1。

建议使用函数式更新:setCount(prev => prev + 1),
这样不依赖外部的 count 变量。

修改后:

tsx
function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return <div>{count}</div>;
}

案例二:Vue 组件中的响应式陷阱

Author 提交了以下代码:

vue
<script setup lang="ts">
import { reactive } from 'vue'

const state = reactive({
  user: { name: 'Alice', age: 25 }
})

function resetUser() {
  state.user = { name: '', age: 0 }
}

function updateName(name: string) {
  let { user } = state
  user.name = name
}
</script>

Reviewer 评论:

[Should Fix] updateName 中的解构 let { user } = state 在当前
代码中可以工作,因为 user 是对象引用。但需要注意,如果将来改
为 let { name } = state.user 这种基本类型的解构,就会丢失
响应性。

建议直接通过 state.user.name = name 修改,或者使用 toRefs
来保持响应性,避免后续维护者踩坑。

案例三:异步竞态条件

Author 提交了以下代码:

tsx
function SearchResults({ query }: { query: string }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    fetchResults(query).then(data => {
      setResults(data);
    });
  }, [query]);

  return <ResultList items={results} />;
}

Reviewer 评论:

[Must Fix] 这里存在竞态条件。当用户快速输入时(比如从 "ab"
变为 "abc" 再变为 "abcd"),会同时发出多个请求。如果
"abc" 的请求比 "abcd" 的请求晚返回,界面会显示 "abc" 的
结果,而不是最新的 "abcd" 的结果。

建议使用 AbortController 取消过时的请求,或者使用 boolean
标记来忽略过期的响应。

修改后:

tsx
function SearchResults({ query }: { query: string }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    const controller = new AbortController();

    fetchResults(query, { signal: controller.signal })
      .then(data => {
        setResults(data);
      })
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      });

    return () => controller.abort();
  }, [query]);

  return <ResultList items={results} />;
}

四、如何提交好的 PR(Author 视角)

4.1 PR 大小控制

PR 的大小是 Code Review 质量的最关键因素。 Google 的数据显示,PR 越大,Review 质量越低,Review 时间越长,被拒绝修改的概率也越高。

PR 大小与 Review 质量的关系

Review
质量

  │  × × ×
  │          ×
  │             ×
  │                ×
  │                   ×
  │                      ×  ×  ×  ×  ×

  └──────────────────────────────────────── PR 行数
   0    100   200   400   600   1000+

PR 超过 400 行后,Review 质量急剧下降。
建议控制在 200-400 行。
PR 大小Review 效果建议
< 50 行精细 Review,高质量反馈小 Bug Fix、文档更新
50-200 行优秀的 Review 效果理想的 Feature PR 大小
200-400 行良好,但需要更多时间可接受的上限
400-1000 行质量明显下降应考虑拆分
> 1000 行流于形式,几乎无法有效 Review必须拆分

如何拆分大 PR:

  • 按功能层次拆分:先 Model 层 → 再 Service 层 → 最后 UI 层
  • 按模块拆分:用户模块和订单模块分开提交
  • 重构和功能分开:先提一个纯重构 PR,再提一个功能 PR
  • 基础设施先行:先提工具函数/类型定义,再提业务代码

4.2 PR 描述模板

一个好的 PR 描述让 Reviewer 能快速理解改动的上下文,大幅提升 Review 效率。

markdown
## 背景(Why)

关联 Issue: #123
描述这个 PR 要解决什么问题,以及为什么需要这个改动。

## 改动内容(What)

- 新增了 UserProfile 组件,支持展示和编辑用户信息
- 重构了 useAuth Hook,增加了 Token 刷新逻辑
- 修复了登录页面在 Safari 上的样式问题

## 设计决策(How)

选择了 A 方案而非 B 方案,原因是...
(如果有重要的设计决策,在这里说明)

## 测试方法

- [ ] 单元测试覆盖了核心逻辑
- [ ] 手动测试了以下场景:
  - 正常登录流程
  - Token 过期后自动刷新
  - 网络断开后的错误处理

## 截图/录屏

(UI 相关的改动必须附截图或录屏)

| Before | After |
|--------|-------|
| 截图1  | 截图2 |

## 影响范围

- 影响页面:登录页、个人中心页
- 影响接口:/api/user/profile
- 是否需要配合后端发布:否

## Checklist

- [ ] 自审完成
- [ ] CI 通过
- [ ] 文档更新(如果需要)
- [ ] 变更日志更新(如果需要)

4.3 自审(Self-Review)

提交 PR 之前,Author 应该先做一次完整的自审。自审不仅能减少 Reviewer 的负担,还能帮助 Author 本人发现许多低级错误。

自审清单:

Self-Review 检查流程

第一遍:通读 diff
├── 是否有遗留的调试代码?(console.log、debugger)
├── 是否有不需要的文件被包含?
├── 是否有拼写错误?
└── 是否有被遗忘的 TODO?

第二遍:逻辑审查
├── 每个函数的输入输出是否正确?
├── 边界条件是否处理?
├── 错误情况是否处理?
└── 是否有明显的性能问题?

第三遍:从 Reviewer 角度看
├── PR 描述是否清晰?
├── 改动是否容易理解?
├── 是否需要额外的注释或文档?
└── 是否有需要特别说明的设计决策?

实践中,很多团队要求 Author 在 PR 上 给自己的代码加评论,主动说明一些可能引起疑问的设计决策,或者标注"这里我不太确定,请重点 Review"。这种做法能显著提升 Review 效率。

4.4 Commit 规范

规范的 Commit Message 不仅让 Review 更容易理解每次提交的目的,也方便后续的变更追溯和自动化发布。

Conventional Commits 规范:

<type>(<scope>): <subject>

<body>

<footer>

类型说明:

Type说明示例
feat新功能feat(auth): add OAuth2 login
fixBug 修复fix(form): resolve date picker crash on Safari
refactor重构(不改变功能)refactor(api): extract request interceptor
perf性能优化perf(list): add virtual scrolling for large datasets
style代码格式(不影响运行)style: format with prettier
docs文档docs(readme): update deployment guide
test测试test(auth): add unit tests for login flow
chore构建/工具/依赖chore(deps): upgrade React to v19
ciCI 配置ci: add bundle size check to pipeline

好的 Commit Message 示例:

feat(search): implement debounced search with AbortController

Previously, the search component sent a request on every keystroke
without canceling previous requests, causing race conditions.

This commit introduces:
- Debounced input (300ms) to reduce request frequency
- AbortController to cancel stale requests
- Loading and error states for better UX

Closes #456

4.5 如何回应 Review 意见

Author 回应 Review 意见的态度和方式同样重要:

场景建议做法避免做法
同意 Reviewer 的建议"Good catch! 已修改"什么都不说直接改
不同意 Reviewer 的建议给出理由和数据,友好地讨论"你不懂"、"之前就是这样写的"
不理解 Reviewer 的评论"能详细解释一下吗?"假装理解然后改错
认为是 Nit 但不想改"确实如此,我创建了一个后续 Issue"直接忽略
收到表扬"谢谢!"已读不回

五、Code Review 反模式

5.1 常见反模式

即使团队建立了 Code Review 流程,如果不注意,也容易陷入一些反模式。

橡皮图章(Rubber Stamping)

表现: Reviewer 不认真看代码,直接点 Approve。通常评论只有 "LGTM" 一条,或者根本没有评论。

原因:

  • Reviewer 太忙,没有时间认真 Review
  • 团队文化不重视 Code Review
  • PR 太大,Reviewer 看不动
  • Reviewer 对涉及的模块不熟悉,不知道看什么

危害: Code Review 流于形式,完全失去了质量保障作用。Bug 带着"已 Review"的标签进入生产环境。

吹毛求疵(Nit-picking)

表现: Reviewer 过度关注微小细节(变量名、空格、换行),忽略了更重要的设计和逻辑问题。一个 PR 下面几十条评论全是 Nit。

原因:

  • Reviewer 不理解业务逻辑,只能看表面
  • 团队没有配置好自动化工具(Lint、Prettier)
  • Reviewer 缺乏 Review 经验

危害: Author 感到沮丧和被针对,Review 效率极低,重要问题被淹没在大量 Nit 中。

攻击性评论(Hostile Comments)

表现: 评论带有人身攻击或轻蔑的语气。如"这代码谁写的?""你是不是没学过数据结构?""这也太差了吧。"

原因:

  • Reviewer 缺乏同理心
  • 团队文化有毒
  • Reviewer 把代码质量等同于个人能力评判

危害: 破坏团队信任和协作氛围,Author 不敢提交代码,团队成员回避 Review。

PR 太大(Large PRs)

表现: 动辄上千行甚至几千行的 PR。包含多个功能、重构和 Bug 修复混在一起。

原因:

  • 开发者不习惯拆分 PR
  • 功能耦合严重,难以拆分
  • 开发周期太长才提一次 PR

危害: Reviewer 看不动,Review 质量急剧下降。合并冲突频繁。出问题难以定位。

长时间不 Review(Review Bottleneck)

表现: PR 提交后好几天没人 Review,Author 被阻塞。

原因:

  • Reviewer 太忙
  • 没有明确的 Review 时效要求
  • Review 分配不均匀

危害: 阻塞开发流程,Author 可能在等待期间开始新任务导致上下文切换成本增高。分支长期未合并导致冲突增多。

5.2 反模式 vs 正确做法对比

反模式表现正确做法
橡皮图章不看代码直接 Approve至少花 15 分钟认真审查,对每个文件的改动都有了解
吹毛求疵几十条 Nit,忽略正确性和设计优先关注正确性和设计,配置好 Lint 工具处理风格问题
攻击性评论"这代码太烂了""这里有一个更好的方式来处理这个场景..."
PR 太大一次提交 2000 行拆分为 3-5 个小 PR,每个 < 400 行
Review 瓶颈3 天后才看 PR24 小时内给出初步反馈
只说问题不给方案"这里有问题""这里有问题,因为 X 原因,建议改为 Y"
要求完美不完美就不批准区分 Must Fix 和 Nit,允许后续迭代
拒绝被 Review"我这么写有我的理由"开放心态接受反馈,用数据和论据讨论分歧
拖延修改收到反馈后拖很久才改24 小时内回应评论,及时修改
忽略上下文不看 PR 描述直接看代码先理解改动背景和目的,再审查代码实现

六、Code Review 工具

6.1 主流 Review 平台

GitHub Pull Request Review

GitHub 是目前最流行的代码托管和 Review 平台。

核心功能:

功能说明
Inline Comment在代码行上直接评论
Suggestion提供可一键应用的代码修改建议
Review StatusApprove / Request Changes / Comment 三种状态
CODEOWNERS自动分配 Review 人
Required Reviews设置合并前必须的 Approve 数量
Branch Protection分支保护规则,禁止直接推送到主分支
Draft PR标记为草稿,表示尚未准备好 Review
PR Template统一 PR 描述格式

CODEOWNERS 文件示例:

*.js    @frontend-team
*.ts    @frontend-team
*.css   @frontend-team @design-team
/src/auth/    @security-team
/src/payment/ @payment-team @security-team
/docs/        @docs-team

Branch Protection Rules 推荐配置:

main 分支保护规则

├── Require pull request reviews before merging
│   ├── Required number of approvals: 1
│   ├── Dismiss stale reviews when new commits are pushed: ✅
│   └── Require review from Code Owners: ✅

├── Require status checks to pass before merging
│   ├── lint: ✅
│   ├── typecheck: ✅
│   ├── test: ✅
│   └── build: ✅

├── Require branches to be up to date before merging: ✅

├── Require conversation resolution before merging: ✅

└── Do not allow bypassing the above settings: ✅

GitLab Merge Request

GitLab 提供了企业级的 Code Review 功能:

功能说明
Merge Request Approvals灵活的审批规则配置
Code Quality Report集成代码质量报告到 MR 中
Merge Trains自动排队合并,减少合并冲突
Review Apps自动为每个 MR 部署预览环境
Suggested Reviewers基于 Git 历史自动推荐 Reviewer
Merge Request Dependencies声明 MR 之间的依赖关系

Gerrit

Gerrit 是 Google 开源的 Code Review 工具,特点是逐 Commit Review(而非逐 PR Review),适合对代码质量要求极高的团队。

特点说明
Review 粒度逐 Commit,而非逐 PR
投票机制+2 / +1 / 0 / -1 / -2 五级评分
Submit 规则可配置复杂的合并条件
适用场景Android 开源项目、Linux 内核等

6.2 自动化辅助工具

人工 Review 应该聚焦在高层次的设计和逻辑审查上,低层次的检查交给自动化工具。

工具类型作用
ESLint静态分析JavaScript/TypeScript 代码规范检查
Prettier格式化代码格式统一化
Stylelint静态分析CSS/SCSS 代码规范检查
SonarQube代码质量平台综合的代码质量扫描:Bug、坏味道、安全漏洞、重复率
CodeClimate代码质量平台代码可维护性评分、技术债追踪
Snyk安全扫描依赖漏洞检测
Codecov覆盖率测试覆盖率报告,在 PR 中展示覆盖率变化
Danger.jsPR 自动化自动化 PR 检查规则(PR 大小、描述格式等)
size-limit性能检查 PR 对 Bundle Size 的影响
Changeset版本管理追踪变更日志和版本号

Danger.js 配置示例:

typescript
import { danger, warn, fail, message } from 'danger'

const pr = danger.github.pr

if (pr.body.length < 50) {
  fail('PR 描述太短,请提供足够的上下文信息。')
}

const bigPRThreshold = 600
const linesChanged = pr.additions + pr.deletions
if (linesChanged > bigPRThreshold) {
  warn(`这个 PR 改动了 ${linesChanged} 行代码,建议拆分为更小的 PR。`)
}

const hasTests = danger.git.modified_files.some(f =>
  f.includes('.test.') || f.includes('.spec.')
)
if (!hasTests) {
  warn('这个 PR 没有包含测试文件的改动,请确认是否需要补充测试。')
}

const packageChanged = danger.git.modified_files.includes('package.json')
const lockfileChanged = danger.git.modified_files.includes('package-lock.json')
  || danger.git.modified_files.includes('pnpm-lock.yaml')
if (packageChanged && !lockfileChanged) {
  fail('package.json 被修改但 lockfile 没有更新,请运行 npm install 或 pnpm install。')
}

6.3 AI Code Review

随着 AI 技术的发展,AI 辅助 Code Review 正在成为新的趋势。

工具特点定位
CodeRabbitAI 自动 Review,支持多种语言GitHub/GitLab 集成
GitHub Copilot PR ReviewGitHub 官方 AI Review与 GitHub 深度集成
Amazon CodeGuruAWS 出品,聚焦性能和安全企业级
SourceryPython 重构建议Python 专精
Codeium代码建议和 Review多语言支持

AI Code Review 的定位:

AI Review 与人工 Review 的协作模式

代码提交


┌──────────────────────────────────────────────┐
│              AI 自动 Review                   │
│                                              │
│  ✅ 适合发现:                                │
│  - 常见 Bug 模式(空指针、越界、竞态)         │
│  - 安全漏洞(XSS、注入)                      │
│  - 性能反模式(N+1 查询、不必要的重渲染)      │
│  - 代码异味(过长函数、重复代码)               │
│  - 风格不一致                                 │
│                                              │
│  ❌ 不适合判断:                               │
│  - 业务逻辑正确性                             │
│  - 架构设计合理性                             │
│  - 产品需求匹配度                             │
│  - 团队特有的约定和习惯                        │
└──────────────────────┬───────────────────────┘


┌──────────────────────────────────────────────┐
│              人工 Review                      │
│                                              │
│  聚焦于 AI 无法判断的高层次问题:               │
│  - 设计合理性和架构决策                        │
│  - 业务逻辑正确性                             │
│  - 可维护性和可扩展性                          │
│  - 团队规范和上下文相关的问题                   │
└──────────────────────────────────────────────┘

AI Review 不是要替代人工 Review,而是作为 第一层过滤器,帮助 Reviewer 减少低层次的审查负担,从而将更多精力投入到高价值的设计和逻辑审查中。


七、面试高频问题

7.1 为什么 Code Review 很重要?请从多个维度说明

回答思路:

从四个维度展开:

  1. 质量维度:Code Review 是 Bug 的前置拦截器。研究表明 Review 可以发现 60-90% 的代码缺陷,且修复成本远低于线上发现。具体举例:曾在 Review 中发现了一个竞态条件,如果上线可能导致数据不一致。

  2. 知识维度:Review 是知识共享的最佳渠道。通过 Review,团队成员了解其他模块的设计和实现,降低 Bus Factor。新人通过被 Review 快速学习团队规范。

  3. 文化维度:Code Review 塑造团队的工程文化。一个重视 Review 的团队,代码质量和工程成熟度通常更高。Review 过程中的讨论有助于形成团队共识。

  4. 长期效率维度:短期看 Review 会增加 10-15% 的时间成本,但长期来看,通过降低 Bug 率、减少技术债、加速新人融入,总体交付效率反而更高。

7.2 如何处理 Code Review 中的分歧?

回答思路:

分步骤说明处理方式:

  1. 确认分歧类型:是客观正确性问题,还是设计偏好问题?客观问题应通过数据和事实解决,偏好问题可以适度妥协。

  2. 用数据说话:如果是性能相关的分歧,用 Benchmark 数据说明;如果是方案选择的分歧,列出各方案的优缺点对比表。

  3. 参考业界实践:引用权威文档、开源项目的做法,增加说服力。

  4. 升级讨论:如果双方无法达成一致,可以拉第三方(Tech Lead 或架构师)参与讨论。但这应该是最后手段,不应该频繁使用。

  5. 记录决策:最终的决策应该记录在 PR 评论或 ADR(Architecture Decision Record)中,方便后续回顾。

核心原则:对事不对人,数据驱动决策。

7.3 你在 Review 别人代码时主要关注什么?

回答思路:

按优先级说明关注点:

  1. 正确性第一:代码是否正确实现了需求,边界条件和错误处理是否完善。
  2. 设计合理性:抽象层次是否合理,是否遵循现有架构模式,模块边界是否清晰。
  3. 可维护性:命名是否清晰,函数复杂度是否合理,其他人能否轻松理解和修改。
  4. 前端专项:组件设计是否合理,状态管理是否恰当,是否有不必要的重渲染,是否有可访问性问题。
  5. 安全:是否有 XSS 风险,是否有敏感信息泄露。

强调代码风格问题应该由工具(ESLint、Prettier)保障,不占用 Review 时间。

7.4 如果你收到了一个 2000 行的 PR,你会怎么处理?

回答思路:

  1. 第一步:与 Author 沟通,友好地建议拆分 PR。可以帮助 Author 制定拆分策略(按功能层次、按模块、重构与功能分离)。

  2. 如果确实无法拆分(例如大规模重构),按以下方式处理:

    • 先了解整体改动背景和目标
    • 分多个 Session Review,每次不超过 60 分钟
    • 按文件或目录分批审查
    • 优先关注核心逻辑变更,跳过自动生成或格式化的变更
  3. 制度层面:推动团队建立 PR 大小的指导原则,比如"单个 PR 不超过 400 行",在 Danger.js 中配置自动警告。

7.5 如何提高团队 Code Review 的效率?

回答思路:

从流程、文化、工具三个方面展开:

流程优化:

  • 小 PR 策略:引导团队提交小而聚焦的 PR
  • 明确时效要求:24 小时内必须给出初步反馈
  • CI 先行:自动化检查通过后再人工 Review
  • PR 描述模板:减少 Reviewer 理解上下文的时间

文化建设:

  • 评论分级:区分 Must Fix / Should Fix / Nit
  • 建设性反馈:对事不对人,给出具体建议
  • 鼓励学习:Review 不是挑错,是共同学习
  • Review 轮值:避免 Review 压力集中在少数人身上

工具支撑:

  • 完善 CI Pipeline,自动化所有能自动化的检查
  • 使用 CODEOWNERS 自动分配 Reviewer
  • 引入 AI Review 作为第一层过滤
  • 使用 Danger.js 自动化 PR 规范检查

7.6 你如何看待 AI Code Review?它会取代人工 Review 吗?

回答思路:

  1. AI Review 的优势:速度快、不疲劳、覆盖面广,擅长发现常见 Bug 模式、安全漏洞、性能反模式等。可以 7x24 小时工作,对每个 PR 都一视同仁。

  2. AI Review 的局限:无法理解业务上下文,无法判断架构决策的合理性,无法识别团队特有的约定。容易产生误报(false positive),如果不加筛选会增加噪音。

  3. 结论:AI Review 是人工 Review 的有力补充,而非替代。最佳实践是将 AI 作为"第一层过滤器",处理低层次的代码质量检查,让人工 Reviewer 聚焦在高层次的设计和逻辑审查上。

  4. 实践建议:在团队中引入 AI Review 工具时,先小范围试用,收集反馈,调整配置,减少误报率后再全面推广。

7.7 Code Review 和结对编程有什么区别?各有什么优劣?

回答思路:

维度Code Review结对编程
时间异步,代码写完后 Review同步,编码时实时协作
参与方式Reviewer 阅读代码并评论两人一起编写代码
反馈速度延迟反馈(几小时到一天)即时反馈
适用场景常规开发、分布式团队复杂问题攻坚、新人培训
知识共享深度中等(通过代码和评论)深度(通过实时讨论和操作)
成本较低(一人时间)较高(两人时间)
覆盖范围可以覆盖所有代码通常只用于关键模块
心理压力较低(异步,可以自己节奏)较高(实时被观察)

两者不是互斥的,可以组合使用。复杂功能可以先结对编程,再 Code Review;简单功能直接 Code Review 即可。

7.8 如何衡量 Code Review 的效果?

回答思路:

可以从以下指标来衡量:

指标说明目标
Review 响应时间从提交 PR 到收到第一条评论的时间< 24 小时
Review 周期从提交 PR 到合并的总时间< 48 小时
PR 平均大小每个 PR 的平均改动行数< 400 行
评论密度每 100 行代码的评论数适中(过高或过低都不好)
Review 参与率团队成员参与 Review 的比例> 80%
线上 Bug 率Review 后代码的线上 Bug 率持续下降
Review 后返工率合并后因 Review 遗漏导致的返工比例< 5%

注意:这些指标应该用于团队整体改进,而非个人考核。用于考核会导致数据造假和行为扭曲(比如为了减少 Review 时间而 Rubber Stamping)。


八、延伸阅读

Google 工程实践:

  • Google's Engineering Practices documentation(Google Code Review 指南,开源且详尽)
  • "How Google Does Code Review"——详细描述了 Google 内部的 Review 流程和文化

书籍:

  • 《Code Review Best Practices》——系统性地讲解 Code Review 的方法论
  • 《Software Engineering at Google》——第九章专门讲 Code Review
  • 《The Pragmatic Programmer》——强调代码审查在软件工艺中的作用

研究论文:

  • "Code Reviews Do Not Find Bugs"(Microsoft Research)——微软研究院关于 Code Review 价值的研究,发现 Review 的主要价值不在于找 Bug,而在于知识共享和代码可维护性
  • "Modern Code Review: A Case Study at Google"——Google 大规模 Code Review 实践的学术分析
  • "Expectations, Outcomes, and Challenges of Modern Code Review"——对现代 Code Review 实践的系统性研究

工具文档:

  • GitHub Pull Request Review 官方文档
  • GitLab Code Review 最佳实践
  • Danger.js 官方文档
  • CodeRabbit 文档

社区资源:

  • "How to Do Code Reviews Like a Human"——Mtlynch 的博客系列,从人文角度讲 Review 技巧
  • "Conventional Comments"——标准化 Review 评论格式的提案
  • Awesome Code Review(GitHub)——Code Review 资源合集

用心学习,用代码说话 💻