开源EaseLint,帮助团队轻松落地Lint

1,942 阅读7分钟

没有人敢保证自己的代码不出bug,但是用Lint至少可以保证所有人都少犯低级错误,守住团队代码质量的底线。

大纲:

  • 为什么说 Lint 对于团队有重要价值
  • 想让Lint丝滑的融入工作流,不得不对Lint进行二开
  • EaseLint 项目介绍

为什么说 Lint 对于团队有重要价值

我发现很多时候大家容易把IDE的语法错误提示与 Lint 搞混,语法检测就如同写word文档时提示单词拼写错误,Lint则像是grammarly(辅助写作软件),辅助作者改进句子措词,使其更为优雅得体,更符合上下文。

Android 官方对 Lint功能的描述非常准确:通过进行 lint 检查来改进代码 。这里关键词是改进,也就是当开发者写了一行语法正确并且运行正常的代码,但是它可能不是最优或最安全的写法,或最优的API组合,经过Lint 检测将发现这些坏代码,通过实时提示来辅助开发者改进为更健壮更安全的写法。

下面就举几个例子来体现这一特性。

  • 案例1:检测代码规范

在接入 ViewBinding 时,为了保证xml内的view Id 与 ViewBinding 保持一致性的驼峰命名,需要避免使用 btn_xx 下划线命名,这样方便阅读代码与检索代码。通常像这样的规范会在团队内集中同步,如何能保证在这个规范自同步后新提交的代码viewId 100% 是驼峰命名呢?注意这里说的是达到100%符合规范,要彻底杜绝因人为导致的疏漏。

在实践中从两个点下手就能100%达成这一目的,第一是本地借助IDE实时检测xml文件,辅助开发者快速改进代码,像下面这样

第二个是下文会提到的主动执行lint task 进行检测,并通过lint报告输出问题代码清单

  • 案例2:屏蔽不安全的原生API

在使用 Integer.parseInt 等方法时有可能会触发 NumberFormatException 导致Crash。有两种方式来规避,一是使用统一封装的工具类,二是必须包一个try catch。下图是实现这个检测要求的Lint效果:

  • 案例3:检测导致Crash的风险代码

在App侧一定会用到序列化用于解析接口数据,实现序列化会带来崩溃风险,如果一个实现了序列化接口的类

它的成员变量应用的类未实现序列化接口,就有可能引发崩溃。

这种问题靠code review 来杜绝难度是蛮大的,通常也希望线上code review 时更多关注业务逻辑而非低级的编码错误,这样能有效减少 code review耗时。

针对这类问题需要编写一个Lint规则,检测实现了序列化接口的类其成员变量的类型是否实现了序列化接口,像下面这样:

上面三个例子分别从团队规范与风险代码管理阐述了Lint的作用,也体现了出来一点,Lint 天生是依赖团队来放大价值的,即将团队的沉淀的技术经验,通过Lint快速触达到每一个成员与每一个项目中,依此来不断拉高团队代码质量的底线,并长期保证不劣化。

上述的例子都是自定义 Lint 规则,只需要稍加学习便可以实现。比如入门可以看 google simples 中的android-custom-lint-rules,再看看官方文档 Lint API Guide,基本就可以大胆进行自定义规则开发了。还有一个非常重要的资料时看 com.android.tools.lint:lint-checks:2.xx 库里的几百个检测器,这些检测器是自定义 Lint时最好的示范。

基本功能就是这样,但想要完全发挥 Lint的效益还需要下不少功夫,为了让Lint丝滑的融入工作流,需要对 Lint做一些二开。

想让Lint丝滑的融入工作流,不得不对Lint进行二开

落地Lint会自定义Lint就行了吗?当然还不够,有几个核心问要解决:

要支持自定义扫描目标

大型项目全量扫描非常耗时,历史代码运行良好,扫描出来去改动无太大必要反而徒增风险。另外也应该满足谁动的代码谁负责处理Issue。所以这自定义扫描目标是非常必要的功能(增量扫描并不准确),比如单次commit的文件,合并代码时两个分支之间的diff文件,就算是全量扫描也需要设置一个 git 时间线,屏蔽掉大量历史代码。

Android Studio 是支持的设置扫描范围的,但难以自动化。关于如何实现自定义扫描目标市面上有挺多方案的,下面是一些网络上被验证可行的方案:

  • 1.美团外卖Android Lint代码检查实践中提出的在扫描时获取到file,与使用git命令筛选好的文件列表进行比对,匹配上才扫描,否则忽略。
  • 2.Android Lint增量扫描实战纪要,二开 lint gradle 包下的LintGradleExecution,LintGradleClient ,LintRequest等文件,为设置扫描目标开一个接口出来供外部插件传参,EaseLint也是这个方案。
  • 3.相对早一点的还有流利说开源的okcheck

动态上线新规则,动态下线异常规则

这一步相对较为简单,既然都已经接管了LintGradleClient 等核心类,那么外部的Lint Task也需二开进行适配。在这个过程中很容易就可以接管 runLint等关键方法,准确找到 LintOptions 的干预时机,来完成动态配置解析与设置。

扫描时机要融入工作流,要自动化,让开发丝滑无感

这一步有很多选择,至于最终选择哪一种要根据团队工作流来确定。下面列举一些常用时机:

  • 1.编码时IDE会自动加载自定义的Lint规则,进行实时提醒
  • 2.本地commit时利用git hook 的pre commit 执行脚本出发lint task,检索执行结果,不成功则commit会失败

实际发现这一步是比较耗时的,主要是lint task 会触发一次完整的编译(这可以进行干预,但不优雅)

#!/bin/sh
echo "========pre commit start============="
pwd
pwd
chmod 777 ./gradlew
logs=./gradlew easeLint --stacktrace
echo "taskLog:$logs"
# 检索 日志中是否包含==lint_passed==
if [[ $logs == *"==lint_passed=="* ]];
then
  echo "success:pre commit check has passed"
  exit 0
else
echo "fail:pre commit check has not passed"
exit 1
fi
  • 3.只依赖本地环境还不够,开发人员的本地开发环境总会有差异,IDE bug 或者误操作关闭了 Lint 等等。我认为最好的时机在提交PR(Pull request) 或MR(Merge Request) 时,使用git hook 通知CI服务器执行Lint扫描,检测不通过不允许合并,需要改进代码后重新提交并再次扫描,这样保证合并到主干的代码一定会被检测。

  • 4.大厂巨型团队还会在打包前做一次全量扫描作为兜底,这个视具体情况而定。我们之前的分支管理是绝对不会出现绕过MP卡点的,这一步并非必须。

Lint报告要同步到企业IM或邮箱,完成自动化通知

将扫描报告上传到文件服务器上(像阿里云的OSS,腾讯云的COS都是成本低且非常易接入的文件服务器),同时对扫描报告xml文件进行初步解析,将报告html文件与解析结果一并发至对应开发邮箱或企业IM,点开即看,完成闭环。

使用Lint数据量化代码优化成果

Lint支持输出xml与html 两种 ,xml 格式可以用于解析并将数据存档。分析频率并不高,根据实际分析体量耗时来决定是否需要试用数据库。

将这些数据按照项目,时间做一张表,再配合线上Crash率等一起呈现,是非常不错的数据分析方式。更细化的便是对线上Crash进行标记归类,选出Lint 可以杜绝的部分来单独呈现,Lint扫描出的Error 与 crash率 一定是成负相关的。

EaseLint 项目介绍

前往仓库查看最新文档:github.com/Western-par…


滑到底部可点赞评论

浏览橘子树其他文章:橘子树的飞书写作记录