Contents

Checker 框架简介

1. 概述

Java 8版本开始,可以使用所谓的Pluggable Type Systems 编译程序——它可以应用比编译器应用的检查更严格的检查。

我们只需要使用几个可用的Pluggable Type Systems提供的注解。

在这篇简短的文章中,我们将探索由华盛顿大学提供的 Checker 框架。

2. Maven

要开始使用 Checker 框架,我们需要先将其添加到我们的pom.xml 中:

<dependency>
    <groupId>org.checkerframework</groupId>
    <artifactId>checker-qual</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
    <groupId>org.checkerframework</groupId>
    <artifactId>checker</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
    <groupId>org.checkerframework</groupId>
    <artifactId>jdk8</artifactId>
    <version>2.3.2</version>
</dependency>

可以在Maven Central 上检查最新版本的库。

前两个依赖项包含Checker Framework的代码,而后者是Java 8类的自定义版本,其中所有类型都已由Checker Framework的开发人员正确注释。

然后我们必须适当地调整maven-compiler-plugin以使用Checker Framework作为可插入类型系统:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArguments>
            <Xmaxerrs>10000</Xmaxerrs>
            <Xmaxwarns>10000</Xmaxwarns>
        </compilerArguments>
        <annotationProcessors>
            <annotationProcessor>
                org.checkerframework.checker.nullness.NullnessChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.interning.InterningChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.fenum.FenumChecker
            </annotationProcessor>
            <annotationProcessor>
                org.checkerframework.checker.formatter.FormatterChecker
            </annotationProcessor>
        </annotationProcessors>
        <compilerArgs>
            <arg>-AprintErrorStack</arg>
            <arg>-Awarns</arg>
        </compilerArgs>
    </configuration>
</plugin>

这里的重点是annotationProcessors标签的内容。在这里,我们列出了我们想要针对我们的源运行的所有检查器。

3. 避免 NullPointerExceptions

Checker 框架可以帮助我们的第一个场景是识别可能产生NullPoinerException的代码段:

private static int countArgs(@NonNull String[] args) {
    return args.length;
}
public static void main(@Nullable String[] args) {
    System.out.println(countArgs(args));
}

在上面的示例中,我们使用*@NonNull注释声明countArgs()args*参数必须不为空。

不管这个约束如何,在main()中,我们调用传递一个确实可以为 null 的参数的方法,因为它已经用@Nullable进行了注释。

当我们编译代码时,Checker Framework会及时警告我们代码中的某些内容可能是错误的:

[WARNING] /checker-plugin/.../NonNullExample.java:[12,38] [argument.type.incompatible]
 incompatible types in argument.
  found   : null
  required: @Initialized @NonNull String @Initialized @NonNull []

4. 正确使用常量作为枚举

有时我们使用一系列常量,因为它们是枚举项。

假设我们需要一系列国家和行星。然后,我们可以使用*@Fenum*注释对这些项目进行注释,以对属于同一“假”枚举的所有常量进行分组:

static final @Fenum("country") String ITALY = "IT";
static final @Fenum("country") String US = "US";
static final @Fenum("country") String UNITED_KINGDOM = "UK";
static final @Fenum("planet") String MARS = "Mars";
static final @Fenum("planet") String EARTH = "Earth";
static final @Fenum("planet") String VENUS = "Venus";

之后,当我们编写一个应该接受作为“planet”的字符串的方法时,我们可以正确地注释参数:

void greetPlanet(@Fenum("planet") String planet){
    System.out.println("Hello " + planet);
}

错误地,我们可以使用尚未定义为行星可能值的字符串调用greetPlanet() ,例如:

public static void main(String[] args) {
    obj.greetPlanets(US);
}

Checker 框架可以发现错误:

[WARNING] /checker-plugin/.../FakeNumExample.java:[29,26] [argument.type.incompatible]
 incompatible types in argument.
  found   : @Fenum("country") String
  required: @Fenum("planet") String

5. 正则表达式

假设我们知道一个String变量必须存储一个至少有一个匹配组的正则表达式。

我们可以利用Checker 框架并像这样声明这样的变量:

@Regex(1) private static String FIND_NUMBERS = "\\d*";

这显然是一个潜在的错误,因为我们分配给FIND_NUMBERS的正则表达式没有任何匹配组。

事实上,Checker 框架会在编译时勤奋地通知我们我们的错误:

[WARNING] /checker-plugin/.../RegexExample.java:[7,51] [assignment.type.incompatible]
incompatible types in assignment.
  found   : @Regex String
  required: @Regex(1) String