代码评审(Code Review)是软件工程里性价比最高的协作环节。一份 Pull Request 在合并前被认真审过,往往比事后加班救火更省钱。本文把代码评审拆成「提交自检 → 写好 PR → 高效审读 → 评论艺术 → CI 兜底」五个阶段,给出可直接套用的清单与模板,帮个人和团队把评审从负担变成知识流动的引擎。
一、为什么代码评审值得投入
很多团队把代码评审当成“流程税”,能省就省。但数据反复证明,评审的回报远超成本:Google 的工程研究指出,正式评审能在编码阶段拦截 60%–90% 的可维护性与逻辑缺陷;更隐性的收益是“上下文扩散”——每多一双眼睛看过代码,团队里懂这块逻辑的人就多一个,总线因子(bus factor)随之下降。对成长中的团队,评审还是最低成本的传帮带:新人看资深同学的评论,比读文档学得更快。
把评审做好的目标不是“挑出更多 bug”,而是用最小摩擦完成三件事:确保正确、统一风格、传递上下文。下面按阶段给出可操作的打法。
二、提交前的自检:让评审一次通过
2.1 小步提交,一次只做一件事
评审最怕“巨石 PR”:几千行改动混着重构、新功能、修 bug,审稿人根本无从下手。经验法则是单条 PR 控制在 200–400 行变更以内,且只解决一个问题。如果一次改动必须很大,用前缀拆分提交(commit),让评审者能逐 commit 阅读。
2.2 写好提交信息与 PR 描述
提交信息遵循 Conventional Commits,让历史可读、可自动生成 changelog:
# 类型(必填): feat/fix/docs/refactor/test/chore
# 范围(可选): 受影响模块
git commit -m "fix(auth): 修复 token 刷新竞态导致偶发 401
刷新期间并发请求共用同一 refresh,产生脏 token。
改为加锁串行刷新并复用首次结果。
Closes #1287PR 描述用模板约束,避免“请看代码”式敷衍。一个够用的模板:
## 背景
为什么做这个改动?(链接需求/issue)
## 改动点
- 要点 1:……
- 要点 2:……
## 测试
- [ ] 单测:覆盖 XX 分支
- [ ] 本地验证:复现步骤 / 截图
## 风险与回滚
可能影响哪些调用方?如何回滚?三、PR 描述怎么写:给评审者一张地图
审稿人的时间很贵。好的描述要回答三个问题:“你要解决什么、你改了什么、我怎么相信它是对的”。下面是一份贴近真实场景的示例:
## 背景
列表页在筛选条件下偶发白屏(#1423),根因为空数组未做兜底。
## 改动点
- `useList` 增加 `data ?? []` 空值兜底
- 抽离 `filterToQuery` 纯函数,便于单测
- 补充 3 个边界用例
## 测试
- [x] Vitest 覆盖空/单条/分页三种输入
- [x] 本地 `npm run dev` 复现并验证白屏消失
## 风险
仅改前端取值,不影响接口契约;回滚直接 revert 本 PR。测试部分可顺手链到本站 前端自动化测试:Vitest 与 Playwright 实战,把单测习惯固化进流程;本地验证思路也与 系统化调试思维 一脉相承。
四、评审者的高效读法(六步)
别从第一行逐字读。成熟审稿人有一套固定动线:① 读 PR 描述与 diff 概览,建立意图;② 看测试是否覆盖主路径与边界;③ 读核心函数签名与边界;④ 扫全局风险(并发、越权、资源泄漏);⑤ 只看“这次改的”,不纠结历史代码;⑥ 对不确定的点本地跑一遍。用 VS Code 的 GitLens / 本地 diff 插件 能快速看每行作者与历史,定位“为什么这么写”。
4.1 先读意图,再读实现
实现可以挑,意图要先认同。如果连“为什么做”都不认可,应该在评论里先对齐方向,而不是陷入细节争论。方向错了,改得再漂亮也是浪费。
4.2 本地复现,用 CLI 拉分支
对可疑逻辑,用 GitHub CLI 一键拉到本地复现:
# 拉取指定 PR 到本地并切分支
gh pr checkout 1423
# 只看某个文件的改动行
git diff main...HEAD -- src/useList.ts
# 跑测试
npm test -- useList本地跑测试时如需起依赖,可用 Docker Compose 一键编排 拉起 MySQL/Redis,保证评审环境与 CI 一致。
五、评论的艺术:对事不对人
评审的对象是代码,不是作者。同样一个问题,说法不同,效果天差地别。下表给出常见改写:
| 场景 | 不推荐的说法 | 更专业的说法 |
|---|---|---|
| 命名不清 | 这名字太烂了 | `tmp` 语义模糊,能否改为 `retryToken` 让意图更明确? |
| 逻辑疑问 | 这里写错了吧 | 这块在空值时会怎样?我担心 `data` 为 undefined 会抛错,能否加兜底? |
| 风格偏好 | 必须用我这种方式 | 团队约定用 early return,可读性更好;要不要顺手统一一下? |
| 建议非阻塞 | 无所谓,随便 | (可选)如果时间允许,抽个纯函数更易测,不强求本 PR。 |
关键区别:用提问代替断言,用“我们”代替“你”,把阻塞项(必须改)和可选项(nit)分开标注。这样作者不会 defensive,评审也能真正推进。
六、常见反模式与排障
| 反模式 | 表现 | 影响 | 对策 |
|---|---|---|---|
| LGTM 文化 | 扫一眼就批准 | 漏掉真实缺陷,评审形同虚设 | 设最低停留时长,强制看 diff |
| 巨石 PR | 一次改 2000 行 | 没人真看完,合并即埋雷 | 拆小,超 400 行要求拆分 |
| 评论混战 | 在正文长串反驳 | 噪音淹没关键意见 | 移到线程讨论,结论回写 PR |
| 作者抗辩 | “你不懂我的场景” | 陷入口水,方向无人拍板 | 升级到 Tech Lead 拍板 |
| 无人接审 | PR 挂三天 | 阻塞交付,dwell time 飙升 | 定 SLA:24h 内首评 |
七、把评审交给 CI:自动化兜底
人审该审的(逻辑、设计、安全),机器审重复的(格式、类型、覆盖率)。把 lint、单测、类型检查写进 GitHub Actions CI 流水线,评审者点开 PR 时先看绿不绿,把精力留给真正需要判断的地方:
# .github/workflows/pr.yml
name: pr-check
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint # 格式/规范
- run: npm run typecheck # 类型
- run: npm test -- --coverage # 单测+覆盖率门禁更进一步可用 reviewdog 把 lint 结果直接评论到 PR 行内,让机器评审“长”在代码旁边:
# 在 CI 中把 ESLint 输出转成 PR 行内评论
- name: reviewdog
uses: reviewdog/action-eslint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
level: warning八、团队评审规范速查清单
| 环节 | 检查项 | 达标判据 |
|---|---|---|
| 提交前 | 单 PR 单目标 | 改动 ≤400 行,关一个 issue |
| 提交前 | 提交信息规范 | 符合 Conventional Commits |
| PR | 描述三问齐全 | 背景/改动/测试 都有 |
| 评审中 | 先意图后实现 | 方向认同再谈细节 |
| 评审中 | 阻塞/可选分开 | 标 must / nit |
| 合并前 | CI 全绿 | lint+test+typecheck 通过 |
| 合并前 | 至少 1 审批 | 按分支保护规则 |
把这份清单沉淀进仓库的 CONTRIBUTING.md,新成员第一天就能对齐预期。评审不是某个人的责任,而是团队共享的工程纪律。
九、结语
代码评审的终极目标不是“拦住坏代码”,而是让好代码以更低成本、更高共识进入主干。当你把提交自检、清晰描述、对事不对人的评论和 CI 兜底组合成习惯,评审会从“流程税”变成团队最便宜的能力放大器。从下一个 PR 开始,试着写一份让人一眼读懂的描述吧。




