当前位置: 首页 > 科技观察

CodeReview效率低下?试试智能语法服务

时间:2023-03-17 00:35:25 科技观察

在人工代码审查(CodeReview,CR)中,明文浏览代码必然会消耗大量时间,影响CR效率。那么有没有更聪明的方法呢?阿里云智能代码语法服务是基于云备份的快速代码导航服务。无需本地克隆,即可在页面上体验熟悉的定义引用快速查看和跳转功能,大大提高代码审查的效率和质量。本文分享相关技术原理和实现方法。一、前言代码正文不是简单的二维平面结构。理解一段代码,需要反复跳转定义和引用,才能充分理解代码的深层逻辑和片段的影响范围。纯文本形式的代码浏览是网页代码审查的最大痛点之一。朱熹先生常说“心不在,眼不细看,不能长久”。代码文本的扁平化、漫无边际的阅读只能达到看得见、说得出来的境界,如果你是一个认真负责的代码审查者,阿里云的智能代码语法服务一定能帮助你全面理解代码变化,超越眼睛和嘴巴,达到“心到”境界的功能。既然心来了,眼睛和嘴巴就不在乎了?那么代码智能语法服务是什么?语法服务提供基于云备份的快速代码导航服务,无需本地克隆即可在页面体验熟悉的定义和引用快速查看跳转功能,大大提高代码审查的效率和质量。2.技术基础阿里云云效代码智能语法服务的底层技术是LSIF(LanguageServerIndexFormat),这是一种持久化语言索引的图形存储格式。图形的格式代表“代码文档”->“SyntaxSmartResults”事件关系。在LSIF之前,LSP(LanguageServerProtocol)定义了编码语言与各种终端代码编辑器之间的交互协议。本来,开发者需要为每个编辑器定义和适配一个语法分析服务应用,所以如果要在M个代码编辑器中使用M种语言,就需要MxN个应用。随着LSP的出现,开发者在解析代码语法时只需要遵循LSP协议格式,实现代码补全、定义显示、代码诊断等接口,只需要开发M+N个应用即可。但是,代码分析通常需要花费大量时间和资源。当用户请求语法服务时(比如查看定义),后台需要克隆代码,下载依赖包,解析语法,建立索引(类比IntelliJIdea初始化项目场景),编辑场景用户习惯了这种方式,等几分钟可能问题不大。但是在CR场景或者轻量级代码浏览场景下,这种方式显得时效性较差。几分钟后,用户可能已经完成了代码浏览,缺少持久化存储会导致资源过度消耗。因此,LSIF就是在这样的背景下应运而生,秉承以空间换取时间的思想,提前计算语法分析结果并以特定的索引格式存储在云端,从而快速响应多个请求来自不同的用户。引用官方的例子简单介绍一下LSIF,下面代码如下://thisisasampleclasspublicclassSample{}假设只有一个交互,当鼠标移到Sample的类名上时,“thisisasampleclass”会出现注释信息。LSIF的图形可以描述如下。示例文件包含一个范围信息,这个范围与一个hoverResult相关联。意思是在文件的某个位置范围内,如果触发了hover事件,则给出hoverResult中存储的结果。如果用Json文件来描述这张图片的存储,可以得到如下结果:{id:1,type:"vertex",label:"document",uri:"file:///abc/sample.java",languageId:"java"}{id:2,type:"vertex",label:"range",开始:{line:0,character:13},end:{line:0,character:18}}{id:3,type:"edge",label:"contains",outV:1,inVs:[2]}{id:4,type:"vertex",标签:"hoverResult",result:{["这个isasampleclass"]}}{id:5,type:"edge",label:"textDocument/hover",outV:2,inV:4}一个项目的实际LSIF图会非常复杂,往往包含数百个数千个节点。有兴趣的同学可以参考LSIF的具体说明[1]。三种实现方式阿里云云效语法服务架构图主要分为两部分:基于用户请求语法服务响应的事件触发索引构建过程1索引构建用户对于代码的浏览场景主要集中在codereview和mainbranchBrowsing的代码,所以我们目前主要支持两种场景的语法服务。GrammarService接收来自代码平台的事件消息,如代码推送事件、评论创建、更新、合并、关闭和重新打开事件,触发GrammarService的构建。我们构建的工作流调度主要是基于阿里巴巴开源的分布式调度框架tbschedule。系统通过zookeeper维护一个任务集群,通过zookeeper进行节点管理和任务分发,快速处理调度任务,不重不漏。对于不同的语言,我们只需要实现一次从源代码到LSIF格式的转换,就可以适用于各种场景。多种代码语言代码语言会被解析成一个统一的LSIF格式文件。针对阿里巴巴内部主要的Java语言,我们使用开源的Java代码分析工具Spoon[2],将Java源代码分析成AST(抽象语法树),然后捕获定义与引用、定义与注释的关联,以及将坐标信息转换为,标注内容,文本类型,所属文件等信息聚合,输出为统一的LSIFJson格式。开发期间对lsif-java的一些问题进行了修复和适配,比如locationrange信息混乱,各种缺失高亮字型的recall,适配非Maven仓库的索引构建等。同时修复了注解中部分注解无法正确解析的问题,该PR已被Spoon社区采纳合并[3]。lsif.json文件生成后,由于Json文件较大,直接从前端加载响应请求不太合理,而且后期增量生成和维护也非常困难,所以需要又一步:将lsif.json转化为结构化数据,从而按需响应用户的查询请求。lsif的图存储格式自然让人联想到图数据结构存储,图查询速度也比较快。但由于索引变化迭代快,ID变化频繁,图存储很难适应增量方案。不同的代码库很难在一张图中构造不同语言的索引数据。在参考了社区的相关实践后,考虑到成本和性能,由于ES天生适合大规模数据存储和索引,最终选择使用ES(Elasticsearch)作为结构化数据存储。我们将这个结构化数据上传到ES,然后语法服务后台服务器会根据用户的语法请求、查询定义、引用或评论信息构造ES请求查询,组装返回。对于分支机构,我们将持续更新并保留最新版本的索引数据;对于代码审查,我们将构建源分支的每个Push版本和源目标分支的合并基础版本的索引。索引构建的另一个难点是增量计算。前文提到,语法服务索引的构建对资源的要求非常高,而现实中代码库难免会有频繁的commit。这就引出了两个优化点:使用增量的方式减少存储内容的变化,加快索引的构建。使用分布式定时锁,减少频繁请求带来的压力。增量方案每次分支索引建立成功后,我们都会在数据库中记录该分支对应的版本号。当分支有新的提交时,生成lsif.json后,系统会比较两个分支的Diff,得到Go到changefile和changetype,进一步通过changefile提取索引影响的文件(the引用或定义的坐标信息发生变化),分析所有受影响的文件和相应的ES增删改操作,完成增量索引上传。这个增量过程平均减少了45%的分支构建时间。定时锁管理根据库的大小,LSIF的索引构建时间从10秒到几分钟不等,用户对同一个代码仓库提交操作的峰值可能达到每分钟近百次,即使我们使用增量技术也难以满足高频构建请求,在提交事件触及调度任务执行时无法保证准确的时机。综上所述,我们需要一个分布式时序锁来保证任务调度的顺序,尽量减少重复调度。当来自同一个代码库的不同推送消息进来时,Redis维护的分布式锁会做如下判断:如果库中当前没有正在运行的任务,则将该任务放在队列的头部,立即运行;如果已经有正在运行的任务,则比较新的Push消息是否是最新的,如果是,则加入队尾;当团队有两个成员时,丢弃任务,因为每次执行任务时,系统都会克隆分支代码,根据最新版本构建索引,从而避免了尽可能多地执行索引构建的可能性推动。考虑到线程意外退出,组长会全局每5秒发送一次心跳。当队尾或新任务监听到心跳超时时,队首的任务将被放弃,执行新的任务。2GrammarServiceResponse如前言示例,用户在使用GrammarService时,主要有以下三个请求:每次打开文件时获取所有可点击的高亮词点击高亮词获取对应的定义和参考列表点击Definitionand参考第一次请求,系统会根据文件路径构造过滤条件构造ESQuery请求,将当前文件的所有高亮词坐标信息发送给前端,避免前端做语法词segmentation,而且没有建好的文件自然不会在页面上高亮显示。另外,为了避免超大文件对ES的压力,前端会进行动态的批量加载。对于第二个请求,在获取定义和引用列表的过程中,我们不仅需要获取文件名和位置信息,还需要显示相应的代码内容,方便用户理解。为了实现这个效果,我们增加了批量获取文件碎片的接口。第三个请求,在同一个文件内跳转会自动高亮相应的代码行,在不同文件间跳转会打开一个新的页面并跳转。语法服务响应和语法索引构建完全异步,相互独立,支持资源独立伸缩。3索引清理语法服务的索引大小大约是代码文件内容的数倍,消耗存储资源。因此,根据用户平时的使用习惯和场景,制定了一系列的索引清理任务,避免过度消耗资源。当代码审查被合并或删除,以及分支被删除时,系统将开始执行索引清理任务以释放索引资源。4语法服务展望缺乏符号跳转一直是页面阅读代码的痛点之一。各种语法协议和技术层出不穷,LSIF、Kythe、SARIF、UAST、Tree-sitter、ctags等,全世界的技术人员都在为之努力。更智能的代码分析、更好的代码体验、更高的代码质量都在努力。云霄语法服务未来将逐步加快语法建设,支持更多代码语言,满足更多语法场景,提升用户代码浏览体验。相关链接[1]https://microsoft.github.io/language-server-protocol/specifications/lsif/0.4.0/specification/[2]http://spoon.gforge.inria.fr/[3]httpshttps://github.com/INRIA/spoon/pull/3513