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

January 19, 2017 at 21:57

NullPointerException в Java. Аннотации как щит. FindBugs.

4. Аннотации в борьбе за безопасность кода. Немного о FindBugs.

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

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

Эти предложения по добавлению аннотаций в стандартные библиотеки для упрощения проверки безопасности кода были вынесены на обсуждение в JSR 305. Было это аж в махровом 2006 году. Мдя…

Небольшое отступление о том, что такое JSR. JSR (Java Specification Request/Запрос на Спецификацию Java) является частью JCP (Java Community Process - процесс создания новых спецификаций для платформы Java) и по своей сути есть формальный документ, аргументированно предлагающий добавить что-то новое к Java платформе. В документе описывается предлагаемая функциональность для того, что бы все желающие могли с ней ознакомиться, обсудить, высказать мнение и, разумеется, скорректировать. По мере уточнения, статус JSR меняется и на каком-то этапе может быть сделана открытая эталонная (reference) реализация предложенных спецификаций. Позже могут появиться и другие реализации. Включая коммерческие. Также, компания Oracle может сделать свою реализацию JSR и включить её в стандартные библиотеки платформы.

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

К сожалению, JSR 305 не перешёл в состояние, достаточное для стандартизации и попадания в стандартный набор библиотек, распространяющийся с JRE. В 2012 году этот запрос (JSR 305) был переведён в состояние “спячки”. Нет, он не был отвергнут, он был тактично отложен до будущих лучших времён.

Тем не менее, сама идея использования аннотаций для пометки опасных мест в коде остаётся достаточно популярной. Посему, большинство разработчиков интегрированных средств разработки (IDE) реализовали свои аннотации и предлагают использовать их для выявления потенциальных ошибок.

Насколько я знаю, эти возможности предлагают IntelliJ IDEA, Eclipse, NeatBeans.

Для того, что бы воспользоваться этими аннотациями, вам, как правило, необходимо добавить содержащие их библиотеки в ваш проект. И, возможно, “попросить” среду разработки анализировать эти аннотации. То есть, указать где-то в опциях настройки то, что вы желаете использовать их в проекте и хотите получать предупреждения о потенциальных ошибках.

Традиционно, для отслеживания ситуаций вида NullPointerException используется два вида аннотаций: @Nullable и @NotNull (@NonNull, @Nonnull)

Аннотацией @Nullable вы помечаете те места кода, где переменная может быть равна null. Скажем, если какой-то метод написан так, что может вернуть null, то пометьте его вот так:


    @Nullable
    Integer myMethod()
    {
        ...
    }
    

Теперь, когда кто-то будет использовать этот метод, инспектор кода среды разработки подскажет, что после вызова данного метода вы должны проверить его возвращаемое значение на null.

Можно сделать иначе, и пометить, что метод не может вернуть null (а значит, нет смысла лишний раз проверять возвращаемое значение). Это будет выглядеть так:


    @NotNull
    Integer myMethod()
    {
        ...
    }
    

Если вы теперь попытаетесь вернуть в этом методе null, то среда разработки сделает вам замечание. Также, если вы переопределите метод в потомке, то она обратит ваше внимание на то, что и там нельзя вернуть null и желательно переопределённый метод также пометить ярлычком @NotNull. Для этого, помимо стандартного Java компилятора, используется встроенный инспектор кода.

Таким же образом можно пометить и параметры, передаваемые в некий метод. Выглядит это так:


    Integer myMethod( @Nullable Integer i )
    {
            ...
    }
    

Пометив параметр таким образом, вы увидите предупреждение от среды, если попытаетесь использовать этот параметр, не проверив предварительно его на null. И наоборот, если вы пометите параметр как @NotNull, но затем, перед его использованием, будете проверять его на null, то среда разработки вам подскажет, что эта проверка избыточна, поскольку параметр помечен аннотацией как всегда ненулевой.

Как правило, среда разработки позволяет вам указывать степень важности её совета, основанного на аннотации. Обычно это просто совет-предупреждение. Но можно сделать так, что это будет считаться ошибкой и код не будет скомпилирован.

Выше, я описал несложный сценарий из двух популярных аннотаций, реализации которых предлагают разные инструменты. Скажем, для того, что бы использовать описываемые аннотации в IntelliJ IDEA, вы можете использовать их реализацию от IntelliJ IDEA. Для этого вам вначале нужно добавить jar, реализующий эти аннотации в список библиотек проекта. Он расположен в каталоге /redist/annotation.jar (или /redist/annotation-java8.jar). После этого вы сможете помечать нужные места аннотациями от IntelliJ IDEA. Реализация этих аннотаций находится в пакете org.jetbrains.annotations.

