ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从形式主义到高效协作:代码评审流程优化实践指南

从形式主义到高效协作:代码评审流程优化实践指南 1. 先给Review成了形式主义把个脉代码评审Code Review这个动作几乎每个开发团队都声称在做。打开GitLab或者GitHubPull Request列表里挂满的LGTM1看着热闹但你要是追问一句这些评论到底有多少真的影响过代码方向大多数人心里其实有数——少得可怜。我认识一位做基础架构的工程师他所在的团队规定所有改动必须经过2人评审才能合入。结果呢大家为了不卡别人后腿Quick Review成为一种默契扫一眼diff标题点赞走人。真正的问题被藏在了流程合规的糖衣下面。这种环境里评审成了一种仪式跟祈福差不多——做了不代表有用。open-code-review这个标题我个人的理解不是某个新工具的代号而是指一种状态把评审真正开放出来让意见流动、让上下文透明、让评审从门禁变成交流。理解这一点比下载一百个Review工具都重要。这篇文章不聊那些高不可攀的团队文化口号完全是我在实际项目里把Review从形式拉回正轨的过程涉及流程设计、评审检查清单、评论话术、冲突处理、工具链分工这些具体层面。无论你是在10人小团队还是百人规模的大组这套思路应该都能用得上。1.1 LGTM文化一句最快、也最危险的反馈Looks Good To Me这句话本身没有错。问题出在它出现的时机和频率上。我去翻过自己参与维护的某个项目的Merge记录抽样了最近100个MRMerge Request发现有接近四成的改动从提交到合入不到20分钟。一个涉及6个文件、改动300行的重构怎么可能20分钟审完唯一的解释是没人真正审大家都在用LGTM做社交礼仪。LGTM最大的危险不在于这一条没审出来而在于它制造了一种虚假的安全感。审查者觉得我看过了没问题作者觉得有人把关了很安全。两边都放心了缺陷就安心住进去了。这种心理现象在行为经济学里叫责任分散翻译成大白话就是一旦有人认为这么多人都看过了出错也算不到我头上那所有人都会放松警惕。我后来跟我们团队定的规矩很简单不经过思考的LGTM不允许发。你可以回复改动范围很小我看过了逻辑没问题也可以明确说这部分我不熟只确认了风格没有明显问题。总之把我审了和我路过点了个赞区分开。别小看这个口头的转变它逼着每个评审者至少在打开diff的那一刻动一下脑子。1.2 评审形式化的三个根因老话说对症才能下药。我把团队里Review走形式的根子归结为三条你们自己对照一下根因一评审被定位成了事后检查。代码写完了、自测通过了、CI挂了又绿了走review流程只是为了合规。这种定位下评审意见的含金量天然为零。因为如果设计师90%的工作已经定完你很难在交付阶段让作者推翻重来——没人有那个预算和时间。根因二评审上下文太稀薄。多数时候评审者拿到的是一堆diff文件没有为什么要这么改考虑了哪些替代方案哪些部分是我想请你特别注意的。评审者在缺乏上下文的情况下只能评论一些命名不规范格式有问题的表层内容。时间一长大家都觉得评审没价值。根因三反馈机制本末倒置。我们习惯性地把提出多少条评论当成评审者尽责的指标把被评论多少条当成作者代码质量的指标。于是作者本能地防御评审者本能地挑刺一场协作就这么变成了辩论赛。上面这几条是后面所有操作的前提。流程设计、检查清单、话术技巧本质上都是在针对性地拆掉这三堵墙。下面我按实际操作顺序一条条说。2. 流程设计把评审门槛放到正确的位置上代码评审的流程设计核心目标只有一个在做改动的成本和出问题的成本之间找一个动态平衡点。评审门槛设得过高团队会窒息设得过低防线形同虚设。关键是让每一次评审都能发生在成本可控的范围内。2.1 变更粒度300行是分水岭我在团队里给过一个硬性建议尽量别提交超过400行的MR用于评审。这不是拍脑袋定的是吃过亏之后才逆向出的结论。人的工作记忆是有限的。评审一个MR的时候评审者需要在脑子里同时维护旧代码长什么样、新代码改了什么、为什么这么改、潜在的边界情况在哪四份上下文。实验心理学里有个经典的7±2法则说的是工作记忆的容量上限。放在代码评审场景里一旦到400行以上评审者脑内上下文的维护成本就指数级上升。有人会说一次大重构没法拆成小MR啊。那是没拆透。我在之前的团队遇到过一个大模块的数据库迁移按应用层适配和存储层改造切成了7个MR前后横跨两周被拆开的每一步都是独立可运行、可测试、可评审的。把大量耦合项提前解耦本质上也降低了评审难度。所以我的经验是如果代码量即将突破400行先问一句这个改动能否再拆一步。拆不出来那就把评审人员增加一倍接力审——不要指望一个人从头到尾看完几百上千行还保持清醒。2.2 评审人机制先让最相关的人表态评审人选定也是个技术活。很多团队的默认设置是拉上组长、拉上隔壁组同事、拉一个前端凑三个人就算完事。这不是评审这是检查。参与的人越多责任越分散流于形式的概率越大。我倾向于推荐的模式是主持人责审人两级评审责审人Reviewer1-2人必须是对这段代码最熟悉、或受改动影响最大的那个人。他需要逐行审负主要责任。主持人Assignee负责拉齐资源、确认评审进度、转发关键争论通常由提交者或资深的模块负责人充当。别小看这个分工。它至少解决了三个问题责任落地、进度有人管、关键的讨论不会被海量的评论淹没。对于新成员加入的时候我还会再加一条规定新人第一周尽量安排跟随评审跟着责审人一块看几轮MR先看别人怎么审的再自己上手。这比任何代码规范培训都有效。2.3 从提交到合入一个可复用的检查节点设计评审流程不是一个独立的关卡它得嵌在开发全流程里。我总结了一套比较好用的节点设计下面按顺序写出来供参考节点动作目的提交前提交信息写清楚改了什么、为什么给评审人提供上下文打开Merge RequestMR绑定关联的Issue/Task、指明优先级和需要重点关注的文件缩小评审范围MR描述区补充自测结果、截图/日志、以及我做了哪些权衡降低评审者的认知负荷CI通过后机器先跑格式、静态检查把低级的客观点留给机器人只看逻辑人工评审责审人逐行审主持人确认无阻塞项保证逻辑和质量对缺陷负责合入前作者回复所有评论、更新测试、解决阻塞项让评审意见真正落进代码里这套环节里最容易被跳过的是MR描述区。开发者的天性是不爱写文档但是写过MR描述的未来你会感谢现在的自己——因为你一个月后回来回溯问题时翻到那个为什么这么干的记录能省下两个小时。顺带提一句如果团队用的是GitLab可以把MR描述为空则禁止创建做成服务端钩子。这条规则不需要任何前端自觉从流程上锁死效率极高。3. 评审现场我Review代码时逐层看什么流程建好了接下来是最核心的问题代码打开眼睛往哪放很多新人评审者面对一个几百行的diff完全不知道从何看起只能整体扫一眼然后随便找几个格式问题糊弄过去。真正的评审是有层次感的一层一层往里剥。3.1 第一层设计意图与架构影响拿到diff第一件事不是看代码而是先看这个改动值不值得做。评审者要有能力判断这个改动解决的是真实问题还是在绕圈子有没有一个更简单的方式能达到同样的目的有个很典型的场景产品要支持排序开发者直接写了一个通用的排序工具类里面塞了各种排序算法的实现。这时候评审者应该问的不是排序算法写得对不对而是我们用得到这么多排序算法吗——如果目前只有一个场景那就用最简单直接的方案。YAGNI原则在评审视角里其实是第一位的。还有一个隐蔽的架构影响问题是隐式耦合。一个改动看起来在A模块里自洽运行但它悄悄改了某个全局配置、某个静态变量、某个外部服务的行为这种改动经常会带着我测试过了没问题的注释通过评审最后在生产环境引发诡异故障。所以我在评审的第一步基本是在心里回答三个问题这个改动是否必须这么复杂它会影响哪些我肉眼没看见的相邻模块改动是沿着现有架构方向走的还是在逆着架构打补丁这三个问题有一个回答不了就应该直接提问而不是等到后面去抠细节。3.2 第二层边界条件、并发与错误路径架构层面看完了进入代码的死角。经验告诉我们正常路径上的代码都差不多真正区分代码质量的是处理异常和边界情况的姿态。边界条件这一步我一般会拿一张破坏性输入清单挨个过空字符串、负值、极大值、并发请求、网络超时、缓存穿透、重复提交。一个个在脑内模拟看代码是否扛得住。这里举一个之前踩过的真实的例子一位同事写了个批量导入功能正常数据量下一切正常但线上出现了一万行的超大文件程序直接超时。评审时如果能问一句当输入规模远超正常值时会怎样就不至于等到线上事故了。并发和线程安全更是重灾区尤其在Go、Java这种普及并发编程的语言里。评审时最需要警惕的是共享状态被隐式修改。看到全局变量、静态字段、缓存对象被写入脑子里马上拉响警报。还有一个常见问题加锁的顺序不一致导致的死锁这个在评审里未必能直接找到答案但如果发现两个不同的锁在异步代码里交叉使用你应该立刻指出来。错误处理这一层我见过三种最常见的漏洞捕获异常后只打印日志继续执行等于把问题吞了错误返回值被忽略后续的逻辑拿到脏数据继续跑错误信息写得过于笼统出了问题根本定位不到是哪一行触发的。对于这些最好的评审建议不是你要处理错误而是请明确指出这个错误发生后程序的下一步行为会怎样。3.3 第三层可维护性与代码气味走到这一层评审者要带着半年后我还能不能看懂这段代码的问题去审视它。我用的一个比较好用的标准是可解释性判定如果一个已经不在原团队的新人拿到这段代码没有文档辅助能否在一个小时内说出它的行为如果答案是否定的那无论它逻辑多正确这个改动在可维护性上就是负分的。代码气味有很多种评审时最常见的高频项我列在下面函数太长。看到超过50行的函数第一反应不是这是坏事而是这个函数做了几件事能不能拆命名与意图不符。比如叫data的参数实际存的是用户一次性访问令牌这种命名纯粹是给自己挖坑。魔法数。代码里飘着一堆数字/字符串常量没有枚举没有常量定义注释也没有。这必须要被拦下来。重复代码。类似逻辑出现两三次应该提取而不是C-V一份。过早优化的痕迹。性能优化的代码铺满了主线流程为了快而牺牲可读性且没有基准测试说这块确实是瓶颈。要记住这层评审的关键不是消灭所有味道而是把风险以可理解的方式暴露出来。有些代码气味其实是合理的工程取舍评审者的职责是让作者意识到这个取舍存在而不是替作者做决定。3.4 第四层测试真的在测东西吗最后是测试的评审。很多开发者觉得有测试就算通过但测试写得好不好价值差距非常大。我会重点看两点一是断言有没有意义。好的断言是行为断言它验证的是当X发生时Y应该发生。而大量无效测试是输出断言验证的是程序没有崩溃。比如一个解析函数测试断言是返回的错误包含error字符串这个是没有意义的。对它正确的测试是输入格式错误时返回特定错误码输入正确时返回正确解析结果。二是测试有没有覆盖危险分支。对着diff看测试如果主分支全测了但是异常分支、边界分支一个都没有说明测试是照着正常路径写的是在给实现背书根本盖不住回归风险。补一条实战小技巧评审时试着删掉一段实现跑一下测试。如果所有测试还是绿的说明这些测试在测的不是这段逻辑。这个方法很粗暴但确实能删掉一批为了覆盖率而写的假测试。4. 写评审意见的技术让人愿意回复而不是防御代码评审不是一个人单方面输出意见而是两个人通过一段代码互相传递信息和价值。同样的一句话用不同的表达方式效果天差地别。这个小结可以说是全文最贴近实操的部分是我在无数场线上对峙中摸爬滚打出来的。4.1 用问题代替指令这个函数不应该超过30行拆掉。——听上去像命令很多作者第一反应是我辛辛苦苦写的你凭什么说拆就拆。换成问题呢这个函数有60多行里面有A、B、C三个职责是不是拆成三个函数会更便于测试和复用——这不只是在问意见更是传达了我看到了你的工作并且替你考虑了下一步。人类的大脑天然讨厌被命令。写评审时把非阻塞的建议全压成提问模式会大幅降低作者的心理防御。但对确认是bug的阻塞项依然要用明确的指示句这里会触发空指针请先处理再合入。4.2 把你写错了改成这里可能有风险的说话框架实际团队协作中我们遇到的最大阻力不是技术问题而是情绪对抗。老练的评审者都会遵循一个基础框架先肯定具体优点。这一段的状态机处理得很清晰条件分支的粒度刚好。再指出具体风险。不过有一点我不太确定主流程里同时出现了两个defer清理动作如果第一个失败第二个还会不会执行最后提供可选方向。可以考虑把两个清理动作合并成一个带状态机的方法或者用一个兜底的清理函数确保二次清理。你看看哪个更贴合当前的架构。这套框架本质上在做一件事让作者明确接收到你针对的是代码不是人。我实践下来的感受是它能让评审被接受的效率至少翻倍。很重要的一点是不要在一条评论里塞超过三个问题。问题太多会触发全部否认或全部忽略的心理开关——反正也改不完干脆只看最上面的两个。4.3 针对新人的渐进式引导新人提交的代码往往让评审者血压升高。但这里我强烈建议控制住情绪千万别一上来就霹雳啪啦扔出20条评论。我在带新人的时候通常会把评审意见分成三个梯队A级必须改正确性、安全性问题一般不超过3条。B级建议改可维护性问题会列出来但允许延迟。C级商量着来风格偏好类问题用nit:前缀在评论里标注。新人的第一份MR我一般只强调A级问题B、C级先发一条汇总说这些方向可以下一轮再调整。为什么因为新人第一次拿到批评大脑处于应激状态能被有效吸收的信息量很有限。等他的A级问题改完下一轮氛围缓和了再去推进B级就自然很多。事实上后来我发现这个渐进策略不只是对新人有效对于压力比较大的老同事也同样适用——与其说这是对新人的引导不如说是在降低一次反馈的认知负荷。4.4 评论留痕把异步沟通做成可追溯的对话最后说一个不太常被提到、但在跨时区或远程团队里极其重要的点尽量把讨论留在MR评论区内符合可发现、可关联、可追溯原则。现在很多团队习惯用IM讨论代码——我刚发的那个函数你还记得吗——这种对话过两天再去复盘除了几段嗯和好的碎片完全不剩任何上下文。而MR评论区天然带有上下文哪一行、哪个方法、哪次提交一点开就能复原讨论场景。我自己维护项目的习惯是所有技术争论即使最初是在IM里起的头也会在结论敲定后回到MR评论区补一条总结标结论采用方案B原因是XXX。这等于给后人来读这段代码时留了一盏灯——他们不仅能看到代码长什么样还能知道这段代码为什么长这个样子。5. 评审冲突现场那些最常见的拉锯战与解法流程定得再完美话术练得再纯熟总会碰到硬刚的场面。这一章专门聊评审过程中最常见也最磨人的三类冲突每类背后都有对应的处理策略。5.1 先合入再改一次妥协的成本有多高这个场景简直是团队协作中的经典款需求上线日期快到了作者说这行代码先让我合进去阻塞项我下周马上改评审者一犹豫就放行了。结果几乎可以预测——下周永远不会来因为新的需求又来了优先级永远更高那行有问题的代码就在生产环境里住了下来。这里我说一个真实教训。我之前的团队在某个老模块里留下了一个先合入再修的TODO半年后这个TODO所在的位置引发了一次线上数据错乱。排查时发现当初这个TODO只有一行字这里后续检查一下重复消费的可能。而相关代码已经经历三轮重构没人还记得约定过什么。那次事故之后团队立了规矩阻塞项评论如果没有在这个MR里解决而是被挪到TODO backlog也必须由同一个评审者在新MR里追着验收不允许评论区已读不回。对待先合入再改我的统一态度如果是格式、注释这类零风险问题可以接受只要涉及逻辑正确性或数据安全一律不合入。流程是可以适当灵活的但不能用灵活给事故开绿灯。5.2 风格之争区分客观缺陷与主观偏好评审吵架最没价值的焦点就是风格。有人喜欢命名用userAddr有人喜欢用userAddress有人习惯函数前面空一行有人不空。这些讨论一旦卷进去纯属浪费时间而且特别容易把评审氛围带向低级。我面对风格冲突的做法是三个字对齐标准。团队里必须有成文的、白纸黑字的Code Style Guide哪怕只有两页纸也比每个人脑子里的风格强。在标准覆盖范围内的问题不讨论执行即可。在标准没覆盖的角落评审者一律让步不要把自己个人的审美强加给作者。除了代码风格还有一种更隐蔽且更消耗精力的主观偏好就是技术选型洁癖。比如作者用了一个不常见的第三方库评审者只用过自己用惯的库上来就说你为什么不用XX库——如果这个库本身没有硬伤这种意见就不该成为阻塞项。正确的做法是让作者补充说明选型理由只要理由站得住脚就放行你是评审不是喜好警察。5.3 异步评审的节奏管理远程办公、跨时区协作越来越普遍异步评审的节奏问题也日益突出。作者在中国评审者在欧洲一个简单问题的确认可能要等上一天MR长时间泡在待评审列表里团队整体交付节奏会被拖垮。我的节奏管理经验是这样的明确响应时限职责评审者接到评审请求后8个工作时内必须完成首轮反馈。无法完成的要在评论里说明原因并指定代理评审人。把复杂讨论拆成小项一次扛多个评论时按问题-上下文-期望把每一点都写清楚让对方不用追问你是指哪一行就能直接回复。定时复习每天早上抽15分钟快速浏览头天挂着的MR对于只剩一个阻塞项的能顺手答复答复不能答复的至少评论一句我看到了预计明天给你回复。这句我看到了能极大降低作者的不确定焦虑。坦白说异步评审的体验很难做到和面对面一样流畅但其好处在于所有的意见都在可追溯的记录里。把响应节奏管理好异步的劣势就能被压到最低。6. 工具与度量把评审体验做顺别做指标奴隶评审最终要落回工具使用和团队机制上。工欲善其事必先利其器这最后一部分聊聊我在实际操作中对流程、自动化与度量的几点心得。6.1 Git工作流与MR结构对评审体验的影响Git工作流的习惯会直接影响评审的有效性。我见过最让人头大的提交方式开发者把两个功能互不相关的改动放在同一个分支里打开PR一看60个commit、30个文件一半跟需求无关。评审者的第一反应就是反正你也没指望我审随后这个MR会走向走个过场的命运。要解决这个问题最好的方式依然是拆分——按功能拆分按逻辑单元拆分让每个MR只干一件事。我用过一段时间一个MR对应一个语义化提交的规则feat: 增加用户列表导出、fix: 修复导出乱码问题。这会在目录层面把MR的职责理得非常清晰。另外提交信息本身的价值我前面已经强调过。一个规范的提交信息等于给评审者递上一份阅读地图。我实战中还喜欢在分支名后面加一个wip前缀来表示还在开发中先别导入评审池等自测通过后再去掉。这个小小的前缀约定能过滤掉大量被打断的评审。6.2 自动化检查与人工评审的分工边界很多团队把大量精力投在自动化检查上结果CI脚本写了一堆人工评审环节形同虚设。反过来也有团队嫌弃机器人抢戏把自动化检查做得极其简陋。合理的分工应该是自动化管常规人工管创造性判断。拿Lint举例格式错误、未使用的变量、未处理的Promise这些就是应该被CI拦截的客观点不允许出现在人工评审里。测试覆盖率让CI去提示哪几行没覆盖到人工只去看哪些分支值得测但被作者跳过了。安全漏洞扫描Snyk这类工具能做初筛人再去判断这个漏洞在我们当前的使用场景里是否真的可利用。我把二者的分工简化成了一句话机器负责标准答案的部分人负责没有标准答案的部分。评判逻辑、权衡判断、上下文理解、产品意图——这些是任何工具都无法替代的人工价值。如果有一天你发现评审意见里有一半是这个变量名好奇怪这里有个多出来的空白那不是评审者的问题是自动化做得太差了。6.3 评审度量警惕指标的游戏化最后说说度量。代码评审是一件行为上的事它和程序员写代码的效率一样很难被量化。但团队管理又绕不开我需要知道Review这件事做得怎么样。我之前见过一个团队试行评审积分制——每条有效评论记一分、每完成一个MR的review记两分。结果不到一个月评论量质的下降——大量碎碎念的意见出现在MR里纯粹为了凑分。后来这个制度被撤了。我的做法是用定性为主、定量为辅的度量方式比如抽样分析MR看评论里有多少是阻塞项、多少是建议项、多少是无效吹毛求疵记录从MR创建到首轮评审完成的耗时关注体验而非数量每个月开一次Review Retro团队成员反馈评审中最卡人的环节。量化的指标最多的时候让我知道团队Review的节奏是不是失衡了但真正的改进动作还是靠人和人之间的直接沟通。评审是手段不是目的指标是参考不是KPI。6.4 我从LGTM机器变成会评审的人的几个阶段顺便聊聊个人的成长路径给还在摸索的同行一个参考。我刚开始做Review的时候根本不敢在别人的代码里提意见总觉得他自己写的肯定比我明白。后来敢提了又一头扎进挑刺模式——每看到一个不满意的点仿佛都非写不可。最尴尬的阶段是提完意见后带着优越感觉得我这轮Review太有价值了。后来在一次Review争论中我提了大概15条阻塞意见作者直接逐条反驳了11条。冷静下来之后我重新读了代码发现确实有7条是我没理解上下文、空想出来的伪问题。从那一刻起我调整了自己的姿态先花时间读上下文、理解作者的意图再开嘴。现在的我一个MR通常只会挑出3到5条真正重要的意见剩下的用建议可选这些软化词带过。这个变化没有让我变成老好人而是让我Review里面每一句这个需要改都更有分量。如果你的评审意见总是被无视很大原因不是说得不够多而是没有让人感觉到你真的在读他的代码。我在实际项目中还有一个收尾的小习惯每个版本结束之后翻一遍本月所有MR的讨论串挑出一两条被反复提起的问题整理成下一期team review的主题。这样整个团队就会感受到每一次Review的讨论最终都会回到团队的代码库质量里。这也是我理解中开放式评审的落点——评论不是终点而是下一轮知识循环的起点。
返回列表