Opakowywać zmienną w Optional czy jednak spodziewać się NullPointerException? W swojej karierze zawodowej spotkałem się właśnie z takimi dwoma obozami. Jedni wolą jak program wyrzuci im słynny wyjątek, bo przynajmniej wiedzą, w której linii doszło wywalenia się programu. Inni są zwolennikami przespanych nocy i wszystko opakowują w Optional.

Jak słusznie zaznaczył to Kilian Foth w odpowiedzi na SoftwareEngineering, nieużywanie Optional w kodzie powoduje, że staje się on tykającą bombą:

Without Optional, every reference in your code is like an unexploded bomb. Accessing it may do something useful, or else it may terminate your program wth an exception.

Znowu programiści nastwieni na performance powiedzą, że opakowywanie obiektu w obiekt powoduje spadek wydajności. No i oczywiście każdy ma trochę racji.

Kiedy nie warto stosować Optional?

Nie o to Optional walczył!

1
2
3
4
5
6
7
8
public String getPersonName(int id) {
   Optional<Person> optionalPerson = getPersonFromDb(id);

   if(optionalPerson.isPresent()) 
    return optionalPerson.get().getName();
   else
    throw new NotFoundPersonException();
}

Jeżeli ktoś używa takiego zapisu jak powyżej to naprawdę nie warto zaprzątać sobie głowy dodatkową składnią. Ten kod tak naprawdę niczym się nie różni od przykładu z użyciem null.

1
2
3
4
5
6
7
8
public String getPersonName(int id) {
  Person person = getPersonFromDb(id);

  if(person != null)
    return person.getName();
  else
    throw new NotFoundPersonException();
}

Nawet jest on czytelniejszy, ponieważ nie ma chain’a na metodzie get Optionala. Natomiast jeżeli kod zostanie napisany w następujący sposób to ma on jak największy sens.

1
2
3
4
5
6
7
public String getPersonName(int id) {
  Optional<Person> optionalPerson = getPersonFromDb(id);

  return optionalPerson
      .map(Person::getName)
      .orElseThrow(NotFoundPersonException::new);
}

Zapis jest naprawdę przyjemny dla oka. Widać od razu co się dzieje w metodzie oraz nie ma potencjalnego miejsca na rzucenie NullPointerException. Nie trzeba robić żadnych dodatkowych sprawdzeń.

Walczymy o każdą milisekundę

Jeżeli nasza core’owa funkcjonalność wymaga jak najmniejszego latency to jak najbardziej nie można korzystać z dobroci Optional. Trzeba wszędzie szukać optymalizacji, więc stosowanie generycznych rozwiązań może nie być najszczęśliwsze. Nie wiem na ile poprawne są wyliczenia znalezione na StackOverflow, ale używanie według nich Optional może spowolnić nasz kod 2 czy nawet 3 krotnie!

Kiedy warto używać Optional?

Myślę, że powodów jest więcej, aby stać po tej stronie barykady. Java powstała z myślą o tym, aby realizować w prosty sposób wymagania biznesowe. Jej składnia ma nas chronić przed zagłębianiem się w tajniki zarządzania pamięcią znaną z C++. Z tego powodu powstają kolejne mechanizmy jak np. Optional. Fajnie podsumował to Jakub Gardo w swojej prezentacji na Warszawskim JUG.

Piszcie czytelny kod, bo taki kod działa najlepiej.

Jakub Gardo

Z tego powodu naprawdę warto skorzystać z dobrodziejstw jakie daje nam Optional i nie tworzyć choinek sprawdzeń “czy dana wartość na pewno nie jest null”. Argument za niestosowaniem Optional “bo będzie wiadomo gdzie coś się wywaliło przez rzucenie wyjątku NullPointerException” sam siebie nie broni. Takie sytuacje powinny po prostu wyłapać nam testy, a kod podążający za SOLID nie dopuści do takiej sytuacji.

Źródła: