ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

代码审查最佳实践:从形式主义到真正的质量闸门

代码审查最佳实践:从形式主义到真正的质量闸门

代码审查这件事,我在三个团队里做过三种完全不同的姿势。第一个团队基本不审,合并全靠自觉,线上事故一年几次;第二个团队审了但流于形式,评论里全是“LGTM”和“变量名建议”;第三个团队才真正把代码审查做成了质量闸门。所以每次有人问我代码审查到底有没有必要,我的回答从来不是“很有必要”这种空话,而是反过来先问一句:你打算怎么做?本文就围绕代码审查的必要性和最佳实践,把我踩过的坑、试出来的流程、沉淀下来的检查清单一次讲清楚。它适合刚开始引入审查的团队,也适合那些已经审了但觉得没效果的开发者和技术负责人。

1. 代码审查的必要性:它到底在解决什么问题

1.1 质量闸门:给代码配上第二双眼睛

很多团队的第一反应是:我自己写代码的时候已经很小心了,单元测试也写了,CI也过了,代码审查不是多此一举吗?这里有个根本误区:编译器和静态检查只能抓语法、格式、空指针这类规则明确的问题,但抓不住“这个接口设计不合理”“这个分支逻辑走不到”“这块复杂度以后没人改得动”。它们只是代码的“拼写检查”,不是“逻辑校对”。

代码审查的核心价值,是让另一个拥有上下文的人重新读一遍变更,验证“需求是不是真的被正确实现了”,而不是“代码看起来能不能跑”。我自己写代码时容易陷入思路惯性,会顺着自己的脑回路默认为某段逻辑是合理的,这个盲区靠自己是很难看出来的。换一双眼睛之后,很多原本被忽略的边界条件、异常分支、并发冲突都会暴露出来,这就是代码审查最朴素的必要性:减少由于思维惯性导致的“想当然”。

我经常拿写文章打比方。自己写完一篇文章检查三遍,依然会漏掉错别字和逻辑断层,但别人一眼就能看出来。代码比文章更严格,因为代码里的微小逻辑错误不止影响一份稿件,它可能影响一次支付、一批订单、一台设备的运行。代码审查就是那个在你发出“稿件”之前,帮你认真读一遍的人。

1.2 缺陷成本:越早发现,成本越低

为什么要花人力去提前看这些“还没爆发的bug”?因为缺陷的修复成本是随着发现阶段往后推移而快速上升的。设计阶段发现需求矛盾,改一页文档就行;编码阶段发现逻辑问题,改几行代码重跑测试就行;代码审查阶段发现,提交前改完、补上测试,成本和编码阶段差别不大。但等合并进了主干,测试阶段才发现,你可能要花半天去定位、复现,改完还要做回归验证。如果一直漏到线上,那就是事故:用户反馈、日志排查、紧急回滚、修复发版、复盘报告,一次线上故障消耗的人力往往超过十次代码审查的总和。

我团队里有过一次很典型的例子:一个活动配置系统因为一个边界条件没被关注,当时在审查会上被提了出来,提交者在合入前补了两行判断。结果第二周线上真的出现了类似的数据异常,但已经被提前拦住了。那次之后,组里基本没人再质疑“审查到底值不值得花时间”,因为大家都明白,审查阶段发现问题的成本几乎为零,而线上发现问题时,成本是几十倍、上百倍。

这也是我理解代码审查必要性的核心逻辑:它不是额外增加的工作量,而是把“未来可能要花的时间”提前支付了一部分。用流行的说法叫做“时间换空间”,把问题的发现从线的末端挪到源头,这比事后救火要划算得多。

1.3 知识传递:审查是异步结对编程

代码审查另一个常被低估的作用是知识传递。一个后端工程师写完支付模块,另一个同事在审查时问“这里为什么这样设计”,作者要解释,审查者要理解,这个过程中双方都完成了对这段代码的共同认知。以后这块代码出了问题,不再只有作者一个人能救火,至少还有一双曾经认真看过的眼睛。

这个价值在团队人员流动时格外明显。如果某个模块只有一个人写过、看过、维护过,其他人都不敢碰,那就是典型的单点故障。哪怕没有人员离职,一旦负责人休假,模块就进入停滞状态。如果模块每一行代码都经过另一个人的审查,即使那个人没有亲手写,他对核心链路、异常处理、边界条件也有基本了解,接手成本会低很多。

