checkstyle

第四十六章 静态分析工具Checkstyle

1. 简介

我们在上一章介绍了Java编程规范,并且解释了在Google Java Style编程规范中的一些重要规则。那么,在项目开发中,项目管理人员如何能确保所开发的代码遵守团队的编程规范呢?在Java项目开发中,开发人员可以使用Checkstyle工具来自动检查所有源代码。在通常情况下,Checkstyle是项目构建中的一个步骤。在项目构建完毕之后,Checkstyle会自动生成检查报告,指出未遵守规则的代码位置和原因。如果开发团队使用敏捷开发(Agile Development)流程的话,Checkstyle通常会集成到持续集成(Continuous Integration)的过程中。一旦开发人员提交新的代码,Checkstyle都会将所有代码检查一遍。

Checkstyle是一款源代码静态分析工具(Static Analasis Tool)。它会阅读并分析源代码的结构和内容,根据一系列内部规则来检查源代码。和其他静态分析工具一样,Checkstyle并不会编译或者运行源代码。Checkstyle支持灵活的配置;它能够支持并检查绝大多数流行的Java编程规范的规则。

我们将在本章中介绍Checkstyle的使用方法,并例举一些常用的Checkstyle配置规则。当Checkstyle运行时,它会读取一个XML格式的配置文件,并按照文件中指定的规则检查源代码。通常情况下,Checkstyle是集成在项目构建环境中运行的。因此,本章会分别介绍如何在MavenGradle环境中集成和运行Checkstyle,以及如何配置Checkstyle的规则内容。

2. 运行Checkstyle

2.1 在Maven环境中运行Checkstyle

为了方便的将Checkstyle集成入Maven项目环境,Apache Maven提供了Maven Checkstyle Plugin插件。使用这个插件,开发人员只需修改Maven工程中pom.xml文件的内容就能集成和运行Checkstyle工具。点击这里查看Apache Maven的配置和工作原理。

方法一:在项目pom.xml文件中添加如下内容。这些配置内容设置了Checkstyle Plugin插件的名字和版本,并且使用Google Java Style编程规范来检查项目源代码。

Checkstyle Plugin插件自带了Google Java Style(google_checks.xml)和Sun Code Conventions(sun_checks.xml)两个配置文件。默认情况下,Checkstyle会使用Sun Code Conventions配置文件。

<project>
  ...
   <reporting>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-checkstyle-plugin</artifactId>
          <version>3.1.1</version>
          <configuration>
            <configLocation>google_checks.xml</configLocation>
          </configuration>
        </plugin>
      </plugins>
    </reporting>
  ...
</project>

开发人员可以运行如下命令生成checkstyle报告。这条命令会执行Maven Site Plugin插件,并生成报告。

mvn site

或者,开发人员也可以给出自定义的编程规范的配置文件checks.xml,并将其放置在项目的根目录下。

<project>
  ...
   <reporting>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-checkstyle-plugin</artifactId>
          <version>3.1.1</version>
          <configuration>
            <configLocation>checks.xml</configLocation>
          </configuration>
        </plugin>
      </plugins>
    </reporting>
  ...
</project>

方法二:如果开发人员希望将Checkstyle集成在构建过程中,如果发现有违反规则的情况,则将构建设置为失败,那么可将Checkstyle设置在Maven的validate阶段或者verify阶段。设置在validate阶段和verify阶段不同之处在于:validate阶段在源代码编译阶段之前运行,而verify阶段在代码编译和测试阶段之后运行。因此,如果在validate阶段执行Checkstyle,Checkstyle几乎会在每个构建过程中运行。然而,如果在verify阶段执行Checkstyle,如果在构建过程中出现编译错误,或者在测试阶段发生失败,则Checkstyle可能不会被执行(视情况而定,因为根据不同配置,构建过程可能会遇到编译错误或者测试失败而停止)。因为verify阶段在测试阶段之后,所以,mvn test命令也不会运行Checkstyle。

下面是在validate阶段执行Checkstyle的例子。如果想将Checkstyle移至verify阶段执行,可将标签phase的值改为verify。

<project>
  <build>
    <plugins>
	  ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>3.1.1</version>
        <configuration>
          <configLocation>checkstyle.xml</configLocation>
        </configuration>
        <executions>
          <execution>
            <id>validate</id>
            <phase>validate</phase>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      ...
	</plugins>
  </build>
</project>

在运行之后,Checkstyle运行的结果会保存在target/checkstyle-result.xml中。该文件会包含出错的源代码文件名,行号和出错原因等信息。开发人员可以根据这些提示信息改进源代码。如下是Checkstyle结果文件的一个例子。

