前言:Code Review 的残酷现实
理想中的 Code Review 是这样的:
sequenceDiagram
participant A as 作者
participant R as Reviewer
participant C as 代码库
A->>R: 提交 PR,附上详细描述
R->>C: 认真阅读每一行代码
R->>R: 深度思考业务逻辑和技术方案
R->>A: 提出有建设性的修改意见
A->>R: 感谢 Reviewer 的认真审查
A->>C: 修改并提交
R->>C: 再次确认,Approve
C->>C: 代码质量显著提升
现实中的 Code Review 是这样的:
sequenceDiagram
participant A as 作者
participant R as Reviewer
participant S as Slack
participant M as 会议
A->>R: 提交 PR,@你一下
S->>R: 各种消息轰炸
M->>R: 连续三个会议
R->>R: 瞟了一眼 PR,diff 有 800 行
R->>R: 滚动了一下,看起来没问题?
R->>A: LGTM,先合了吧
Note over A,R: 三天后
A->>R: 那个 PR 有个 bug 上线了
R->>R: 我当时没仔细看...
R->>A: 啊,这个我当时注意到了但忘说了
本文就是为第二种场景服务的——当你实在没时间、没精力、没背景知识看懂一个 PR 的时候,如何在不暴露自己的前提下,给出一个看起来很专业的 Review。
第一章:Reviewer 画像分类
在学习话术之前,先了解 Code Review 中各种 Reviewer 的生存形态:
quadrantChart
x-axis "看得少" --> "看得多"
y-axis "说得少" --> "说得多"
quadrant-1 "完美Reviewer"
quadrant-2 "话痨Reviewer"
quadrant-3 "橡皮图章"
quadrant-4 "沉默观察者"
"摸鱼型": [0.15, 0.2]
"事必躬亲型": [0.9, 0.9]
"挑刺专业户": [0.3, 0.85]
"潜水围观型": [0.8, 0.15]
"装懂高手": [0.25, 0.6]
"真正大佬": [0.95, 0.75]
我们今天重点培养的是装懂高手的能力——看得不多,但说得有分量。
第二章:进入 PR 前的准备工作
不打无准备之仗。打开 PR 之前,先做三件事:
2.1 读 PR 标题和描述(必做,耗时 30 秒)
这是你唯一需要认真读的部分。PR 描述里通常包含:
- 做了什么:给你提供评论的弹药
- 为什么这样做:给你提供质疑的切入点
- 测试了什么:给你提供追问的方向
如果 PR 没有描述,恭喜你,你拥有了第一条评论的素材:
「能否补充一下这个 PR 的背景和改动原因?方便 Review」
这条评论专业、合理、无懈可击,而且能为你争取到额外的理解时间。
2.2 看 Diff 的数字(必做,耗时 10 秒)
graph TD
OPEN[打开 PR] --> CHECK{看 Diff 统计}
CHECK --> |改动小于 50 行| SMALL[小 PR
可以认真看
这是难得的机会]
CHECK --> |改动 50-200 行| MEDIUM[中等 PR
重点看关键文件
其余走话术]
CHECK --> |改动 200-500 行| LARGE[大 PR
重点看入口和出口
中间假装看了]
CHECK --> |改动大于 500 行| HUGE[巨型 PR
先问为什么不拆
这是合理要求
也为你赢得时间]
SMALL --> REAL[认真 Review]
MEDIUM & LARGE --> SKILL[启动话术体系]
HUGE --> SPLIT[要求拆分 PR
专业且负责任]
style REAL fill:#51cf66
style SKILL fill:#ffd43b
style SPLIT fill:#74c0fc
2.3 看改动了哪些文件(必做,耗时 20 秒)
文件名能告诉你这个 PR 的性质:
| 文件特征 | 含义 | 你的策略 |
|---|
只改了 *Test.java | 纯测试改动 | 问覆盖率,稳赚不赔 |
改了 *Controller.java | 接口层改动 | 问入参校验和响应码 |
改了 *Repository.java | 数据层改动 | 问索引和 N+1 问题 |
改了 *Config.java | 配置改动 | 问各环境是否同步 |
改了 pom.xml | 依赖升级 | 问是否测试过兼容性 |
| 改了几十个文件 | 重构或格式化 | 问有没有功能性改动混进来 |
第三章:万能评论话术库
这是本文的核心章节。以下话术经过真实 Code Review 场景验证,覆盖率达到 95% 以上的场景。
3.1 开场白类——让人觉得你认真看了
话术 A:整体肯定型
「整体看下来思路很清晰,几个小点想确认一下。」
效果:暗示你看完了全部代码,同时为后续问题铺垫。
话术 B:背景确认型
「想先确认一下理解是否正确:这个改动主要是为了解决 XX 问题,对吗?」
效果:即使你没看懂,也显得你在思考业务逻辑。如果理解错了,作者会纠正你,你反而能借此真正理解 PR 的目的。
话术 C:点赞占位型
「这个设计思路不错,之前类似场景我们都没处理这么优雅。」
效果:作者立刻对你有好感,后续即使你提不出什么实质意见,这个 PR 大概率也能顺利 Approve。
3.2 专业质疑类——让人觉得你很懂
话术 D:边界条件万能问
「这里如果入参为 null 或者空集合,会怎么处理?」
适用场景:任何有参数的方法。成功率 100%,因为总会有某个地方没考虑边界。
话术 E:并发安全万能问
「这个方法在高并发场景下是线程安全的吗?」
适用场景:任何 Service 层代码。即使方法本身是无状态的,作者也得解释一番,你就赢了。
话术 F:性能担忧万能问
「数据量大的时候这里的性能怎么样,有没有考虑过分页或者缓存?」
适用场景:任何涉及数据库查询或集合遍历的代码。
话术 G:异常处理万能问
「这里如果下游接口超时或者返回异常,上层会怎么处理?」
适用场景:任何有外部调用的地方。支付系统尤其好用。
话术 H:测试覆盖万能问
「这块逻辑有对应的单元测试吗?特别是异常分支。」
适用场景:任何你看不懂的代码。不管有没有测试,这个问题都成立。
3.3 看起来读懂了类——最高段位话术
graph LR
subgraph 初级话术 人人会说
L1A[这里可以加个注释]
L1B[变量名可以更语义化]
L1C[魔法数字建议提成常量]
end
subgraph 中级话术 显得有经验
L2A[这个方法是不是可以复用现有的工具类]
L2B[异常信息建议更具体一些方便排查]
L2C[日志级别这里用 warn 还是 error 更合适]
end
subgraph 高级话术 显得很资深
L3A[这里的设计和我们 XX 模块有点类似
要不要考虑抽象一层]
L3B[这个改动会不会影响 XX 的现有行为]
L3C[从可维护性角度这里以后扩展方便吗]
end
L1A & L1B & L1C --> MID[中等水平印象]
L2A & L2B & L2C --> GOOD[良好水平印象]
L3A & L3B & L3C --> GREAT[资深水平印象]
高级话术的关键是关联到系统全局,显示你不只是在看这一个 PR,而是在思考整个系统。
3.4 Nit 评论类——小改动显示认真
「Nit」是 Code Review 中的高频词,意思是「小挑剔,可改可不改」,英文全称 Nitpick。妙处在于:
- 提了意见,显得认真
- 说了是 Nit,对方可以不改
- 两边都不得罪
- 实际上你什么都没看懂
Nit: 这里的变量名 `data` 可以改成 `accountList` 更语义化,不改也行。
Nit: 这个方法有点长了,以后有机会可以考虑拆一下,不影响这次合并。
Nit: 注释里有个错别字,`帐户` 建议改成 `账户`。(错别字类 Nit 是最安全的,跟代码逻辑完全无关)
第四章:各种棘手场景的应对策略
4.1 场景一:代码涉及你完全不懂的领域
比如密码学、复杂算法、你从来没接触过的业务模块:
graph TD
UNKNOWN[遇到完全不懂的代码] --> STRATEGY{选择策略}
STRATEGY --> S1[策略一:请教式提问]
STRATEGY --> S2[策略二:转移焦点]
STRATEGY --> S3[策略三:找盟友]
S1 --> T1[这里的实现原理能简单解释一下吗
想更好地理解这个设计]
S2 --> T2[这块逻辑本身没问题
我主要关注一下周边的错误处理]
S3 --> T3[这块建议也拉上 XX 看一下
他对这个模块更熟悉]
T1 --> RESULT1[作者给你科普
你顺便真的看懂了]
T2 --> RESULT2[成功把注意力引开
没人发现你没看那块]
T3 --> RESULT3[甩锅成功
而且显得你很负责任]
style RESULT1 fill:#51cf66
style RESULT2 fill:#ffd43b
style RESULT3 fill:#74c0fc
4.2 场景二:PR 改动了 800 行,你只有 5 分钟
这是最常见的困境。解决方案:战略性精读。
第 1 分钟:读 PR 描述,搞清楚「改了什么」
第 2 分钟:看文件列表,找最核心的那 1-2 个文件
第 3 分钟:只看核心文件的入口方法和主逻辑
第 4 分钟:翻一下测试文件,看测试了什么场景
第 5 分钟:写评论
5 分钟后你能给出的评论:
「核心逻辑看起来没问题,主要改动集中在 TransferService 的 process 方法,逻辑比之前清晰多了。
几个小点:
processInternal 方法的异常有没有统一处理?- 测试里好像没有覆盖金额为零的边界场景?
其他改动主要是重构,LGTM。」
对方看到这条评论会觉得:这哥们认真看过核心逻辑,而且提了有价值的问题。
4.3 场景三:你 Approve 了,结果出 bug 了
这是最需要话术的场景:
graph LR
BUG[线上出 Bug 了] --> CAUSE{Bug 来源}
CAUSE --> |和你 Review 的 PR 有关| PRESSURE[被问到你当时看没看]
CAUSE --> |和你 Review 的 PR 无关| SAFE[松了一口气]
PRESSURE --> RESPONSE{你的回应}
RESPONSE --> R1[坦诚型
我当时没注意到这个分支
下次会更仔细]
RESPONSE --> R2[技术型
这个 Bug 属于边界场景
静态分析很难发现
需要更完善的集成测试]
RESPONSE --> R3[流程型
这说明我们需要
更严格的测试环境验证
而不只是依赖 Code Review]
R1 --> HONEST[显得诚实有担当]
R2 --> DEFLECT[把问题转移到测试覆盖]
R3 --> PROCESS[把问题转移到流程建设]
style HONEST fill:#51cf66
style DEFLECT fill:#ffd43b
style PROCESS fill:#74c0fc
人生建议:R1 是最好的选择。承认自己没看到,然后说下次会怎么做。这比任何防御性话术都更赢得信任,而且不会让人觉得你在甩锅。
4.4 场景四:作者追问你某个技术细节
作者:你觉得这里用 CompletableFuture 还是用 reactive stream 更合适?
你(内心):我不知道这两个有什么区别。
你(嘴上):?
正确应对:
方案 A(最推荐):「这个要看你们的具体场景,如果整体技术栈已经是响应式的,统一用 reactive 更好;如果只是这一处异步,CompletableFuture 更轻量。你们当前栈是什么?」
把问题还给对方,显得你在做权衡,而不是不知道答案。
方案 B:「我觉得都行,你对这个更熟,你定吧,我更关注接口的行为是否正确。」
优雅放弃技术判断,把自己定位成业务正确性的把关人。
第五章:不同类型 PR 的专属话术
5.1 Bug Fix PR
✅ 专业开场:
「能否在描述里补充一下这个 Bug 的复现步骤?方便以后排查类似问题。」
✅ 核心追问:
「这个 Fix 有没有对应的回归测试?防止以后又被改回去。」
✅ 延伸关联:
「同样的逻辑在 XX 地方也有类似代码,要不要一起看看?」
5.2 新功能 PR
✅ 专业开场:
「这个功能的设计文档或者技术方案有吗?想对齐一下实现和设计的一致性。」
✅ 核心追问:
「异常情况下用户看到的是什么?前端有没有对应的错误处理?」
✅ 延伸关联:
「这个功能上线后怎么验证效果?有没有埋点或者监控指标?」
5.3 重构 PR
✅ 专业开场:
「重构前后行为完全一致吗?有没有哪些边界 case 在重构过程中可能被改变了?」
✅ 核心追问:
「测试覆盖率有没有变化?重构后的测试和重构前相比是增加了还是减少了?」
✅ 最安全的评论:
「这个重构让代码清晰多了,特别是 XX 部分。」
(XX 部分填你唯一看懂的那个方法名)
5.4 依赖升级 PR
✅ 没有任何背景知识也能问的问题:
「这个版本的 Release Notes 有没有 Breaking Changes?」
「CI 上所有测试都过了吗?」
「各个环境都验证过了吗?还是只在开发环境测了?」
「有没有影响到其他依赖这个包的模块?」
依赖升级 PR 是最适合「装懂」的场景,因为所有问题都是合理的,没人能说你没在认真看。
第六章:Code Review 的段位体系
graph TD
D0[青铜:不看直接 Approve
俗称橡皮图章]
D0 --> D1[白银:看了但不知道说什么
只会写 LGTM]
D1 --> D2[黄金:能找出格式和命名问题
靠 Sonar 和 Lint 工具辅助]
D2 --> D3[铂金:能发现逻辑漏洞和边界问题
每次 Review 都有实质意见]
D3 --> D4[钻石:能从架构层面评审
发现设计问题而非实现问题]
D4 --> D5[星耀:Read Between The Lines
看出作者想掩盖的技术债]
D5 --> D6[王者:不需要看代码
光看 PR 描述就知道哪里有风险]
style D0 fill:#868e96
style D1 fill:#c0c0c0
style D2 fill:#ffd43b
style D3 fill:#74c0fc
style D4 fill:#9775fa
style D5 fill:#ff6b6b
style D6 fill:#51cf66
本文教的是从青铜升到黄金/铂金的过渡期生存技能。
真正的目标是王者,但那需要的不是话术,而是时间和经验的积累。话术只是过渡期的拐杖,不是终点。
第七章:你不该假装看懂的情况
前面说了这么多「如何装」,最后必须说清楚什么时候不能装:
graph TD
REVIEW[开始 Review] --> RISK{评估风险}
RISK --> |涉及核心支付逻辑| STOP1[不能装
必须认真看或者找懂的人一起看]
RISK --> |涉及安全相关代码| STOP2[不能装
一个漏洞可能导致资损]
RISK --> |数据库 Schema 变更| STOP3[不能装
改错了数据回不来]
RISK --> |权限和鉴权相关| STOP4[不能装
出了问题是安全事故]
RISK --> |一般业务逻辑| OK1[可以适当使用话术]
RISK --> |格式和重构改动| OK2[话术完全适用]
RISK --> |文档和注释更新| OK3[随便看看就行]
STOP1 & STOP2 & STOP3 & STOP4 --> ACTION[要么认真看
要么明确说不够熟悉
请更合适的人 Review]
OK1 & OK2 & OK3 --> SKILL[合理使用话术体系]
style STOP1 fill:#ff6b6b
style STOP2 fill:#ff6b6b
style STOP3 fill:#ff6b6b
style STOP4 fill:#ff6b6b
style ACTION fill:#ffd43b
style SKILL fill:#51cf66
这不是在开玩笑。支付系统的一个漏洞,轻则线上故障,重则资金损失。在核心路径上假装看懂了,后果是实实在在要承担的。
第八章:如何成为一个真正好的 Reviewer
既然写了这么多「装懂」的技巧,也应该说说真正好的 Code Review 长什么样:
graph LR
GOOD[真正有价值的 Code Review]
GOOD --> G1[提问而不是命令
考虑用 X 方法可能更好 vs 必须改成 X]
GOOD --> G2[区分阻塞和建议
明确标注 Blocker 和 Nit]
GOOD --> G3[给出理由
为什么要改而不只是改什么]
GOOD --> G4[认可好的代码
不只是挑问题]
GOOD --> G5[关注意图而非风格
别在格式上浪费时间
交给 Linter]
GOOD --> G6[同步理解
不确定就先问再评论]
最好的 Reviewer 不是那个每次能找出最多问题的人,而是那个让作者在 Review 过程中成长的人。
结语:装懂是手段,看懂才是目的
graph LR
START[刚加入团队
什么都不懂] --> FAKE[靠话术撑过最初几个月]
FAKE --> LEARN[在话术的掩护下慢慢真的看懂]
LEARN --> REAL[成为真正有价值的 Reviewer]
REAL --> MENTOR[帮助下一个什么都不懂的新人]
MENTOR --> START2[新人靠话术撑过最初几个月...]
style START fill:#ff6b6b
style FAKE fill:#ffd43b
style REAL fill:#51cf66
style MENTOR fill:#74c0fc
Code Review 的话术,本质上是一种学习型假装——你用这些问题去探测代码的边界,在作者的回答里真正理解设计意图。每一个「边界场景怎么处理」的问题,背后都是你在建立自己的知识体系。
假装看懂是起点,不是终点。
如果三年后你还在用本文的话术,而没有进化成真正能看懂的那个人——那才是真正的放弃。