Pull to refresh

Comments 28

Почему Вами были выбраны именно аннотации intellij?
Насколько я помню, с помощью них можно обнаружить ошибки только на этапе компиляции. Для отлова NPE в runtime в одном из проектов мною использовались javax.validation.constraints.
Idea только проверяет корректность, в ней указываются маркеры-аннотации для проверок. Автор же использует аннотации из findbugs, но можно свои написать и использовать с тем же успехом.
Да, верно. Просто эти уже используются в guava и guice. Первую я использую вообще во всех своих проектах, а второй там, где spring stack избыточен.
Скорей всего вы использовали их для Design by Contract. Помнится имплементация Hibernate Validation для первой версии стандарта так умела. Это другой подход, решающий несколько иную задачу. Здесь же речь идет не о ловле ошибки в runtime, а о том чтобы ее туда вообще не пропустить.

Java Bean Validation тоже активно использовал там, где это уместно. В плане обязательности/необязательности у них не нравится, что по-умолчанию все считается Nullable и нельзя это поменять.
При запуске кода из-под IDEA проверки работают и в runtime'е, т.к. она сама добавляет в нужные места Assert'ы
… с помощью них можно обнаружить ошибки только на этапе компиляции...

это их основная прелесть, обнаружение ошибки в рантайме может дорого стоит )
Стоило упомянуть и сам FindBugs, который помимо использования этих аннотаций анализирует легаси-код и сам размечает неразмеченные функции, порой делая очень нетривиальные выводы. Если в функции аргумент сравнивается с null, он помечается Nullable. Если сразу разыменовывается, но помечается NotNull и так далее. Это работает рекурсивно: если вы получив аргумент первым делом передаёте его в фунцкию, в которой он первым делом разыменовывается, то и для текущей функции аргумент будет NotNull. У меня FindBugs реально отлавливал потенциальные NPE на глубине в три вызова в неразмеченном аннотациями коде. Кроме того, часть методов стандартной библиотеки (включая методы интерфейсов) захардкожена в FindBugs, если по документации известно, Nullable они или нет. Благодаря мне вот парочка появилась :-)

Nullable/NotNull серьёзно помогают, но с ними есть трудности, когда работаешь с коллекциями или массивами. Было бы круто иметь отдельную аннотацию, например, «массив ненулевых элементов».
Последнее время мне как-то везло либо на новые проекты, либо с уже какой-то внедренной политикой обработки опциональности. Поэтому посмотреть внешние стат. анализаторы с одной стороны все хочется, а с другой стороны как-то не особо и надо. Спасибо, что напомнили про FindBugs, возможно в текущем проекте это как раз то, что доктор прописал. А там как практика с ним появится, так и упомяну. :-)

