Как стать автором
Обновить

Комментарии 20

Не со всеми примерами данного цикла статей я согласен. А вот с Brian Goetz (судя по приведенному высказыванию в переводе автора) — напротив.


  • Optional не стоит злоупотреблять. Он точно не должен быть полем класса. Хотя бы из приведенных в статье соображений.
  • Он уж точно не должен возвращаться геттерами полей класса. Геттеры должны быть прозрачными, а не плодить лишние обертки.
  • Он не должен использоваться в прозрачных сеттерах полей.
  • Его не надо использовать там, где это ведет к загромождению интерфейса и к увеличению объема кода со стороны пользователя. Яркий пример — Map-ы, List-ы и им подобные. Ни в коем случае нельзя возвращать Optional на запрос значения по ключу/индексу, если пользователь явно не попросил вернуть именно Optional.

Что касается значений по умолчанию, то использовать или нет здесь Optional — весьма спорный вопрос. Классический вариант с null, 0 или -1 чаще оказывается нагладнее и правильнее. Да, Optional "безопаснее", т.е. он подстегивает программиста проверять, действительно ли значение есть, а также избавляет от магических чисел, но какой ценой… Ценой подсовывания вместо реального объекта обертки над ним! А это, знаете ли, протекание реализации в контракт метода, причем не самое хорошее. Возможность возврата или установки null должна быть оговорена в контракте метода, но именно оговорена (задокументирована, зааннотирована), а не обернута в Optional или еще что-то.


Что касается NullPointerException (NPE в народе), то это вполне штатная ситуация времени разработки. Я считаю, что проверка на null входных значений (сеттеров, методов, конструкторов) всегда должна присутствовать в явном или неявном виде. Для этого даже стандартных helper-метод есть. Бояться словить или бросить NPE не надо. Просто условия их возникновения или не возникновения, как ранее было сказано, должны присутствовать в контракте метода. И не в виде Optional, поскольку это сбивает с толку, заставляет оборачивать аргументы в Optional насильно, а также приводит к полной неразберихе, когда в старых кусках кода значения по умолчанию обозначается через null, а в новых — уже гордо через Optional. Кроме того, планирование интерфейсов усложняется. И тут может закрасться мысль вообще все оборачивать в Optional...


Вот в каких случаях использование Optional действительно целесообразно и оправдано:


  • Стримы.
  • Методы-генераторы или источники данных. Например, получение (первого) объекта из БД по запросу. Здесь Optional будет выглядеть элегантнее null или xxxOrDefault. Про коллекции см. выше.
  • (Сомнительно и с опаской) Конструкторы и методы изменения состояния (в отличие от прозрачных сеттеров). См. Socket. Однако переусердствовать тоже не стоит. В прозрачные сеттеры и прозрачные конструкторы лучше все-таки по старинке передавать null. А логику переключения состояния, если какой-то объект устанавливается в null/Optional.empty() — по старинке обрабатывать отдельным методом или прописать поведение в контракте (что не всегда приемлемо).

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

Других возможностей его применения я не вижу.

В джаве особых возможностей и нет.


В других языках, где это органично вписано в язык, испольуется очень широко. Kotlin: прямо в системе типов есть типы, которым нельзя присваивать null (кстати, обёртки там нет, никакого снижения производительности). Rust: нет нулевых указателей, но зато есть оптимизация, что Option с указателем — не обёртка, а просто указатель. Scala: Option реализована как обёртка, обладает кучей возможностей и интегрирована в стандартную библиотеку. Например, у всех коллекций есть методы и для возвращения обычных значений и для Option, можно использовать что хочется в зависимости от ситуации.

В том-то и дело, что в java это совершенно новая вещь, которая ни разу не интегрирована со стандартной библиотекой и тем более не имеет поддержки на уровне языка (боже упаси). Это, в общем-то, и вынуждает нас идти на некие уступки и не использовать повально Optional везде, где это только можно, возможно, было бы удобно.

А какой интеграции со стандартной библиотекой Вам недостаёт?

