pmd

第四十七章 静态分析工具PMD

1. 简介

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之后再未更新。如果读者对其感兴趣的话,可自行查看其使用方法。

2. 项目集成

开发人员通常将PMD集成在项目中,在项目构建时运行。在Maven和Gradle中集成PMD非常方便。

2.1 Maven项目集成

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的范例。

2.2 Gradle项目集成

类似的,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的范例。

3. PMD配置

3.1 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>
3.2 PMD内置配置

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能够检查出:

  1. 在变量被赋值后,程序并未再次读取该变量的值。
  2. 在变量被赋值后,在该变量第二次被赋值前,程序并未读取该变量的值。

因此,如果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();
        }
    }
}

4. 总结

本章介绍了静态分析工具PMD的用法以及常见的几种检查规则。因为检查规则很多,本章无法涵盖所有规则项,有兴趣的读者可点击这里阅读和参考PMD官方文档以及每个规则的参数配置。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.