Подтвердить что ты не робот

JetBrains @Контрактная аннотация

Как работает аннотация org.jetbrains.annotations.Contract? Как поддерживает IntelliJ IDEA?

4b9b3361

Ответ 1

Прежде всего, я должен сказать, что эта аннотация предназначена только для IDEA для проверки возможных ошибок. Компилятор Java будет игнорировать его почти полностью (он будет в скомпилированном артефакте, но не имеет никакого эффекта). Сказав это...

Цель аннотации состоит в том, чтобы описать контракт, который будет подчинен методу, что поможет IDEA уловить проблемы в методах, которые могут вызвать этот метод. Контракт, о котором идет речь, представляет собой набор разделяемых разделов с запятой, каждый из которых описывает ввод и вывод, который гарантированно произойдет. Причина и эффект разделяются -> и описывают случай, когда при предоставлении X методу Y всегда будет иметь значение. Вход описывается как список, разделенный запятыми, описывающий случай с несколькими входами.

Возможные входы: _ (любое значение), null, !null (not-null), false и true, а возможные выходы добавляют fail к этому списку.

Так, например, null -> false означает, что при условии ввода null в качестве результата будет 24 > boolean. null -> false; !null -> true расширяется на этом, чтобы сказать, что null всегда будет возвращать false, а значение не null всегда будет возвращать true и т.д. Наконец, null -> fail означает, что метод будет генерировать исключение, если вы передадите ему нулевое значение.

Для примера с несколькими аргументами null, !null -> fail означает, что в методе с двумя аргументами, если первый аргумент равен null, а второй - не null, исключение будет выбрано, гарантировано.

Если метод не изменяет состояние объекта, а просто возвращает новое значение, тогда вы должны установить pure в значение true.

Ответ 2

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

В условиях неспециалиста:

  • Контракт может содержать 1 или более связанных с ним статей.
  • Предложение всегда [args] -> [effect]
  • Args - это 1 или более ограничений, которые определены как any | null | !null | false | true
  • Эффекты - это только одно ограничение или fail

Давайте рассмотрим быстрый пример - один из моих фаворитов: "Независимо от того, что вы переходите в этот метод, это вызовет исключение".

@Contract("_-> fail")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

Здесь мы используем _ или "any", чтобы указать, что независимо от того, что мы передаем в этот метод, мы собираемся исключить исключение.

Что, если мы солгали и сказали, что этот метод вернет true безоговорочно?

@Contract("_-> true")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

IntelliJ вызывает некоторые предупреждения об этом.

введите описание изображения здесь

Он также (очевидно) расстроен, что мы сказали, что возвращаем логическое значение, когда мы находимся в методе void...

введите описание изображения здесь


Основные моменты, когда вы захотите использовать @Contract, - это когда:

  • Вы хотите гарантировать, что вы вернете true или false
  • Вы хотите гарантировать, что вы возвращаете ненулевое значение заданных ограничений
  • Вы хотите четко указать, что вы можете вернуть нулевое значение заданных ограничений
  • Вы хотите четко указать, что вы выбрали исключение, заданное ограничение

Чтобы не сказать, что @Contract совершенен; отнюдь не. Он не может делать очень глубокий анализ в определенных контекстах, но наличие этого в вашей базе кода позволяет вашей инструментарию делать этот вид анализа бесплатно.

Ответ 3

Как работает аннотация org.jetbrains.annotations.Contract?

Хотя предыдущие ответы являются информативными, я не чувствую, что они обращаются к оперативному слову "работа" в вашем вопросе. То есть они не объясняют, что IntelliJ делает за кулисами, чтобы реализовать свои аннотации, чтобы вы могли легко создавать свои собственные с нуля.

Если вы посмотрите на исходный код ниже, вы можете подумать, что это кажется слишком сложным (или, по крайней мере, многословным) для чего-то столь же простого звучания, как @NotNull. Я согласен с вами, и это одна из причин, по которой я обычно избегаю @Contract -подобных предложений, которые не являются "простыми и простыми" (например, @NotNull), а вместо этого JavaDoc my prereqs напрямую.