从这个角度看,代码审查还是一种可积累的组织资产。团队里每个人读过的PR、提出的问题、被修正过的理解,都会变成隐性知识沉淀下来。长期坚持之后,新人能通过阅读别人的PR快速学会团队写法,老人能在解释自己代码的过程中重新审视自己的设计。这也是为什么我一直坚持:代码审查的最佳实践里,一定包含心态上的转变——它不是“检查你有没有做好”,而是“我们一起把这段代码弄明白”。

1.4 反过来推动大家写出更清晰的代码

很多人没注意到的是,代码审查本身会改变作者写代码的方式。当你知道这段代码会被另一个人逐行检查时,你会不自觉地做几件事:把变量命名理清楚,把无关的改动拆开,把复杂的函数拆成更容易解释的小函数,把注释写在真正需要解释的地方。

这种“因为要给人看,所以写得更认真”的效果,是一种正向的自我约束。就像你知道桌上要放一份给客户看的文档时,自然不会随手写一堆乱七八糟的草稿。代码审查做得越多,团队代码的可读性就会越好,因为每个人都学会了站在读者的角度写代码。这也是很多团队在做了一两个季度审查后普遍发现的一个事实:审查意见变少了,不是因为大家不再认真看,而是因为提交者的代码质量本身提高了。

2. 为什么多数团队的代码审查流于形式

2.1 心态跑偏:把审查当成审批或者挑刺

代码审查最容易死掉的第一种心态是“审批心态”。审查者觉得自己是门卫,生怕出了问题背锅,于是不信任任何提交,要么不看内容直接打回,要么找到一点小毛病就要求修改,逼得作者不得不防御性修改。另一种心态是“挑刺心态”,审查者专门盯命名、缩进、注释,评论冒出一大堆“这里应该加个空格”“这个名字看不懂”,却对真正的逻辑漏洞视而不见,最后作者被淹没在琐碎的意见里,身心俱疲。

这两种心态都会让审查迅速变成形式主义。正确的出发点应该是:审查者不是最终裁决人,而是这段代码的共同责任人。你评论的是代码,不是人;你的目标是帮助这次变更变好,而不是证明自己比作者高明。具体到评论方式上,我建议多用提问而不是命令,多说“这里会不会出现某种情况”,而不是“你这个写法不对”。提问式评论天然带着讨论的味道,命令式评论容易把整个PR变成辩论赛。

我见过太多团队最后Review模块里的对话全是“建议修改”“已修改”“同意”,却没有真正的讨论,这就是典型的形式主义。代码审查如果失去了“讨论感”,那它和机器跑一遍质量检查没有任何区别。

2.2 流程设计败笔:一个PR塞进几千行

代码审查流于形式的第二个常见原因,是变更本身拆得太烂。我见过一个PR同时包含一个数据库表结构变更、一个接口逻辑重写、一次前端样式调整,还顺带做了几轮代码格式化,总变更量超过三千行。这种PR根本没法审,reviewer看完前一百行就已经晕了,后面只能目送通过。

要让审查真正有效,有一个绕不开的最佳实践:小步提交,单一职责。一个PR尽量只做一件事,功能开发和重构要拆开,重构和格式调整也要拆开。新功能尽量拆成几个有顺序、有依赖关系的小PR,按依赖顺序合并。这样做的好处是,每一次合并都可以独立审查、独立测试、独立回滚,出问题的时候也更好定位。

同时,PR描述本身也是流程的一部分。如果一次提交的描述只有一句“fix bug”,审查者连背景都要靠猜,那审查的门槛就被人为抬高了一大截。好的流程会让提交者主动提供足够的上下文,让审查者把精力放在判断上,而不是放在考古上。

2.3 没有自动化兜底,人工全去看琐碎问题

第三个失败原因是人工承担了太多本该由工具完成的事。如果一个团队连格式化、Lint、基础单元测试都没有接入自动检查,reviewer就不得不花大量时间去评论“这里多了个空格”“缩进不对”“import排序要调整”。这些意见本身没有错,但它们是机械的、低密度的,很快会把审查者的精力和耐心消耗完。

