PMD (Programming Mistake Detector)是一款开源的、源代码静态分析工具 (Open Source Static Source Code Analysis Tool)。它能够帮助开发人员检查发现源代码中性能不佳(Inefficient Codes)或者不良的编码习惯(Bad Programming Habits)的问题。PMD内置了一些常用的检查规则,与此同时,它也支持开发人员自定义个性化的规则。PMD主要用在Java工程中,它还支持检查JavaScript、PLSQL、XML、Scala等多种语言。和Checkstyle相似,PMD能够有效的帮助开发人员提高代码质量,发现代码问题。
本章主要重点介绍PMD的使用方法和一些PMD中使用的规则。更详细的内容可查阅PMD的手册。FindBugs也是一款Java程序的静态分析工具。与PMD相似的是,它也能够帮助开发人员检查源代码中潜在的问题。但是,FindBugs自2015年发布版本3.0.1之后再未更新。如果读者对其感兴趣的话,可自行查看其使用方法。
开发人员通常将PMD集成在项目中,在项目构建时运行。在Maven和Gradle中集成PMD非常方便。
Maven提供了Apache Maven PMD Plugin插件。在Maven项目中,只需在pom.xml文件中添加如下内容,PMD就会在项目构建时运行,并生成报告。在下面的例子中,指定了PMD插件的名称为maven-pmd-plugin,使用的版本为3.13.0。点击这里查看Apache Maven的配置和工作原理。
<project>
...
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.13.0</version>
</plugin>
</plugins>
</reporting>
...
</project>
如果PMD检查出问题,开发人员希望将构建设置为失败的话,可将PMD添加在verify阶段,如下面的配置所示。在配置中,设置了<failOnViolation>为true,且<printFailingErrors>为true。其意思是如果发现了任何错误,则打印出该错误,并且将构建设置为失败(fail)。Maven在运行verify阶段时,会运行PMD插件的check目标。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<failOnViolation>true</failOnViolation>
<printFailingErrors>true</printFailingErrors>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
开发人员还可以执行下面的命令来手动运行PMD检查。
mvn pmd:pmd
如果开发人员未指定PMD配置文件时,Maven PMD插件会使用一个默认的配置文件。在运行时,Maven会将默认的配置文件放置在target/pmd/rulesets目录下。如果开发人员需要使用个性化的PMD规则时,可以在pom.xml文件中的<ruleset>指定PMD规则配置文件,如下所示。
<project>
...
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<rulesets>
<ruleset>pmd_rules.xml</ruleset>
</rulesets>
</configuration>
</plugin>
</plugins>
</reporting>
...
</project>
在运行后,PMD会将检查结果写入项目的target/pmd.html文件中。该文件可能包含如下内容。
PMD Results
The following document contains the results of PMD 6.21.0.
PMD found no problems in your source code.
点击这里下载Maven工程使用PMD的范例。
类似的,Gradle也提供了The PMD Plugin插件,帮助开发人员快速集成PMD。点击这里查看Gradle的配置和工作原理。
开发人员需要在build.gradle文件中添加PMD插件,如下所示。
plugins {
id 'pmd'
}
如果开发人员希望定义个性化规则的话,可在ruleSets中设置规则配置文件的路径。
pmd {
consoleOutput = true
toolVersion = "6.21.0"
ruleSets = ["pmd_rules.xml"]
}
运行后,PMD检查的结果会放在build/reports/pmd目录下。其结果可能包含如下内容。
PMD report
Problems found
#File Line Problem
点击这里下载Gradle工程使用PMD的范例。
PMD运行的规则是由一个XML格式的配置文件描述的。该文件给出了一组规则的定义。这些规则可以是PMD的内置规则,也可以是开发人员自行定义的规则。在PMD中,因为一个配置文件定义了一组规则,因此,配置文件又称为RuleSet文件。
一个RuleSet文件包含一个根节点<ruleset>。在根节点下,开发人员可以在<description>节点中提供描述信息。余下的就是一组规则节点<rule>和<include-pattern>/<exclude-pattern>节点。节点<rule>用于指出PMD依次运行的规则; 而<include-pattern>/<exclude-pattern>节点则指出哪些文件会被PMD扫描。
<?xml version="1.0"?>
<ruleset name="Custom Rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
My custom rules
</description>
<!-- Your rules will come here -->
<rule ref="category/java/errorprone.xml/EmptyCatchBlock" />
<rule ref="category/java/bestpractices.xml/MissingOverride" />
<exclude-pattern>.*/some/package/.*</exclude-pattern>
<include-pattern>.*/some/package/ButNotThisClass.*</include-pattern>
</ruleset>
PMD提供了许多内置的规则。我们将在本小节中介绍一些常用的规则。读者可在这里查看PMD提供的所有规则。
3.2.1 检查标注Override MissingOverride规则用于检查开发人员是否漏掉了使用标注Override的地方。如果在PMD配置文件中配置了如下规则:
<rule ref="category/java/bestpractices.xml/MissingOverride" />
那么,当PMD扫描如下代码时,会在报告中指出Foo类的run()方法未使用标注Override。
public class Foo implements Runnable {
// 该方法应该使用标注Override
public void run() {
..
}
}
3.2.2 检查未使用的变量 UnusedAssignment规则检查程序中是否包含对变量赋值后,却未使用该变量的情况。因为,这样的赋值是没有必要的。PMD能够检查出:
因此,如果RuleSet文件中包含如下配置:
<rule ref="category/java/bestpractices.xml/UnusedAssignment" />
那么,当PMD扫描如下代码时,会在报告中指出f变量的初始化的值未被使用。
public class Example {
// 初始化赋值是无用的,因为f的值会在构造函数中重新设置。
int f = 1;
Example(int f) {
this.f = f;
}
}
3.2.3 检查未使用的import语句
UnusedImports规则用于检查未使用的import语句。如果RuleSet配置了如下规则的话,
<rule ref="category/java/bestpractices.xml/UnusedImports" />
PMD会对下面的程序报错。
import java.io.File; // 程序未使用这条import语句
import java.util.*; // 程序未使用这条import语句
public class Foo {}
3.2.4 检查未使用的成员变量和临时变量
UnusedPrivateField和UnusedLocalVariable分别用于检查未被使用的私有成员变量和临时变量。如果RuleSet配置了如下规则的话,
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable" />
<rule ref="category/java/bestpractices.xml/UnusedPrivateField" />
那么,PMD会对下面的程序报错。
public class Something {
private int i = 5; // 成员变量i未被使用
private int j = 6;
public int addOne() {
int k = 5; // 临时变量k未被使用
return j++;
}
}
3.2.5 检查深度嵌套
AvoidDeeplyNestedIfStmts用于检查if语句嵌套的深度。如果RuleSet配置了如下规则的话,
<rule ref="category/java/design.xml/AvoidDeeplyNestedIfStmts">
<properties>
<property name="problemDepth" value="3" />
</properties>
</rule>
那么,PMD会对下面的代码报错。开发人员可以通过problemDepth属性来修改允许的嵌套层数。默认值为3,即当程序中if语句的嵌套层数达到3时,PMD会报错。
public class Foo {
public void bar(int x, int y, int z) {
if (x>y) {
if (y>z) {
if (z==x) {
// 嵌套层数太多
}
}
}
}
}
3.2.6 检查成员数量
如果一个类定义了过多的成员,那么说明这个类设计地过于复杂,需要将其拆分。规则TooManyFields和TooManyMethods用于检查类中定义的成员变量和成员方法的个数。如果RuleSet文件中提供了如下配置:
<rule ref="category/java/design.xml/TooManyFields">
<properties>
<property name="maxfields" value="15" />
</properties>
</rule>
<rule ref="category/java/design.xml/TooManyMethods">
<properties>
<property name="maxmethods" value="10" />
</properties>
</rule>
那么,当一个类的成员变量超过15个时,或者当成员方法超过10个时会报错。开发人员也可以自定义其阈值。
3.2.7 检查错误的null值检测
BrokenNullCheck规则用于检查开发人员是否使用了错误的逻辑检查null值。如果RuleSet文件中包含了如下配置:
<rule ref="category/java/errorprone.xml/BrokenNullCheck" />
那么,PMD在检查下面的代码时,会报告错误。
public String foo(String string) {
// 这条语句中应使用 &&
if (string != null || !string.equals(""))
return string;
// 这条语句中应使用 ||
if (string == null && string.equals(""))
return string;
}
3.2.8 检查资源是否关闭
CloseResource规则用于帮助开发人员检查资源对象是否关闭,以防止资源泄露。当RuleSet文件中包含了如下配置时:
<rule ref="category/java/errorprone.xml/CloseResource" />
PMD会对如下代码检查并报告错误。
public class Bar {
public void withSQL() {
Connection c = pool.getConnection();
try {
// do stuff
} catch (SQLException ex) {
// handle exception
} finally {
// 在这里需要关闭连接c
// c.close();
}
}
public void withFile() {
InputStream file = new FileInputStream(new File("/tmp/foo"));
try {
int c = file.in();
} catch (IOException e) {
// handle exception
} finally {
// 在这里需要关闭file
file.close();
}
}
}
本章介绍了静态分析工具PMD的用法以及常见的几种检查规则。因为检查规则很多,本章无法涵盖所有规则项,有兴趣的读者可点击这里阅读和参考PMD官方文档以及每个规则的参数配置。
注册用户登陆后可留言