OPPO自研代码审查系统火眼Code Review实践

3,720 阅读8分钟

1. 背景

随着OPPO互联网业务快速增长,团队规模的不断扩大,对代码质量的要求也在不断地提高,而现有的代码审查工具GitLab和Gerrit已经无法满足我们的评审需求,主要凸显以下几个问题:

  • 无法高效的审查代码中的bug或潜在的质量问题

  • 团队内部的代码规范难以践行

  • 新人从代码中得到成长有限

  • 代码评审的便利性有待提高

我们迫切需要一款高效、简洁的代码审查工具来辅助我们做代码评审。

2. 自研还是改造

如果只做一个评审工具,缺少代码管理功能,需要频繁在代码评审工具和代码托管工具之间切换,用户体验不友好。

代码审核和代码管理最好在一个系统中,但要开发一个两者兼备的工具系统存在两个问题:

第一、要实现具备代码管理功能的代码评审系统,需要开发一套较为完善的代码管理系统,而已有的代码托管平台本身已比较成熟,再另外开发其难度和工作量都比较大。

第二、项目风险高,开发出来的系统稳定性和可靠性存在一定的挑战。

是否可以存在一种方法:既不用开发代码管理系统但又能实现Code Review的简单解决方案。在进行深度调研之后,我们发现在GitLab之上开发Code Review具有可行性,经过几月的研发,我们的Code Review系统火眼就这么诞生了。

火眼是OPPO互联网自研的代码审查工具,它融合了Gerrit的评审功能和GitLab代码管理功能,其主要功能包括:

  • 强制/非强制代码审查

  • Merge Request

  • 代码仓库管理

火眼相较于其他代码评审工具,主要有以下特点:

  • 评审代码冲突预检测

  • 灵活的评审人以及规则(普通和必要评审人,并设置通过的阀值)

  • 灵活的分支评审规则(自定义需要评审的分支)

  • 支持多种协同开发模式(包括强制评审、Merge Request两种模式)

  • 无缝对接内部通讯即时聊天工具和邮件系统

3. 火眼的原理

为了方便大家理解后文,这里对火眼原理做一个简单的介绍。火眼主要利用了Git的refs命名空间的特性,下图是某一个Git仓库在服务器存储的目录结构:

这个结构主要包含以下几个部分:

  • hook目录存放项目的服务端钩子脚本,代码提交仓库之前会触发pre-receive脚本,提交后会触发post-receive脚本,我们主要就用到了这两个脚本;

  • objects目录存储着Git数据库的所有内容;

  • refs 目录存储着所有分支指向各自提交对象的指针,每个指针指向对应分支或者tag最新的CommitId。默认的分支都会在refs/heads这个子文件夹下,例如我们经常使用的master分支指向的就是refs/heads/master。

火眼在实现Code Review利用的就是将对分支refs/heads/{Branch}的提交,改为指向到refs/changes/{ChangeId}/{Branch},其中ChangeId是指的某个特性,Branch是相应的开发分支,通过这个特性来存储待评审的Commit内容,当评审通过时,将refs/changes/{ChangeId}/{Branch}合并到原先正常的分支中。以开发dev分支为例,提交的模型如下:

对于每个特性(ChangeId),都独立的产生评审,当评审完成之后再合并到其对应的分支。当初我们的隔离维度是分支维度,相同分支产生的评审存储在一起。当我们发现这些评审存储在一起后,评审的代码会相互干扰,影响评审的独立性,所以才产生了现有的以特性为隔离维度的评审模式。

4. 火眼技术架构

下图展示了火眼系统目前的架构:

4.1 火眼RPC

火眼RPC是一个Dubbo实现的代理服务,其主要完成以下Git操作:

  • 代码合并

  • 标签操作

  • 分支操作

  • 对比操作

  • 提交历史操作

火眼Web调用火眼RPC来完成对Git仓库的操作。火眼RPC的核心处理Git底层数据是采用JGit(一款开源的Java操作Git的工具)实现,火眼RPC在JGit基础上实现了所有的Git查看操作,但是JGit并没有提供基于裸库(服务端存储Git文件的形式)的合并方法。我们就基于JGit提供的底层的原子方法,经过封装、改造得到了我们想要的基于裸库的合并算法。