正确分工是:机器能判定的问题,尽量在PR阶段就由自动检查拦住,不要让人来看。人工审查应当聚焦在机器做不到的事情上:业务逻辑是否正确、边界是否安全、设计是否合理、后续是否好维护。前面也说了,静态检查能告诉你某个变量可能为空,但它不会告诉你这个位置返回空值是不是一个合理的设计选择;CI能跑通单元测试,但它不会思考这项变更是否偏离了需求本意。自动化是审查的前置过滤器,它可以减少人看的噪音,但永远替代不了人看。

2.4 审查结果没有闭环

形式主义还有一个典型特征:合入后,评论里的问题没有责任人。作者可能看到了评论,但不知道哪些是必改,哪些是建议;审查者也懒得追问,最后代码带着一堆未解决的讨论项就进了主干。一次两次之后,大家默认“Review就是走过场”,下一次提交和审查都会变得更敷衍。

所以流程里必须明确闭环规则:必须修改的项不处理完,PR不准合并;作者要逐条回应评论,哪怕回复“不改,原因是……”也算回应;平台如果没有强制拦截,团队也要约定一个“红线”。这个话题在下一部分讲最佳实践时会具体展开,这里先给出一个判断标准:你们团队的合并请求里,是否还残留着未解决的讨论?如果有,说明流程已经松了。

3. 代码审查最佳实践:从提交前到合并后的完整流程

3.1 审查前:把PR描述写成“队友读得懂”的样子

高效的代码审查,一半的工作在提交者这边。我见过最快的审查,几乎打开PR描述就已经知道要看哪里、重点检验什么;我也见过最慢的审查,reviewer要在几百条提交记录里翻背景,来回问“这个问题从哪来的”“为什么要动这里”。差别不在审查者的水平,而在提交者给的上下文。

一个实用的PR描述模板可以覆盖我总结的四件事:目的、改动概览、测试说明、风险与注意事项。

部分要写什么为什么需要
目的与背景这次改动解决什么问题,关联什么需求或工单让审查者知道验收标准是什么
改动概览改了哪些模块、哪些文件,变更如何组织让审查者快速建立代码地图
测试说明手工验证了哪些场景,自动测试覆盖哪些用例减少审查者的重复验证成本
风险与注意事项可能影响哪些调用方、回滚方案、降级策略让审查者聚焦在风险最高的区域

我团队里公开推荐的模板大致长这样:

## 目的 一句话说明这个PR要解决什么问题。 ## 背景 补充问题场景、相关需求、历史尝试,方便reviewer快速理解。 ## 改动点 按业务模块或目录层级列出变更点,不要只是列文件清单。 ## 测试验证 - 单元测试:新增XX用例,覆盖XX边界 - 手工验证:运行XX命令,构造了XX数据 ## 风险说明 - 是否影响已有接口/数据存储 - 是否存在迁移/回滚风险 - 是否有需要灰度观察的点 ## 自检 - [ ] 已运行本地格式化与静态检查 - [ ] 已补充/更新相关测试 - [ ] 已在目标环境完成冒烟验证

别小看这个模板,很多团队把PR描述质量提升起来之后,审查响应时间肉眼可见地下降。因为审查者在拿到一个描述清晰的PR时,不需要先花20分钟读上下文,直接就能给出有质量的反馈。

3.2 审查中:分层看代码,而不是从头扫到尾

很多reviewer的习惯是从第一个文件点到最后一个文件,这其实很低效。更好的做法是分层审查,每一层关注不同维度。

第一层看整体设计。这个改动放在当前模块是否合适?接口设计是否合理?有没有重复造轮子,有没有新的循环依赖,是否在错误的位置处理了逻辑?这一轮不需要逐行读,先把核心文件的接口、抽象、调用关系看明白。

第二层看关键逻辑。进入核心函数后,逐行读数据流和分支,重点关注边界条件、异常分支、并发场景、权限校验、事务边界。这个阶段才是真正体现代码审查专业性的地方,也是发现问题最多的环节。

第三层看杂项。命名、注释、死代码、调试日志、临时标记等。能自动化处理的交给自动化,人工在这一层只花少量时间,重点是看那些影响后续可维护性的点。

为了方便落地,我把常用检查点整理成了清单。审查不同性质的PR时,不需要每次都完整过一遍,按变更类型挑重点就行。例如改动配置时重点查安全和回滚,改算法时重点查边界和性能。

