Разработка программ. Мои заметки.

January 19, 2017 at 22:45

NullPointerException в Java. Checker Framework и Lombok.

5. Немного о Checker Framework и Lombok.

Checker Framework оказался чуть менее известен, но выглядит как мощный и аккуратно разрабатываемый инструмент со всей необходимой документацией. Я бы даже сказал, что выглядит он несколько более основательным и задокументированным, чем ранее рассматриваемый FindBugs.

Найти подробную историю его создания у меня не получилось. Но, по некоторым признакам, истоки восходят к известнейшему Вашингтонскому университету (washington.edu, Сиэтл, штат Вашингтон). Видимо, там его родина. Последняя версия (на момент написания статьи) — 2.1.7 (от 19 января 2017).

В проекте участвует много людей, он размещён на Checker Framework Github. Кстати, помимо стандартной документации на страничке проекта, фреймворк имеет и неплохое рабочее описание от David Bürgin в его проекте Spring PetClinic Sample Application (Детальный пример реализации Веб приложения на Spring).

Checker Framework опирается на аннотации. И для их анализа использует возможность Java компилятора иметь плагины и запускать их в процессе компиляции исходного кода. То есть, инструмент работает с исходным текстом программы. Плагины, которые предоставляет этот инструмент и которые проверяют код, так и называются — чекеры (checkers). Иногда их также называют обработчиками аннотаций (annotation processors). Каждый чекер обрабатывает “свои” аннотации в коде и пытается отыскать потенциальные ошибки строго определённого вида.

Разного рода чекеров чуть более десятка. Нас интересует чекер ситуаций, способных вызывать NullPointerExceptions. Он называется Nullness checker и понимает несколько аннотаций. В том числе ходовые @NonNull and @Nullable. Как и в других инструментах, @NonNull означает, что поле, параметр или метод не могут хранить null и наоборот @Nullable означает, что null значение возможно. Тут есть один нюанс, по умолчанию, если возле поля или параметра нет никаких аннотаций, то фреймворк полагает, что там не может быть null (как будто стоит @NonNull, посему такая аннотация используется редко, ибо работает умолчание). Это сделано для того, что бы не рассыпать горсти аннотаций по всему программному коду.

Кстати, момент довольно важный, поскольку у других инструментов могут быть совершенно другие соглашения по умолчанию. Вот у рассматриваемого нами выше FindBugs, поле или параметр без аннотаций, потенциально может содержать null. По мне, так это выглядит более логично. Всё-таки, если на ящике совсем ничего не написано, то безопаснее предполагать, что внутри может оказаться всё что угодно, а не исключительно чистые пустые пластиковые бутылки без пробок. Но не все инструменты так “считают”.

Пару слов как запускать Checker. Для общего понимания. Можно просто из командной строки натравить компилятор с плагином на ваш класс. Как-то вот так: javac -processor NullnessChecker MyFile.java. javac – это стандартный компилятор Java. Он должен быть виден в системе. Но можно запускать и из Ant, Maven, Gradle, IntelliJ IDEA, Eclipse… Как настроить используемую среду разработки подробно написано в документации.

Одной из интересных особенностей Checker Framework является гибкая поддержка JSR 308. Этот JSR определяет возможности и правила аннотирования типов (Type Annotations). Аннотирование типов, это небольшое расширение синтаксиса языка в плане того, где теперь в коде можно использовать аннотации. Согласно JSR 308 теперь аннотацию можно приткнуть практически везде, где указан некий тип данных (имя класса). Включая дженерики (generics) и явные преобразования типов (cast), Чуть выше я уже упоминал это и приводил примеры. В отличие от JSR 305, триста восьмой таки был реализован в Java 8.

Использовать такой синтаксис в Java 5, 6 и 7 – нельзя. Но Checker таки даёт возможность поддержать их и в Java 5/6/7, банально предлагая обрамлять аннотацию символом комментария. Вот так: List</* @NonNull */ String>. В таких случаях наш Checker инструмент способен их обработать, а стандартный компилятор – не заметит. При развёртывании проекта нужно помнить, что те аннотации, которые были использованы полноценно, согласно синтаксиса Java 8, могут потребовать включения библиотек Checker Framework не только на время компиляции. Те же возможности, что “достаются” из комментариев, не потребуют дополнительных библиотек времени выполнения (run time). Безусловно, из документации на проект Checker Framework вы сможете узнать много чего ещё интересного.

