静态分析:用Checkstyle实施Java代码规范
众所周知,良好的代码规范有助于提升可维护性和协作效率,预防缺陷。但怎样才能保证有效执行呢?
我所在团队早期曾需要在代码审查中投入时间保证规范。通过引入静态分析,问题代码被100%提前拦截,而开发人员也在即时反馈中养成了良好习惯。
本文将介绍如何为Java项目配置代码规范检查。
关于静态分析
为保证软件质量,开发团队通常需要针对每项功能进行特定的代码审查和测试,其效果受功能复杂度和经验影响并需要投入大量时间。
静态分析允许我们:
在不运行程序的情况下
根据最佳实践或常见缺陷模式
对代码本身执行统一、跨功能的自动化检查
从而有效扩大覆盖面、降低成本
关于Checkstyle
Checkstyle初发布于2001年,是目前GitHub上人气最高 (★4.6k)、持续活跃的Java静态分析工具。
它主要关注代码风格方面的检查,目前支持14大类、162个检查项,提供Google/Sun代码风格规范对应的配置文件,同时支持灵活的定制与扩展。
Checkstyle拥有丰富的集成方式:CLI (命令行)、构建工具支持、IDE支持等。
快速集成——Gradle
让我们以流行的Java构建工具Gradle为例,给项目添加Checkstyle检查。
利用Gradle,我们只需配置一次,即可在本地Shell构建、IDE构建和CI构建中统一生效:
集成后目录结构如下:
步骤
1
下载配置文件
首先,下载官方提供的Google Checkstyle配置文件,将其保存为config/checkstyle/checkstyle.xml :
https://github.com/checkstyle/checkstyle/blob/checkstyle-8.22/src/main/resources/google_checks.xml
2
添加插件
接下来,向 build.gradle中添加Checkstyle Gradle插件,并指定所用的Checkstyle库版本。注意:不同版本的Gradle插件默认使用的库版本是不一样的,显式指定版本可以避免兼容性问题。
3
执行构建
执行Gradle构建 (或单独运行checkstyleMain /checkstyleTest任务),可以看到检查已经生效:
4
查看报告
访问 build/reports/checkstyle 目录,可以查看报告:
Checkstyle工作流程
在做进一步详细配置前,我们先来了解一下Checkstyle的工作流程:
初始化
无论我们使用哪种集成方式调用Checkstyle,集成接口都会根据收到的参数及Checkstyle配置文件来:
调用Checkstyle相关组件,创建 Configuration对象;
使用Configuration创建 Checker (根模块) 实例;
生成输入文件列表,将其传给 Checker开始执行。
执行
收到文件列表后,Checker模块会调用前面初始化的四种不同职责子模块完成检查:
其中“检查”阶段最常用的模块是 TreeWalker,它负责Java AST (抽象语法树) 检查的公共流程。通过配置它的子模块可以:
完成各方面的AST检查 (如变量名、import语句等)。
过滤事件。此处利用AST信息可以实现比顶层Filters更精细的过滤。
配置文件
了解完工作流程,我们就可以根据项目情况做进一步配置了:
Checkstyle配置文件: 检查规则、文件预过滤、事件过滤、TreeWalker内部过滤等绝大部分核心行为。
集成接口层参数: 文件列表,配置文件路径,属性值外部定义,集成接口自有输出选项等。
Gradle等集成接口的参数较为简单并已有合理的默认值,我们关注的重点还是配置文件。配置文件与Checkstyle基于Composite (组合) 模式的模块化相对应,每个 <module> 元素包含该模块的属性值定义及子模块的配置:
典型配置文件结构如下 (属性略):
全局属性
我们可以配置根模块属性来控制Checkstyle的全局行为。常用的属性包括:
severity : 违规事件的严重程度
charset : 用于解析输入文件的字符集 (如 utf-8 )
fileExtensions : 受检查的文件扩展名 (逗号分隔)
基中部分属性 (如 severity ) 也适用于子模块,可在子模块配置中覆盖从父级继承的值。
完整列表请参阅:
https://checkstyle.org/config.html#Checker_Properties
实例
为确保代码规范的有效执行,我们希望把Checkstyle检查出来的问题报告为错误,中断Gradle构建。这可以通过配置 severity 实现:
重新运行 checkstyleMain 任务,得到错误信息:
自定义检查项
通过配置TreeWalker Check或独立的FileSetCheck,我们可以自定义检查项。
CheckStyle目前共支持14大类、162个检查项。想了解常用的检查项,最简单的方式就是对照Google Java Style Guide,阅读Google Checkstyle配置文件。
-
完整列表:
https://checkstyle.org/checks.html
-
Google Checkstyle配置文件:
https://github.com/checkstyle/checkstyle/blob/checkstyle-8.22/src/main/resources/google_checks.xml
-
检查项与Style Guide的对应关系:
https://checkstyle.org/google_style.html
实例
From: Code2048
缩进圣战,是一场双方实力悬殊但仍在激烈持续的大型宗教战争,打响于人们使用编辑器编写代码伊始。
为了安抚习惯了4空格缩进和超大屏幕的团队成员,让我们在Google配置文件的基础上做点修改——缩进2→4,行宽100→150:
重新运行 checkstyleMain 任务,可见错误数显著下降,团队内的紧张气氛也随之化解了:
跳过文件
通过配置FileFilters,我们可以直接跳过文件,不对其执行任何检查。目前Checkstyle提供了BeforeExecutionExclusionFileFilter,可在其中指定要跳过的文件名正则表达式:
忽略事件
FileFilter的过滤条件不涉及文件内容。如要针对违规的位置或种类等进行过滤,可以使用TreeWalkerFilter或独立Filter,对已经产生的事件进行过滤。
下面介绍两个典型用法。完整Filter/TreeWalkerFilter列表请参阅:
https://checkstyle.org/config_filters.html
实例:SuppressionFilter
利用SuppressionFilter这一顶级Filter,我们可以在单独的文件中定义事件过滤规则。一个常见的做法是在不同的项目间共享Checkstyle配置文件,同时分别维护各自的suppressions.xml文件。
config/checkstyle/checkstyle.xml:
(注:config_loc的值由Gradle插件传入,默认为 $rootProject.projectDir/config/checkstyle )
config/checkstyle/suppressions.xml:
实例:SuppressionCommentFilter
即便制定了最完善的代码风格规范,代码里仍可能会存在一些“合理”的违规。如果我们既不想禁用一项检查,又不想为每个特例手动修改配置文件该怎么办呢?
这时我们可以考虑SuppressionCommentFilter这一TreeWalkerFilter。定义好特殊的代码注释模式后,我们就能随时在代码中标记排除范围。例如,对由CHECKSTYLE:OPEN和 CHECKSTYLE:CLOSE 包围的代码块,忽略全部检查事件:
完整属性列表和更多实例请参阅:
https://checkstyle.org/config_filters.html#SuppressionCommentFilter
扩展Checkstyle
如果现有的检查、过滤器和报告还不能满足项目需要,我们还可以对其进行扩展。这里就不详细展开了,请参考官方文档:
https://checkstyle.org/writingchecks.html
了解更多Java编程,请长按二维码关注微公众号