检查维度常见问题举例
逻辑正确性分支条件是否写反、边界值是否取错、状态转换是否完整
边界与异常空值、超长、并发、重复请求、部分失败如何处理
安全输入校验、权限控制、越权访问、敏感信息泄露
性能循环内查询、N+1问题、大对象加载、缓存缺失
可测试性是否能方便地构造输入输出,是否容易单测 lokal
一致性命名、风格、错误处理方式是否与现有代码一致
可维护性功能是否过于集中、注释是否解释“为什么”而非“是什么”

审查过程中写评论也有技巧。不要用多种不同表达重复同一个意见,尽量合并成一条总览;不要看到小问题就立刻零散评论,先看完整段代码再一起提。评论原则上区分优先级,我习惯用三级:

  • 必须修改:不修会出现bug、安全隐患,或明显偏离需求。这类评论不解决,不允许合并。
  • 建议修改:不是缺陷,但会影响可维护性、一致性。作者可以不改,但需要给出理由。
  • 纯建议:个人偏好,可做可不做。作者可以简单回复,避免话题无限延伸。

这种分级最大的好处是让作者知道哪些问题需要立刻处理,哪些可以放一放,避免在“作者觉得没必要、审查者觉得不放心”的灰色地带反复拉扯。

3.3 审查后:作者要主动闭环,别让PR“死”在列表里

作者收到评论之后,最忌讳的是默默改完代码就重新推送,不解释、不回评论、也不标记讨论状态。审查者看了看,发现之前的疑问没有任何回应,只好再问一遍;提问者觉得“你根本没看我的评论”,被提意见的又觉得“我改了但你没看到”,整个PR就在这种沟通损耗中拖了很久。

好的闭环动作是:作者对每条评论给一个明确回应。“已修改,提交在新版本中”“不修改,原因是……”“这个意见我记录了,后续单独优化”都算有效回应。尤其对“建议修改”和“纯建议”类的意见,一句“明白了,先不处理”比沉默好得多。

修改完成后,主动重新触发review,不要等着别人发现你更新了。很多平台都有重新请求review的功能,作者要用起来,否则PR会埋没在长长的列表里,白白增加排队时间。合入之后,如果这次变更后来引发了回滚或线上问题,不要只归因到作者身上,可以把原因补到团队的知识库里,看看是审查时漏了哪类问题,流程上缺了哪个环节。这不是追责,而是让代码审查这个习惯持续进化。

3.4 自动化与人工怎么分工

我在第二部分说过,没有自动化兜底的审查,最后都会耗死在低价值评论上。所以最佳实践中一定要有清晰的自动化分工:提交者在推送后,自动检查先跑;不通过,根本不进入人工审查环节。检查项可以包含格式化、Lint、静态分析、单元测试、构建和部署前校验。这些都是事实校验,机器做比人做稳定得多。

自动化跑完之后,剩下的时间才轮到人做价值判断。人的优势在于理解上下文:这个需求为什么这么定义,这个接口的未来演进是什么,这个改动的长期维护成本能不能接受。我需要强调一个原则:人的判断不能被自动化完全替代。比如一个AI辅助工具可以帮你扫出很多类似的模式问题,但它是基于统计,不是基于你对业务的理解;它提出的“建议”可能听着合理,但放到当前业务背景下反而是错的。

我现在的习惯是把AI工具当成“第一遍草稿审查”,让它快速标出明显的安全风险、重复代码、可疑逻辑,我再根据业务上下文做二次判断。真正重要的PR,最后拍板的一定是人。自动化负责提高效率,人负责兜底质量,这两者不是二选一,而是流水线上的两个环节。

4. 常见问题与排查技巧:让代码审查真正落地

4.1 审查积压、PR排队怎么办

很多团队失败在审查响应太慢。早上创建PR,下午没人管,第二天还在队列里,作者干脆在私聊里挨个求人看。这种情况下,审查体验极差,大家逐渐把PR推到最后一起批量合并,审查自然名存实亡。

要让审查保持节奏,建议给首次响应定一个SLA。比如“24小时内必须有人第一次回复,哪怕结果是‘我看完了,逻辑上没问题,等CI过就合’”。注意这里的SLA是首次响应,不是要求24小时内审完。它传达的信息是:“你的PR没有被人遗忘”,这对作者的体验非常重要。