4.2 火眼Web

火眼Web是一个 Web工程,其主要处理以下内容:

  • 评审、评论以及其状态扭转

  • 设置(评审人/审核规则/通知/仓库设置)

  • 仓库的基本管理

  • GitLab权限同步

评审

在用户提交了评审之后,评审人会收到评审邀请链接,点击后即可进行评审。对于评论我们设置了一个标记为解决的状态,方便评审人或评审发起者跟踪问题。

为了使评审功能更加完善,我们将GitLab的Merge Request模式移植了过来,并做了一些改进。其中一个特别的改进点是:锁定提交。 当用户在选择锁定提交的时候,合入分支的后续提交将在本次Merge Request不会被合入。我们认为这点在多人开发的时候很重要,保证了需要被合并的代码就只包含发起Merge Request时的代码。

冲突预检测

以往其他团队在使用Gerrit做评审的时候的一个比较大的痛点是:在评审流程走完之后合并代码的时候发现被评审的代码与已经合入到仓库里面的代码有冲突,这个时候又不得不再次合并代码,重新发起评审流程。火眼特别解决了这个问题,在用户做评审时,将被评审的代码和将要合入分支在内存中合并一次,如果发生了冲突就在评审界面醒目的提示发生冲突,并告知解决冲突方案。

仓库的基本管理

像主流的代码托管工具一样我们在界面上提供了可视化操作Git基本数据的功能。通过火眼RPC目前实现了以下功能:分支管理、文件管理、标签管理、提交历史查看、对比等。

GitLab权限同步

我们并未在火眼系统中单独开发一套权限系统,整个系统是复用的GitLab权限,每隔一段时间或GitLab权限发生改变时,火眼会拉取一遍GitLab的权限数据,主要包括:项目,组,项目与人的关系,组与人的关系。

4.3 火眼Git钩子

火眼Git钩子是火眼系统实现的两个Git服务端两个钩子:提交前钩子(pre-receive)和提交后钩子(post-receive)。提交前钩子用于提交代码用户选择普通提交并开启强制评审时,系统拒绝本次提交。提交后钩子用于当评审代码已成功提交,调用火眼平台,生成评审任务。下图展示了两个钩子在提交代码过程中各自的功能:

4.4 火眼命令行

火眼命令行是火眼用于提交评审的客户端命令行。因为如果需要提交评审任务,需要执行*git push HEAD:refs/changes/{ ChangeId } /{Branch}*命令,该命令太长,使用者体验不友好,上手难度高,所以我们决定使用客户端命令行的方式来简化提交过程,并降低使用成本。

为了让各个平台(Linux/Windows/Mac)都能使用火眼命令行,火眼命令行用Go语言编写,并编译成各个平台的二进制文件。

5. 火眼使用步骤

准备工作:安装火眼命令行,下列为具体使用步骤:

  1. 编写代码:和往常编写的代码步骤一样,写完后*git add*, ***git commit***代码。

  2. 提交评审:使用火眼命令行提交代码,系统会反馈代码评审地址,并用内部通讯工具通知相关评审人。下图展示了使用火眼命令行提交评审、生成任务的过程:

  1. 评审代码:在火眼平台对代码进行审查,审查通过后代码会自动合入(可配置手动合入)。

在开发流程上和日常开发差异不大,只是用火眼的提交命令替代了Git的提交命令。

6. 未来的规划

6.1 自动扫描

Code Review能解决业务人工评审问题,但部分代码bug或smell(代码臭味)可以通过代码扫描工具提前发现,因此后续我们将引入自研的自动化扫描工具,并在评审流程中嵌入此过程,以辅助评审。

6.2 自由评审

自由评审模式相较提交后即自动发起评审模式,区别在于其能够评审任意提交,该模式适用于代码开发完毕回顾或审查代码,但不涉及到合并代码的场景,如代码评审会议。

7 总结

火眼系统是基于GitLab提交代码的过程做拦截,现有GitLab用户可以在很低的使用成本上使用火眼。火眼系统灵活的评审模式、多样化的评审规则以及灵活的评审分支选择可以很好的辅助团队做好代码评审工作,达到了该系统开发的预期效果。