Mam kod, który zawodzi z powodu NullPointerException. Wywoływana jest metoda na obiekcie, na którym obiekt nie istnieje.
Doprowadziło mnie to jednak do zastanowienia się, jak to naprawić. Czy zawsze koduję defensywnie dla wartości zerowych, aby w przyszłości sprawdzać kod wyjątków wskaźnika zerowego, czy też powinienem naprawić przyczynę wartości zerowej, aby nie wystąpiła ona w dalszym ciągu?
Jakie są Twoje myśli?
Odpowiedzi:
Jeśli null jest rozsądnym parametrem wejściowym dla metody, popraw metodę. Jeśli nie, napraw dzwoniącego. „Rozsądny” to elastyczny termin, dlatego proponuję następujący test: Jak metoda powinna podawać wartość zerową? Jeśli znajdziesz więcej niż jedną możliwą odpowiedź, wartość null nie jest rozsądnym wejściem.
źródło
Precondition.checkNotNull(...)
. Zobacz stackoverflow.com/questions/3022319/…IllegalArgumentException
wartość NULL. Sygnalizuje to dzwoniącym metody, że błąd znajduje się w ich kodzie (a nie w samej metodzie).Nie używaj null, użyj Opcjonalnie
Jak wskazałeś, jednym z największych problemów
null
w Javie jest to, że można jej używać wszędzie , a przynajmniej we wszystkich typach referencyjnych.Nie można powiedzieć, że to może być,
null
a co nie może być.Java 8 wprowadza wiele lepiej kierunkowa:
Optional
.I przykład z Oracle:
Jeśli każdy z nich może, ale nie musi, zwrócić wartość udaną, możesz zmienić interfejsy API na
Optional
s:Dzięki jawnemu kodowaniu opcjonalności w typie interfejsy będą znacznie lepsze, a kod będzie czystszy.
Jeśli nie używasz Java 8, możesz zajrzeć na
com.google.common.base.Optional
Google Guava.Dobre wyjaśnienie zespołu Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained
Bardziej ogólne objaśnienie wad do zera, z przykładami z kilku języków: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/
@Nonnull, @Nullable
Java 8 dodaje te adnotacje, aby pomóc narzędziom do sprawdzania kodu, takim jak IDE, wykrywać problemy. Ich skuteczność jest dość ograniczona.
Sprawdź, kiedy ma to sens
Nie pisz 50% kodu sprawdzającego wartość null, szczególnie jeśli nie ma nic sensownego, by twój kod mógł zrobić z
null
wartością.Z drugiej strony, jeśli
null
można go użyć i coś znaczyć, należy go użyć.Ostatecznie nie można oczywiście usunąć
null
z Javy. Zdecydowanie zalecam zastępowanieOptional
abstrakcji, gdy tylko jest to możliwe, i sprawdzanie,null
kiedy można zrobić z tym coś rozsądnego.źródło
NullPointerException
?NullPointerException
Może się zdarzyć dosłownie każdym wywołaniu metody instancji w Javie. Miałbyśthrows NullPointerException
prawie każdą metodę.Jest wiele sposobów na poradzenie sobie z tym, pieprzenie kodu
if (obj != null) {}
nie jest idealne, jest bałaganiarskie, powoduje hałas podczas czytania kodu później podczas cyklu konserwacji i jest podatne na błędy, ponieważ łatwo jest zapomnieć o owijaniu płyty kotła.Zależy to od tego, czy chcesz, aby kod działał cicho, czy nie. To
null
błąd lub oczekiwany warunek.Co jest zerowe?
W każdej definicji i przypadku Null oznacza absolutny brak danych. Wartości null w bazach danych oznaczają brak wartości dla tej kolumny. zerowe
String
to nie to samo co pusteString
, zeroweint
to nie to samo, co ZERO w teorii. W praktyce „to zależy”. PusteString
może stanowić dobrąNull Object
implementację dla klasy String,Integer
ponieważ zależy to od logiki biznesowej.Alternatywy to:
Null Object
Wzór. Utwórz instancję swojego obiektu, która reprezentujenull
stan, i zainicjuj wszystkie odwołania do tego typu za pomocą odwołania doNull
implementacji. Jest to przydatne w przypadku prostych obiektów typu wartości, które nie mają wielu odniesień do innych obiektów, które również mogą byćnull
i powinny byćnull
w stanie poprawnym.Użyj narzędzi zorientowanych na aspekt, aby utkać metody o
Null Checker
aspekcie, który zapobiega zerowaniu parametrów. Dotyczy to przypadków, w którychnull
występuje błąd.Używaj
assert()
niewiele lepiej niżif (obj != null){}
mniej hałasu.Użyj narzędzia do egzekwowania umów, takiego jak Kontrakty dla Java . Taki sam przypadek użycia jak w przypadku AspectJ, ale nowszy i wykorzystuje Adnotacje zamiast zewnętrznych plików konfiguracyjnych. Najlepsze z obu dzieł Aspektów i Asertów.
1 jest idealnym rozwiązaniem, gdy wiadomo, że przychodzą dane
null
i należy je zastąpić pewną wartością domyślną, aby odbiorcy nie musieli zajmować się całym kodem kontrolnym zerowania. Sprawdzanie ze znanymi wartościami domyślnymi również będzie bardziej wyraziste.2, 3 i 4 są po prostu wygodnymi alternatywnymi generatorami wyjątków do zastąpienia
NullPointerException
czymś bardziej pouczającym, co zawsze stanowi ulepszenie.Na końcu
null
w Javie prawie we wszystkich przypadkach występuje błąd logiczny. Zawsze powinieneś dążyć do wyeliminowania pierwotnej przyczynyNullPointerExceptions
. Powinieneś starać się nie używaćnull
warunków jako logiki biznesowej.if (x == null) { i = someDefault; }
po prostu dokonaj wstępnego przypisania do tej domyślnej instancji obiektu.źródło
null
jest to nieoczekiwane, to jest to prawdziwy błąd, wtedy wszystko powinno się całkowicie zatrzymać.null == null
(nawet w PHP), ale w bazach danych,null != null
ponieważ w bazach danych reprezentuje nieznaną wartość, a nie „nic”. Dwie niewiadome niekoniecznie są równe, podczas gdy dwa nic nie są równe.Dodanie kontroli zerowej może sprawić, że testowanie będzie problematyczne. Zobacz ten wspaniały wykład ...
Sprawdź wykład Google Tech: „Czysty kod mówi - nie szukaj rzeczy!” mówi o tym około 24 minuty
http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E
Programowanie paranoiczne polega na dodawaniu wszędzie zerowych kontroli. Na początku wydaje się to dobrym pomysłem, ale z perspektywy testowania sprawia, że testowanie typu zerowania jest trudne.
Ponadto po utworzeniu warunku wstępnego istnienia jakiegoś obiektu, takiego jak
uniemożliwia ci tworzenie Domu, ponieważ będziesz rzucał wyjątek. Załóżmy, że twoje przypadki testowe tworzą fałszywe obiekty do testowania czegoś innego niż drzwi, cóż, nie możesz tego zrobić, ponieważ drzwi są wymagane
Ci, którzy cierpieli z powodu fałszywego stworzenia, są świadomi tego rodzaju irytacji.
Podsumowując, zestaw testowy powinien być wystarczająco solidny, aby testować drzwi, domy, dachy itp., Bez konieczności paranoikowania. Serio, jak trudno jest dodać test zerowy dla określonych obiektów w testach :)
Zawsze powinieneś preferować aplikacje, które działają, ponieważ masz kilka testów, które POTWIERDZAJĄ, że to działa, zamiast NADZIEI, że działa po prostu dlatego, że masz całą masę wstępnych kontroli zerowych w całym miejscu
źródło
tl; dr - DOBRE jest sprawdzanie nieoczekiwanych
null
s, ale ZŁE dla aplikacji, która próbuje je naprawić.Detale
Oczywiste jest, że istnieją sytuacje, w których
null
poprawne dane wejściowe lub wyjściowe są przekazywane do metody, i inne, w których tak nie jest.Zasada nr 1:
Reguła nr 2:
Biorąc pod uwagę wyraźne określenie „umowy” metoda vis-a-vis
null
s, jest to błąd programowania przejść lub zwrócinull
gdzie nie powinny.Zasada nr 3:
Jeśli metoda wykryje,
null
że nie powinno jej tam być, nie powinna próbować rozwiązać problemu, zamieniając ją w coś innego. To po prostu ukrywa problem przed programistą. Zamiast tego powinno pozwolić na wystąpienie NPE i spowodować awarię, aby programiści mogli dowiedzieć się, co jest główną przyczyną i naprawić go. Mam nadzieję, że awaria zostanie zauważona podczas testów. Jeśli nie, to mówi coś o twojej metodyce testowania.Reguła # 4:
Jeśli masz w kodzie błędy, które powodują wiele NPE, najtrudniejsze może być ustalenie, skąd pochodzą te
null
wartości. Jednym ze sposobów ułatwienia diagnozy jest napisanie kodu, aby zostałnull
on wykryty jak najszybciej. Często możesz to zrobić w połączeniu z innymi czekami; na przykład(Są oczywiście przypadki, w których reguły 3 i 4 powinny zostać zahartowane w rzeczywistości. Na przykład (reguła 3) niektóre rodzaje aplikacji muszą próbować kontynuować po wykryciu prawdopodobnie błędów programowania. I (reguła 4) zbyt wiele sprawdzania złych parametrów może mieć wpływ na wydajność).
źródło
Poleciłbym ustalenie metody na defensywną. Na przykład:
Powinny być bardziej zgodne z tym:
Zdaję sobie sprawę, że jest to absolutnie trywialne, ale jeśli wywołujący oczekuje, że obiekt da mu domyślny obiekt, który nie spowoduje przekazania wartości null.
źródło
new String()
.null
s jest najgorszą możliwą opcją, gorszą niż rzucanie NPE.Do tej pory bardzo pomogły mi następujące ogólne zasady dotyczące wartości null:
Jeśli dane pochodzą spoza twojej kontroli, systematycznie sprawdzaj wartości zerowe i działaj odpowiednio. Oznacza to albo zgłoszenie wyjątku, który ma sens dla funkcji (zaznaczone lub niezaznaczone, po prostu upewnij się, że nazwa wyjątku mówi ci dokładnie, co się dzieje). Ale NIGDY nie pozwól, aby w twoim systemie spadła jakaś wartość, która mogłaby potencjalnie przynieść niespodzianki.
Jeśli wartość Null znajduje się w domenie odpowiednich wartości dla twojego modelu danych, postępuj z nią odpowiednio.
Zwracając wartości, staraj się nie zwracać wartości null, gdy tylko jest to możliwe. Zawsze preferuj Puste listy, puste ciągi znaków, wzorce zerowych obiektów. Zachowaj wartości null jako zwracane wartości, gdy jest to najlepsza możliwa reprezentacja danych dla danego przypadku użycia.
Prawdopodobnie najważniejszy ze wszystkich ... Testy, testy i testy ponownie. Podczas testowania kodu nie testuj go jako kodera, testuj go jako nazistowską dominację psychopaty i spróbuj wyobrazić sobie różne sposoby torturowania tego kodu.
Ma to nieco paranoiczną stronę w odniesieniu do zer, które często prowadzą do elewacji i serwerów pośredniczących, które łączą systemy ze światem zewnętrznym i ściśle kontrolowanych wartości wewnątrz z dużą obfitością redundancji. Świat zewnętrzny tutaj oznacza prawie wszystko, czego sam nie kodowałem. Ma czas działania, ale jak dotąd rzadko musiałem to optymalizować, tworząc „bezpieczne sekcje” o zerowym kodzie. Muszę jednak powiedzieć, że głównie tworzę systemy działające długo dla służby zdrowia, a ostatnią rzeczą, jakiej chcę, to podsystem interfejsu przenoszący twoje alergie na jod do skanera CT ulega awarii z powodu nieoczekiwanego wskaźnika zerowego, ponieważ ktoś w innym systemie nigdy nie zdawał sobie sprawy, że nazwy mogą zawierają apostrofy lub znaki takie jak 但 耒耨。
w każdym razie .... moje 2 centy
źródło
Proponuję użycie wzorca Option / Some / None z języków funkcjonalnych. Nie jestem specjalistą od Java, ale intensywnie wykorzystuję własną implementację tego wzorca w moim projekcie C # i jestem pewien, że można go przekonwertować na świat Java.
Ideą tego wzorca jest: jeśli logicznie jest sytuacja, w której istnieje możliwość nieobecności wartości (na przykład podczas odzyskiwania z bazy danych według identyfikatora), podajesz obiekt typu Opcja [T], gdzie T jest możliwą wartością . I przypadek nieobecności obiektu wartości klasy None [T] zwrócił, w przypadku gdy istnienie wartości - obiekt Some [T] zwrócił, zawierający wartość.
W takim przypadku musisz poradzić sobie z możliwością braku wartości, a jeśli zrobisz przegląd kodu, łatwo znajdziesz miejsce niepoprawnej obsługi. Aby uzyskać inspirację z implementacji języka C #, zapoznaj się z moim repozytorium bitbucket https://bitbucket.org/mikegirkin/optionsomenone
Jeśli zwrócisz wartość zerową, która jest logicznie równoważna z błędem (na przykład żaden plik nie istnieje lub nie można się połączyć), powinieneś zgłosić wyjątek lub użyć innego wzorca obsługi błędów. Pomysł, który za tym stoi, kończy się rozwiązaniem, gdy trzeba poradzić sobie z sytuacją braku wartości i łatwo można znaleźć w kodzie miejsca nieprawidłowej obsługi.
źródło
Cóż, jeśli jednym z możliwych wyników twojej metody jest wartość null, powinieneś dla tego kodować defensywnie, ale jeśli metoda ma zwracać wartość inną niż null, ale nie jest, na pewno to naprawię.
Jak zwykle zależy to od przypadku, jak w większości rzeczy w życiu :)
źródło
Biblioteki języka Apache Commons zapewniają obsługę wartości zerowych
metoda defaultIfNull w klasie ObjectUtils umożliwia zwrócenie wartości domyślnej, jeśli przekazywany obiekt ma wartość null
źródło
Nie używaj typu Opcjonalny, chyba że naprawdę jest opcjonalny, często wyjście jest lepiej traktowane jako wyjątek, chyba że naprawdę spodziewałeś się wartości null jako opcji, a nie dlatego, że regularnie piszesz błędny kod.
Jak wskazuje artykuł Google, problem nie jest zerowy jako typ, który ma swoje zastosowania. Problem polega na tym, że wartości zerowe powinny być sprawdzane i obsługiwane, często można je z wdziękiem wychodzić.
Istnieje wiele przypadków zerowych, które reprezentują nieprawidłowe warunki w obszarach poza rutynowym działaniem programu (nieprawidłowe wprowadzanie danych przez użytkownika, problemy z bazą danych, awarie sieci, brakujące pliki, uszkodzone dane), i do tego służą sprawdzone wyjątki, obsługa ich, a nawet jeśli to tylko się zalogować.
Obsługa wyjątków jest dozwolona dla różnych priorytetów operacyjnych i uprawnień w JVM w przeciwieństwie do typu, takiego jak Opcjonalny, który ma sens ze względu na wyjątkowy charakter ich wystąpienia, w tym wczesne wsparcie dla priorytetowego leniwego ładowania procedury obsługi do pamięci, ponieważ nie trzeba go cały czas kręcić.
Nie musisz pisać wszędzie zerowych modułów obsługi, tylko tam, gdzie są one prawdopodobne, wszędzie tam, gdzie potencjalnie uzyskujesz dostęp do niewiarygodnej usługi danych, a ponieważ większość z nich dzieli się na kilka ogólnych wzorców, które można łatwo wyodrębnić, naprawdę potrzebujesz połączenie z przewodnikiem, z wyjątkiem rzadkich przypadków.
Sądzę więc, że moją odpowiedzią byłoby zawinięcie jej w sprawdzony wyjątek i obsłużenie go lub naprawienie niewiarygodnego kodu, jeśli jest on w twoich możliwościach.
źródło