Кроме стримов он пока нигде не используется. Не использовался. До Java 9. С Java 9 он начал появляться в новых APIшках. Местами в старых. ServiceLoader, например, где он как раз и уместен — те самые "источники данных", о которых я упоминал ранее. Но в целом Optional сейчас есть только в стримах и в фишках вокруг них.

А вот поддержка на уровне языка (в виде расширения синтаксиса) может и была бы интересна. Я мог бы себе представить что-то вроде Елвис-оператора специально для Optional. (Это идея не новая, например предложено здесь.)
Кроме того, методы типа or(...) позволяют (фактически) строить workflow примерно как BPM. Другими словами, ветвления потоков управления и данных можно было бы поддерживать на уровне синтаксиса. Но для этого больше подходят двухмерные текстовые структуры либо графика. А это уже слишком большой прыжок от нынешних реалий.
А вот поддержка на уровне языка (в виде расширения синтаксиса) может и была бы интересна.

Я бы так не сказал. Посмотрите на C#. Во что он превратился. В него тащут все, что считают полезным, и это печально. Сложно сохранять парадигму языка, постоянно навешивая новые языковые конструкции, которые порой только ухудшают восприятие кода. Сокращая количество часов на ввод кода мы увеличиваем в несколько раз количество часов на последующий анализ этого кода нами самими и/или другими разработчиками. Получается какой-то зоопарк конструкций, где еще и совершенно разные по смыслу вещи могут обозначаются одинаково/слишком похоже в разных контекстах. Хотя не секрет, что не для всего это верно.


Но это в целом. Что же касается оператора Элвиса, не считаю конкретно его необходимым на уровне языка, даже при том, что он весьма удобен. И хотя он и удобен, мы теряем контроль над потоком управления, получаем дополнительную сложность в отладке, дополнительный оверхед при чтении таких конструкций, особенно в случае длинных цепочек вида a?.b.c?.d?:e. Повторюсь, не стоит тащить все, что удобно, в язык, тем более такой консервативный язык, как java.

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

Оператор Елвиса либо if..else, да ещё со скобками. Одно из двух необходимо применять. В простых ситуациях оператор Елвиса удобне, с моей точки зрения.
Но решение уже принято. Нового синтаксиса в связи с Optional в Java не добавили.
Позволю себе с Вами не согласиться.
Про Kotlin и его не-нулевые типы. Насколько я понял, создатели Kotlin пытались за счёт введённой программистом информации проследить импользование переменной и уже на этапе компиляции найти места возникновения NPE. Дело безусловно очень хорошее. И вообще Kotlin содержит много полезных штук. И тем не менее — Optional — это не только и не столько предохранение от NPE, что я пытался показать на многочисленных приближенных к реальности примерах во второй и третьей статьях серии. Да и результаты опроса в конце первой статьи указывают, что многие программисты Java ценят полный спектр методов Optional.
Сравнение с Rust наверное не очень корректное, поскольку у Java и Rust разные целевые группы и базовые парадигмы.
Scala я знаю только в теории. Как я понимаю, именно оттуда взяты многие концепции для Optional в Java. А какие практически интересные возможности Option и его окрестностей в Scala представляются Вам остро недостающими в Java Optional?

Если что, я последнее время на джаве не писал. Могу ошибаться. Итак, ссылочки:



Часть неудобства при использовании связана с самим языком (тут уж ничего не поделать, не менять же весь язык ради одного класса). Например, метод orElse(new SomeObject()) в скале принимает лениво вычисляемое значение, в нём можно написать какую-нибудь логику типа создания нового элемента. Или, например, есть приятный синтаксис for (a <- option1; b <- option2) yield a+b, благодаря которому удобно работать с несколькими Option одновременно.


Часть неудобств вызвана именно реализацией класса (вернее, отсутствием некоторой функциональности). Почему в скале Option Serializeble, а в джаве — нет? И ещё — в скале Option довольно сильно похожа на маленькую коллекцию — есть приятные методы типа foreach, exists, forall, contains. С их помощью код становится лаконичнее. (В джаве похожая функциональность есть у стрима, в который можно превратить Optional, но мне такой способ кажется немного странным, тем более превращать его придётся вручную).


