Dlaczego do sprawdzania wartości zmiennej używana jest opcjonalna preferencja?

25

Weź dwa przykłady kodu:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

O ile mogę stwierdzić, najbardziej oczywistą różnicą jest to, że Opcjonalne wymaga utworzenia dodatkowego obiektu.

Jednak wiele osób zaczęło szybko przyjmować Opcjonalne. Jaka jest zaleta korzystania z opcji zamiast kontroli zerowej?

William Dunne
źródło
4
prawie nigdy nie chcesz używać isPresent, zwykle powinieneś używać ifPresent lub map
jk.
6
@jk. Ponieważ ifstwierdzenia są baaardzo ostatniej dekadzie, a wszyscy są za pomocą abstrakcji monada lambdy i teraz.
user253751
2
@immibis Miałem kiedyś długą debatę na ten temat z innym użytkownikiem. Chodzi o to, if(x.isPresent) fails_on_null(x.get)że wychodzisz z systemu typów i musisz zachować gwarancję, że kod nie złamie się „w twojej głowie” na (co prawda krótkiej) odległości między warunkiem a wywołaniem funkcji. W optional.ifPresent(fails_on_null)systemie typu ta gwarancja jest dla Ciebie i nie musisz się martwić.
ziggystar
1
Główną wadą Java przy użyciu Optional.ifPresent(i różnych innych konstrukcji Java) jest to, że można skutecznie modyfikować tylko zmienne końcowe i nie można zgłaszać sprawdzonych wyjątków. Jest to wystarczający powód, aby często ifPresentniestety unikać .
Mike

Odpowiedzi:

19

Optionalwykorzystuje system typów do wykonywania pracy, którą w innym przypadku musiałbyś wykonać w swojej głowie: pamiętając, czy dane odniesienie może być, czy nie null. To jest dobre. Zawsze sprytnie jest pozwolić kompilatorowi radzić sobie z nudnymi lekarstwami i zarezerwować ludzkie myśli na twórcze, interesujące prace.

Bez tego Optionalkażde odniesienie w twoim kodzie jest jak niewybuchowana bomba. Dostęp do niego może zrobić coś pożytecznego, lub może zakończyć działanie twojego programu z wyjątkiem.

Z Opcjonalnym i bez null, każdy dostęp do normalnego odwołania powiodło się, a każde odniesienie do Opcjonalnego powiodło się, chyba że jest ono rozbrojone i nie sprawdziłeś tego. To ogromna wygrana w utrzymywalności.

Niestety większość obecnie oferowanych języków Optionalnie została zniesiona null, więc możesz czerpać korzyści z tej koncepcji, wprowadzając ścisłą zasadę „absolutnie nie null, nigdy”. Dlatego Optionalnp. Java nie jest tak atrakcyjna, jak powinna być.

Kilian Foth
źródło
Ponieważ twoja odpowiedź jest najważniejsza, warto dodać korzystanie z Lambdas jako korzyści - dla każdego, kto przeczyta to w przyszłości (patrz moja odpowiedź)
William Dunne
Większość współczesnych języków zapewnia typy zerowalne, Kotlin, maszynopis, AFAIK.
Zen
5
IMO lepiej radzi sobie z adnotacją @Nullable. Nie ma powodu, aby używać Optionaltylko dla przypomnienia, że ​​wartość może być zerowa
Hilikus,
18

Wprowadza Optionalsilniejsze pisanie w operacjach, które mogą się nie powieść, jak opisano w innych odpowiedziach, ale jest to dalekie od najciekawszych lub najcenniejszych rzeczy, jakie Optionalsprzynoszą na stół. Znacznie bardziej użyteczna jest możliwość opóźniania lub unikania sprawdzania awarii oraz łatwego komponowania wielu operacji, które mogą się nie powieść.

Zastanów się, czy masz optionalzmienną z przykładowego kodu, musisz wykonać dwa dodatkowe kroki, z których każdy może potencjalnie zawieść. Jeśli jakikolwiek krok po drodze zakończy się niepowodzeniem, zamiast tego chcesz zwrócić wartość domyślną. Używając Optionalspoprawnie, otrzymujesz coś takiego:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

Z nulltym musiałbym sprawdzić trzy razy nullprzed kontynuowaniem, co dodaje wiele trudności i problemów związanych z konserwacją kodu. Optionalsmieć tę kontrolę wbudowaną w funkcje flatMapi orElse.

Uwaga: nie zadzwoniłem isPresentani razu, co należy traktować jako zapach kodu podczas używania Optionals. To niekoniecznie oznacza, że nigdy nie powinieneś używać isPresent, po prostu powinieneś dokładnie przeanalizować każdy kod, który ma, aby sprawdzić, czy jest lepszy sposób. W przeciwnym razie masz rację, zyskujesz tylko marginalną korzyść bezpieczeństwa w porównaniu do używania null.

