Должен ли получатель Java 8 возвращать необязательный тип?
Optional
Тип, введенный в Java 8, является новой вещью для многих разработчиков.
Возвращает ли метод получения Optional<Foo>
введите вместо классического Foo
хорошая практика? Предположим, что значение может быть null
,
7 ответов
Конечно, люди будут делать то, что они хотят. Но у нас было четкое намерение при добавлении этой функции, и она не должна была быть типом общего назначения Maybe, так как многие люди хотели бы, чтобы мы это сделали. Мы намеревались предоставить ограниченный механизм для возвращаемых типов библиотечных методов, где должен быть понятный способ представления "нет результата", и использовать null
поскольку такое в подавляющем большинстве случаев может привести к ошибкам.
Например, вы, вероятно, никогда не должны использовать это для чего-то, что возвращает массив результатов или список результатов; вместо этого верните пустой массив или список. Вы почти никогда не должны использовать его в качестве поля чего-либо или параметра метода.
Я думаю, что регулярное использование его в качестве возвращаемого значения для получателей определенно было бы чрезмерным.
В Optional нет ничего плохого в том, что его следует избегать, просто это не то, чего хотят многие люди, и, соответственно, мы были весьма обеспокоены риском ревностного чрезмерного использования.
(Объявление о государственной службе: НИКОГДА не звоните Optional.get
если вы не можете доказать, что это никогда не будет нулевым; вместо этого используйте один из безопасных методов, таких как orElse
или же ifPresent
, Оглядываясь назад, мы должны были позвонить get
что-то вроде getOrElseThrowNoSuchElementException
или что-то, что сделало гораздо яснее, что это был очень опасный метод, который подорвал всю цель Optional
на первом месте. Урок выучен. (ОБНОВЛЕНИЕ: Java 10 имеет Optional.orElseThrow()
, который семантически эквивалентен get()
, но чье имя больше подходит.))
Проведя небольшое собственное исследование, я наткнулся на несколько вещей, которые могли бы предложить, когда это уместно. Наиболее авторитетной является следующая цитата из статьи Oracle:
"Важно отметить, что цель класса Optional состоит не в том, чтобы заменить каждую пустую ссылку. Вместо этого его цель - помочь разработать более понятные API, чтобы, просто читая сигнатуру метода, вы могли сказать, может ожидать необязательное значение. Это заставляет вас активно разворачивать необязательное значение, чтобы справиться с отсутствием значения. " - Устали от исключений нулевого указателя? Подумайте об использовании Java SE 8 необязательно!
Я также нашел этот отрывок из Java 8 Необязательно: как его использовать
"Необязательный не предназначен для использования в этих контекстах, так как он ничего нам не купит:
- в слое модели предметной области (не сериализуемо)
- в DTO (по той же причине)
- во входных параметрах методов
- в параметрах конструктора "
Который также, кажется, поднимает некоторые действительные пункты.
Я не смог найти никаких негативных коннотаций или красных флажков, чтобы предположить, что Optional
необходимо избегать. Я думаю, что общая идея заключается в том, что если это полезно или улучшает удобство использования вашего API, используйте его.
Причина Optional
был добавлен в Java, потому что это:
return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.findFirst()
.getOrThrow(() -> new InternalError(...));
чище, чем это:
Method matching =
Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.getFirst();
if (matching == null)
throw new InternalError("Enclosing method not found");
return matching;
Я хочу сказать, что Optional был написан для поддержки функционального программирования, которое было добавлено в Java в то же время. (Пример предоставлен блогом Брайана Гетца. Лучшим примером может служить orElse()
метод, так как этот код в любом случае вызовет исключение, но вы получите картину.)
Но сейчас люди используют Optional по совсем другой причине. Они используют его для устранения недостатка в дизайне языка. Недостаток заключается в следующем: невозможно указать, какие параметры API и возвращаемые значения могут быть нулевыми. Это может быть упомянуто в javadocs, но большинство разработчиков даже не пишут javadoc для своего кода, и не многие проверяют javadocs при их написании. Таким образом, это приводит к большому количеству кода, который всегда проверяет наличие нулевых значений перед их использованием, даже если они часто не могут быть нулевыми, потому что они уже неоднократно проверялись девять или десять раз в стеке вызовов.
Я думаю, что была настоящая жажда устранить этот недостаток, потому что так много людей, которые видели новый класс Optional, предполагали, что его цель - внести ясность в API. Вот почему люди задают такие вопросы, как "должен ли получатель вернуть опционально?" Нет, они, вероятно, не должны, если вы не ожидаете, что геттер будет использоваться в функциональном программировании, что очень маловероятно. Фактически, если вы посмотрите, где Optional используется в Java API, то это в основном в классах Stream, которые являются ядром функционального программирования. (Я не очень тщательно проверил, но классы Stream могут быть единственным местом, где они используются.)
Если вы планируете использовать геттер в небольшом количестве функционального кода, было бы неплохо иметь стандартный геттер и второй, который возвращает Optional.
Да, и если вам нужно, чтобы ваш класс был сериализуемым, вы не должны использовать Optional.
Необязательные решения - очень плохое решение для недостатка API, потому что они очень многословны и б) они никогда не предназначались для решения этой проблемы.
Гораздо лучшим решением для недостатка API является проверка на нулевое значение. Это процессор аннотаций, который позволяет вам указать, какие параметры и возвращаемые значения могут иметь значение null, пометив их @Nullable. Таким образом, компилятор может сканировать код и выяснить, передается ли значение, которое может фактически быть нулевым, значению, для которого нулевое значение недопустимо. По умолчанию предполагается, что ничто не может быть пустым, если это не аннотировано. Таким образом, вам не нужно беспокоиться о нулевых значениях. Передача нулевого значения параметру приведет к ошибке компилятора. Тестирование объекта на нулевое значение, которое не может быть нулевым, выдает предупреждение компилятора. Результатом этого является изменение NullPointerException с ошибки времени выполнения на ошибку времени компиляции.
Это меняет все.
Что касается ваших добытчиков, не используйте Optional. И попробуйте создать свои классы, чтобы ни один из участников не мог быть нулевым. И, возможно, попробуйте добавить Nullness Checker в ваш проект и объявить ваши методы получения и установки @Nullable, если они в этом нуждаются. Я сделал это только с новыми проектами. Он, вероятно, выдает много предупреждений в существующих проектах, написанных с множеством лишних тестов на ноль, поэтому может быть сложно модернизировать. Но это также поймает много ошибок. Я люблю это. Мой код намного чище и надежнее из-за этого.
(Существует также новый язык, который решает эту проблему. Kotlin, который компилируется в байт-код Java, позволяет вам указать, может ли объект быть нулевым, когда вы объявляете его. Это более чистый подход.)
Приложение к исходной записи (версия 2)
После долгих размышлений я неохотно пришел к выводу, что допустимо возвращать Optional при одном условии: что полученное значение может фактически быть нулевым. Я видел много кода, где люди обычно возвращают Optional от получателей, которые не могут вернуть null. Я считаю это очень плохой практикой кодирования, которая только добавляет сложности к коду, что повышает вероятность ошибок. Но когда возвращаемое значение может фактически быть нулевым, продолжайте и оберните его в Необязательном
Помните, что методы, разработанные для функционального программирования и требующие ссылки на функцию, будут (и должны) записываться в двух формах, одна из которых использует Optional. Например, Optional.map()
а также Optional.flatMap()
оба берут ссылки на функции. Первый принимает ссылку на обычный получатель, а второй принимает тот, который возвращает Необязательный. Таким образом, вы никому не делаете одолжение, возвращая Optional, где значение не может быть нулевым.
Сказав все это, я все еще вижу, что подход, применяемый средством проверки на нулевое значение, является лучшим способом справиться с пустыми значениями, поскольку они превращают исключения NullPointerException из ошибок времени выполнения в ошибки времени компиляции.
Я бы сказал, что в целом было бы неплохо использовать необязательный тип для возвращаемых значений, которые могут иметь значение NULL. Однако в отношении фреймворков я предполагаю, что замена классических геттеров необязательными типами вызовет много проблем при работе с фреймворками (например, Hibernate), которые полагаются на соглашения о кодировании для геттеров и сеттеров.
Если вы используете современные сериализаторы и другие фреймворки, которые понимают Optional
тогда я обнаружил, что эти рекомендации хорошо работают при написании Entity
бобы и доменные слои:
- Если уровень сериализации (обычно БД) позволяет
null
значение для ячейки в столбцеBAR
в таблицеFOO
тогда получательFoo.getBar()
может вернутьсяOptional
указав разработчику, что это значение может быть разумно ожидаемым, чтобы быть нулевым, и они должны обработать это. Если БД гарантирует, что значение не будет нулевым, то получатель не должен переносить это вOptional
, Foo.bar
должно бытьprivate
и не бытьOptional
, Там действительно нет причин для этого бытьOptional
если этоprivate
,- Сеттер
Foo.setBar(String bar)
должен взять типbar
и неOptional
, Если можно использоватьnull
аргумент, то укажите это в комментарии JavaDoc. Если это не нормально использоватьnull
IllegalArgumentException
или какая-то подходящая бизнес-логика, ИМХО, более уместна. - Конструкторы не нужны
Optional
аргументы (по причинам, аналогичным пункту 3). Обычно я включаю в конструктор только аргументы, которые должны быть ненулевыми в базе данных сериализации.
Чтобы сделать вышеперечисленное более эффективным, вы можете отредактировать шаблоны IDE для генерации геттеров и соответствующие шаблоны для toString()
, equals(Obj o)
и т. д. или используйте поля непосредственно для них (большинство генераторов IDE уже имеют дело с нулями).
Вы должны иметь в виду, что часто цитируемые советы исходили от людей, у которых был небольшой опыт работы вне Java, с типами опций или с функциональным программированием.
Так что принимайте это с недоверием. Вместо этого давайте посмотрим на это с точки зрения «хорошей практики»:
Хорошая практика означает не только вопрос «как мы пишем новый код?», но и «что происходит с существующим кодом?».
В случае моя среда нашла хороший и простой ответ:
обязательно указывать необязательные значения в
records
:record Pet(String name, Optional<Breed> breed, Optional<ZonedDateTime> dateOfBirth)
Это означает, что существующий код хорош как есть, но код, использующий
record
(то есть «новый код») вызывает широкое распространение
Optional
вокруг него.
Результатом стал полный успех с точки зрения удобочитаемости и надежности. Просто перестань использовать
null
.
TL;DR Да, это должно быть по мере необходимости.
Optional
поскольку это свободная реализация шаблона проектирования интерфейса или метод гибкого программирования, вопрос может быть общим. Является ли внедрение шаблонов проектирования хорошей практикой?
С точки зрения обслуживания и расширяемости это хорошая практика, с точки зрения информатики шаблоны проектирования менее эффективны. Язык программирования Java является языком программирования высокого уровня, эффективность которого зависит от его рабочего контекста (JVM, реализованного JRE), который может меняться со временем независимо от шаблонов проектирования, реализованных в приложениях, для написания которых он используется, поэтому основные проблемы приложений Java по своей конструкции являются обслуживанием и расширяемостью.