
看完这篇文章,你会发现自己过去很多 Code Review 可能都在浪费时间。
开篇:一个扎心的场景
你花两天精心实现了一个功能,提交 PR 后满心期待审核。结果等来 20 条评论:
- “这个变量名建议改成
userData”
- “这里用三元运算符更简洁”
- “建议把这段提取成独立函数”
逐条改完再提交,代码顺利上线。然后生产环境炸了——因为并发场景下的 race condition 没人发现。
这就是大部分团队 Code Review 的现状:把精力耗在不重要的细枝末节上,真正致命的问题反而视而不见。
作为一个前端老兵,我见过太多类似的情形。今天想聊聊高级开发者到底怎么做 Code Review,以及为什么他们的方法和你想的截然不同。
第一个颠覆认知:先问“该不该存在”,别管“写得好不好”
大部分人的审查顺序是错的
打开一个 PR,99% 的人第一反应是看实现细节:
- 变量命名规范吗?
- 函数拆分合理吗?
- 有没有遵循最佳实践?
但高级开发者的第一个问题是:这段代码应该存在吗?
我见过一个经典案例。某同学提交了一个完美的虚拟滚动实现,处理了边界情况,写了完整测试,代码优雅得让人想点赞:
// PR标题: 为表格添加虚拟滚动优化性能
function VirtualizedTable({ data, rowHeight = 50 }) {
const [scrollTop, setScrollTop] = useState(0);
const containerHeight = 600;
// 计算可见区域
const visibleStart = Math.floor(scrollTop / rowHeight);
const visibleEnd = Math.ceil((scrollTop + containerHeight) / rowHeight);
const visibleData = data.slice(visibleStart, visibleEnd);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: data.length * rowHeight }}>
<div style={{ transform: `translateY(${visibleStart * rowHeight}px)` }}>
{visibleData.map(row => <TableRow key={row.id} data={row} />)}
</div>
</div>
</div>
);
}
代码挑不出毛病。但问题在于:为什么要一次性加载 10000 条数据?后端 API 明明支持分页,为什么不用?
虚拟滚动解决的是“如何高效渲染大量 DOM”,可真正的问题是“为什么要在前端渲染这么多数据”。好比家里着火,你却去买个更好的灭火器,而不是找出起火原因。
真正的 Code Review 应该质疑解决方案本身,而不是解决方案的实现质量。
这需要跳出代码看问题:
- 理解业务背景和技术架构
- 思考有没有更简单的方案
- 判断这个 PR 是在解决症状还是病根
这种思维方式,才是高级开发者和普通开发者的分水岭。
第二个颠覆认知:懂得什么不重要,比懂得什么重要更重要
注意力是有限资源,别浪费在无关紧要的地方
我有个可能引起争议的观点:大部分代码细节根本不重要。
- 变量叫
userData 还是 user?无所谓
- 用
if-else 还是三元运算符?无所谓
- 10 个元素用
.map() 还是 for 循环?也无所谓
看到这里可能有人要跳起来:“代码质量很重要!细节决定成败!”
我没说代码质量不重要,我是说:注意力是稀缺资源,应该花在刀刃上。
建筑学里有个概念很有启发——承重墙和非承重墙。承重墙支撑整个结构,拆了房子就塌;非承重墙只是分隔空间,改了无伤大雅。
代码也一样。有些决策是“承重”的:
- 影响性能的算法选择
- 关系到安全的权限设计
- 决定可维护性的架构方案
这些值得死磕。但更多的是“非承重”的偏好:
// 这三种写法值得你留 10 条评论吗?
// 写法1
const isValid = data.name && data.email;
// 写法2
const isValid = Boolean(data.name && data.email);
// 写法3
const hasName = !!data.name;
const hasEmail = !!data.email;
const isValid = hasName && hasEmail;
写法 3 可读性可能好一点点。但值得让作者改代码、推新 commit、延迟功能上线吗?
高级开发者知道什么时候该闭嘴。 不是妥协代码质量,而是清楚:在无关紧要的问题上消耗团队精力,就没时间关注真正重要的事了。
第三个核心技能:预见什么会炸
线上事故都是可以预防的,如果你知道该看哪里
经历过几次生产事故后,你就会形成一种直觉——知道哪些代码模式容易出问题。
最容易被忽视的就是错误处理。 不是“有没有 try-catch”,而是“出错了会怎样”?
async function getUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
}
这代码开发环境跑得好好的。但上线后:
- API 返回 500 怎么办?
- 响应是 HTML 错误页而不是 JSON 怎么办?
- 网络超时了怎么办?
- 用户看到的是什么?白屏?报错?还是无限 loading?
初级 Review 可能会说:“加个 try-catch 吧。”
高级 Review 会问:“用户体验应该是什么?显示错误提示?重试?用缓存数据?哪种失败模式对用户最友好?”
这才是关键差异——不是检查代码在理想情况下能不能跑,而是思考在最糟糕的情况下用户会遇到什么。
所有系统都会失败。API 会挂,网络会断,数据库会超时,第三方服务会返回垃圾数据,用户会做你意想不到的操作。真正的代码审查,是在代码进入生产前,就预见到这些失败场景。
第四个维度:关注 PR 里没有的东西
最隐蔽的 bug 往往藏在“没写的代码”里
优秀的 Code Review 不只看写了什么,更要问:没写什么?
function useUserData(userId) {
const [data, setData] = useState(null);
useEffect(() => {
fetchUser(userId).then(setData);
}, [userId]);
return data;
}
这代码看起来没问题。但高级开发者会问:
“如果 userId 变化了,但上一个请求还没返回呢?组件会先设置旧数据,再被新数据覆盖吗?”
这就是经典的 race condition。用户快速切换 tab,页面显示错乱,没人知道为什么。
修复其实很简单,但前提是你要想到去找这个问题:
function useUserData(userId) {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
fetchUser(userId).then(user => {
if (!cancelled) setData(user);
});
return () => { cancelled = true };
}, [userId]);
return data;
}
这类问题需要你在脑子里“运行”代码:
- 组件卸载时会发生什么?
- 函数同时执行多次会怎样?
- 异步操作完成前用户跳转了呢?
线性阅读代码是不够的,你需要在多个维度上模拟执行。 这是经验积累的结果,也是高级开发者最值钱的技能之一。
第五个判断标准:知道什么时候该 Block
不是所有问题都值得阻止合并
这可能是 Code Review 里最微妙的技能:判断什么问题必须现在解决,什么可以后续优化。
必须 Block 的:
- ✋ 会导致数据丢失的问题
- ✋ 有安全风险的代码
- ✋ 影响用户体验的性能问题
- ✋ 会造成内存泄漏的逻辑
- ✋ 有 race condition 的并发处理
- ✋ 未来难以修改的架构决策
可以放行的:
- ✅ 可读性改进
- ✅ 不常执行路径的小性能问题
- ✅ 非核心功能的边界测试
- ✅ 代码风格偏好
对比这两段代码:
// 这个必须 Block - 会留下一堆脏数据
async function deleteUser(userId) {
await db.users.delete(userId);
// 等等,用户的帖子、评论、关系怎么办?
// 删不干净会导致数据一致性问题
}
// 这个可以放行 - 只是风格问题
function getUserDisplayName(user) {
return user.firstName + ' ' + user.lastName;
// 可以用模板字符串,但这样也能用
}
第一个会在生产环境造成数据混乱,第二个只是写法偏好。
判断标准很简单:这会导致生产事故吗?会让代码库显著难维护吗? 如果答案是否,建议改进但别阻止合并。
第六个沟通技巧:解释“为什么”,而不只是“怎么做”
好的 Review 是在教育,而不只是审查
对比这两条 Review 评论:
弱评论:“这里应该用 useMemo”
强评论:“这个计算每次渲染都会跑,处理 1000+ 项目。用 useMemo 包裹,依赖项设为 [items],可以避免其他状态变化时重复计算。我本地 profiling 显示渲染时间从 80ms 降到 5ms。”
差异在哪?
第二条评论不只说了做什么,还解释了:
- 问题是什么:每次渲染都重复计算
- 为什么重要:处理大量数据,影响性能
- 怎么解决:用
useMemo 缓存结果
- 效果如何:实际性能提升数据
当你解释“为什么”时,你不是在给指令,而是在传授思维方式。 开发者学到的不是规则,而是原则。下次遇到类似场景,他们就能自己判断该怎么做。
这在反对某种方案时尤其重要:
❌ “这个方法不对”
✅ “这个方法在下个 sprint 加入多用户功能时会出问题,因为它假设只有一个活跃用户。我们需要重构成支持多用户的结构,现在改比上线后改容易得多。”
第二种方式提供了上下文,让开发者理解更大的图景。这样的反馈引导理解,而不只是强制服从。
第七个长远视角:为未来的维护者写代码
今天写的代码,明天就会变成别人的噩梦
代码的生命周期远比你想象的长。今天你花 2 小时写的功能,可能半年后需要改动。那时候:
- 你可能已经忘了细节
- 可能是新同事在改
- 可能是凌晨 3 点线上出 bug 你在查
高级开发者审查代码时,想的是:“这段代码能不能脱离作者独立存在?”
// 缺少上下文 - 半年后没人知道 0.88 是什么
function calculatePrice(item) {
return item.basePrice * 0.88;
}
// 有清晰上下文 - 未来维护者能理解
function calculatePrice(item) {
// 扣除 12% 平台手续费 (0.88 = 1 - 0.12)
// 详见定价文档: https://docs.company.com/pricing
return item.basePrice * 0.88;
}
魔法数字 0.88 让人一头雾水。注释解释了它的含义和出处,未来的开发者不用到处找文档或问人。
同样重要的是识别“技术上正确但让人困惑”的代码:
// 能跑,但看着累
const isValid = !!data && !!data.name && data.name.length > 0;
// 同样逻辑,意图清晰
const hasValidName = data?.name && data.name.length > 0;
第二种写法更容易理解。有人快速浏览代码时,不用解析布尔逻辑就能明白在检查什么。
可维护性不是奢侈品,而是必需品。 每次 Review 都是在为未来投资,减少技术债务,让代码库保持健康。
第八个判断力:知道什么时候该当面聊
有些问题靠评论解决不了
当一个 PR 方向根本就错了,你写再多评论也没用:
- 架构选择有根本问题
- 对需求理解有偏差
- 技术方案从起点就跑偏
这时候别写长篇大论的评论,直接拉个会聊。
“嘿,我觉得咱们应该聊一下这个 PR。我对整体方案有些担忧,实时讨论会比来回留言快。”
当面讨论的好处:
- 作者避免走弯路浪费时间
- 审查者不用打字打到手抽筋
- 双方能快速达成共识
- 可以即时澄清误解
Code Review 评论适合处理具体的、局部的修改建议。对于需要重新思考整体方案的情况,它就是错误的沟通方式。
知道什么时候切换沟通渠道,这是高级开发者的关键软技能。
第九个平衡术:信任与教导之间的微妙关系
Code Review 最难的不是技术,是人
做 Code Review 最难的部分不是技术判断,而是社交平衡。
你需要:
- 信任作者经过了思考,有他们的理由
- 怀疑他们可能没看到某些坑
- 教导他们不知道的模式和风险
- 尊重他们的决策,即使和你的不同
这种平衡很难把握。太严格会打击积极性,太宽松会放过问题。
一个技巧是从好奇开始,而不是纠正:
❌ “你应该用方案 Y 而不是 X”
✅ “我好奇为什么选择方案 X 而不是 Y?我之前遇到过 Y 在这种场景下更合适的情况”
第二种说法:
- 假设作者有理由(也许他们试过 Y 不行)
- 表达了你的经验,但不强加观点
- 开启了对话而不是命令
- 也给你机会学习(也许他们知道你不知道的事)
同时,该教的时候别藏着掖着。初级开发者不知道自己不知道什么。你发现的模式和坑,如果不解释清楚,他们下次还会犯。
这种社交技能——在信任、教导和共情之间找平衡——才是区分高级审查者和一般审查者的关键。
最后的底线:完美不是目标,出货才是
Code Review 不是追求完美,而是在保证质量的前提下让代码尽快上线。
每条评论都有成本:
- 作者要花时间修改
- 功能上线被延迟
- 可能产生团队摩擦
所以问题不是“什么地方可以更好?”——因为代码永远可以更好。
真正的问题是:“什么必须在上线前改变?”
这个列表要短得多:
- ✋ 会造成生产事故的问题
- ✋ 会让代码库难以维护的决策
- ✋ 有明显安全隐患的实现
其他的:
- ✅ 作为下次的改进建议
- ✅ 写在文档里给未来参考
- ✅ 作为个人偏好就别提了
最好的 Code Review 是那些发现了微妙的 race condition 或架构问题,避免了几周痛苦的 Review。 不是那些纠结变量命名和格式的 Review。
知道区别。审查真正重要的东西。
写在最后
如果你读到这里,我希望你能重新思考 Code Review 的目的。
它不是展示你知道多少规则的舞台,不是刷存在感的机会,更不是追求完美代码的工具。
Code Review 的真正价值在于:在代码进入生产前,用经验和判断力筛选出真正重要的问题。
下次做 Review 时,问问自己:
- 这个问题会导致生产事故吗?
- 这个建议会显著提升代码质量吗?
- 这条评论值得占用团队的时间吗?
如果答案是否,就让它过。把精力留给真正重要的战斗。
因为注意力是有限的,leverage 才是无限的。