如果经常出现没人看的情况,说明审查负担分配有问题。可以试每周指定一个“review值班人”,专门负责当天新提交PR的初审和分发;也可以每周站会花五分钟过一遍open PR列表,由值班人重新分配Reviewer。如果某个PR阻塞超过两天,升级给技术负责人判断是否继续等、还是安排人手支援。

还有一个常见的坑是“紧急修复”被滥用。真正的线上事故可以走紧急通道先合并后补审,但每次这么做都应该登记为一笔“review债”,24小时内补齐审查和事后说明。如果紧急通道每周用上好几次,说明你们的流程不是紧急,而是日常没有走对路。

4.2 风格争论和“我更喜欢”问题

代码审查中最容易引发情绪消耗的争论,往往不是逻辑问题,而是风格偏好。A说“这里应该用early return”,B坚持“现在的if嵌套没问题”;A说“函数名应该叫extract”,B认为“parse更直观”。两个人各有各的道理,最后为了一个不影响正确性的命名来回拉锯,一个PR拖上两三天。

我处理这种分歧有一个硬性原则:编码规范里明确写了,按规范走,不要在PR里反复讨论;规范里没写的,提议者不能把它当成“必须修改”的阻塞性评论。如果你希望这个风格以后成为团队标准,正确做法是单独提一个规范变更,在团队评审中讨论,而不是借某个PR的评论区推行个人偏好。

规范的更新也要有版本记录。一旦某个风格问题被反复提出,就说明它值得被写进规范文档,哪怕只是写一条“无特殊理由时使用某种写法”。下次再有人提同样的问题,直接给链接,就不用在评论里翻来覆去说原理了。规范不是用来限制讨论的,而是用来终结重复讨论的。

4.3 新人不会审、不敢审怎么办

不少团队里新人是不参与审查的,原因是“刚接手,别人的代码我看不懂,怎么敢提意见”。这个心态其实可以理解,但也恰恰是错误的使用方式。新人提问的价值不在“提得对”,而在“能暴露不清楚的地方”。很多你以为“基本常识”的代码,在新人眼里其实很难理解,而“别人看不懂”本身就是可维护性差的信号。

我给新人建议的第一种评论方式就是提问。不需要你下结论,不需要你说“这里错了”,只需要问:“这里为什么不用已有的那个函数?”“这块逻辑我想了一下没明白,能不能加个注释?”这种问题很容易被作者接受,也能让作者意识到,自己的代码在别人眼中长什么样。

想培养新人的审查能力,最好的方式是“先结对,后独立”。新人跟着一位有经验的同事一起看一个PR,看对方先看哪些文件、怎么判断优先级、如何写评论,然后让新人自己独立描述一遍这个PR的逻辑。独立审查的最开始,可以从优先级最低的“纯建议”开始,慢慢过渡到能提出“必须修改”级别的问题。这个过程不用急,让它自然发生。

4.4 用数据找瓶颈,而不是用数据考核人

我曾见过一个团队引入了完整的审查工具和平台,但很快大家就不愿意认真写评论了。原因很简单:管理者开始统计“每个人提了多少条意见”“每条意见修改了几次”,并把数据当成变相的绩效考核。于是审查者开始刷评论数量,被审者开始委屈,整个项目很快退化成一场症状式的表演。这件事给我留下的教训很重。

度量数据本身没有错,错的是使用方式。我建议记录三类数据就够了:PR首次响应时间、PR从创建到合并的耗时、未解决问题数量。这三个指标是用来找流程瓶颈的,比如“响应慢是因为人员分配不均”“合并慢是因为PR经常拆得太大”“未解决问题多是因为评论分级不清晰”。不要拿这些数据去给个人排队、打分、排名,那样只会培养出一切以“过关”为导向的形式主义。

最后再分享一个我坚持了很久的小习惯:每隔一两周做一次“Review复盘会”。拉出最近合并的10个PR,公开讨论哪些评论是有价值的,哪些是噪音,哪些类型的bug在审查中反复漏掉。通过这种复盘,团队会自然沉淀出新的检查项和规范,也会让每个人都觉得审查真的在起作用。我在实际项目里的体会是,一旦这种习惯建立起来,代码审查就不再是流程制度,而是一种团队自觉;它给团队带来的影响,远不止减少bug那么简单。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进