Я бы порекомендовал просмотреть типы и описания чекеров, которые они предлагают. Даже если вы не собираетесь их использовать. Подробно описывая свои чекеры, ребята упоминают много безопасных паттернов использования конструкций языка, которые могли пройти мимо вашего внимания. Я, возможно, ещё вернусь к этой теме.

Прежде чем перейти к проекту Lombok, который не является статическим анализатором кода, но таки по своему борется с NullPointerException посредством аннотаций, хотелось бы подытожить тему статических анализаторов. Во-первых, их довольно много. Есть даже специальная страничка в Википедии, где перечислены многие из них для разных языков программирования. Правда, упустили Checker Framework, зато старательно упомянули много других малоизвестных проектов.

Тут моё мнение по ситуации со статическими анализаторами…

Вначале пару слов о Lombok. Lombok (перец Чили) — это попытка сделать Java код менее многословным. Много людей чехвостят Java за многословность. В большинстве случаев я с ними не согласен. По мне, так многословность языка — это очень даже неплохо для последующей поддержки кода. Меня больше пугают скупые немногословные языки программирования, код которых нужно долго и старательно расшифровывать. Особенно после мальчиков-ботаников, сполна самореализовавшихся в коде, а затем, почему-то, поменявших место работы. Но возможен и компромисс. И код короткий и его понятность на высоте. Lombok пошёл по этому пути.

Как мы знаем, технически, однословной аннотацией, можно заменить кусок кода. Вот создатели Lombok и решили, а чего бы и не сделать таким образом исходный код программы более элегантным, предоставив разработчикам возможность генерировать некие стандартные вещи, присутствие которых требуется по школе, автоматически, аннотациями. И сделали. Теперь, если вы дружите с Lombok, то имеете такой незамысловатый сервис, как генерация некоторых стандартных кусочков кода автоматически, используя аннотации. К примеру, вам лень писать геттеры и сеттеры для ваших полей. Но, по уму, и по разным соглашениям, они в коде нужны. Ну и ненужно их писать руками. В дружбе с Lombok, вы элегантно пишете лишь пару слов:


    @Getter @Setter private int myField = 1;

И получаете для этого поля сладкую парочку геттера с сеттером автоматически. Таких автоматических генераторов полезных кусков кода там десятка полтора. Некоторые из них особенно полезны. Скажем, генераторы методов hashCode и equals. Да там вообще есть аннотация (@Data), которая одним словом сразу генерирует и геттеры с сеттерами для всех полей (кроме сетеров для final) и конструктор и … В общем, очень такая хозяйственная и полезная в быту конструкция.

Что касается уважаемого нами NullPointer-а, то для него также есть своя фишка. Вы можете пометить параметры метода или конструктора как @NonNull и Перец Чили за вас сгенерирует некий код вида:


    if( parameter == null ) {
        throw new NullPointerException( "parameter" );
    }

И при выполнении, если что не так, вы получите NullPointerException сразу в том месте, где к вам пришёл некорректный параметр, а не где-то там позже в сложной логике метода, где он позже понадобился.

Что бы использовать этот инструмент, пишут, что нужно сделать две вещи.

Первая — скачать и запустить lombok.jar (для этого кликнуть на нём мышкой, если Java установлена и связана с jar файлами, либо написать в командной строке: java -jar lombok.jar). Запустив его, вы активируете инсталятор, который попытается найти у вас на компьютере среду разработки (IDE). Или Eclipse, или NeatBeans (другие пока не поддерживаются). И прописаться в них. Что даст возможность “прозрачно” использовать Lombok под вашей средой разработки. Скажем, все автоматические дописыватели кода (code completion) будут сразу знать о методах, которые за вас ранее “написал” Lombok. Разумеется, Lombok можно получить и через Maven. Maven - это святое.

Вторая вещь — добавить lombok.jar в CLASSPATH проекта.

Добавить Lombok в IntelliJ IDEA также несложно. Есть Lombok Plugin for IntelliJ IDEA. Он занесён в репозиторий плагинов и легко находится прямо в среде разработки. Понятно, что плагин нужно инсталлировать, но и не забыть также включить lombok.jar в CLASSPATH проекта. Заняло это несколько минут и заработало сходу.

В целом, Lombok выглядит как интересный и неопасный инструмент. В случае разочарования, имеется утилита Delombok, которая превращает исходный Java код с Lombok аннотациями, в просто обычный Java код c конструкциями сгенерированными Lombok. И вы ничего не теряете, кроме времени. Что, впрочем, уже очень много. Но ведь вы и выигрываете время, используя Lombok. И напоследок ссылка на альтернативное описание проекта Lombok.

Автор — Владимир Рыбов