<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="8.29">
<file name="/home/ji/Downloads/Checkstyle/maven-checkstyle/src/main/java/com/littlewaterdrop/App.java">
<error line="3" severity="warning" message="First sentence of Javadoc is missing an ending period." source="com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck"/>
<error line="8" column="1" severity="warning" message="&apos;{&apos; at column 1 should be on the previous line." source="com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck"/>
</file>
</checkstyle>

点击此处下载Maven项目使用Checkstyle的模板。

2.2 在Gradle环境中运行Checkstyle

类似的,Gradle也提供了Gradle Checkstyle Plugin插件,以便于将Checkstyle集成进Gradle项目中。点击这里查看Gradle的配置和工作原理。

例如,在build.gradle文件中加入如下配置。该配置使用checkstyle插件。checkstyle的配置文件默认放置在config/checkstyle/checkstyle.xml。开发人员可以使用configFile file()修改checkstyle配置文件的路径。

plugins {
    id 'checkstyle'
}

Checkstyle插件提供了三个任务(Task)。

  1. checkstyleMain:运行Checkstyle,检查产品源代码(Production Java Source Files)。
  2. checkstyleTest:运行Checkstyle,检查产品测试源代码(Test Java Source Files)。
  3. checkstyleSourceSet:运行Checkstyle,检查所有Java源代码。

在运行./gradlew build之后,checkstyle的结果会放在build/reports/checkstyle目录下。下面是XML格式的结果示例(main.xml)。

<?xml version="1.0" encoding="UTF-8"?>
  <checkstyle version="8.27">
  <file name="/home/ji/Downloads/Checkstyle/gradle-checkstyle/src/main/java/com/littlewaterdrop/App.java">
    <error line="1" severity="error" message="Missing package-info.java file." source="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck"/>
  </file>
</checkstyle>

点击此处下载Gradle项目使用Checkstyle的模板。

3. Checkstyle配置文件

在运行Checkstyle时,Checkstyle需要读取配置文件中指定的规则来检查Java源代码文件。开发人员需要修改Checkstyle配置文件来制定个性化的编程规范的规则。在本节中,我们将介绍Checkstyle配置文件的结构和常用的一些配置选项。

3.1 Checkstyle配置文件结构

Checksyle配置文件是一个树形结构的XML文件。在该XML文件中,大多数节点为节点,它们代表着一个模块(module)。该模块的名称由属性name指定。在根节点下,可配置以下三类节点,它们又被称为根节点的子节点或者子模块。在绝大多数情况下,Checkstyle配置文件使用Checker模块作为根节点。

  1. FileSetCheck模块:这些模块读取一些文件,并根据规则检查这些文件。必要时会输出出错信息。
  2. Filter模块:这些模块用于过滤审计事件(Audit Events),包括过滤审计消息(Audit Messages)。
  3. AuditListener模块:这些模块报告接收的事件(Report accepted events)。
  4. FileFilter模块:这些模块会过滤一些文件。Checkstyle不会处理那些被过滤的文件。

一个简单的配置文件如下:在根节点Checker模块下面包含了JavadocPackage模块和TreeWalker模块。TreeWalker模块包含了ConstantName模块。它们的标签名均为module。当Checkstyle程序读取这个配置文件时,Checkstyle会根据属性名name指定的名称应用相应的规则检查源代码。例如:JavadocPackage模块用于检查每个Java包是否都有文档说明。ConstantName模块用于检查Java源代码中常量名称是否符合编程规范。

<module name="Checker">
  <module name="JavadocPackage"/>
  <module name="TreeWalker">
    <module name="ConstantName"/>
  </module>
</module>

节点内部,还可以提供节点,调整模块使用的默认值。例如,在如下使用MethodLength模块的例子中,MethodLength模块用于检查函数的长度。该模块允许的最大长度为150行。开发人员也可以使用节点将其调整为最长60行。

<module name="MethodLength">
  <property name="max" value="60"/>
</module>

3.2 TreeWalker模块

TreeWalker模块是一种FileSetCheck类型的模块。它会读取Java源代码文件,将每个文件转换成一颗抽象语法树(Abstract Syntax Tree)。然后它会将这颗抽象语法树传递给它的子模块,再依次运行其子模块进行编程规范检查。TreeWalker模块还可以定义一些Property,供它的子模块使用。

因为TreeWalker的子模块是真正检查编程规则的模块。因此,它们又被称为Checks。Checkstyle提供了非常多的子模块,每个子模块检查一项规则。我们仅在本章中挑选一些常用的子模块在下面几个小节中介绍。大家可以在这里查看所有Checkstyle支持的子模块。

3.2.1 检查通配符导入语句AvoidStarImport

我们在Java编程规范章节中介绍了使用通配符导入的利与弊。假如开发人员不希望在源代码中使用通配符导入的话,可以在TreeWalker模块下配置AvoidStarImport子模块。例如:

