Usługa sieciowa zwraca ogromny plik XML i muszę uzyskać dostęp do głęboko zagnieżdżonych pól. Na przykład:
return wsObject.getFoo().getBar().getBaz().getInt()
Problemem jest to, że getFoo()
, getBar()
, getBaz()
mogą zwracać null
.
Jeśli jednak sprawdzę, czy null
we wszystkich przypadkach, kod staje się bardzo rozwlekły i trudny do odczytania. Ponadto mogę przegapić kontrole niektórych pól.
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
Czy można pisać
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
czy też byłoby to uważane za anty-wzór?
java
exception
nullpointerexception
null
custom-error-handling
David Frank
źródło
źródło
null
kontrolom, ponieważwsObject.getFoo().getBar().getBaz().getInt()
jest to już zapach kodu. Przeczytaj, czym jest „Prawo Demeter” i wolisz odpowiednio zmienić kod. Wtedy problem znull
czekami również zniknie. I pomyśl o użyciuOptional
.wsdl2java
, który nie szanuje prawa Demeter.Odpowiedzi:
Łapanie
NullPointerException
jest naprawdę problematyczne, ponieważ może się zdarzyć prawie wszędzie. Bardzo łatwo jest go złapać przez błąd, złapać go przez przypadek i kontynuować tak, jakby wszystko było normalne, ukrywając w ten sposób prawdziwy problem. Trudno sobie z tym poradzić, więc najlepiej całkowicie tego unikać. (Na przykład pomyśl o automatycznym rozpakowywaniu wartości nullInteger
).Proponuję
Optional
zamiast tego użyć klasy. Jest to często najlepsze podejście, gdy chcesz pracować z wartościami, które są obecne lub nieobecne.Używając tego, możesz napisać swój kod w ten sposób:
Dlaczego opcjonalne?
Używanie
Optional
s zamiastnull
dla wartości, które mogą być nieobecne, sprawia, że fakt ten jest bardzo widoczny i jasny dla czytelników, a system typów zapewni, że przypadkowo o tym nie zapomnisz.Otrzymasz również dostęp do metod wygodniejszej pracy z takimi wartościami, takich jak
map
iorElse
.Czy nieobecność jest ważna czy błąd?
Ale zastanów się również, czy prawidłowy wynik dla metod pośrednich zwraca wartość null, czy też jest to oznaka błędu. Jeśli zawsze jest to błąd, prawdopodobnie lepiej jest zgłosić wyjątek niż zwrócić specjalną wartość lub aby same metody pośrednie zgłosiły wyjątek.
Może więcej opcji?
Jeśli z drugiej strony nieobecne wartości z metod pośrednich są prawidłowe, być może możesz przełączyć się na
Optional
je również s?Następnie możesz ich użyć w ten sposób:
Dlaczego nie opcjonalne?
Jedynym powodem, dla którego mogę wymyślić, aby go nie używać,
Optional
jest to, że jest to w naprawdę krytycznej dla wydajności części kodu i jeśli obciążenie związane ze zbieraniem elementów bezużytecznych okazuje się problemem. Dzieje się tak, ponieważ kilkaOptional
obiektów jest przydzielanych za każdym razem, gdy kod jest wykonywany, a maszyna wirtualna może nie być w stanie ich zoptymalizować. W takim przypadku oryginalne testy if mogą być lepsze.źródło
Try
zamiastOptional
. Chociaż nie maTry
takiego interfejsu API języka Java, istnieje wiele bibliotek, które je zapewniają, np. Javaslang.io , github.com/bradleyscollins/try4j , functionjava.org lub github.com/jasongoodwin/better-java-monadsFClass::getBar
itp. byłby krótszy.static
metody, co spowoduje bardzo niewielką karę.Proponuję rozważyć
Objects.requireNonNull(T obj, String message)
. Możesz zbudować łańcuchy ze szczegółową wiadomością dla każdego wyjątku, na przykładSugerowałbym, abyś nie używał specjalnych wartości zwracanych, takich jak
-1
. To nie jest styl Java. Java zaprojektowała mechanizm wyjątków, aby uniknąć tego staromodnego sposobu, który pochodzi z języka C.Rzucanie
NullPointerException
nie jest najlepszą opcją. Można podać własne wyjątek (co sprawdzone , aby zagwarantować, że będzie on obsługiwany przez użytkownika lub niekontrolowany przetwarzać je w łatwiejszy sposób) lub użyć specyficzny wyjątek od parsera XML, którego używasz.źródło
Objects.requireNonNull
ostatecznie wyrzucaNullPointerException
. Więc to nie sprawia, że sytuacja jest inna niżreturn wsObject.getFoo().getBar().getBaz().getInt()
if
s, jak pokazał OPOptional
klasy, a może zwrócić wartość nullInteger
Zakładając, że struktura klas jest rzeczywiście poza naszą kontrolą, jak się wydaje, w tym przypadku, myślę, że wychwycenie NPE, jak sugerowano w pytaniu, jest rzeczywiście rozsądnym rozwiązaniem, chyba że wydajność jest głównym problemem. Jednym małym ulepszeniem może być zawinięcie logiki rzutów / łapania, aby uniknąć bałaganu:
Teraz możesz po prostu zrobić:
źródło
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");
nie daje błędu w czasie kompilacji, który mógłby być problematyczny.Jak już zauważył Tom w komentarzu,
Poniższe stwierdzenie jest niezgodne z prawem Demeter :
To, czego chcesz, to
int
i możesz to uzyskaćFoo
. Prawo Demeter mówi, nigdy nie rozmawiaj z nieznajomymi . W swoim przypadku możesz ukryć rzeczywistą realizację pod maskąFoo
iBar
.Teraz można utworzyć metodę w
Foo
celu pobraniaint
odBaz
. Ostatecznie,Foo
będziemy miećBar
iBar
możemy uzyskać dostępInt
bezBaz
bezpośredniego wystawianiaFoo
. Tak więc sprawdzanie wartości null jest prawdopodobnie podzielone na różne klasy i tylko wymagane atrybuty będą współdzielone między klasami.źródło
null
sprawdzania własnych tagów podrzędnych.Moja odpowiedź jest prawie w tym samym wierszu co @janki, ale chciałbym nieco zmodyfikować fragment kodu, jak poniżej:
Możesz również dodać sprawdzenie wartości null
wsObject
, jeśli istnieje jakakolwiek szansa, że ten obiekt będzie pusty.źródło
Mówisz, że niektóre metody „mogą powrócić
null
”, ale nie mówisz, w jakich okolicznościach powracająnull
. Mówisz, że łapieszNullPointerException
ale nie mówisz, dlaczego to łapiesz. Ten brak informacji sugeruje, że nie masz jasnego zrozumienia, do czego służą wyjątki i dlaczego są one lepsze od alternatywy.Rozważ metodę klasy, która jest przeznaczona do wykonywania akcji, ale metoda nie może zagwarantować, że wykona akcję, z powodu okoliczności niezależnych od niej (co w rzeczywistości dotyczy wszystkich metod w Javie ). Nazywamy tę metodę i zwraca. Kod, który wywołuje tę metodę, musi wiedzieć, czy się powiodła. Skąd to może wiedzieć? Jak można zorganizować radzenie sobie z dwiema możliwościami, sukcesu lub porażki?
Korzystając z wyjątków, możemy pisać metody, które mają powodzenie jako warunek wiadomości . Jeśli metoda zwraca, powiodła się. Jeśli zgłosi wyjątek, nie powiodło się. To duża wygrana dla jasności. Możemy napisać kod, który w jasny sposób przetwarza normalny przypadek sukcesu i przenieść cały kod obsługi błędów do
catch
klauzul. Często okazuje się, że szczegóły tego, jak lub dlaczego metoda zakończyła się niepowodzeniem, nie są ważne dla wywołującego, więc ta samacatch
klauzula może być używana do obsługi kilku typów niepowodzeń. A często zdarza się, że metoda nie wymaga wyjątków dotyczących połowów w ogóle , ale można po prostu pozwolić im propagować do swojego rozmówcy. Wyjątki spowodowane błędami programu znajdują się w tej ostatniej klasie; kilka metod może odpowiednio zareagować, gdy pojawi się błąd.Więc te metody, które powracają
null
.null
wartość wskazuje na błąd w kodzie? Jeśli tak, nie powinieneś w ogóle łapać wyjątku. Twój kod nie powinien próbować odgadnąć samego siebie. Po prostu napisz to, co jest jasne i zwięzłe przy założeniu, że zadziała. Czy łańcuch wywołań metod jest jasny i zwięzły? Następnie po prostu ich użyj.null
wartość wskazuje na nieprawidłowe dane wejściowe w programie? Jeśli tak, aNullPointerException
nie jest odpowiednim wyjątkiem do zgłoszenia, ponieważ zwykle jest zarezerwowany do wskazywania błędów. Prawdopodobnie chcesz zgłosić wyjątek niestandardowy pochodzący zIllegalArgumentException
(jeśli chcesz niezaznaczony wyjątek ) lubIOException
(jeśli chcesz sprawdzić wyjątek). Czy Twój program jest zobowiązany do dostarczania szczegółowych komunikatów o błędach składni w przypadku nieprawidłowych danych wejściowych? Jeśli tak,null
to jedyne, co możesz zrobić , to sprawdzenie każdej metody pod kątem wartości zwracanej, a następnie zgłoszenie odpowiedniego wyjątku diagnostycznego. Jeśli program nie wymaga szczegółowej diagnostyki, łączenie wywołań metod w łańcuchy, wychwytywanie dowolnego,NullPointerException
a następnie wyrzucanie niestandardowego wyjątku jest najwyraźniejsze i najbardziej zwięzłe.Jedna z odpowiedzi głosi, że wywołania metod łańcuchowych naruszają prawo Demeter, a zatem są złe. To twierdzenie jest błędne.
źródło
Aby poprawić czytelność, możesz chcieć użyć wielu zmiennych, takich jak
źródło
Nie łap
NullPointerException
. Nie wiesz, skąd się bierze (wiem, że to nie jest prawdopodobne w twoim przypadku, ale może coś innego rzuciło) i jest powolne. Chcesz uzyskać dostęp do określonego pola i w tym przypadku każde inne pole nie może być puste. To doskonały powód, aby sprawdzić każde pole. Prawdopodobnie sprawdziłbym to w jednym jeśli, a następnie stworzyłbym metodę na czytelność. Jak wskazywali inni, już zwracanie -1 jest bardzo oldschoolowe, ale nie wiem, czy masz ku temu powód, czy nie (np. Rozmowa z innym systemem).Edycja: Jest to dyskusyjne, jeśli jest niezgodne z prawem Demeter, ponieważ WsObject jest prawdopodobnie tylko strukturą danych (sprawdź https://stackoverflow.com/a/26021695/1528880 ).
źródło
Jeśli nie chcesz refaktoryzować kodu i możesz użyć języka Java 8, możesz użyć odwołań do metod.
Najpierw proste demo (przepraszam za statyczne klasy wewnętrzne)
Wynik
Interfejs
Getter
jest tylko interfejsem funkcjonalnym, możesz użyć dowolnego odpowiednika.GetterResult
klasa, metody dostępu zostały usunięte dla przejrzystości, przechowują wynik łańcucha pobierania, jeśli taki istnieje, lub indeks ostatniego wywołanego gettera.Metoda
getterChain
to prosty, szablonowy fragment kodu, który można wygenerować automatycznie (lub ręcznie w razie potrzeby).Skonstruowałem kod tak, aby powtarzający się blok był oczywisty.
Nie jest to idealne rozwiązanie, ponieważ nadal musisz zdefiniować jedno przeciążenie
getterChain
na liczbę pobierających.Zamiast tego dokonałbym refaktoryzacji kodu, ale jeśli nie możesz i często używasz długich łańcuchów getterów, możesz rozważyć zbudowanie klasy z przeciążeniami, które zajmują od 2 do, powiedzmy, 10, getterów.
źródło
Jak powiedzieli inni, poszanowanie prawa Demeter jest zdecydowanie częścią rozwiązania. Inną częścią, jeśli to możliwe, jest zmiana tych połączonych metod, aby nie mogły wrócić
null
. Możesz uniknąć powrotu, zwracającnull
zamiast tego pustyString
, pustyCollection
lub inny fikcyjny obiekt, który oznacza lub robi wszystko, z czym wywołujący zrobiłbynull
.źródło
Chciałbym dodać odpowiedź, która skupia się na znaczeniu błędu . Wyjątek zerowy sam w sobie nie zapewnia żadnego pełnego znaczenia błędu. Dlatego radziłbym unikać bezpośredniego kontaktu z nimi.
Istnieją tysiące przypadków, w których twój kod może się nie udać: nie można połączyć się z bazą danych, wyjątek IO, błąd sieci ... Jeśli zajmiesz się nimi jeden po drugim (tak jak tutaj sprawdzanie zerowe), byłoby to zbyt kłopotliwe.
W kodzie:
Nawet jeśli wiesz, które pole jest puste, nie masz pojęcia, co poszło nie tak. Może Bar jest pusty, ale czy jest oczekiwany? A może to błąd danych? Pomyśl o ludziach, którzy czytają Twój kod
Podobnie jak w przypadku odpowiedzi Xenterosa, proponuję użycie niestandardowego niezaznaczonego wyjątku . Na przykład w tej sytuacji: Foo może mieć wartość null (prawidłowe dane), ale Bar i Baz nigdy nie powinny być puste (nieprawidłowe dane)
Kod można przepisać:
źródło
NullPointerException
jest wyjątkiem w czasie wykonywania, więc generalnie nie zaleca się jego przechwytywania, ale unikania go.Będziesz musiał złapać wyjątek wszędzie tam, gdzie chcesz wywołać metodę (lub będzie propagować stos). Niemniej jednak, jeśli w twoim przypadku możesz kontynuować pracę z tym wynikiem z wartością -1 i masz pewność, że nie zostanie on propagowany, ponieważ nie używasz żadnego z "elementów", które mogą być zerowe, to wydaje mi się słuszne, aby Złap to
Edytować:
Zgadzam się z późniejszą odpowiedzią od @xenteros, lepiej będzie uruchomić własny wyjątek zamiast zwracać -1, możesz to nazwać
InvalidXMLException
na przykład.źródło
Śledzę ten post od wczoraj.
Komentowałem / głosowałem na komentarze, które mówią, że łapanie NPE jest złe. Oto dlaczego to robię.
Wynik
3.216
0,002
Widzę tutaj wyraźnego zwycięzcę. Posiadanie czeków jest o wiele tańsze niż złapanie wyjątku. Widziałem, jak działa Java-8. Biorąc pod uwagę, że 70% obecnych aplikacji nadal działa na Javie-7, dodaję tę odpowiedź.
Podsumowanie W przypadku aplikacji o znaczeniu krytycznym obsługa NPE jest kosztowna.
źródło
Jeśli problemem jest wydajność, należy rozważyć opcję „połowu”. Jeśli nie można użyć słowa „catch”, ponieważ byłoby to propagowane (o czym wspomina „SCouto”), należy użyć zmiennych lokalnych, aby uniknąć wielokrotnych wywołań metod
getFoo()
,getBar()
igetBaz()
.źródło
Warto zastanowić się nad stworzeniem własnego Wyjątku. Nazwijmy to MyOperationFailedException. Możesz go rzucić zamiast zwracać wartość. Rezultat będzie taki sam - wyjdziesz z funkcji, ale nie zwrócisz zakodowanej na stałe wartości -1, która jest anty-wzorcem Java. W Javie używamy wyjątków.
EDYTOWAĆ:
Zgodnie z dyskusją w komentarzach pozwolę sobie dodać coś do moich wcześniejszych przemyśleń. W tym kodzie są dwie możliwości. Po pierwsze, akceptujesz wartość null, a po drugie, że jest to błąd.
Jeśli jest to błąd i występuje, możesz debugować kod przy użyciu innych struktur do celów debugowania, gdy punkty przerwania nie są wystarczające.
Jeśli jest to dopuszczalne, nie obchodzi Cię, gdzie pojawił się ten null. Jeśli to zrobisz, zdecydowanie nie powinieneś łączyć tych żądań.
źródło
Metoda, którą masz, jest długa, ale bardzo czytelna. Gdybym był nowym programistą przychodzącym do twojej bazy kodu, mógłbym dość szybko zobaczyć, co robisz. Większość innych odpowiedzi (w tym wychwytywanie wyjątku) nie wydaje się czynić rzeczy bardziej czytelnymi, a niektóre sprawiają, że moim zdaniem jest mniej czytelny.
Biorąc pod uwagę, że prawdopodobnie nie masz kontroli nad wygenerowanym źródłem i zakładając, że naprawdę potrzebujesz dostępu do kilku głęboko zagnieżdżonych pól tu i tam, zalecałbym zawijanie każdego głęboko zagnieżdżonego dostępu metodą.
Jeśli zauważysz, że piszesz wiele z tych metod lub jeśli kusi Cię, aby utworzyć te publiczne metody statyczne, utworzę oddzielny model obiektowy, zagnieżdżony tak, jak chcesz, z tylko polami, na których Ci zależy, i przekonwertuj go z sieci Services do modelu obiektów.
Podczas komunikacji ze zdalną usługą internetową bardzo typowe jest posiadanie „domeny zdalnej” i „domeny aplikacji” i przełączanie się między nimi. Domena zdalna jest często ograniczona protokołem sieciowym (na przykład nie można wysyłać metod pomocniczych tam iz powrotem w czystej usłudze RESTful, a głęboko zagnieżdżone modele obiektów są powszechne, aby uniknąć wielu wywołań API), a więc nie są idealne do bezpośredniego użycia w Twój klient.
Na przykład:
źródło
stosując prawo Demeter,
źródło
Dając odpowiedź, która wydaje się inna niż wszystkie inne.
Powód:
Aby uczynić swój kod łatwym do odczytania, spróbuj tego, aby sprawdzić warunki:
EDYTOWAĆ :
Wszelkie sugestie będą mile widziane .. !!
źródło
wsObject
będzie zawierać wartość zwróconą przez usługę sieciową .. !! Usługa zostanie już wywołana iwsObject
otrzyma długieXML
dane jako odpowiedź usługi sieciowej .. !! Nie ma więc nic podobnego do serwera znajdującego się na innym kontynencie, ponieważgetFoo()
jest to tylko element pobierający metodę pobierania, a nie wywołanie usługi Webservice .. !! @xenterosNapisałem klasę o nazwie,
Snag
która pozwala zdefiniować ścieżkę poruszania się po drzewie obiektów. Oto przykład jego zastosowania:Oznacza to, że instancja
ENGINE_NAME
skutecznie wywołaCar?.getEngine()?.getName()
przekazaną do niej instancję i zwróci,null
jeśli zwróci jakiekolwiek odwołanienull
:Nie jest opublikowany w Maven, ale jeśli ktoś uzna to za przydatne, jest tutaj (oczywiście bez gwarancji!)
To trochę proste, ale wydaje się, że spełnia swoje zadanie. Oczywiście jest bardziej przestarzały w nowszych wersjach języka Java i innych języków JVM, które obsługują bezpieczną nawigację lub
Optional
.źródło