主题
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 Size | bundlesize / size-limit | 包体积变化自动检测 |
| 提交规范 | commitlint | Commit 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 |
| fix | Bug 修复 | 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 |
| ci | CI 配置 | 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 #4564.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 天后才看 PR | 24 小时内给出初步反馈 |
| 只说问题不给方案 | "这里有问题" | "这里有问题,因为 X 原因,建议改为 Y" |
| 要求完美 | 不完美就不批准 | 区分 Must Fix 和 Nit,允许后续迭代 |
| 拒绝被 Review | "我这么写有我的理由" | 开放心态接受反馈,用数据和论据讨论分歧 |
| 拖延修改 | 收到反馈后拖很久才改 | 24 小时内回应评论,及时修改 |
| 忽略上下文 | 不看 PR 描述直接看代码 | 先理解改动背景和目的,再审查代码实现 |
六、Code Review 工具
6.1 主流 Review 平台
GitHub Pull Request Review
GitHub 是目前最流行的代码托管和 Review 平台。
核心功能:
| 功能 | 说明 |
|---|---|
| Inline Comment | 在代码行上直接评论 |
| Suggestion | 提供可一键应用的代码修改建议 |
| Review Status | Approve / 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-teamBranch 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.js | PR 自动化 | 自动化 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 正在成为新的趋势。
| 工具 | 特点 | 定位 |
|---|---|---|
| CodeRabbit | AI 自动 Review,支持多种语言 | GitHub/GitLab 集成 |
| GitHub Copilot PR Review | GitHub 官方 AI Review | 与 GitHub 深度集成 |
| Amazon CodeGuru | AWS 出品,聚焦性能和安全 | 企业级 |
| Sourcery | Python 重构建议 | 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 很重要?请从多个维度说明
回答思路:
从四个维度展开:
质量维度:Code Review 是 Bug 的前置拦截器。研究表明 Review 可以发现 60-90% 的代码缺陷,且修复成本远低于线上发现。具体举例:曾在 Review 中发现了一个竞态条件,如果上线可能导致数据不一致。
知识维度:Review 是知识共享的最佳渠道。通过 Review,团队成员了解其他模块的设计和实现,降低 Bus Factor。新人通过被 Review 快速学习团队规范。
文化维度:Code Review 塑造团队的工程文化。一个重视 Review 的团队,代码质量和工程成熟度通常更高。Review 过程中的讨论有助于形成团队共识。
长期效率维度:短期看 Review 会增加 10-15% 的时间成本,但长期来看,通过降低 Bug 率、减少技术债、加速新人融入,总体交付效率反而更高。
7.2 如何处理 Code Review 中的分歧?
回答思路:
分步骤说明处理方式:
确认分歧类型:是客观正确性问题,还是设计偏好问题?客观问题应通过数据和事实解决,偏好问题可以适度妥协。
用数据说话:如果是性能相关的分歧,用 Benchmark 数据说明;如果是方案选择的分歧,列出各方案的优缺点对比表。
参考业界实践:引用权威文档、开源项目的做法,增加说服力。
升级讨论:如果双方无法达成一致,可以拉第三方(Tech Lead 或架构师)参与讨论。但这应该是最后手段,不应该频繁使用。
记录决策:最终的决策应该记录在 PR 评论或 ADR(Architecture Decision Record)中,方便后续回顾。
核心原则:对事不对人,数据驱动决策。
7.3 你在 Review 别人代码时主要关注什么?
回答思路:
按优先级说明关注点:
- 正确性第一:代码是否正确实现了需求,边界条件和错误处理是否完善。
- 设计合理性:抽象层次是否合理,是否遵循现有架构模式,模块边界是否清晰。
- 可维护性:命名是否清晰,函数复杂度是否合理,其他人能否轻松理解和修改。
- 前端专项:组件设计是否合理,状态管理是否恰当,是否有不必要的重渲染,是否有可访问性问题。
- 安全:是否有 XSS 风险,是否有敏感信息泄露。
强调代码风格问题应该由工具(ESLint、Prettier)保障,不占用 Review 时间。
7.4 如果你收到了一个 2000 行的 PR,你会怎么处理?
回答思路:
第一步:与 Author 沟通,友好地建议拆分 PR。可以帮助 Author 制定拆分策略(按功能层次、按模块、重构与功能分离)。
如果确实无法拆分(例如大规模重构),按以下方式处理:
- 先了解整体改动背景和目标
- 分多个 Session Review,每次不超过 60 分钟
- 按文件或目录分批审查
- 优先关注核心逻辑变更,跳过自动生成或格式化的变更
制度层面:推动团队建立 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 吗?
回答思路:
AI Review 的优势:速度快、不疲劳、覆盖面广,擅长发现常见 Bug 模式、安全漏洞、性能反模式等。可以 7x24 小时工作,对每个 PR 都一视同仁。
AI Review 的局限:无法理解业务上下文,无法判断架构决策的合理性,无法识别团队特有的约定。容易产生误报(false positive),如果不加筛选会增加噪音。
结论:AI Review 是人工 Review 的有力补充,而非替代。最佳实践是将 AI 作为"第一层过滤器",处理低层次的代码质量检查,让人工 Reviewer 聚焦在高层次的设计和逻辑审查上。
实践建议:在团队中引入 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 资源合集