Zauważ też, że nie martwię się tak bardzo o zawarcie tego wszystkiego w jednej funkcji, aby chronić inne części mojego kodu przed zerowymi wskaźnikami przed wynikami pośrednimi. Jeśli .orElse(defaultValue)na przykład sensowniej jest mieć moją w innej funkcji, mam o wiele mniej skrupułów, aby ją tam umieścić i o wiele łatwiej jest skomponować operacje między różnymi funkcjami w razie potrzeby.

Karl Bielefeldt
źródło
10

Podkreśla możliwość zerowania jako prawidłowej odpowiedzi, której ludzie często zakładają (słusznie lub niesłusznie), że nie zostanie zwrócona. Podświetlenie kilku razy, gdy wartość null jest ważna, pozwala pominąć zaśmiecanie kodu ogromną liczbą bezcelowych kontroli null.

Idealnym ustawieniem zmiennej na wartość NULL byłby błąd czasu kompilacji w dowolnym miejscu oprócz opcji opcjonalnej; eliminowanie wyjątków zerowego wskaźnika środowiska wykonawczego. Oczywiście zgodność wsteczna wyklucza to, niestety

Richard Tingle
źródło
4

Dam ci przykład:

class UserRepository {
     User findById(long id);
}

Jest całkiem jasne, do czego służy ta metoda. Ale co się stanie, jeśli repozytorium nie zawiera użytkownika o podanym identyfikatorze? Może generować wyjątek, a może zwraca wartość null?

Drugi przykład:

class UserRepository {
     Optional<User> findById(long id);
}

Co się stanie tutaj, jeśli nie będzie użytkownika o podanym identyfikatorze? Tak, masz instancję Optional.absent () i możesz to sprawdzić za pomocą Optional.isPresent (). Podpis metody z Opcjonalnie jest bardziej wyraźny na temat umowy. Od razu wiadomo, jak ma się zachowywać ta metoda.

Ma również zaletę podczas odczytywania kodu klienta:

User user = userRepository.findById(userId).get();

Wytrenowałem oko, by widzieć .get () jako natychmiastowy zapach kodu. Oznacza to, że klient celowo ignoruje umowę wywoływanej metody. O wiele łatwiej to zobaczyć niż bez Opcjonalnego, ponieważ w takim przypadku nie od razu wiesz, jaki jest kontrakt wywoływanej metody (niezależnie od tego, czy zezwala on na wartość zerową, czy nie), więc musisz to najpierw sprawdzić.

Opcjonalny jest używany z konwencją, że metody z typem zwracanym Opcjonalne nigdy nie zwracają wartości null, a zwykle również z (mniej mocną) konwencją, że metoda bez opcjonalnego typu zwracania nigdy nie zwraca wartości null (ale lepiej jest dodać adnotację @Nonnull, @NotNull lub podobnym) .

qbd
źródło
3

Optional<T>Pozwala mieć „niepowodzenie” lub „nie wynik” jako ważnej wartości odpowiedź / zwrot z metod (myślę, EG, z odnośnika bazy danych). Użycie Optionalzamiast zamiast nulldo wskazania niepowodzenia / braku wyniku ma pewne zalety:

  • Wyraźnie informuje, że „awaria” jest opcją. Użytkownik Twojej metody nie musi zgadywać, czy nullmoże zostać zwrócony.
  • Wartość nullta, przynajmniej moim zdaniem, nie powinna być używana do wskazywania czegokolwiek, ponieważ semantyka może nie być całkowicie jasna. Powinien być użyty do poinformowania śmieciarza, że ​​poprzednio wskazany obiekt może zostać zebrany.
  • OptionalKlasa udostępnia wiele metod ładne warunkowo pracę z wartościami (np ifPresent()) lub zdefiniować domyślne wartości w przejrzysty sposób ( orElse()).

Niestety nie eliminuje to całkowicie potrzeby nullsprawdzania w kodzie, ponieważ Optionalsam obiekt nadal może być null.

pansen
źródło
4
Nie do tego tak naprawdę służy opcja. Aby zgłosić błąd, użyj wyjątku. Jeśli nie można tego zrobić, należy użyć typu, który zawiera zarówno wynik lub kod błędu .
James Youngman,
Definitywnie się z tym nie zgadzam. Opcjonalnie.empty () jest moim zdaniem świetnym sposobem na pokazanie niepowodzenia lub nieistnienia.
LegendLength,
@LegendLength, muszę zdecydowanie się nie zgodzić w przypadku awarii. Jeśli funkcja zawiedzie, lepiej wytłumacz mi, a nie tylko puste „zawiodłem”
Winston Ewert,
Przepraszam, zgadzam się, mam na myśli „oczekiwany błąd”, ponieważ nie mogę znaleźć pracownika o nazwisku „x” podczas wyszukiwania.
LegendLength,
3

