1. 代码评审到底在评什么——先从需求和心态说起
做研发这些年,我见过不少团队把代码评审(Code Review)当成“上线前的关卡”:CI 过了、测试过了,代码丢给搭档点个“通过”,就算评审完了。也有人把评审当成“挑错大会”:评论里全是“这里要加注释”“那里命名不规范”,提交者看得心烦,评审者看得心累,最后流于形式。我一直觉得,这都不是代码评审本来的样子。
我参与推进的这套 open-code-review 实践,最终想解决的其实是三个很朴素的问题:这次改动到底在做什么、它做得对不对、它会不会给后面的人挖坑。如果你的评审没有在回答这三个问题,那不管流程多严谨、平台多强大,都只是在走一个空壳。
1.1 评审不只是挑毛病:它是在回答三个问题
第一个问题“在做什么”,听起来简单,实际操作中特别容易被忽视。很多提交者上来就甩出几百行 diff,标题写“修复登录问题”,然后呢?为什么会有这个问题?这个修复思路是什么?影响范围有多大?
我要求团队在提交 MR 时,描述区必须填清楚三件事:背景、方案、影响面。没有这三个要素,我不看代码。这不是为难人,是给评审者省时间。评审者看到的 diff 是结果,但代码评审最重要的是还原过程——只有理解了作者当时的约束条件和选择逻辑,你才能真正判断这个实现“值不值得”。否则你只能看懂语法,看不懂意图,最后给出的建议全在表层打转。
第二个问题“做得对不对”,是大多数人心里的评审。但我要说,“对”这个字太绝对了。在我看来,更准确的表达是:这个实现是不是在现有约束下最合理的选择。约束包括技术栈、时间、团队水平、既有架构的兼容性。评审者不能拿“最佳实践”“设计模式”去套一切,很多代码看起来不够优雅,是因为在当时的场景里它就是最优解。评审的价值在于帮作者识别“表面丑但实际合理”和“看似漂亮但引入了问题”之间的差别。
第三个问题“会不会挖坑”,最容易被漏掉。我见过太多新功能上线没问题,三个月后接手的人无从下手;也见过为了短期性能优化,引入了一个几乎不可维护的状态管理方案。代码评审的复利效应全在这了——你所省下的每一分钟,背后都可能藏着未来别人花十小时去填的坑。
1.2 为什么很多团队的评审越做越僵
先说结论:大多数评审走形,不是因为人不行,而是因为评审没标准、没重点、没节奏。
没标准,指的是评审者和提交者对“什么算好代码”没有共识。两个人对一个函数该不该拆成三个,争论半天,其实谁都没错,只是风格不同。没有共识的评审,最后比的是嗓门和资历。
没重点,指的是看见什么评论什么。有人把大量时间花在空格、缩进、命名上,真正影响架构、性能、稳定性的点却没人细看。我见过一个团队,一次评审里 80% 的评论是格式层面,核心的并发问题谁都没提。
没节奏,更常见。有人攒了五天的工作量一次性提交,评审者面对 2000 行 diff 无从下嘴。还有人工作日天天被评审请求轰炸,干脆统一“下班前批完”,速度有了,质量没了。
open-code-review 这套实践的第一原则,就是先把“评审什么、不评审什么、什么时候评审”定下来。格式问题交给工具,风格问题靠团队规范,评审者只做机器做不了的事:判断设计与逻辑。这一条如果做不到,下文所有的清单、流程、工具配置都白搭。
2. 让评审真正跑起来:我的落地流程与检查清单
我下定决心推进这套实践,是因为一次足够痛的上线事故。某次改版涉及核心服务的认证模块,代码写得挺规整,CI 也过了,但评审只走了一遍流程,没人细看那个会话状态的清理逻辑。结果上线后偶发串号,排查了整整两个晚上。代码没错在一些显而易见的地方,错在“下一个请求是否复用缓存状态”这类边界里。那之后我就意识到:没有一套明确流程和检查清单的评审,就是在赌运气。
2.1 从一次失败的提交开始
那次事故之后,我在团队里做了一次复盘。复盘的结论很简单:谁都没“故意”放过问题,但所有人都默认“别人会看”。提交者觉得反正有人评审,细节晚点再补;评审者觉得大方向对就行,细节提交者应该自己已经把过关。责任被流程模糊掉了,问题就被漏掉了。
open-code-review 的第一个改动,就是明确“谁为这次提交的质量兜底”。答案只有一个:提交者自己。提交之前,作者必须拿着 checklist 过一遍自己的代码,确认没有低级问题,才允许发出去。评审者的职责是发现问题,不是帮作者做自检。
这套实践也改了 MR 描述模板。模板要求必须写明:改动目的、关联需求、重点评审区域、自测情况、影响范围。有了这个模板,评审者拿到 MR 后能在两分钟内理解上下文,而不是悬空评论。
2.2 一套可以照着做的审查清单
我整理了一套四层检查清单,分享出来供大家参考。它不像很多网上的清单那么宏大,每一条都来自实际踩坑。
第一层:逻辑正确性。这是评审的重中之重。核心路径是否与需求一致?边界条件是否覆盖?异常分支有没有合理处理?状态是否在必要时重置?有没有并发访问共享可变状态?有数据变更的操作有没有考虑幂等?这一层占评审精力的 50% 以上。
第二层:架构契合度。这个改动是否在现有分层里待在该待的位置?是否绕过抽象直接操作了不该碰的底层?是否引入了本可避免的耦合?新增的依赖是否值得?放在这里的关键判断是:这个方案是顺着系统脉络长的,还是为了省事打的一块补丁。
第三层:可读性与可维护性。命名有没有反映真实含义?函数是不是承担了不止一件事?有没有留下“这里很奇怪但我不敢改”的注释?复杂逻辑有没有测试覆盖?不要求每处代码都符合教科书风格,但至少让接手的人能看懂。
第四层:性能与安全。有没有明显的循环内查询?有没有不必要的无边界查询?用户输入有没有被合理处理?敏感数据有没有出现在不该出现的地方?这层在普通业务功能评审里容易被跳过,但它恰恰是线上故障的重灾区。
这四层不是让评审者按顺序机械执行的,而是提供一个思路框架。评审者自己建立习惯之后,看到 diff 时会下意识地对号入座。
2.3 给新人看的评审规范草案
有了清单,还得让整个团队说同一种语言。我根据整个团队的实际情况整理了一份简单的评审规范,当时贴在了技术文档库里,核心就几条。
关于评审范围:格式、注释、命名风格这类问题不做硬性评论,交给代码格式化和团队统一规范。评审只关注逻辑边界、设计取舍、潜在风险。如果作者写了明显反常规的代码,用提问式口吻询问,而不是“必须改”。
关于评论语气:禁止“你写错了”,改成“这里我没看懂,为什么这样处理?”。禁止“这代码太糟糕了”,改成“这个实现会不会导致后续扩展困难?”。语气一变,接受度完全不一样。
关于评审节奏:小 MR(小于 200 行)优先处理,大 MR(超过 400 行)建议拆分成小步。评审者承诺当天响应,提交者必须在评审通过前保持在线响应。评审不是异步的扔过去就完事,而是一次短暂但专注的实时协作。
这些规范落地两周后,团队的评审效率有了明显提升。不是说问题变少了,而是评审开始真的在对设计和逻辑说话,不再是一堆风格之争。
3. 评审里的“人”,比代码本身更值得花心思
很多技术团队误以为代码评审是一个纯粹的技术活动,只要大家技术够强,评审自然就有效果。我原来也这么认为,但实际推进之后发现,代码评审中 60% 的问题出在人身上,不是出在代码身上。
3.1 风格冲突与技术债的边界
团队里 A 同学是从后端转过来的,风格是“一个函数做完整件事”;B 同学是科班出身,习惯“小函数 + 清晰的抽象层次”。两个人评审彼此的代码时,经常互发“这个问题不大但建议改”这种评论。表面上是技术分歧,实际上是风格冲突。这种冲突没有对错,但如果不停留在“标准化”层面,它就会持续消耗评审的能量。
我的解决路数是:把纯风格问题从评审讨论里剥离,统一通过代码格式化工具和一份大家都认的编码规范来解决。你在规范里写清楚“函数尽量控制在 50 行以内”,但不要对“某个具体实现用了多分支”这种没有明确好坏的地方发表强制意见。评审要集中火力在“改错不改对”“改严重问题不改风格偏好”上。
技术债则不同。评审时你看到一段“当初为赶进度临时拼凑的代码”,直接要求作者重构通常不现实。正确的做法是:记录债务,明确责任人,在下一个迭代里偿还,而不是让它在这次评审里被推倒重来。我会在评审中专门区分“必须现在改”和“可以后续处理”。前者是正确性和线上安全;后者是架构演进和代码整洁。分不开这两者,评审就会变成一场灾难。
3.2 如何在评论里把话说清楚
很多评审者喜欢“给结论”,把话说得又短又硬:“这段代码有问题,改。”新手作者收到这种评论,根本不知道怎么改。我给自己定了一个“评论三件套”:
第一件,说清楚观察到的现象——不评价人,只描述代码。比如“我注意到这个函数在循环里每次都查了用户表”。
第二件,描述潜在后果——告诉作者为什么这个现象值得关注。“如果列表有一百条数据,就会出现一百次数据库往返,接口延迟会被放大。”
第三件,给出建议方向或替代方案——允许讨论,但至少要给出一个方向。“是不是可以把用户查询提出循环,用批量接口一次取回来?”
这种“现象-后果-建议”结构,比单纯甩结论更容易被接受,也更容易引发真正的技术讨论。我团队里后来有新人这么写评论,我看了很欣慰——这说明评审文化在往“共同解决问题”走,而不是“我来教你写代码”。
3.3 评审节奏与时间窗口
代码评审特别讲究时机。改动越小、越新鲜,评审效果越好。作者对自己刚写的代码上下文记得最清楚,你问他任何问题都能第一时间解答;隔了三天再评审,他自己都忘了当初为什么那么写。
我给自己定的节奏是:早上到岗先花 15 分钟把昨天的评审请求清掉,中午之前把早上新来的小 MR 处理完。大 MR 绝不拖延到下午快到下班的时段,因为那时候注意力已经开始下降,更适合做代码走查的是上午清醒时段。评审不是“有空再说”的活,它跟写代码一样需要整块的注意力和稳定的节奏。
这里我也想提醒一下“评审马拉松”。有人喜欢一次性召集三五个评审者,拉一个两小时的会议把 MR 逐行走一遍——对大型重构也许有必要,但对大多数日常 MR 来说,这种方式效率极低,而且评审质量递减非常明显。我的建议是:日常 MR 的评审者两到三人足够,走查以异步评论为主,遇到争议点再临时 15 分钟语音对齐。
4. 自动检查与人眼复核的配合打法
进入公开代码评审(open-code-review)的实践环节后,我做的第一件事就是把团队代码仓库里的自动化检查体系补齐。很多人一听到“代码评审”就想到“人看人”,但其实这个时代,能交给机器做的检查一块都不要留给人工。
4.1 静态检查能替代什么、不能替代什么
静态检查的强项在于,它可以系统性地捕捉那些人类容易疲劳、忽略的低级问题:未使用变量、空指针隐患、明显错误的模式、安全风险、不符合规范的分支覆盖。这些事让人来做,纯属浪费注意力。人脑应该用来处理那些静态检查无法量化的问题。
但静态检查永远不能替代人的原因是:它不理解业务,不理解设计意图,更不懂“为什么这个看似绕弯的实现反而是合理的”。静态检查只能告诉你“这行代码可能有风险”,但它无法告诉你“这个风险在业务场景里会不会发生、发生后影响多大”。这就是评审者不可替代的价值。
所以在 open-code-review 里,我明确把评审资源切成了两块:机器负责“语义之外”的所有确定性检查;人只负责“语义之内”的所有判断。机器检查通过不代表代码合格,但机器检查都过不去的代码,根本不该送到人面前。
4.2 我常用的自动化检查组合
我当时的配置其实很朴素,但很有效。整个检查体系分成三个层次:
第一层是提交前钩子。也就是在本地提交时代码格式化工具就自动跑一遍,还有基础 lint 检查。这一层没过完,你连 git commit 都提交不上去。别小看这一层,它挡掉了大约 60% 的“空格问题”和“明显命名问题”,让评审者一眼看过去,diff 是干净的,注意力全部集中在逻辑上。
第二层是 CI 里的静态检查。包括编译检查、单元测试覆盖率、静态分析工具、依赖安全检查。我要求合并请求必须通过这一层才能进入人工评审环节。没有通过自动检查的 MR,人工评审阶段我不会碰——这既保护了评审者的时间,也让提交者养成了“先自查再送审”的习惯。
第三层是测试环境里跑的集成测试。这一步针对的是多模块联调的类型,防止某个改动在单测里都好看,一合起来就炸了。集成测试不需要全覆盖,但核心链路一定要有。
三层配合下来,人工评审面对的 diff 已经是被机器过滤过一遍的“高信号”代码,评审者要做的事情少了很多,评审质量也相应提升。
4.3 一个典型 MR 的完整走查顺序
我总结了一套个人用的走查顺序,按序做下来基本不会漏:
- 先看 MR 描述,搞清楚背景、方案、影响面;
- 再看测试,确认新增功能有测试覆盖,而不是临时用日志验证过就算完事;
- 然后读核心逻辑,沿着主流程走一遍,在脑内模拟输入输出;
- 再关注边界分支:空值、重复调用、并发、失败回滚;
- 最后扫一眼改动文件清单,看有没有改到不该改到的文件;
- 把所有疑问记录成评论,按严重程度排序。
这个顺序的意义在于:它先建立“整体认知”,再进入“细节扫视”。如果你一上来就逐行阅读,很容易被细节带偏,看完一遍之后说不出这次改动到底在干嘛——这是很多评审无效的真实原因。
5. 实测踩过的坑:三条典型的“评审失效”链路
这部分算是我最想写给同行看的。我推进 open-code-review 的过程中遇到过好几个“看似合理但实际失效”的典型案例,每一个都让评审体系几乎形同虚设。我把它们整理出来,配上当时的排查链路和修正方式,给后来者一些参照。
5.1 大 MR 无人敢批的恶性循环
第一个坑是“超大 MR”。某次后端同学把两周的功能开发一次性提交过来,diff 线数超过 2000。我点开 MR 的时候,评审页面加载了五秒钟,然后我看到 diff 里几十个文件、遍布的功能改动,心里一沉。
排查下来的过程很有意思:不是没人认真看,而是所有人都看了,但没人“看完”。有人看了第一个模块,发现时间不够就批了“建议优化”,没人敢真正说“通过”。提交者在线等了半天,没人批,只好去催。催完的结果是,评审者在十分钟内匆匆给了“通过”或者“请修改”,但具体改了哪里,自己都不清楚。
这个坑的根源在“变更粒度”。我后来定的规矩是:一个 MR 尽量控制在一个可独立交付的功能单元内,超过 400 行的 diff 必须拆分成小步。拆分不是让提交者把一个改动强行切碎,而是让每个 MR 的改动在概念上真正独立——先做基础设施,再上具体业务,最后做联调改动。 这样评审者每次面对的都是一个可理解的任务,而不是一团混在一起的改动。
5.2 评论爆炸却没人修改的“无效评审”
第二个坑更隐蔽。有一段时间,团队里一个模块的评审评论区平均每天新增四十多条评论,但到合并之前,真正被采纳修改的却寥寥无几。我当时觉得奇怪,拉了下评论列表仔细看,发现问题很典型。
一半以上的评论是“少了空行”“命名建议改成 xxx”“这里是不是该加注释”这类纯风格问题,提交者回复了一个“收到”,但压根没改——因为这类评论不痛不痒,改了反而可能引发新的格式问题。剩下的一些评论虽然涉及逻辑,但措辞太模糊:“这段逻辑看起来有点怪”“这里是不是有并发问题?”提交者不知道从何下手,也就干脆晾着了。
解决方案就是我前面提到的“评论三件套”和“评审边界”。从那以后,我要求团队所有评审评论必须有明确的事实依据和具体的建议方向,纯风格问题直接交给工具和规范处理。两个星期后,评论数量降到了每天十条以内,但被采纳率提升到了百分之八十以上。评审效率不看评论数量,看的是“有效评论密度”。
5.3 持续重构下“评审疲劳”的恶性循环
第三个坑发生在一个长期维护的老项目里。代码库大、历史包袱重,几乎每次新功能都是在旧代码上打补丁。某同学接手一个优化任务,需要动到一个核心模块。改动不大,但是他连续提交了四个版本:第一版改接口,第二版加缓存,第三版处理边界,第四版统一风格。每个版本都要评审一遍。
到第三版的时候,评审者和提交者都已经明显倦怠了。评审者的速度在下降,提交者看到“请修改”就条件反射地想“又要改”。最后的第四版,几乎没人认真审查就合并了。结果上线后没几天,就暴露出一个评审早期提醒过但后期没人再提的坑——旧逻辑里一个全局状态的副作用没处理干净。
这事的教训很深刻:评审疲劳会直接降低审查质量。治本的办法是尽量避免把一个复杂改动拆成多次提交、多次评审;如果实在要拆,每次提交必须是逻辑上完整、可独立合入的,而不是“一次任务的某个中间过程”。我后来还把“评审疲劳系数”纳入了一个个人工提醒,如果一个模块在一周内被连续提出超过三次评审请求,我会主动介入,一起确认是不是推进方式出了问题。
6. 评审工具与流程配置:我更推荐的轻重搭配
聊完人文层面,回到工具层面。很多人一听说代码评审就想到企业级的付费平台,我一向的主张是:先用轻量、免费、和现有工具链贴合最紧的方案把流程跑通,再谈酷炫的高级功能。
6.1 在现有代码托管平台上开箱即用
大多数团队用的是带 MR/PR 功能的代码托管平台,本身就内置了评审能力——行内评论、评审意见、批准/拒绝、合并保护。open-code-review 实践的第一步,就是把这些原生能力配置好。
我会给分支设置合并保护规则:核心分支不允许直接推送,只有通过 CI 检查、至少获得一个评审批准后才能合并。这条规则本身就能挡掉不少直接往主干塞代码的情况,也能让团队成员从流程上习惯“我的代码要被别人看”这件事。
在合并保护之上,我还习惯配置几条自动化的“底线检查”:代码格式化检查、基础静态检查、单测跑批。这些检查不通过,提交者没法请求评审。这等于把机器该把关的口子全堵住了,人工只面对机器筛过一遍的问题。
6.2 自定义提示语,让评审规范长在流程里
一步很值得做的配置是给 MR/PR 模板增加自定义内容。当提交者新建一个 MR 时,平台会自动填充模板。我会把“背景/方案/影响面/自测情况/重点评审区域”这些字段嵌到模板里,提交者必须填写才能发送评审请求。
这个操作的好处在于,它不依赖任何人反复口头强调评审规范,规范长在了流程的必经之路上。新同学入职第一天,第一次提交 MR 就能看到模板要求,自然养成了写清楚上下文的好习惯。这也比贴一份文档到 wiki 上有效得多——因为文档可以放在那儿一年不看,而模板是每次提交都躲不掉的。
6.3 自动化检查的“最小可用集”
如果团队很大、基础设施很强,上强大的自动化分析平台确实有优势。但对大多数中小团队,我建议不要一上来就搞重装备。
我的最小可用集是四样东西:代码格式化工具、Lint 工具、单元测试框架、基础静态检查。这四样都是免费开源的,配置起来一个下午就能搞定。它们解决的问题非常明确:确保代码仓库风格统一、语法层面没有明显错误、核心逻辑有测试兜底、常见代码坏味道被机器拦下。
有团队问我要不要接复杂的架构可视化、依赖分析平台。我的回答是,等日常评审跑通、团队已经有固定的评审习惯之后再考虑。工具是流程的放大器,不是流程的替代品。如果流程本身没立住,再强的工具也只是让混乱跑得更快。
说回 open-code-review 本身,我不把它理解成某个具体的软件工程产品,而是一套以“评审对象清晰、评审边界明确、评审节奏稳定”为核心的原则组合。把这些原则落到团队日常里,比引入任何单点工具都更重要。
如果你所在的团队正被评审形式化困扰,我的建议是从最小的一步开始:把 MR 描述模板补齐,把合并保护打开,把纯风格问题交给工具。这三个改动做完,你不需要等什么复杂平台,就能感受到评审开始“活”过来。代码评审这件事,最怕的不是工具弱,而是大家把它当作一个过场。