Для коллекций вроде как TypeAnnotations должны эту проблему решить, так же как и для прочих Generic'ов типа Future<@Nullable User>.
В Java 8 и в библиотеках типа Guava есть тип Optional. Часть NPE закрывается использованием Optional.ofNullable().
Это фактически монада MayBe. Чтобы прижилось нужны лямбды, да и сама реализация мне пока только в Scala понравилась (может за счет pattern matching'а). А так да, появляются compile time проверки на опциональность. Правда за все хорошее приходится платить и в runtime будет overhead.
В Котлине реализация куда лучше на мой взгляд. Она там практически зеро-оверхед в плане количества кода. И это свойство языка там.
А в Java 8 тоже есть лямбды и Optional отлично выглядит. Собственно у себя в проекте его и используем.
Это все здорово, но не подскажите методы борьбы с NPE при method chaining и legacy кодом?
Например есть такая ужасная цепочка вызовов getOffer().getOrderActionRef().getOrder().getRootCustomer().getCode() NPE может быть где-угодно, ловить NPE как-то рука не поднимается.
Раньше я тоже думал, что chaining — это проблема, но опыт показал, что в таких цепочках возврат null — нештатная ситуация и лучше кидать явное исключение бизнес-уровня, чем возвращать null. В вашем примере мне не очень понятна логика методов, поэтому сочиню свой:
long getUserQuota(String userName, String resourceName) {
  return getDatabaseEngine().getUserInfo(userName).getResourceLimits(resourceName).getQuota();
}

Когда может getDatabaseEngine() вернуть null? Когда произошло что-то фатальное, нет соединения с базой, не удалось загрузить jar с драйвером JDBC и т. д. Не лучше ли кинуть что-то типа new DatabaseInitializationException(), которое выложит в лог подробную историю, а пользователю покажет «извините, фатальная ошибка, позвоните в саппорт»?
Когда может getUserInfo(userName) вернуть null? Наверно, когда такого юзера нет в базе. Но мы же как-то пришли в эту функцию с этим параметром. Либо юзер залогинен, либо мы выбрали его из комбобокса в админке. Возможно, другой админ в это время удалил этого же юзера. Это редкая и нештатная ситуация. Спокойно кидаем new UserNotFoundException(userName), который превратится в красивое сообщение в UI. Что-то аналогичное и с последним вызовом.

Во многих случаях кинуть конкретное информативное исключение, содержащее не только стек, но и важные для контекста параметры значительно лучше, чем вернуть null. В тех редких случаях, когда возврат null может оказаться штатной ситуацией и оборачивать в try-catch некрасиво, я пишу парный метод типа optUserInfo, который никогда не кидает исключений, но может вернуть null (навеяно библиотекой org.json).

Кстати, считаю Optional страшным извратом, который загрязняет код. Всё решается правильной и последовательной системой исключений.
Я бы сделал так. Если непонятно где возврат null — штатная ситуация, то сначала анализировать и аннотировать. Затем все таки явно обрабатывать null там где они допустимы. Всякие удобные операторы типа .? из Groovy и Kotlina могут проглатывать null там где его быть не должно, но он вызван переходом программы в недопустимое состояние (IllegalState). В этом случае лучше все таки явно падать, а не продолжать работу.
Ради справедливости надо упомянуть, что в Eclipse поддержка null-аннтотаций тоже есть.
Причём можно выбрать классы и из JSR, и из IDEA, так и свои собственные эклипсовские.
раньше не использовал nullable, а в чем основной профит?
       @Nullable User foundUser = findUserByName("vasya");

        if(foundUser == null) {
            System.out.println("User not found");
            return;
        }

то есть проверка на null никуда не ушла, а @Nullable — типа доки/коммента что из метода может вернуться null?
не только вернуться, но так же и передаться в метод

public void setParam(@Nullable Object param){
// Тут уже можно делать или не делать проверку на param==null в зависимости от логики метода.
// Но нормальная IDE или Findbugs так же сделают инспекцию на передаваемое значение, проверив, что нигде в метод не будет попыток передать null
}

public void setParam(@Nullable Object param){
// Тут уже можно делать или не делать проверку на param==null в зависимости от логики метода.
// Но нормальная IDE или Findbugs так же сделают инспекцию на передаваемое значение с учётом что можно передать и null
}


дополнительное чтиво: habrahabr.ru/post/139736/#comment_4670467
выше копипаста очепятная была. первый блок должен так выглядеть:
public void setParam(@NotNull Object param){
// Тут уже можно делать или не делать проверку на param==null в зависимости от логики метода.
// Но нормальная IDE или Findbugs так же сделают инспекцию на передаваемое значение, проверив, что нигде в метод не будет попыток передать null
}
UFO just landed and posted this here
В указанном примере задание @Nullable для переменной излишне, т.к. IDE и так знает, что метод findUserByName может вернуть null. Помечать необходимо все методы, параметры методов и поля классов. Тогда, как показывает практика, NPE уходит из вашей жизни почти полностью.
В случае метода — это контракт на «опциональность» между вызывающим и вызываемым. Для объекта и его полей — это инвариант. Даже без всяких статических анализаторов, просто прописав их, вы сможете рассматривать гораздо меньше вариантов состояния и поведения приложения при написании вашего кода.

Ну, и естественно, не надо рыскать по всему коду на тему «а может ли прийти вот сюда null» или строить сомнительные предположения.
остается вопрос — почему @Nullable не используется в крупных проектах? Сталкивался с сорцами Android и ElasticSearch — ни одного @Nullable. В чем причина?
Ну, например, в Jetbrains, используют. А так помнится linux kernell был вообще без тестов много лет, ситуация как-то начала меняться совсем недавно. Что вовсе не означает, что так стоит жить. :-)
Честно говоря, выглядит уже не так интересно в плане затрачиваемых ресурсов/приносимой пользы. Да и Intellij Idea specific, как я понимаю. Лучше добавьте поддержку «аннотирования» внешних библиотек. ;-)
Блин, а ведь и правда. По Alt+Enter на внешнем коде предлагает его проаннотировать.

В заблуждение ввела сначала вот эта часть доки:

If an annotation pertains to the SDK, configured for your project, the path is to be defined in the SDK settings. If an annotation pertains to a module, the path should be defined in the module settings.

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

Вообще круто, но в feature requests я бы добавил suggestion на добавление external annotation (если они включены) в подобных случаях:
Map<String, String> map = ImmutableMap.of("bca", "cab"); @Nullable String str = map.get("abc");

Ну, и какое-то отображение в исходниках проаннотированной библиотеки, кроме Ctrl+Q. Хотя и с трудом представляю себе как. Хитрые подчеркивания разве что.
Sign up to leave a comment.

Articles