Я вообще не рекомендую использовать сложные аннотации контрактов, несмотря на ненависть, которую я мог бы получить за пропуски этого модного нового поезда, и вот некоторые причины, по которым:

  • Аннотации могут быть сложными -e.g. с несколькими вложенными аннотациями в своих собственных определениях и/или "грамматикой" с появлением полноты Тьюринга. Это может привести к ложному чувству уверенности во время компиляции, маскируя истинного виновника ошибки за слоями неясности/абстракции и не создавая изначально намеченных предупреждений.
  • Подобно, но отличается от моего предыдущего момента, аннотации часто скрывают сложную логику от разработчика в нескольких ключевых словах, создавая трудно понятный код для людей и/или необычное поведение, которое может быть сложно отлаживать.
  • Конфигурация приложения часто увидела маскарад в качестве аннотаций. Взгляните на Spring framework.
  • Синтаксис для определения контрактов по-и-большому (IMHO) довольно уродлив и Makefile-ish.. Например, взгляните на некоторые определения аннотаций JetBrains и поддерживающие файлы разбросанный по его репо. Обратите внимание на многочисленные XML файлы и обширную саморегуляцию? Я бы вряд ли назвал это забавой для написания и поддержки, особенно учитывая постоянно меняющийся характер аннотаций, возглавляемых back-and-forth между Android и более крупным сообществом Java.

Некоторые вопросы для рассмотрения:

  • Заходит ли он слишком далеко, когда исходный код аннотации подходит к сложности самого источника, который он комментирует?
  • Является ли код ниже на самом деле намного лучше, чем просто проверять значение null во время выполнения и регистрировать исключения с помощью stacktraces? Включение чего-то подобного заставляет ваших пользователей читать, понимать и, возможно, исправлять ошибки в другом наборе зависимостей, которые определяют ваши аннотации.

Заимствованный из еще один длинный пост о семантике контракта, о котором я бы сказал, только далее служит моей точке:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;

/**
 * This annotation can be applied to a package, class or method to indicate that the class fields,
 * method return types and parameters in that element are not null by default unless there is: <ul>
 * <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
 * case the annotation of the corresponding parameter in the superclass applies) <li> there is a
 * default parameter annotation applied to a more tightly nested element. </ul>
 * <p/>
 * @see https://stackoverflow.com/a/9256595/14731
 */
@Documented
@Nonnull
@TypeQualifierDefault(
{
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.FIELD,
    ElementType.LOCAL_VARIABLE,
    ElementType.METHOD,
    ElementType.PACKAGE,
    ElementType.PARAMETER,
    ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}

Когда и какие контракты вы должны использовать?

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

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

Примером сортировки, чтобы избежать, являются такие, как Android и IntelliJ @Contract(...). Хотя мне нравятся их IDE, детали их аннотаций довольно сложны и в конечном итоге превратились в источник большего количества проблем и несовместимости платформы для меня, чтобы отслеживать (по общему признанию, из-за моего собственного незнания в создании новых контрактов почти в 100% случаев, но почему так сложно понять это?)

Резюме/Заключение

Аннотации - отличная идея, явно созданная программистами, которые хотят "кодифицировать" свою документацию. Я чувствую, что они зашли слишком далеко в последнее время, превратив документацию в код семантики, который приводит к серьезным головоломкам и неудобным ситуациям. Хуже того, они иногда дают ложное представление о безопасности во время компиляции, не обнаруживая проблем, проявляющихся в их собственных реализациях. Придерживайтесь самых простых и избегайте всего, что похоже на язык, который не является Java (именно это вы и предполагали писать в первую очередь).

Дополнительная литература

Этот краткий список представляет собой сочетание в основном критических источников (w/optimism!) как из StackOverflow, так и из Интернета, которые, как я чувствую, иллюстрируют некоторые мои моменты.

Без особого порядка:

И после всего этого я понял, что, возможно, все еще не смог полностью решить ваш оригинальный вопрос:)