Статьи

Использование Java 8 ‘Необязательно’ является необязательным

После статьи прошлой недели «Необязательно в коллекциях» сегодня я не могу не говорить немного больше об одном и том же звере.

Класс Optional был изначально представлен Google Guava, а затем включен в пакет Java 8 просто как оболочка, которая оборачивает необязательный объект. Обернутый объект является необязательным в том смысле, что он либо есть, либо в нем нет объекта, и в этом случае он пуст. Там не так много магии. В конце концов, ноль — это ноль, а не объект. Объект никогда не бывает нулевым. Только ссылка на объект может быть нулевой.

Это нюансы, мелкие детали; но важны мелкие детали. В конце концов, эти мелкие детали — это те, которые требовали введения Optional. Средние Java-кодеры не видят важности таких крошечных деталей. Они думают, что Optional так же хорош, как и переменная для самого обернутого объекта, предполагая, что переменная также может быть нулевой. На каком-то уровне они правы. На своем уровне.

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

«Работать» и «понятно» — самые основные требования к программному обеспечению. Раньше мы говорили, что если программное обеспечение работает, то все в порядке, и для обслуживания было достаточно, если бы два человека могли понять код: кодер, который его создал, и Бог, который создал кодер. К счастью, есть и уровни выше. (Я имею в виду выше кодера. Не выше Бога.)

«Код работает» и «легко (не так сложно) понять» — это следующий уровень. Это важно в случае, если вам необходимо отладить код и определить причину некоторой неисправности. «Код работает» и «легко изменить» снова являются новыми шагами вверх по лестнице. Я видел код, который мог легко понять. Код был запущен. Но зависимости между различными модулями были такими сложными, как макраме или традиционный итальянский спагетти. Где бы я ни хотел что-то изменить, чтобы исправить ошибку здесь, было несколько других мест, где программа начала давать сбой. Легко изменить: такого кода не было.

Следующий уровень — «работа с кодом», «легко модифицировать» и «сложно создавать плохие модификации». Это означает, что код предоставляет стиль и внутренние структуры данных и API, которым обслуживающий персонал будет следовать до некоторого уровня, и создаст работающую измененную базу кода, которая все еще работает, проста для понимания и легко модифицируется. Это точка, где мы дошли до точки Факультативного.

Когда метод возвращает Optional, он говорит, что он может вернуть что-то или просто ничего. Необязательный <Integer> может вернуть Integer, но он может просто вернуть пустой Optional, что означает: не было никакого Integer, которое я мог бы вернуть. Почему это лучше, чем возвращать целое число, которое может быть нулевым?

Необязательный метод Возвращаемое значение

Ответ таков: в случае возврата необязательного <Integer> вы не сможете

integer = methodReturningIntegerOrNull();
otherInteger = integer +1;

that causes NPE. Why would you do that? Because you forget to check, and the JavaDoc mentions the possibility somewhere at the end of the description that is not visible in the mouse over window when you code. In case of Optional<Integer> you are forced to

optionalInteger = methodReturningOptionalInteger();
if( optionalInteger.isPresent() ){
  otherInteger = optionalInteger.get() +1;
  }

Still there is a small chance that you will write:

optionalInteger = methodReturningOptionalInteger();
otherInteger = optionalInteger.get() +1;

but in that case you deserve what you get.

Optional helps you to create more code and less documentation. It gives a semantic to pass on some optional value in a way that is harder to neglect than a nullable value. It says: I do not trust you handling null properly, therefore I give you a wrapped object so you have to handle optionality explicitly.

If you consider that you can easily answer the questions

  • Requiring Optional<Something> as a method argument
  • Having a private field optional.

are good ideas.

Optional method argument

There are pros and cons. When the argument says

countFrom(Optional<Date> from, Date to);

it is clear that the from value may be missing and there should be some special default semantics when an absent value is passed. On the other hand the caller may pass null to get the special behavior. It is less likely that the caller will pass null just by mistake neglecting the optionality. Even if the argument is Optional the argument actually passed can still be null and I expect that the method throws NPE in this case. Last, but not least there is another danger in introducing Optional: the caller may pass an Optional embracing an object that is not a Date. Generics can be circumvented in Java easily, and a sloppy coder may pass a wrong Optional. It means that you have to implement assertions in your method:

  • Argument is not null,
  • Argument is of the proper type.

Also recall that Optional, in case of a method return value says: I do not trust you handling null properly, therefore I give you a wrapped object so you have to handle optionality explicitly. What would this message be when you create the API requiring Optional as an argument? Please do not trust me! Give me only Optional because even I do not trust myself to handle a null value properly. Weird… On the other hand I trust that you will not pass null or a wrong type.

In my opinion: in this case using Optional does not deliver more value than having proper documentation for the API and does not force the caller to behave better than it would anyway. On the other side you put extra code on your own shoulders.

Give Optional to code you trust, accept it from code that does not trust your code, but do not request it! Trust yourself!

Private Optional Fields

When you declare a local, private field to be Optional you will force the developer of the class itself to pay more attention to the optional feature of the field. The cost of this is the extra wrapper, the extra clutter in the code handling optional. On the other side there is no much gain, because you can get the same level of quality extending your unit tests checking all cases where null value of the field is to be considered. Since all the code is in the hand of the current developer being responsible for the whole code there is no benefit of Optional. It is again, like you not trusting yourself. That is a serious issue needing more and different treatment than what Optional Java class can provide.

Optional in Functional Programming

You can use Optional to program Java in the functional programming style if you want, but Java is not a functional language and optional and lambda and the functional style methods alone will not make it one. But that is a different topic for later.