Это важно понимать какие действительно реализации аннотаций вы используете в проекте. Ведь их, реализаций, довольно много. И, та же IntelliJ IDEA даёт возможность своим инспектором кода отслеживать опасные ситуации, которые помечены “чужими” реализациями таких аннотаций. То есть, реализация аннотаций сторонняя, подключена другая библиотека аннотаций, но IntelliJ IDEA понимает их синтаксис (а он может чуть отличаться) и продолжает отслеживать их своим инспектором кода.

Настроить это можно тут - File-Settings-Editor-Inspections-Constant conditions & exceptions. Но, те кто реализовал эти другие аннотации, как правило имеют и свои средства отслеживания безопасности кода, заточенные именно под свои аннотации. Другими словами, имеют свой собственный инструмент.

В реальности, реализаций аннотаций для указания разработчикам как и где нужно проверять/не проверять на null — довольно много. К примеру их имеют очень популярные библиотеки от Google: Guice и Gooava. Если вы их используете в своём проекте, то значит можете помечать опасные места аннотациями от Google. Помимо визуального восприятия этих аннотаций разработчиками (и, здравого учёта этого факта при написании кода), эти библиотеки навязывают свою философию и дисциплину оформления кода.

Перед использованием этих библиотек, лучше детальнее ознакомиться с их документацией. К примеру, Gooava даёт возможность следовать своей внутренней дисциплине передачи параметров. А именно, писать код так, что по умолчанию будет ожидаться, что ни один из передаваемых в метод параметров не должен быть null до тех пор, пока он явно не помечен аннотацией @Nullable. И специальными методами проверять, что те параметры, которые должны быть гарантированно неравны null — действительно не равны null. В противном случае, будет сгенерировано исключение от Gooava с точным указанием места ошибки.

Ну и наконец существуют такие известные инструменты как FindBugs, Checker Framework и даже Project Lombok

FindBugs и Checker Framework — интересные и полезные инструменты, призванные помочь в благородном деле улучшения вашего кода. По своей сути, они представляют из себя разным образом реализованные статические анализаторы текста программы, имеющие возможность более глубокой проверки кода (глубже, чем стандартный Java компилятор), особенно если вы используете аннотации, предоставляемые этими фреймворками.

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

Итак, вначале несколько слов о FindBugs. По моим ощущениям он самый известный и популярный. Первая версия FindBugs была реализвана 10 июня 2006 года. Её создателями принято считать Билла Пью (William Worthington “Bill” Pugh Jr.). Профессор Университета Мэриленда (University of Maryland–College Park). Также отмечается большой вклад Давида Хэвмейера (David Hovemeyer) и Кейта Ли (Keith Lea). Сейчас (2016) в команде разработчиков - Билл Пью и Андрей Лоскутов. Последняя версия проекта - 3.0.1 (от 6 марта 2015 года).

Физически, FindBugs представляет из себя обычное “родное” GUI приложение (то, что принято называть standalone) или плагины (plug-ins), добавляемые к популярным IDE. Особенность FindBugs состоит в том, что он опирается на анализ байт-кода, получаемого после компиляции.

Однако, FindBugs имеет и свои реализации аннотации для разных случаев. И, если вы добавляете их в код, то он ими не брезгует. К примеру, что касается ситуаций, связанных с NullPointerException, то в своём арсенале FindBugs имеет несколько полезных аннотаций-помощников.

Это @CheckForNull (поле, параметр или возвращаемое значение метода обязательно должны быть проверены на null!), @NonNull (поле, параметр или возвращаемое значение метода категорически не должны содержать null), @Nullable (поле, параметр, возвращаемое значение метода порой могут содержать null, а значит, надо читать документацию и разбираться когда их надо проверять. По сути, это просто визуальная пометка для разработчика, что никаких предположений про это значение сам инструмент не делает и разработчику придётся самостоятельно разобраться в деталях).

В документации упоминается, что @Nullable это всё равно что отсутствие каких-либо аннотаций (другими словами по умолчанию, FindBugs предполагает, что любое поле без аннотаций потенциально может содержать null), но на практике, из опыта, наличие этой аннотации всё-таки учитывается инструментом. Есть подозрение, что документация не очень точна.

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