← Back to Blog

代码评审不是找茬:把它做成团队质量闸门的三层设计

102 阅读 点赞

代码评审(Code Review)大概是团队里最容易被做坏的一件事。它本该是一道质量闸门,结果常常变成两种极端:要么是走过场的”LGTM”+1,评审记录里只剩下礼貌的点头;要么是逐行的挑刺大会,把一次技术讨论变成关于空格、命名和”我觉得这样更好”的口水战。更麻烦的是第三种——把它当成打卡任务,为了完成流程而审,看完什么都没记住。

我做过很长时间的评审者,也做过被评审的人。回头看,真正让评审产生价值的从来不是”审得多细”,而是”审得对不对”。下面这三个层面,是我认为一次有效代码评审必须同时打通的东西。

极简科技风格的磨砂玻璃评审台,多块半透明玻璃面板层叠排列
一次好的代码评审,不是逐行挑刺,而是把注意力放在机器看不出来的地方

第一层:评审什么,比评审多少更重要

新手评审最容易陷入的是”逐行扫读”。打开 diff,从第一行看到最后一行,看到变量名不够好就提一句,看到函数太长就提一句,看到缺个注释也提一句。这种评审看起来很努力,实际上抓错了重点——它把大量注意力花在工具(linter、formatter、IDE 提示)本来就能解决的问题上,而不是花在只有人才能判断的问题上。

真正需要人眼判断的,是那些机器看不出来的东西:这段逻辑的边界条件对吗?这个抽象是不是引入了一个未来一定会破的假设?这个改动会不会让某个调用方在极端情况下拿到错误结果?这次的方案,是否解决了问题本身,还是只压制了症状?

我会把评审的关注点分成三档,按顺序看:

第一档,正确性。这段代码在正常路径上对不对,在异常路径上会不会炸。有没有并发问题、有没有空指针、有没有漏掉的错误处理。这一档如果过不了,后面都不用看。评审者要做的就是模拟执行——脑子里跑几条数据,特别是边界值:空、零、最大、重复、超时。

第二档,可维护性。半年后接手的人能不能看懂?命名是否表达意图?这段逻辑放在这个位置合不合适?新增的依赖是不是必要的?这一档决定了代码的寿命。

第三档,风格一致性。格式、命名习惯、目录结构。这一档的优先级最低,因为它应该被自动化工具挡在前面。如果团队的 formatter 和 linter 配置到位,评审里就不该出现”这行少了空格”这种留言。

把这三档分开,最大的好处是让评审者有取舍。时间有限时,宁可把正确性看透,也不要花 20 分钟纠结命名。评审的价值密度,取决于你有没有把注意力放在错误成本最高的地方。

第二层:怎么评,决定了对方能不能接住

同样一句意见,说法不同,效果能差出一个数量级。我见过太多评审者用陈述句下判决:”这里应该用 map 而不是 for 循环。” 对方看到的是什么?是一个没有解释的决定,一个隐含的”你这里做错了”。人的第一反应是防御,然后是争论,最后是妥协或者敷衍。技术讨论就这样被消耗掉了。

更有效的做法是把话重新组织成一个可讨论的问题。比如:”这里用 for 循环我理解是为了提前 break,但如果数据量小、可读性更重要,用 map 会不会更容易看懂?” 这带来的差别是:你摆出的是考虑和取舍,对方回应的也变成了技术判断,而不是面子。

我自己的几条习惯,用下来比较管用:

区分”必须改”和”可以聊”。明确标出来哪条是阻塞项(bug、安全问题、明确的规范违反),哪条只是建议。混在一起会让对方不知道哪些真的不能过。我通常会在必须改的留言前加”必须”,在建议前加”可选”或”题外话”,几秒钟的成本,省掉一轮来回。

先问,再给方案。“这里为什么没走缓存?”往往比”你应该加缓存”更有价值。因为对方的回答可能包含你不知道的约束——比如上游数据有实时性要求。如果是这样,你的意见就不成立;如果不是,你的问题本身就已经引导对方自己想通了。

肯定值得肯定的部分。这不是客套。评审里明确说出”这里的错误处理比之前清晰多了”,一是告诉对方什么行为是你欣赏的,二是让后面的批评更容易被听进去。全部是红字的评审,对方的注意力会自动降级为”扫一眼、改一下、交差”。

留言要能落地。“这里可以更好”是最没用的一句评审意见。要么给出具体方案,要么给出判断标准,要么就直接删掉别说。

三层玻璃漏斗式数据可视化,细碎光点经三层玻璃环逐层收敛汇聚成束
评审的关注点应当像漏斗一样分层收敛:正确性 > 可维护性 > 风格一致性

第三层:流程设计,让评审不靠自觉

就算评审者水平都很高、沟通都很得体,如果流程设计有问题,评审依然会退化。我见过最典型的三种退化:

巨大 PR。一个 PR 改了 2000 行、涉及 15 个文件。评审者会疲劳,注意力在前三十分钟就消耗完了,后面几百行基本都是”扫过”。研究结论很一致:评审效率在单个 PR 超过 400 行后急剧下降,超过 1000 行时漏掉的缺陷比例会显著上升。解法不是”你认真点”,而是拆。把一个大改动拆成”重构 + 行为变更”两个 PR,前者只有结构变化、不改变行为,后者只改行为,评审者可以分别聚焦。

没有上下文。PR 描述只有一句”fix bug”。评审者得自己翻 issue、自己猜意图、自己推断哪些行为是故意保留的。这不公平,也低效。一个合格的 PR 描述应该回答三件事:这个改动要解决什么问题?你选择了什么方案、为什么不用其他方案?哪些地方你自己也不确定、希望重点看?最后一条尤其重要——它会直接把评审者的注意力引到你最薄弱的环节上。

评审时间不确定。如果 PR 提上来要等两天才有人看,作者的上下文早就切走了,回来回答问题时得重新加载一遍记忆,评审成本翻倍。让评审有节奏——比如每天固定两个时间段集中处理待评审 PR,或者约定”四小时内给首次响应”——比要求”尽快”有用得多。首次响应只要求”我看过了、我什么时候细看”,不用给出完整意见,但能极大减少等待焦虑。

还有一个容易被忽略的点:让评审的历史留下来。那些在留言里被讨论清楚的技术判断,不应该随着 PR 合并就消失。我会在团队的文档里维护一份”评审中反复出现的判断”,把”为什么我们不用 X 方案””这个模块的错误处理约定是什么”沉淀下来。下次有人问同样的问题,直接给文档链接,而不是再辩论一遍。

赛博朋克风格的深夜开发者桌面,屏幕幽蓝光芒与城市霓虹
流程设计决定了评审能不能持续——它不该依赖任何个人的自觉和状态

结语

代码评审最反直觉的一点是:它表面上在评审代码,实际上在传递标准。一次好的评审,会让作者下次自己就能发现那类问题;一次坏的评审,只会让作者下次写得更小心地藏起问题,或者干脆少提交、多等待。

这三层是依次递进的。先搞清楚评审什么——把注意力放在机器看不出的地方;再学会怎么评——让对方能接住你的意见;最后把它做成流程——不依赖任何个人的自觉和状态。三者齐全,评审才会从一个打卡环节,变成团队里回报率最高的技术活动之一。

如果你所在的团队现在评审只是走流程,不用一次性全改。先从最小的一步开始:下次提 PR 时,多写三行描述,说清楚意图和你不确定的地方。你会很快看到评审质量的变化。

💬 发表评论