<module name="Checker">
  <module name="TreeWalker">
    <module name="AvoidStarImport"/>
  </module>
</module>

那么,在Checkstyle检查如下代码时,会报告"import java.io.*;"语句违反了规则。

import java.util.Scanner;         // OK
import java.io.*;                 // Checkstyle会报告不能使用通配符导入

3.2.2 检查异常捕捉EmptyCatchBlock

检查程序是否正确捕捉和处理异常是一种常见而有效的检查方法。EmptyCatchBlock能够帮助开发人员查找未做任何处理的异常处理分支。EmptyCatchBlock的默认行为是允许开发人员不处理异常,但是开发人员必须给出注释,以确保这是有意为之。开发人员也可以修改默认行为。

例如:在异常处理分支中,如果异常对象的名称为expected时,不报告空的异常捕捉。

<module name="Checker">
  <module name="TreeWalker">
    <module name="EmptyCatchBlock">
      <property name="exceptionVariableName" value="expected"/>
    </module>
  </module>
</module>
try {
  throw new RuntimeException();
} catch (RuntimeException expected) {
    // 因为RuntimeException对象名称为expected,checkstyle不会报告这个分支处理
}

或者,开发人员也可以使用注释的内容来控制是否报告。在如下的例子中,当单行注释为"//This is expected"时,不报告空的异常捕捉。

<module name="Checker">
  <module name="TreeWalker">
    <module name="EmptyCatchBlock">
      <property name="commentFormat" value="This is expected"/>
    </module>
  </module>
</module>
 try {
  throw new RuntimeException();
} catch (RuntimeException ex) {
  //This is expected
}

3.2.3 检查常量命名ConstantName

Checkstyle允许开发人员使用正则表达式(Regular Expression)来检查常量名称。例如:ConstantName模式使用的正则表达式为"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"。这个表达式的意义是一个常量名字须以大写字母(A-Z)开始。随后可以跟随任意多个大写字母,数字(0-9),或者下划线。但是,下划线不能是最后一个字符。

<module name="Checker">
  <module name="TreeWalker">
    <module name="ConstantName">
      <property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*"/>
    </module>
  </module>
</module>
public class MyClass {
  final static int CONSTANT_VARIABLE = 10; // OK
  final static int invalidVariable = 50; // 报错,须使用大写字母。
}

3.2.4 检查局部变量命名LocalVariableName

类似的,LocalVariableName子模块用于检查局部变量的命名。它默认允许的命名规则为"^[a-z][a-zA-Z0-9]*$"。这个表达式的意义是一个局部变量名字须以小写字母(a-z)开始。随后可以跟随任意多个小写字母、大写字母(A-Z)和数字(0-9)。

<module name="Checker">
  <module name="TreeWalker">
    <module name="LocalVariableName">
      <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
    </module>
  </module>
</module>
public class MyClass {
  void MyMethod() {
    int var;  // OK
	int VAR;  // 报错,须使用小写字母。
  }
}

3.2.5 检查函数命名MethodName

类似的,MethodName子模块用于检查函数的命名。它默认允许的命名规则为"^[a-z][a-zA-Z0-9]*$"。这个表达式的意义是一个函数名字须以小写字母(a-z)开始。随后可以跟随任意多个小写字母、大写字母(A-Z)和数字(0-9)。

<module name="Checker">
  <module name="TreeWalker">
    <module name="MethodName">
     <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
    </module>
  </module>
</module>
public class MyClass {
  public void myMethod() {} // OK
  public void MyMethod() {} // 报错,根据规则,首字母须小写。

}

3.2.6 检查行长度LineLength

LineLength子模块用于检查源代码每行的长度。默认的最大长度为80个字符。用户可以通过max属性来自定义行的最大长度。

<module name="Checker">
  <module name="TreeWalker">
    <module name="LineLength">
     <property name="max" value="80"/>
    </module>
  </module>
</module>

3.2.7 函数的长度MethodLength

一般来说,函数的长度不宜过长。过长的函数不易于管理和阅读。因此,MethodLength子模块专门用于检查函数长度。默认允许的最长函数为150行。

<module name="Checker">
  <module name="TreeWalker">
    <module name="MethodLength"/>
  </module>
</module>

3.2.8 检查制表符FileTabCharacter

子模块用于检查源代码文件中是否使用了制表符“\t”。

<module name="Checker">
  <module name="TreeWalker">
    <module name="FileTabCharacter"/>
  </module>
</module>

4. 总结

本章介绍了Checkstyle的用法以及常见的几种检查子模块。因为检查项很多,本章无法涵盖所有检查项,有兴趣的读者可点击这里参考checkstyle官方文档以及每个子模块的参数配置。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.