内容简介:结合静态代码扫描来给插件间接口把把脉
导读
如火如荼的EP建设中小鹅收到了一个小小的需求,如何知道每个版本变更了哪些插件间接口呢,有没有及时覆盖?
问开发,看代码,看变更日志貌似有那么点不太智能,重点是也不能保证有没有遗漏,不能解决测试童鞋的完美主义兼强迫症,有没有一份及时统一的视图可以来review插件间接口的变更和覆盖情况呢?
插件间接口示例
既然是统计插件间接口,我们先认识下手管的插件间接口定义,在手管插件化框架中,各插件相互平行,插件间接口调用即插件数据传递通过框架封装的统一接口进行通信,由框架进行底层的数据封装和传递,具体实现为各插件间维护一份插件对外的插件间接口配置,编译时在框架生成对应的插件常量,插件内部重载消息函数通过判断传递的接口常量进行对应消息处理从而实现接口间同步/异步数据传递。
插件间同步调用示例
插件间接口变更统计
每次编译前框架都会解析接口配置xml生成统一的插件接口常量表,那插件的变化情况我们可以从这里入手,从每次编译生成的常量定义中来找到各版本插件接口的变更情况,通过与上个版本的插件列表定义的插件名参数及返回值做一一对比,就可以知道当前版本的接口变更情况以便及时补充接口用例:
7.4变更接口有69个,数量有些超出大家的想象,也顺手给7.0~7.5版本做了个小统计,统计结果手管目前的插件间接口达到了740+,每个版本呈不断增长趋势。
这么多接口是否都是有效的接口呢?
目前已有的插件间接口用例覆盖程度有多少呢?
经过这么多版本的迭代相信应该有不少多余的水分,插件内的代码各FT通常会清理的比较及时也有一些现成的 工具 做冗余代码清理,但对外的接口大多担心外部兼容性及依赖问题通常清理不及时,有没有什么好的办法来梳理下,给这些对外接口把把脉呢?
插件间接口规则抽象
有没有类似调用链的分析工具呢?但插件化框设计各插件是平行的,调用链均指向框架接口无法解决我们的问题。虽然现成的调用链工具达不到需求,但我们可以借鉴下调用链的方法,重新抽象规则来建立一张我们想要的接口定义-实现-调用的关系图:
接口调用规则示意图
抽取规则如下:定义-实现-调用是一个正常接口的三要素。 如果三要素有任一缺失,我们可以推测该接口可能无人调用可以清理或者实现者已清理但仍有调用。
规则一 :接口定义,在框架中有定义的插件及插件接口常量认为插件已定义。
规则二 :接口实现,在插件工程中有调用到本插件常量的则认为是本插件内部的接口实现,如projectA中有调用CosntA.functionid.interfacea1,可以认为是接口a1已实现,记录插件A的a1接口的实现地址。
规则三 :接口调用,在插件工程中调用到非被插件常量的则认为是外部接口调用,如projectA中有调用ConstB.functionid.interfaceb1,则认为工程A调用了插件B的b1接口,在b1接口的调用链中添加该插件的调用记录及文件地址。
插件间接口规则实现
考虑插件间接口是通过传递接口常量来完成数据传递,我们可以通过代码扫描来构建我们的上述规则,结合我们的自定义需求来看看目前android常用的三款静态代码扫描工具:
从扩展性的角度看,coverity作为商业软件虽然官方文档也支持自定义扩展,但相关资料太少,个人更倾向于lint和findbugs,不会写还可以从源代码里面偷偷师,考虑到插件间接口传递的是接口常量,字节码在编译优化过程中常量字段被替换可能导致部分路径无法回溯,也不利于我们对结果做进一步的整理分析,所以最终选定lint进行源码扫描处理。
选定了工具之后实现部分就水到渠成了,按lint规则扩展来添加需要的检查规则,下图虚线模块是每个自定义规则需要扩展的地方:
AndroidLint自定义规则扩展流程图
1、注册规则,声明扫描范围为JAVA_FILE_SCOPE:
2、实现检测器,检测器是实现检查逻辑的主体,自定义的FunctionDetector检测器继承自Detector并实现Detector.JavaScanner接口,并定义我们关注的扫描节点:
(1)查找插件接口定义:
在扫描工具中我们可以按抽象语法树来进行代码节点的查找,在Android Lint中scanner通过lombok.ast(Abstract Syntax Tree抽象语法树)API来进行代码节点的查找,有兴趣的童鞋可以参照Eclipse AST介绍。
前面说到,手管编译前编译脚本会根据插件配置在框架生成相应的插件及接口常量类:
因此插件接口我们可以重写visitClassDeclaration(ClassDeclarationnode)函数在类声明节点中查找解析相应的类文件,将functionid的内部类的所有常量定义加入接口名list,并收集相应的location信息:
(2)查找插件接口实现和调用:
获取插件接口实现,调用本插件的插件接口常量可以认为是该插件间接口的实现,在visitVariableReference(VariableReference node)重载函数中对于调用到的常量判断为插件常量格式(如PiConst.FunctionId.FunctionName)则获取其插件常量判断是否为本插件的接口,如是,获取其location信息写入实现位置。
获取插件接口调用,调用非本插件的接口常量则认为是对外部接口的调用,将插件名及location信息加入到该接口的调用列表中。
3、确认全部插件工程都扫描完成后,在afterCheckProject(Context)重载函数中判断每个接口状态:
1)有实现有调用列表的为正常接口;
2)无实现仍有调用的为冗余未清理接口,可清理接口定义及调用;
3)有实现但无调用的疑为冗余未清理接口,可清理接口定义及实现;
4)仅有定义,疑为冗余未清理接口,可清理接口定义。
得到了748个接口的状态信息,有30%接口有清理空间,我们抽查了主界面的几个,比如主界面REPORT_MESSAGE接口为5.x的消息中心接口,在7.0改版时该功能已全部去掉但仍有6个其他业务插件引用在继续给主界面发消息。
是否可清理呢?
答案是肯定的,接口定义及外部插件的引用均可删除,只删除定义会导致编译不过通知引用插件删除相应的调用即可。旧版本插件调用是否会有crash问题呢?
插件化框架无法保证插件是一定存在的,插件进行接口间调用时就需要进行容错处理,所以插件间接口也不是只增不减的,可以删除。
我们粗略做个统计:
接口定义(xml配置接口及参数返回值定义不会进入编译)常量接口1行,非normal接口共240个;
接口实现,接口参数及返回值均值为2.05个,假设为10行,有实现但无调用的有148个;
接口调用,import及无效调用假设每个引用5行,无实现的调用列表有40个。
240+148*10+40*5=1920保守估计可以清理约2000行代码,相关的资源及配置也可以做进一步清理。
插件间接口视图的其他应用扩展
除了代码清理,插件间接口梳理结果是否还有其他应用呢?
比如查看插件用例覆盖程度,插件间接口测试也是通过调用插件接口调用来进行接口验证,因此调用列表中包含pitest插件的可认为是已覆盖的插件间接口,过滤调用列表中包含pitest的有178个,目前插件间接口pitest的覆盖率为23.8%。
比如作为插件用例的下架指引,状态为非NORMAL或者插件列表如果仅有pitest插件的可推测该接口已废弃,测试用例可以考虑从日常监控中下架。
比如调用列表数量是否可认为是接口覆盖的优先程度,调用较多的是否可认为该接口使用率更高优先级更高,需要更多的关注和验证呢?
……
插件间接口整理只是我们静态代码扫描在缺陷/规范扫描之外结合业务的一个小应用,通过梳理业务定义处理规则,把代码的问题回到代码中来处理。照此思路,一些日益膨胀的公共lib库,裁剪对外提供的sdk也可以进行精确的梳理瘦身,其他各层级的接口是否也可以梳理出这样的可视化图表呢?统计点是否也可以进行类似的梳理验证呢?
结合业务代码扫描我们还可以做的更多,也许你也有更多的代码扫描的应用场景也欢迎大家一起探讨~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 静态库遇到静态库
- 全局变量,静态全局变量,局部变量,静态局部变量
- Android NDK秘籍--编译静态库、调用静态库
- static特别用法【静态导包】——Java包的静态导入
- c# – 为什么委托在静态方法中使用时不能引用非静态方法?
- nodejs处理静态资源
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。