Oprócz innych odpowiedzi, inną główną zaletą Opcjonalnych, jeśli chodzi o pisanie czystego kodu, jest to, że możesz użyć wyrażenia Lambda w przypadku obecności wartości, jak pokazano poniżej:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));
William Dunne
źródło
2

Rozważ następującą metodę:

void dance(Person partner)

Teraz spójrzmy na kod wywołujący:

dance(null);

Powoduje to potencjalnie trudną do śledzenia awarię w innym miejscu, ponieważ dancenie spodziewał się wartości zerowej.

dance(optionalPerson)

Powoduje to błąd kompilacji, ponieważ taniec oczekiwał PersonnieOptional<Person>

dance(optionalPerson.get())

Jeśli nie mam osoby, powoduje to wyjątek na tej linii, w miejscu, w którym nielegalnie próbowałem przekształcić Optional<Person>osobę w Osobę. Kluczem jest to, że w przeciwieństwie do przekazywania wartości null, oprócz śladów tutaj, a nie innych lokalizacji w programie.

Podsumowując: niewłaściwe użycie opcji opcjonalnych powoduje łatwiejsze wyśledzenie problemów, niewłaściwe użycie wartości null powoduje trudne do wyśledzenia problemów.

Winston Ewert
źródło
1
Jeśli zgłaszasz wyjątki podczas wywoływania Optional.get()kodu, który jest źle napisany - prawie nigdy nie powinieneś wywoływać Optional.get bez uprzedniego sprawdzenia Optional.isPresent()(jak wskazano w OP) lub możesz napisać optionalPersion.ifPresent(myObj::dance).
Chris Cooper
@QmunkE, tak, powinieneś wywoływać Optional.get () tylko wtedy, gdy już wiesz, że opcjonalny nie jest pusty (albo dlatego, że wywołałeś isPresent lub ponieważ twój algorytm to gwarantuje). Martwię się jednak o to, że ludzie na ślepo sprawdzają isPresent, a następnie ignorują nieobecne wartości, tworząc szereg cichych awarii, których prześledzenie będzie uciążliwe.
Winston Ewert
1
Lubię opcje. Ale powiem, że zwykłe stare wskaźniki zerowe bardzo rzadko stanowią problem w kodzie rzeczywistym. Niemal zawsze zawodzą w pobliżu linii kodu. I myślę, że ludzie są przesadzeni, gdy mówią o tym temacie.
LegendLength,
@LegendLength, z mojego doświadczenia wynika, że ​​95% przypadków jest rzeczywiście łatwych do zidentyfikowania, aby ustalić, skąd pochodzi NULL. Pozostałe 5% jest jednak niezwykle trudne do wyśledzenia.
Winston Ewert,
-1

W Objective-C wszystkie odwołania do obiektów są opcjonalne. Z tego powodu kod taki jak napisany przez ciebie jest głupi.

if (variable != nil) {
    [variable doThing];
}

Kod podobny do powyższego jest niespotykany w Objective-C. Zamiast tego programista po prostu:

[variable doThing];

Jeśli variablezawiera wartość, doThingzostanie na niej wywołana. Jeśli nie, nie zaszkodzi. Program potraktuje to jako brak operacji i będzie kontynuował działanie.

To prawdziwa zaleta opcji. Nie musisz zaśmiecać swojego kodu if (obj != nil).

Daniel T.
źródło
Czy nie jest to jednak tylko forma cichego zawodzenia? W niektórych przypadkach możesz się martwić, że wartość jest pusta i chcesz coś z tym zrobić.
LegendLength,
-3

if (opcjonalne.isPresent ()) {

Oczywistym problemem jest to, że jeśli „opcja” naprawdę jest brakujące (czyli jest zerowy), a następnie kod ma zamiar wysadzić z NullReferenceException (lub podobny) próbuje wywołać dowolną metodę na odniesienie obiekt zerowy!

Możesz napisać statyczną metodę klasy Helper, która określa zerowość dowolnego typu obiektu, mniej więcej tak:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

lub, być może, bardziej pożytecznie:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Ale czy którekolwiek z nich są naprawdę bardziej czytelne / zrozumiałe / łatwe do utrzymania niż oryginał?

if ( null == optional ) ... 
if ( null != optional ) ... 
Phill W.
źródło