Ещё часть неудобств вызвана стандартной коллекцией. Например, я не могу в джаве от HashMap получить Option. Конечно, это всё мелочи, можно написать что-нибудь типа Optional.of(hashMap.getOrDefault(key, null)), но получится куча некрасивого кода.


В итоге получается, что код с использованием Option в скале выглядит коротким и зачастую более простым, чем с проверками на null, а в джаве — наоборот. И чтобы Option хорошо вписался в язык, придётся внести кучу изменений, которых делать, скорее всего, не будут.

Во первых — просто дать ссылки на исходники классов — очень хорошая идея. Даже жалко, что я сам не догадался. Респект, как говорят немцы.
Во вторых — сравнивать подходы непросто, не опробовав их интенсивно в реальных условиях. Да, Option в Scala содержит больше методов. Но так ли уж это хорошо?
Тут просматривается интересная диалектика. До появления Optional в Java лучшим его заменителем (т.е. способом решать проблемы, которые потом решились с помощью Optional) считался список из нуля или строго одного элемента. Об этом я писал в первой статье серии. Это решение неудолетворительное, поскольку фактически мы вводим новое понятие специального списка, но технически используем полноценный список. Многие методы из List, например, никогда не применимы к усечённому списку для представления потенциально нулевых обьектов.
В Scala Martin Odersky взял и расширил понятие футляра или контейнера некоторыми методами характерными для списков и итераторов. Тем самым почти придя к ситуации в Java до появления Optional. Вот такая диалектика.
Другими словами, мне кажется, что Option в Scala из чисто догматических соображений перегружен методами, которые технически работают, но концептуально чужды идее футляра или контейнера, в котором может содержаться не более чем один обьект.
Применяю Optional для замены множественных if
Спасибо за интересные соображения.
И всё-таки… класс Optional очень сильно отличается от очень многих других классов в Java. Тут начинается магия программирования. Аналогия с физикой. В уравнении Шрёдингера всего 8 символов, а про него написано много-много статей и книг. В классе Optional всего сто строк кода, а про него написаны уже сотни статей. Может тысячи.
И эта магия меняет менталитет. Я сам вначале его использовал очень осторожно, потом преувеличенно, а сейчас на основании описанных в статье рекомендаций.
Хотя разумеется, эти рекомендации надо адаптировать к условиям каждого конкретного проекта. Наприме, Вы абсолютно правы, в старый код вряд ли следует внедрять Optional.
М-да… законы Мерфи в действии. :-)
Не разовьёте свою мысль? Что вы имеете в виду?
«Всё, что может пойти не так, пойдет не так.»

О чем и говорит цитата Brian Goetz.

Иначе говоря
«Хотели как лучше, а получилось как всегда»
:-)
Понятно. Хотя в нашем случае ситуация несколько другая. Мэрфи писал скоре про отдельные проекты.
Проблема создателей продуктов для массового пользователя состоит в том, что трудно оценить- будут ли продукт использовать. И если да — то так ли, как задумывали авторы.
А среди миллионов программистов всегда найдутся довольные, недовольные и «Левши», которые сумеют подковать блоху. Хоть она после этого и перестанет прыгать.
О том, как они отбирают и оценивают новые фичи, Brain Goetz относительно недавно рассказал в InfoQ: www.infoq.com/news/2018/02/data-classes-for-java
Отчасти согласен с вами.
Но проблема, не в том, что некоторые могут не правильно использовать.
А в том, что нужно прикладывать усилия, чтобы правильно использовали.
Т.е. разъяснять, писать гайдлайны и т.д.
Что для массового продукта не совсем правильно.

Цикл статей интересный, подкинул пищи для размышлений.
Возможно, Вы хотели написать "объект" в предложении "… передаваемый МЕТОД никогда не будет равен null."?

Спасибо дважды. Моя цель в том и состояла, чтобы подкинуть пиши для собственных размышлений. Ну и разумеется за замеченную опечатку. Я её исправил.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации