Перейти к основному содержимому

The Checker Framework — подключаемые системы типов для Java

· 4 мин. чтения

1. Обзор

Начиная с версии Java 8 , можно компилировать программы, используя так называемые системы подключаемых типов , которые могут применять более строгие проверки, чем те, которые применяются компилятором.

Нам нужно только использовать аннотации, предоставляемые несколькими доступными системами подключаемых типов .

В этой быстрой статье мы рассмотрим Checker Framework , любезно предоставленный Вашингтонским университетом.

2. Мавен

Чтобы начать работать с Checker Framework, нам нужно сначала добавить его в наш 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 .

Первые две зависимости содержат код The Checker Framework , а последняя представляет собой пользовательскую версию классов Java 8 , в которой все типы были должным образом аннотированы разработчиками The Checker Framework .

Затем нам нужно правильно настроить maven-compiler-plugin , чтобы использовать The 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. Избегайте исключений NullPointerException

Первый сценарий, в котором нам может помочь Checker Framework , — это определение фрагмента кода, в котором может возникнуть NullPoinerException :

private static int countArgs(@NonNull String[] args) {
return args.length;
}

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

В приведенном выше примере мы объявили с помощью аннотации @NonNull , что аргумент args функции countArgs() не должен быть нулевым.

Независимо от этого ограничения, в main() мы вызываем метод, передавая аргумент, который действительно может быть нулевым, поскольку он был помечен @Nullable .

Когда мы компилируем код, The 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";

После этого, когда мы пишем метод, который должен принимать строку, являющуюся «планетой», мы можем правильно аннотировать аргумент:

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

По ошибке мы можем вызвать welcomePlanet() со строкой, которая не была определена как возможное значение для планеты, например:

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

Checker Framework может обнаружить ошибку:

[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 Framework и объявить такую переменную следующим образом:

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

Это, очевидно, потенциальная ошибка, поскольку регулярное выражение, которое мы присвоили FIND_NUMBERS , не имеет подходящей группы.

Действительно, Checker Framework старательно сообщит нам о нашей ошибке во время компиляции:

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

6. Заключение

Checker Framework — полезный инструмент для разработчиков, которые хотят выйти за рамки стандартного компилятора и улучшить правильность своего кода.

Он способен обнаружить во время компиляции несколько типичных ошибок, которые обычно можно обнаружить только во время выполнения, или даже остановить компиляцию, вызвав ошибку компиляции.

Стандартных проверок намного больше, чем мы рассмотрели в этой статье; ознакомьтесь с проверками, доступными в официальном руководстве The Checker Framework здесь , или даже напишите свои собственные.

Как всегда, исходный код этого руководства с несколькими примерами можно найти на GitHub .