Рады сообщить, что JSpecify 1.0.0 теперь доступен в Maven Central: четыре аннотации, связанные с nullability — @Nullable, @NonNull, @NullMarked и @NullUnmarked — стали официальными, и, что немаловажно, обратная совместимость с ними будет гарантирована.
В новом переводе от команды Spring АйО мы подробнее рассмотрим, какие преимущества и новшества предлагает JSpecify 1.0.0, как это может повлиять на ваш проект и что нам предлагается для борьбы с NPEs.
Команда JSpecify выпустила свой первый релиз, JSpecify 1.0.0. Цель группы — определять общие наборы типов аннотаций для использования в JVM языках, чтобы улучшить статический анализ и взаимодействие языков.
Проекты и группы, вошедшие в объединение, включают:
OpenJDK
EISOP
PMD
Android, Error Prone, Guava (Google)
Kotlin, IntelliJ (JetBrains)
Azure SDK (Microsoft)
Sonar
Spring
Своим участием в JSpecify члены объединения гарантируют, что их проекты будут обратно совместимы на уровне кода. Это означает, что они не будут переименовывать аннотации в jar, перемещать их или вносить другие изменения, которые вызовут фатальную ошибку при компиляции кода по причине произведенного обновления. Другими словами, зависеть от этого кода безопасно, и он никогда не поменяется таким образом, чтобы сломать код приложения.
Этот первый релиз нацелен на использование type-use аннотаций, чтобы указать nullability статус применений статических типов. Основными случаями использования, как ожидается, будут переменные, параметры и возвращаемые значения, но они могут использоваться где угодно, где проверка на равенство null имеет смысл.
Некоторые возможности, однако, все еще не проходят тест на “концептуальный смысл”, например, вы все еще не можете писать такие вещи: class Foo extends @Nullable Bar.
Немного истории про nullability и проверки на равенство null
Тема nullability долгое время была предметом горячих обсуждений в экосистеме Java. Идея состояла в том, чтобы разработчики были лучше защищены от NPEs (Null Pointer Exceptions — прим. пер.), поскольку огромные количества случаев использования, которые могут вызывать NPEs в настоящее время, будут отсеяны на этапе сборки.
Однако эта многообещающая идея уперлась в несколько неприятных фактов, в частности на то, что у Java имеются две вещи:
Долгая история, начавшаяся задолго до появления концепции non-nullable значений в языках программирования.
Устойчивая традиция обратной совместимости.
По этой причине было довольно сложно увидеть правильный путь к добавлению null-awareness к языку в единообразной и устойчивой манере.
Тем не менее, такие попытки совершались — наиболее знаменитой была JSR 305, которая просуществовала несколько лет, но застопорилась в 2012, так не дождавшись своего завершения и полной стандартизации.
После провала этой инициативы многие компании и проекты пытались продолжать двигаться вперед со своими версиями nullability аннотаций. Некоторые из этих проектов пытались решить проблему прежде всего для своих случаев использования, но другие решения предназначены для широкого использования в рамках Java приложений.
Особый интерес вызывает язык Kotlin, который встроил null-awareness в систему типов. Это достигается через предположение, что, например, String означает, что значение не может быть null, таким образом требуя от разработчика указывать nullability в явном виде как String?. По большей части это работает довольно успешно, по крайней мере пока программа пишется на чистом Kotlin.
Java разработчики иногда заявляют, что они "просто хотят то, что есть у Kotlin", но, как становится ясно из документации по Kotlin, здесь имеется несколько тонкостей, особенно при взаимодействии с кодом на Java.
В настоящее время в чистом Java пространстве большинство проектов используют следующий подход к nullability: они относятся к статусу null как к некоей дополнительной информации, которую надо обработать с помощью инструмента для проверки (статического анализа), который работает на этапе сборки. Добавляя дополнительную информацию в форме аннотаций, они задают инструменту проверки цель по устранению NPEs из программы, которая была им проанализирована.
Разные проекты имеют разную философию по поводу того, какое количество “гарантии здоровья” они согласны предложить. Здоровье имеет свою цену — больше сложностей, больше усилий по миграции кодовой базы, больше ложных срабатываний, с которыми приходится иметь дело, поэтому не каждый разработчик захочет полной гарантии.
JSpecify представляет собой попытку унифицировать эти подходы, и первоначальные цели более скромны — версия 1.0.0 задает только четыре аннотации:
@Nullable
@NonNull
@NullMarked
@NullUnmarked
Первые две могут применяться при использовании специфических типов (например, задание параметров или возвращаемые значения метода), в то время как вторая пара может применяться более широко. Вместе они предоставляют полезную декларативную null-awareness по месту использования для типов Java.
Один общий случай использования — это добавить @NullMarked к модулю, пакету или декларации типа, к которым вы добавляете информацию по nullability. Это может сэкономить вам много работы, так как это обозначает, что все остальные не помеченные аннотацией случаи использования типов не являются nullable. Затем все исключения могут быть явным образом помечены как @Nullable, и такой подход является разумным выбором по умолчанию.
Идея состоит в том, чтобы конечные пользователи внедрили у себя (или продолжили использовать) один из участвующих проектов — и разные проекты имеют разные уровни зрелости. Например, IntelliJ поддерживает аннотации JSpecify, но generics еще поддержаны не полностью.
Инструмент для проверки стандарта также доступен, но он на самом деле не предназначен для разработчиков приложений, вместо этого он нацелен на проекты, принимающие участие в JSpecify.
Есть также FAQ, описывающий более глубокие, более технические аспекты архитектуры.
InfoQ связались с одним из ключевых разработчиков, работающих в JSpecify — Юргеном Хёллером (лидер проекта Spring Framework).
InfoQ: Пожалуйста, расскажите нам о своем участии в JSpecify и о своей работе с nullability:
Хёллер: Вместе с Себастианом Делюзом я работал над архитектурой nullability для Spring Framework 5, что привело к нашему участию в JSpecify.
InfoQ: Вы могли бы объяснить, как JSpecify используется в ваших проектах и почему это важно для них?
Хёллер: Проект Spring Framework и многие части более широкого портфолио Spring внедрили архитектуру non-null-by-default в 2017-м, мотивацией для чего послужила интеграция с Kotlin, но это также дало серьезные преимущества проектам на Java (в сочетании с IntelliJ IDEA и инструментами типа NullAway).
Это доступно для вызова с уровня приложения в нашем публичном API, чтобы разработчики могли поразмышлять о nullability параметров и возвращаемых значений. Это также уходит корнями глубоко в кодовую базу, чтобы убедиться в том, что наши предположения и утверждения по nullability соответствуют актуальной логике кода. Это дает огромные преимущества для нашей собственной внутренней архитектуры, а также для поддержки.
Оригинальные аннотации JSR 305 получили широкое распространение в инструментах, несмотря на то, что они никогда не были официально завершены. Собственные аннотации Spring используют JSR 305 в качестве мета-аннотаций для обнаружения инструментами, что привело к немедленной поддержке в IntelliJ IDEA и Kotlin.
Наше участие в JSpecify началось с этого существующего положения дел, так как нас никогда не устраивало состояние аннотаций JSR 305, и мы хотели иметь стратегический путь для поддержки семантики аннотаций и инструментов в будущем, что позволило бы нам развивать наши API и кодовую базу с учетом строгой безопасности от null.
InfoQ: Потребовалось 6 лет, чтобы дойти до версии 1.0.0. Почему по вашему мнению это заняло так много времени?
Хёллер: В то время как наши примеры использования в Spring весьма элементарны и фокусируются на параметрах и возвращаемых значениях, как и на значениях полей, проблема в целом является довольно комплексной. Миссия JSpecify нацелена на поддержку generics, что оказалось довольно сложной задачей. JSpecify 1.0 рассматривает многочисленные требования от многочисленных заинтересованных сторон, и в условиях привлечения к общей работе такого разнообразного контингента добиться консенсуса не так просто.
InfoQ: Каким станет будущее, если JSpecify преуспеет?
Хёллер: Прежде всего, JSpecify имеет все шансы стать преемником JSR 305 в плане поддержки в инструментах и как источник общепринятых аннотаций в open source фреймворках и библиотеках. Очень важно получить такую поддержку поверх общего базисных продуктов, таких, как, например, JDK 17, поскольку крупные части экосистемы open source останутся такого рода базисными продуктами на многие годы. Даже для новых версий Java и Kotlin фреймворки и библиотеки, базирующиеся на JDK 17, все еще будут широко использоваться, и JSpecify может эволюционировать вокруг их потребностей.
InfoQ: Над чем JSpecify будет работать в ближайшем будущем?
Хёллер: Со стороны Spring нам необходима официальная поддержка мета-аннотаций, чтобы мы могли задекларировать наши собственные аннотации nullability мета-аннотациями JSpecify вместо JSR 305 мета-аннотаций.
InfoQ: Спасибо!
Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм - Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
Ждем всех, присоединяйтесь
Regis
К сожалению
@NullMarked
в IDEA на данный момент (IDEA 2024.1.4) поддерживается с ошибками, поэтому если вы используете сейчас другие аннотации, то лучше не торопиться с миграцией.