代码审查的终极产出不是质量,是共识

🔑 关键词:代码审查,团队协作,Bug预防,工程师文化,思考模型

📖 摘要:通过一次支付事故,反思代码审查的真正价值在于打破信息差和建立共同理解,而不是挑刺过关。

那是周三下午三点半,客诉群突然开始刷屏。用户付一次钱被扣了两次,财务在群里发了一串问号,我盯着日志里的回调记录,手指有点发凉。同一个支付回调触发了三次,第一次超时,第二次成功,第三次又把订单状态改回去。这段逻辑是我前一天刚刚提交的,两个同事在PR上挂了approved,没人发现问题。

图片

但最有意思的是,当时那位同事还特别认真地给我留了评论,说这一段命名不好,‘flag’这个词太模糊,建议改成‘isOrderPaid’看起来更明确。于是我们花了十分钟讨论这个命名,最后改了。谁也没有想到,真正的问题藏在另一个房间里——回调重入的时候,状态机根本没有做幂等处理。这条评论现在依然挂在那条PR上,像一个无声的讽刺。

图片

那之后我花了不少时间复盘,发现自己特别讨厌‘代码审查是质量门禁’这句话。门禁的意思是闸口,过了就万事大吉。可实际上,大多数团队里的review并没有给质量兜底,反而是把注意力分散到了最不重要的地方。我们的文化潜意识里认为,评论越多越负责,所以大家会刻意找一些看起来存在感强的细节,比如函数拆短一点、常量命名再改一改。这些东西没有错,但它们是安全的,是‘不管怎样都不会说错’的。

图片

而真正需要动脑子的部分——状态转换、并发边界、失败重试,因为讨论起来要花力气,要暴露自己可能没看懂,于是被所有人默契地跳过了。这有点像开会。会议室里声音最响的,往往是跟问题关系最浅的人。代码审查如果只是在diff上的social activity,那它就会让那些最深的坑看起来最平。

后来我们尝试了一个看起来很笨的办法。PR描述里除了变更内容,必须写清楚三件事:第一,为什么要做这个改动;第二,改动依赖的前提是什么;第三,如果半年后有人接手这段代码,他需要先知道什么。刚开始团队怨声载道,说一次修复一个typo也要写几百字,形式主义。但是坚持了一个多月以后,有个同事在写一条优惠券状态变更的描述时,写着写着突然发现不对:自己根本解释不了为什么先推送再更新数据库。去查架构文档才知道,消息队列是异步的,有延迟,如果先推送,用户会看到过期状态。代码本身没问题,是描述和代码已经对不上了。

图片

reviewer那边也不再只是点一下approve,而是要求用一句话复述这个改动:‘我理解的顺序是分别更新订单、回调网关、发送站内信,对吧?’这种复述一开始让人非常尴尬,像智力测验。但它真的有用。一个老同事在复述时反问了作者:你这里是先改状态再通知网关对吧?作者想了几秒钟,突然意识到自己把依赖关系搞反了,马上回去重写了半个模块。那是一个很难用自动化测试发现的逻辑,因为单独看每一条数据流都是通的。

图片

所以我现在的看法是:代码审查最值钱的产出物不是一个干净的diff,也不是人人都点亮的绿灯,而是参与者之间形成的一段‘共同记忆’。这段记忆会变成团队的隐性文档。哪怕代码被重构了,哪怕评审者离职了,只要大家在讨论中真正把系统理解了一遍,那这个代码就不会变成一座孤岛。Bug只是共识破裂时溅出来的火花,真正的审查对象是每个人脑中的模型。

图片

最后,如果你问我团队应该怎么做代码审查,我的答案会很直白:少挑刺,多问为什么;不要盯着缩进,要盯着前置条件;不要问‘这一行是不是没对齐’,要问‘如果这个函数被并发调用会发生什么’。让每一个人都敢说出‘我没看懂’,比任何强制性的流程都重要。代码审查不是安检门,它是一场关于系统如何存活的对话。更重要的是,作者自己讲的描述,往往比任何人的评论都更能发现错误。审核别人的代码之前,先让自己坦诚地把逻辑说一遍,这比补所有的检查清单都值钱。

🏷️ 标签: