提高代码质量!详解在 Gradle 项目中使用 PMD 的正确姿势
当今的软件开发需要使用许多不同的工具和技术来确保代码质量和稳定性。PMD 是一个流行的静态代码分析工具,可以帮助开发者在编译代码之前发现潜在的问题。在本文中,我们将讨论如何在 Gradle 中使用 PMD,并介绍一些最佳实践。
什么是 PMD?
PMD 是一个用于 Java 代码的静态代码分析工具。它可以帮助开发者找出潜在的问题,如代码重复、未使用的变量、错误的异常处理等。PMD 支持多种规则,可以根据具体项目的需要进行配置。其工作原理参考 How PMD Works。
PMD 支持通过命令行界面(CLI, Command Line Interface for batch scripting)和其他多种集成方式,比如 Maven、Gradle、Java API 等等。
PMD 在 Gradle 中配置和使用
Gradle 中自带了 PMD 插件,插件的默认版本可以通过源码 DEFAULT_PMD_VERSION 知道。使用和配置可以参考 The PMD Plugin,页面左上角可以选择 Gradle 版本,确保查看的版本和你使用的 Gradle 版本一致,因为很多 PMD 的配置属性或者功能不一定在每个版本都有。
通过页面左上角选了其他版本后跳转的地址是 Gradle 文档的首页,而不是 PMD 插件的文档页。我们可以通过修改 The PMD Plugin 链接中的 8.0.2 为其他版本号即可跳转到对应 Gradle 版本包含的 PMD 插件文档的页面。比如:
当前最新版:https://docs.gradle.org/current/userguide/pmd_plugin.html
7.3.3 版本:https://docs.gradle.org/7.3.3/userguide/pmd_plugin.html
在项目 build.gradle 文件中增加以下内容应用插件和扩展 PMD,参考 Usage 和 Configuration,更多的配置属性可以参考 PmdExtension。
插件会生成两个主要的 PMD TaskpmdMain 和 pmdTest 分别对 main 和 test 两个项目源文件目录使用 PMD 进行代码检查。
找到 IDEA Gradle 窗口 > Tasks > other,双击生成的 Task;或者在项目根目录运行./gradlew pmdMain 都可以运行 PMD。检查结果将输出到终端中(前提是配置了 consoleOutput = true),违反了 PMD 规则的类会给出完整的跳转路径以及规则提示信息。
最后还会给出一个报告的地址,内容包含了输出到终端的信息。Problem 列出了规则的提示,点击可以跳转到 PMD 规则描述文档对应的位置。
Gradle PMD Plugin 扩展属性
在这里我们将 PMD 插件的扩展属性作用进行说明,参考 PmdExtension,这个文档详细说明了各个属性的作用、默认值和配置示例。如果文档描述的不是很清楚也可以参考 PMD CLI options 的对应描述。
consoleOutput
是否将结果输出到终端(System.out)允许值为 true|false。
ignoreFailures
如果出现了警告,是否允许继续构建,允许值为 true|false。
配置为否(false),在执行 build 的时候(build 任务中默认包含了 pmdMain 和 pmdTest),如果发现了代码有违反规则,将会中断构建过程;配置为是(true),将不会中断构建,只是输出报告信息。
maxFailures
停止构建前允许的最大失败次数。
incrementalAnalysis
是否开启增量分析,允许值为 true|false。在 pmd docs Incremental Analysis 中详细描述了增量分析的相关信息。简单来说,开启了增量分析,PMD 会缓存分析数据和结果,后续分析仅查看那些新的/已更改的文件,以此显著减少分析的时间,在 Gradle 中,这个功能使用 PMD6.0.0 及以上版本才有。
但是有一些情况会导致增量分析的缓存失效:使用 PMD 的版本发生了变化;使用的规则集已更改;被分析的代码的类路径已更改;被分析代码依赖的库的类路径已更改。具体参考 When is the cache invalidated?
在以上前提下,即使切换分支缓存也是有效的,甚至还支持在不同的机器重复使用缓存文件。参考 Can I reuse a cache created on branch A for analyzing my project on branch B? 和 Can I reuse a cache file across different machines?
reportsDir
报告生成的路径。
ruleSetFiles
要使用的自定义规则集文件路径,可以在 files()中填多个路径。
ruleSetConfig
跟 ruleSetFiles 的作用一样,不过只能填一个文件路径。
ruleSets
指定使用的规则集,默认值为["category/java/errorprone.xml"]。建议如果配置了 ruleSetFiles 或者 ruleSetConfig,就将 ruleSets 配置为空(ruleSets = []),以免互相干扰,官方文档 Custom ruleset 给出的例子也是如此。
rulesMinimumPriority
每个规则都有个优先级,是从 1 到 5 的整数,其中 1 是最高优先级,参考[Message and priority overriding,每个规则的优先级参考 Java Rules。rulesMinimumPriority 的作用是配置报告的最低优先级,低于这个优先级的规则将被忽略。比如配置 rulesMinimumPriority = 4,优先级为 5 的规则将被忽略。
sourceSets
作为 check 和 build 任务的一部分进行分析的源代码集合,配置方式参考 SourceSet。
targetJdk
PMD 使用的 JDK 版本。有些规则可能会要求 JDK 的最低或者最高版本,具体要求参考 Java Rules。
threads
PMD 运行时使用的线程数。
toolVersion
要使用的 PMD 的版本。
为项目自定义合适的规则集
规则分类和查找
PMD 能检测的语音有很多种(后面内容以 Java 为例),针对不同的语音,PMD 内置了很多检测规则,并归为了以下几个类别:
最佳实践(Best Practices):这些规则执行普遍接受的最佳实践。
代码风格(Code Style):这些规则强制执行特定的编码风格。
设计(Design):帮助您发现设计问题的规则。
文档(Documentation):这些规则与代码文档有关。
容易出错(Error Prone):用于检测损坏、极度混乱或容易出现运行时错误的构造的规则。
多线程(Multithreading):这些是在处理多个执行线程时标记问题的规则。
性能(Performance):标记次优代码的规则。
安全性(Security):标记潜在安全漏洞的规则。
在 Java Rules 列出了所有相关的规则,点击蓝色字符可以跳转到规则的详细描述页面。
下图是规则 AbstractClassWithoutAbstractMethod 文档描述的信息,其他规则的描述可能还会包含 JDK 版本的要求,其他可配置属性等等。
需要注意有的规则可能被标记为 Deprecated 代表被弃用了。
配置规则集
我们可以编辑 XML 格式的规则集文件,指定我们项目要执行的规则,参考 Making rulesets。下面是没有包含任何规则的规则集文件的模版。
从上文我们可以知道 PMD 内置的每个规则都会提供引用实例,我们引用单个规则的时候,只需要将示例的 XML 代码复制到规则集文件中即可。
从 ref 中填写的路径 category/java/bestpractices.xml/AbstractClassWithoutAbstractMethod 我们可以明显看到它是按照内置规则集文件路径/规则名称的格式组织的,一个内置规则集文件对应了一个分类。
我们可以引用内置规则集文件实现批量引入分类下的所有规则,每个分类对应的 XML 文件名可以参考 GitHub pmd-java resources。再通过 exclude 指定规则的名称来排除某些规则。
我们可以使用 exclude-pattern 排除某些文件,使其不被 PMD 检查,也可以使用 include-pattern 包含的方式。如果两种方式都包含相同的文件,最终这个文件会被 PMD 检查。
规则集文件编辑好后,使用 ruleSetFiles 或者 ruleSetConfig 配置路径。比如下面配置的意思是指向了项目根目录下的/code-analysis/pmd/rulesets/custom-rule.xml。
配置规则
规则引用的同时,我们可以覆盖其原有的一些配置,比如提示消息 message 和优先级 priority。
某些规则可能有特定的属性,我们也可以将其覆盖。这些特定的属性 Java Rules 中都有提供,比如下面这个例子参考 NPathComplexity。
有些属性可以提供多个值,这种情况下可以通过分隔符来提供,比如竖线(|)或逗号(,)。
抑制警告
有时候 PMD 可能会产生误报,这种时候我们可以通过抑制警告让 PMD 跳过对这些代码的检查。
从 Java 1.5 开始可以使用注解 @SuppressWarnings 来标记类或者方法。
@SuppressWarnings('PMD')抑制所有 PMD 的警告。
@SuppressWarnings("PMD.UnusedLocalVariable")抑制规则 UnusedLocalVariable 的警告。
@SuppressWarnings({"PMD.UnusedLocalVariable", "PMD.UnusedPrivateMethod"})抑规则 UnusedLocalVariable 和 UnusedPrivateMethod 的警告。
@SuppressWarnings("unused")JDK 里面的 unusedPMD 也遵守,抑制所有跟未使用相关的警告。比如:UnusedLocalVariable 和 UnusedPrivateMethod。
在警告提示的代码行的末尾加上注释// NOPMD 也可以抑制这一行引起的警告,参考 NOPMD。
在规则集文件中也可以配置要抑制警告的文件,匹配的方式可以是正则表达式或者 XPath,具体可以了解 The property violationSuppressRegex 和 The property violationSuppressXPath。
第三方规则集
除了 PMD 内置的规则集,我们还可以引入第三方规则集。在 3rd party rulesets 中列出了一些,还有阿里 Java 开发规范 p3c 也基于 PMD 开发一套规则集,从它的 pom.xml 可以了解到是基于 PMD6.15.0 版本。
参考 Dependency management 引入规则集依赖,在规则集配置中引入提供的规则即可。
需要注意的是,第三方的规则集很可能没有按照 PMD 内置规则集那样分类,它们提供的规则配置文件目录也可能不一样,比如 p3c 的规则配置文件都在/resources/rulesets/目录下并独自定义了一套分类。
其他技巧
PMD 的最新官方文档地址是:https://docs.pmd-code.org/latest/pmd_userdocs_tools.html。链接中的 latest 对应了版本号,指向的是当前最新版本,如果想查看其他版本的文档,修改为对应的版号即可。比如 6.39.0 版本的链接为:https://docs.pmd-code.org/pmd-doc-6.39.0/index.html。不过可能只有比较新的一些版本才能看到对应的文档。
官方提供了一个 PMD 的最佳实践可以了解下。
PMD 还有跟特定语言相关的文档,比如 Java support,里面有支持的 JDK 版本等信息。
如果使用过程中遇到了问题,可以参考 Getting Help 从这些网站里面寻找帮助 github discussions、github issues、stackoverflow tagged pmd。
PMD 官方文档还提供了 Copy/Paste Detector (CPD)关信息,CPD 可以用于检测重复代码。还提及了 Duplicate Code 教我们遇到重复代码如何消除,以及一个关于设计模式的网站 Design Patterns。
关于 PMD 这个名字,并没有特殊的含义,作者纯粹只是觉得这几个字母放一起作为名称挺好的,来自 What does 'PMD' mean?
版权声明: 本文为 InfoQ 作者【树上有只程序猿】的原创文章。
原文链接:【http://xie.infoq.cn/article/d4565fbb1dd1c113e48f53acf】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论