找回密码
 会员注册
查看: 15|回复: 0

vivo互联网自研代码评审VCR落地实践

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
72841
发表于 2024-10-4 22:41:28 | 显示全部楼层 |阅读模式
互联网效能平台团队- Chi Wei本文介绍了vivo工程效能团队基于 Gitlab、Gerrit等开源工具搭建的VCR平台,代码评审idea插件开发及开发过程中遇到的挑战、困难,并分享了相应的应对策略和优化方案。代码评审是软件质量保证一种活动,由一个或者多个人对一个程序的部分或者全部源代码进阅读理解。一般来说分为作者和评审者两种角色,作者方提供代码逻辑的介绍和代码,评审者则对提供的代码基于设计,功能性和非功能性等方面认知进行阅读并提出问题。常见的评审组织形式是有同行评审(Peer Review)和小组检查 (Team Inspection)两种方式。在代码评审中,评审的目的在通过代码的评审发现潜在的问题,同时分享和表达是代码评审的重要收获,我们知道人相同在不同的文化下生产力是不同的,代码评审是一个工具,工具受文化的影响的同时也影响着文化,最终朝着我们希望的责任共担、持续改进的方向发展。一、代码评审演进随着互联网的发展,开发人员也越来越重视代码评审带来的代码的代码质量提高以及代码评审间接带来的分享及人员备份效果,已经不满足于只是简单的发现当前问题解决问题记录问题,需要满足从评审基本跟进、评论管理、评审报告以及评审方式多样化、评审与研发流程相结合等需求。① 代码评审检查表:手工定义要检查项,检查完进行打卡标记结果。② 插件快速评审导入导出:快速在插件上进行评论,并将评论结果导出给被评审人,被评审人导入评审结果查看,评审表不可复用,一旦代码变更则无法准确定位、也无法再次跟踪评审修改结果。③ 在线代码评审:在线插件或网页评审,提供提交前提交后评审,可多人评审策略管控、代码评审与需求/缺陷关联管理。④ 自动化代码评审:结合现有的Sonar扫描、安全扫描进行对提交的代码进行自动化检查,使代码在人工评审之前已经经历一轮自动评审,代码评审通过之后可自动触发构建、部署等。⑤ 智能化代码评审:根据AI大模型,可对提交代码进行综合评价(编码标准、可用性、可读性、可维护性、安全性、高性能、异常控制、设计原则、可扩展性、代码复杂度等等)并给出相关测试建议等,未来大模型对代码评审还有更大的空间。二、代码评审解决的需求和痛点是什么?vivo当前已经有EasyCR评审工具,那为什么我们还需要继续开发调研代码评审工具呢?我们先看看下面通过内部调研获取的信息,看看用户希望的代码评审工具需求和痛点是什么针对当前vivo代码评审工具我们继续升级补充场景:增加评审方式:对原自由评审方式(主要是提交后进行代码评审)增加评审控制方式(提交代码至仓库前进行代码评审、合并时提交代码评审)。支持网页/插件:增加网页端评审功能,满足不同角色进行评审及用户体验上的优化,增强插件版评审功能。支持研发流程控制:上线过程中可作为人工卡点一项检查项(可通过代码是否评审、代码评分、代码问题解决情况等进行判断),通过线上管理,提高上线质量。支持自动化检查:代码提交前,提交后可进行代码自动化检查,对代码进行自动评审。增加用户定制化需求:如评审权限、评审通知方式、评审策略多人评审管理、评审报告订阅等。当前市场上有很多优秀的代码评审工具,但是很少有评审工具能满足所有的场景,角色不同,需要的能力不同,同一个角色不同团队使用的方式不同,我们需要一款解决用户痛痒爽的代码评审工具。三、vivo代码评审系统架构四、vivo代码评审工具使用流程在代码评审中,CR可以是一次Commit,也可以是一次MergeCommit,那么针对一次CR我们可以随时对已经提交的commit进行评审,也可以在CRpush至代码库之前拦截,同时也可以在一次合并之前进行代码评审。代码评审模式:1. 提交前评审(Pre-push Code Review)2. 提交后评审(Post-push Code Review)① 合并评审② 自由评审提交前评审:VCR基于VCR在提交push至Gitlab代码仓库之前,对代码进行拦截,并进行评审,支持一次评审请求作为一次评审,可对一次一次评审请求查看所有变更记录并进行评审追踪。利用开源工具Gerrit,将评审请求推送至Gerrit中,评审通过后,将代码从Gerrit同步至Gitlab仓库提交后评审:①合并评审:VCR基于Gitlab 在一次MR的基础上进行代码评审。②自由评审:针对用户当前代码库当前分支信息或历史commit进行评审。五、vivo代码评审工具实施5.1 确认技术架构提交仓库前进行代码评审,我们使用当前成熟的代码评审Gerrit,实施过程中最大的问题是用户如何低成本切换及简单评审的问题,对于当前Gerrit评审工具遇到的问题如何解决呢?1) 我们知道Gerrit评审工具需要提供给用户Gerrit代码库地址,并进行下载使用,当前用户使用的代码库习惯不能更改,也是不愿意修改的,那么我们如何解决呢?给插件加持,提供用户黑盒切换至评审代码库,或执行一键下载代码库功能,底层使用Gerrit与代码托管库同步机制解决代码一致性问题,用户在使用代码库时同原使用方式一致。2) Git代码提交,CR为最小单位,CR可作为一次评审,但还有很多用户使用的习惯是一次push作为一次评审,如何解决用户一次push为一次评审呢?a)需要对代码关系链需要进行整理,识别出一次push作为一次评审记录,用户多次追加提交记录至评审请求,需要重新识别出关系链作为原push请求的评审记录,Git原生对代码变更的情况比较多,我们对一些场景进行分析再特殊处理,不穷举。b)可对最小粒度CR的评审,也同时提供一次push请求内容进行评审,更方便快捷。用户不管是提交前评审、合并时评审,都可能会产生一次push,多次commit,用户需要对最小粒度CR评审,也需要对最新变更所有内容进行评审。5.2 插件改造实施根据我们对用户的调研过程中,用户对代码评审插件网页同时兼容的要求比较高,针对idea插件我们如何改造代码评审,这里我们着重对Gerrit插件改造展开说明。步骤1:了解插件框架、配置、打包、运行1)插件框架整体介绍(图片来源于网络)开发方式:在官网的描述中,创建IDEA插件工程的方式有两种分别是使用DevKit(IntelliJ Platform Plugin 模版创建)和Gradle构建方式,这两种方式在构建项目和打包发布上有所区别,同时官方提供了将Devkit迁移至Gradle的方式。参考:https://plugins.jetbrains.com/docs/intellij/developing-plugins.html框架入口:一个 IDEA 插件开发完,要考虑把它嵌入到哪,比如是从 IDEA 窗体的 Edit、Tools 等进入配置还是把窗体嵌入到左、右工具条还是IDEA窗体下的对话框。UI:思考的是窗体需要用到什么语言开发,没错,用的就是 Swing、Awt 的技术能力。API:在 IDEA 插件开发中,一般都是围绕工程进行的,那么基本要从通过 IDEA 插件 JDK 开发能力中获取到工程信息、类信息、文件信息等。外部功能:这一个是用于把插件能力与外部系统结合,比如你是需要把拿到的接口上传到服务器,还是从远程下载文件等等。2)Gradle创建新版通过 New-> Project->IDE Plugin进行创建,旧版通过New Project->Gradle->IntelliJ Platform Plugin进行创建。项目结构如下:3)配置介绍plugin.xml com.your.company.unique.plugin.id Plugin display name here vs 1.0.0 YourCompany most HTML tags may be used]]> 1.0.0 1.新增xxx功能 2.优化xxx功能 ]]> com.intellij.modules.lang Git4Idea org.jetbrains.idea.maven xxxxx xxxxx com.demo.intellij.plugin.vcr.push.VcrPushExtension$Proxy上述我们看到依赖的Git4Idea 包,如果我们想修改原生的的Git,先看下push依赖包中如何实现的。Git4Idea(plugin.xml) ...intellij-dvcs.jar(plugin.xml) ....从上述可看到,Git4Idea 的GitPushSupport扩展实现push的功能点,接下来我们主要对GitPushSupport进行javassist字节码修改以达到扩展git push组件能力。扩展使用GitPushSupport之前,需要将需要的类进行装载至GitPlugin中,然后再对GitPushSupport进行字节码改造,至此对git Push原生插件页进行改造。步骤5:使用树状列表模式,展示一次push请求VCR提交内容及多个CR情况主要是实现JTreeTable,对VCR与CR进行管理。一次评审请求VCR包含所有CR的提交变更记录,可针对该变更记录进行代码评审,单个CR也可以进行评审。步骤6:展示变更文件视图及定制评论展示模块,精准定位代码代码评审主要根据编辑器获取代码行及位置,评论可精准定位到代码行。1)changeBrowser变更视图展示VCR变更文件信息2)双击文件,diff视图展示inline和side-by-side两种代码差异声明扩展,针对扩展类进行定制化改造。plugin.xml3)添加代码块评论,定位代码块AddCommentAction.javapublic class AddCommentAction extends AnAction implements DumbAware {public AddCommentAction(String label, Icon icon, CommentsDiffTool commentsDiffTool, Editor editor, List fileComments .... ) { super(label, null, icon);}private CommentInput createComment() {//获取用户选择代码位置位置//行的情况下,默认是开头和行结束 得到光标的位置caretModel.getOffset();/*取到插字光标模式对象 CaretModel caretModel = editor.getCaretModel();得到光标的位置int caretOffset = caretModel.getOffset();//得到一行开始和结束的地方int lineNum = document.getLineNumber(caretOffset);int lineStartOffset = document.getLineStartOffset(lineNum);int lineEndOffset = document.getLineEndOffset(lineNum);获取一行内容String lineContent = document.getText(new TextRange(lineStartOffset, lineEndOffset));*/Document document = editor.getDocument();int lineNum = document.getLineNumber(editor.getCaretModel().getOffset()) ;int lineStartOffset = document.getLineStartOffset(lineNum);int lineEndOffset = document.getLineEndOffset(lineNum);String lineContent = document.getText(new TextRange(lineStartOffset, lineEndOffset));.....}}所有评论展示列表如何精准定位代码SafeHtmlHistoryComments.javapublic class SafeHtmlHistoryComments extends JPanel { private Iterable fileComments; private List commentInfos = new ArrayList(); private CommentInfo currentCommentInfo; private SelectedComment selectedComment; private SelectedComment operatorSelectedComment; private Editor editor; public SafeHtmlHistoryComments(Editor editor,Iterable fileComments, Comment selectedComment) { super(new BorderLayout()); .... HistoryCommentListPanel historyCommentListPanel = new HistoryCommentListPanel(fileComments); //双击table某行触发代码定位 historyCommentListPanel.addTableMouseDoubleHit(new Consumer() { @Override public void consume(CommentInfo commentInfo) { codeTextHit(editor,commentInfo); } }); } /** * 定位代码 * @param editor * @param commentInfo */ private static void codeTextHit(Editor editor, CommentInfo commentInfo) { SelectionModel selectionModel = editor.getSelectionModel(); // 优化:如果文件修改过了,则不进行选中操作,换为提示 if (null != commentInfo.startIndex & null != commentInfo.endIndex & commentInfo.startIndex != 0 & commentInfo.endIndex != 0) { editor.getCaretModel().moveToOffset(commentInfo.endIndex); selectionModel.setSelection(commentInfo.startIndex, commentInfo.endIndex); } else if (null != commentInfo.line & commentInfo.line != 0) { int lineNum = commentInfo.line - 1; editor.getCaretModel().moveToOffset(lineNum); CharSequence charsSequence = editor.getMarkupModel().getDocument().getCharsSequence(); if(null!=commentInfo.range) { RangeUtils.Offset offset = RangeUtils.rangeToTextOffset(charsSequence, commentInfo.range); selectionModel.setSelection(offset.start, offset.end); }else{ Document document = editor.getDocument(); int lineStartOffset = document.getLineStartOffset(lineNum); int lineEndOffset = document.getLineEndOffset(lineNum); selectionModel.setSelection(lineStartOffset, lineEndOffset); } } editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); }....}六、未来展望6.1 自动化代码评审代码提交评审或代码合并之前,先自动化检查(Sonar/安全扫描)快速发现并纠正潜在问题,检查成功后提交评审。代码评审通过之后,结合流水线,自定义部署构建策略,实现快速迭代。自动汇聚测试报告,根据评审问题类型进行分类,不断改进Sonar检查规则,从而形成良性循环。6.2智能化代码评审提交代码评审之后,通过AI大模型对代码进行综合评价,并给出建议。通过智能代码评审,产生评审报告,并进行智能化分析。END猜你喜欢海量数据处理利器 Roaring BitMap 原理介绍TiKV 源码分析之 PointGetvivo 制品管理在 CICD 落地实践分布式任务调度内的 MySQL 分页查询优化
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-12 04:03 , Processed in 0.496120 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表