«1. Обзор
Начиная с версии Java 8, можно компилировать программы с использованием так называемых подключаемых систем типов, которые могут применять более строгие проверки, чем те, которые применяются компилятором.
Нам нужно использовать только аннотации, предоставляемые несколькими доступными системами подключаемых типов.
В этой быстрой статье мы рассмотрим Checker Framework, любезно предоставленный Вашингтонским университетом.
2. Maven
Чтобы начать работать с 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>
Главное здесь — это содержимое тега \u003cannotationProcessors\u003e. Здесь мы перечислили все чекеры, которые мы хотим запустить против наших исходников.
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. Регулярные выражения
Предположим, мы знаем, что строковая переменная должна хранить регулярное выражение хотя бы с одной соответствующей группой.
Мы можем использовать 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.