Czy należy sprawdzić wartość zerową, jeśli nie spodziewa się wartości zerowej?

113

W zeszłym tygodniu mieliśmy gorącą dyskusję na temat obsługi wartości NULL w warstwie usług naszej aplikacji. Pytanie dotyczy kontekstu .NET, ale będzie tak samo w Javie i wielu innych technologiach.

Pytanie brzmiało: czy zawsze powinieneś sprawdzać wartości zerowe i sprawiać, by kod działał bez względu na wszystko, czy pozwolić, aby wyjątek pojawił się, gdy nieoczekiwanie otrzymano wartość zerową?

Z jednej strony sprawdzanie, czy null nie jest oczekiwany (tzn. Nie ma interfejsu użytkownika do obsługi), jest moim zdaniem równoznaczne z pisaniem bloku try z pustym zapisem. Po prostu ukrywasz błąd. Błąd może polegać na tym, że coś zmieniło się w kodzie, a wartość null jest teraz oczekiwaną wartością lub występuje inny błąd i niepoprawny identyfikator jest przekazywany do metody.

Z drugiej strony sprawdzanie wartości zerowych może być ogólnie dobrym nawykiem. Co więcej, jeśli pojawi się czek, aplikacja może kontynuować pracę, a tylko niewielka część funkcjonalności nie ma żadnego wpływu. Następnie klient może zgłosić mały błąd, taki jak „nie można usunąć komentarza” zamiast znacznie poważniejszego błędu, np. „Nie można otworzyć strony X”.

Jaką praktykę stosujesz i jakie są twoje argumenty za lub przeciw obu podejściom?

Aktualizacja:

Chcę dodać trochę szczegółów na temat naszej konkretnej sprawy. Pobieraliśmy niektóre obiekty z bazy danych i przetwarzaliśmy je (powiedzmy, stwórzmy kolekcję). Deweloper, który napisał kod, nie przewidział, że obiekt może mieć wartość NULL, więc nie uwzględnił żadnych kontroli, a gdy strona została załadowana, wystąpił błąd i cała strona się nie załadowała.

Oczywiście w tym przypadku powinna była nastąpić kontrola. Następnie wdaliśmy się w spór o to, czy każdy przetwarzany obiekt powinien zostać sprawdzony, nawet jeśli nie ma go brakować, i czy ewentualne przetwarzanie powinno zostać po cichu przerwane.

Hipotetyczną korzyścią byłoby kontynuowanie działania strony. Pomyśl o wynikach wyszukiwania na Stack Exchange w różnych grupach (użytkownicy, komentarze, pytania). Metoda może sprawdzić wartość NULL i przerwać przetwarzanie użytkowników (które z powodu błędu mają wartość NULL), ale zwraca sekcje „komentarze” i „pytania”. Strona będzie nadal działać, z wyjątkiem braku sekcji „Użytkownicy” (co jest błędem). Czy powinniśmy wcześnie ponieść porażkę i zepsuć całą stronę, czy kontynuować pracę i czekać, aż ktoś zauważy, że brakuje sekcji „Użytkownicy”?

Stilgar
źródło
62
Zasada Fox Mulder: Nikomu nie ufaj.
yannis
14
@YannisRizos: ta zasada jest dobra - jest tak dobra, że ​​twórcy Java i C # pozwalają środowisku wykonawczemu języka robić to automatycznie. Dlatego zwykle nie ma potrzeby wykonywania jawnych kontroli wartości null wszędzie w kodzie w tych językach.
Doc Brown
4
możliwy duplikat metody Czy metoda powinna zweryfikować swoje parametry?
Martin York
Zgadzam się z odpowiedzią tdammerów. Uważam, że sprawdzanie wartości null jest po prostu błędne, ponieważ nie masz rozsądnego sposobu, aby sobie z tym poradzić. Jednak w twoim scenariuszu możesz obsłużyć awarię sekcji, taką jak sprawdzanie poprawności użytkownika (lub uzyskiwanie komentarzy lub cokolwiek to może być) i wyświetlanie pola „och nie wystąpił błąd”. Osobiście rejestruję wszystkie wyjątki w mojej aplikacji i odfiltrowuję niektóre wyjątki, takie jak 404 i nieoczekiwane zamknięcie połączenia
2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Odpowiedzi:

105

Nie chodzi o to, czy powinieneś sprawdzić, nullczy środowisko wykonawcze zgłosi wyjątek; tak powinieneś zareagować na tak nieoczekiwaną sytuację.

Twoje opcje to:

  • Rzuć ogólny wyjątek ( NullReferenceException) i pozwól mu się rozwinąć; jeśli nie zrobisz tego nullsamodzielnie, dzieje się to automatycznie.
  • Rzuć niestandardowy wyjątek opisujący problem na wyższym poziomie; można to osiągnąć albo przez rzucenie nullczeku, albo przez złapanie NullReferenceExceptioni rzucenie bardziej szczegółowego wyjątku.
  • Odzyskaj, zastępując odpowiednią wartość domyślną.

Nie ma ogólnej zasady, która jest najlepszym rozwiązaniem. Osobiście powiedziałbym:

  • Ogólny wyjątek jest najlepszy, jeśli warunek błędu byłby oznaką poważnego błędu w kodzie, to znaczy, że wartość null nigdy nie powinna być dozwolona w pierwszej kolejności. Taki wyjątek może następnie przejść do samego logowania błędów, które skonfigurowałeś, aby ktoś został powiadomiony i naprawił błąd.
  • Domyślne rozwiązanie wartości jest dobre, jeśli dostarczenie półużytecznych danych wyjściowych jest ważniejsze niż poprawność, takich jak przeglądarka internetowa, która akceptuje technicznie niepoprawny kod HTML i dokłada wszelkich starań, aby renderować go rozsądnie.
  • W przeciwnym razie wybrałbym konkretny wyjątek i zająłbym się nim w odpowiednim miejscu.
tdammers
źródło
1
@Stilgar dla użytkownika końcowego nie ma różnicy. dla programisty określony wyjątek, taki jak „serwer bazy danych jest nieosiągalny” jest bardziej intuicyjny niż „wyjątek wskaźnika zerowego”
k3b
32
Niepowodzenie na początku i na wczesnym etapie oznacza, że ​​istnieje mniejsze ryzyko, że coś pójdzie bardziej subtelnie na linii, takie jak błędne uwierzytelnienie, uszkodzone dane utrwalone w pamięci, wyciek informacji i inne zabawne rzeczy.
Lars Viklund,
1
@Stilgar: Istnieją tutaj dwie obawy: bezobsługowe zachowanie i łatwość utrzymania. Podczas gdy w przypadku zachowania nienadzorowanego różnica między konkretnym wyjątkiem a ogólnym jest niewielka, w przypadku łatwości utrzymania może mieć znaczenie między „och, więc dlatego wybuchają” i wielogodzinny debugathlon.
tdammers
3
tdammers ma dokładnie rację. „Odwołanie do obiektu nie ustawione na instancję obiektu” jest jednym z najbardziej irytujących wyjątków, jakie widzę z największą częstotliwością. Wolałbym raczej zobaczyć konkretny wyjątek, niezależnie od tego, czy jest to ArgumentNullException z nazwą parametru, czy wyjątek niestandardowy „Brak rekordu Post dla użytkownika TK421”.
Sean Chase,
1
@ k3b Również dla użytkownika końcowego „serwer bazy danych jest nieosiągalny” jest bardzo pomocny. Przynajmniej dla każdego użytkownika końcowego, który ma trochę pojęcia o typowych problemach - może pomóc mu odkryć, że kabel jest luźny, router wymaga ponownego uruchomienia itp., Zamiast złościć się na błędne oprogramowanie.
Julia Hayward
58

IMHO próbuje obsłużyć wartości zerowe, których się nie spodziewasz, prowadzi do zbyt skomplikowanego kodu. Jeśli nie oczekujesz wartości zerowej, wyjaśnij to, rzucając ArgumentNullException. Czuję się naprawdę sfrustrowany, gdy ludzie sprawdzają, czy wartość jest pusta, a następnie próbuję napisać kod, który nie ma żadnego sensu. To samo dotyczy używania SingleOrDefault(lub jeszcze gorzej pobierania), gdy ktoś naprawdę się spodziewa, Singlei wielu innych przypadków, gdy ludzie boją się (naprawdę nie wiem, czego), aby jasno określić swoją logikę.

empi
źródło
Zamiast ArgumentNullExceptionnależy wykorzystywać umów kodu (chyba, że jeśli jest to kod źródłowy pre-2010, który nie obsługuje umów code).
Arseni Mourzenko
Zgadzam się z Tobą. Jeśli nie należy oczekiwać wartości zerowej, nie należy jej traktować w dziwny sposób, a jedynie zgłaszać.
Giorgio
9
jeśli dobrze przeczytam pytanie, wyrzucenie ArgumentNullExceptionliczenia liczy się jako „obsługa” wartości zerowej: zapobiega to późniejszemu wyjątkowi odwołania zerowego.
KutuluMike,
@MichaelEdenfield: Nie sądzę, że liczy się to jako prawdziwa obsługa według zadanego pytania (naszym celem nie jest sprawienie, by kod działał bez względu na wszystko ). Szczerze mówiąc, często zapominam napisać odpowiedni blok, który wyrzuci, jeśli wartość null zostanie przekazana. Uważam jednak, że zgłoszenie wyjątku NullArgumentException jest najlepszą praktyką, a najgorszą praktyką jest, moim zdaniem, pisanie kodu, który nie może obsłużyć wartości NULL, ale zamiast zgłaszania wyjątku zwraca np. Inny wynik NULL jako wynik metody (lub pusty ciąg znaków lub pusta kolekcja lub -1 lub pomija wykonanie metody, jeśli typ zwracany jest nieważny itp.).
empi
2
@Giorgio, ArgumentNullExceptionwyjaśnia, na czym polega problem i jak go naprawić. C # nie ma tendencji do używania „niezdefiniowany”. Jeśli dzwonisz coś w sposób nieokreślony, to sposób, w jaki dzwonisz, nie ma sensu. Wyjątkiem jest właściwy sposób na wskazanie tego. Teraz czasami robię metody analizy składni, które zwracają wartość null, jeśli int(na przykład) nie można przeanalizować. Ale jest to przypadek, w którym niemożliwy do rozdzielenia wynik jest uzasadnioną możliwością, a nie błędem użytkowania. Zobacz także ten artykuł .
Kyralessa
35

Zwyczaj sprawdzania nullmojego doświadczenia pochodzi od byłych programistów C lub C ++, w tych językach masz dużą szansę ukrycia poważnego błędu, gdy nie sprawdzam NULL. Jak wiadomo, w Javie lub C # rzeczy są różne, użycie wartości zerowej bez sprawdzania nullspowoduje wyjątek, więc błąd nie zostanie potajemnie ukryty, dopóki nie zignorujesz go po stronie wywołującej. Dlatego w większości przypadków jawne sprawdzanie nullnie ma większego sensu i bardziej komplikuje kod niż to konieczne. Dlatego nie uważam sprawdzania wartości zerowej za dobry nawyk w tych językach (przynajmniej nie ogólnie).

Oczywiście są wyjątki od tej zasady, oto te, o których mogę myśleć:

  • potrzebujesz lepszego, czytelnego dla człowieka komunikatu o błędzie, informującego użytkownika, w której części programu wystąpiło niepoprawne odwołanie zerowe. Zatem test na wartość NULL rzuca tylko inny wyjątek z innym tekstem błędu. Oczywiście żaden błąd nie zostanie zamaskowany w ten sposób.

  • chcesz, aby Twój program „zawodził wcześniej”. Na przykład chcesz sprawdzić wartość pustą parametru konstruktora, gdzie odwołanie do obiektu w przeciwnym razie byłoby przechowywane tylko w zmiennej składowej i użyte później.

  • możesz bezpiecznie poradzić sobie z sytuacją zerowej referencji, bez ryzyka kolejnych błędów i bez maskowania poważnego błędu.

  • chcesz całkowicie innego rodzaju sygnalizacji błędów (na przykład zwracania kodu błędu) w bieżącym kontekście

Doktor Brown
źródło
3
„chcesz uzyskać lepszy, czytelny dla człowieka komunikat o błędzie” - to moim zdaniem najlepszy powód, aby to zrobić. „Odwołanie do obiektu nie jest ustawione na instancję obiektu” lub „Wyjątek odwołania do wartości zerowej” nigdy nie jest tak pomocne jak „Nie utworzono instancji produktu”.
pdr
@pdr: Innym powodem do sprawdzenia jest upewnienie się, że zawsze jest wykrywany.
Giorgio
13
Być może „byli programiści C”. „Byli programiści C ++” tylko wtedy, gdy sami odzyskują programistów C. Jako nowoczesny programista C ++ byłbym bardzo podejrzliwy wobec kodu, który używa nagich wskaźników lub nierozważnej alokacji dynamicznej. W rzeczywistości pokusiłbym się o odwrócenie argumentu i stwierdzenie, że C ++ nie ma problemu opisanego przez OP, ponieważ jego zmienne nie mają dodatkowej specjalnej właściwości null-ness, którą posiadają Java i C #.
Kerrek SB
7
Twój pierwszy akapit to tylko połowa historii: tak, jeśli użyjesz pustego odwołania w C #, otrzymasz wyjątek, podczas gdy w C ++ otrzymasz niezdefiniowane zachowanie. Ale w dobrze napisanym języku C # utkniesz z o wiele bardziej zerowalnymi referencjami niż w dobrze napisanym języku C ++.
James McNellis
Im lepsza enkapsulacja, tym lepsza jest ta odpowiedź.
radarbob
15

Użyj asercji, aby przetestować i wskazać warunki wstępne / końcowe i niezmienniki dla swojego kodu.

Ułatwia to zrozumienie, czego oczekuje kod i czego można się spodziewać.

Aser, IMO, jest odpowiednim sprawdzianem, ponieważ jest:

  • wydajny (zwykle nieużywany w wydaniu),
  • krótkie (zwykle pojedyncza linia) i
  • wyczyść (naruszono warunek wstępny, jest to błąd programowania).

Myślę, że kod samo-dokumentujący jest ważny, dlatego sprawdzenie jest dobre. Defensywne, niezawodne programowanie ułatwia konserwację, czyli tam, gdzie zwykle spędza się najwięcej czasu.

Aktualizacja

Napisz swoje opracowanie. Zazwyczaj dobrze jest dać użytkownikowi końcowemu aplikację, która działa dobrze w przypadku błędów, tzn. Pokazuje jak najwięcej. Wytrzymałość jest dobra, ponieważ niebo wie tylko, co pęknie w krytycznym momencie.

Jednak programiści i testerzy muszą jak najszybciej zdawać sobie sprawę z błędów, więc prawdopodobnie potrzebujesz struktury rejestrowania z hakiem, która może wyświetlać ostrzeżenie dla użytkownika, że ​​wystąpił problem. Alert powinien być prawdopodobnie wyświetlany wszystkim użytkownikom, ale prawdopodobnie można go dostosować w różny sposób w zależności od środowiska wykonawczego.

Macke
źródło
twierdzenie, że nieobecność w wydaniu jest dla mnie dużym negatywem, wydanie to dokładny czas, w którym chcesz dodać dodatkowe informacje
jk.
1
Cóż, nie krępuj się, skorzystaj z funkcji warunkowego rzucania ^ h ^ h ^ h, która jest również aktywna w wersji, jeśli jej potrzebujesz. Dość często jeździłem.
Macke
7

Zawodzą wcześnie, często zawodzą. nulljest jedną z najlepszych nieoczekiwanych wartości, jakie możesz uzyskać, ponieważ możesz szybko zawieść, gdy tylko spróbujesz z niej skorzystać. Inne nieoczekiwane wartości nie są tak łatwe do wykrycia. Ponieważ nullautomatycznie kończy się niepowodzeniem za każdym razem, gdy próbujesz go użyć, powiedziałbym, że nie wymaga to wyraźnego sprawdzenia.

DeadMG
źródło
28
Mam nadzieję, że miałeś na myśli: porażka wcześnie, porażka szybko, nie często ...
empi
12
Problem polega na tym, że bez wyraźnych kontroli wartość null może czasami rozprzestrzeniać się dość daleko, zanim spowoduje wyjątek, a następnie bardzo trudno jest ustalić, skąd pochodzi.
Michael Borgwardt,
@MichaelBorgwardt Czy nie do tego służą ślady stosu?
R0MANARMY
6
@ R0MANARMY: ślady stosu nie pomagają, gdy wartość null zostanie zapisana w jakimś polu, a wyjątek NullPointerException zostanie zgłoszony znacznie później.
Michael Borgwardt,
@ R0MANARMY, nawet jeśli byłeś w stanie zaufać numerowi linii w śladzie stosu (w wielu przypadkach nie możesz), niektóre linie zawierają wiele zmiennych, które mogą być zerowe.
Ohad Schneider,
6
  • Sprawdzanie wartości zerowej powinno być twoją drugą naturą

  • Z Twojego interfejsu API będą korzystać inne osoby, a ich oczekiwania prawdopodobnie będą inne niż Twoje

  • Dokładne testy jednostkowe zwykle uwidaczniają potrzebę zerowej kontroli referencyjnej

  • Sprawdzanie wartości null nie oznacza instrukcji warunkowych. Jeśli obawiasz się, że sprawdzanie wartości NULL sprawia, że ​​kod jest mniej czytelny, możesz rozważyć kontrakty .NET.

  • Rozważ zainstalowanie ReSharper (lub podobnego), aby wyszukać w kodzie brak sprawdzania referencji zerowej

CodeART
źródło
Korzystanie z umów kodowych dla warunków wstępnych jest po prostu uwielbionymi instrukcjami warunkowymi.
CVn
Tak, zgadzam się, ale uważam również, że kod wygląda lepiej podczas korzystania z kontraktów, tzn. Poprawia się jego czytelność.
CodeART
4

Rozważę to pytanie z punktu widzenia semantyki, tj. Zadaję sobie pytanie, co reprezentuje wskaźnik NULL lub argument odwołania zerowego.

Jeśli funkcja lub metoda foo () ma argument x typu T, x może być obowiązkowe lub opcjonalne .

W C ++ możesz przekazać obowiązkowy argument jako

  • Wartość, np. Void foo (T x)
  • Odniesienie, np. Void foo (T & x).

W obu przypadkach nie ma problemu ze sprawdzaniem wskaźnika NULL. Tak więc, w wielu przypadkach nie ma potrzeby sprawdzania null pointer w ogóle do obowiązkowych argumentów.

Jeśli przekazujesz opcjonalny argument, możesz użyć wskaźnika i użyć wartości NULL, aby wskazać, że nie podano żadnej wartości:

void foo(T* x)

Alternatywą jest użycie inteligentnego wskaźnika, takiego jak

void foo(shared_ptr<T> x)

W takim przypadku prawdopodobnie chcesz sprawdzić wskaźnik w kodzie, ponieważ robi to różnicę w metodzie foo (), czy x zawiera wartość, czy nie ma jej wcale.

Trzecim i ostatnim przypadkiem jest użycie wskaźnika jako obowiązkowego argumentu. W takim przypadku wywołanie foo (x) za pomocą x == NULL jest błędem i musisz zdecydować, jak sobie z tym poradzić (tak samo jak w przypadku indeksu poza zakresem lub dowolnego niepoprawnego wejścia):

  1. Brak kontroli: jeśli występuje błąd, pozwól mu się zawiesić i mam nadzieję, że ten błąd pojawi się wystarczająco wcześnie (podczas testowania). Jeśli tak się nie stanie (jeśli nie powiedzie się wystarczająco wcześnie), miej nadzieję, że nie pojawi się w systemie produkcyjnym, ponieważ wrażenia użytkownika będą polegać na tym, że nastąpi awaria aplikacji.
  2. Sprawdź wszystkie argumenty na początku metody i zgłoś nieprawidłowe argumenty NULL, np. Wyrzuć wyjątek od foo () i pozwól, aby inna metoda go obsłużyła. Prawdopodobnie najlepszą rzeczą jest pokazanie użytkownikowi okna dialogowego z informacją, że aplikacja napotkała błąd wewnętrzny i zostanie zamknięta.

Oprócz lepszego sposobu na niepowodzenie (dostarczenie użytkownikowi więcej informacji) zaletą drugiego podejścia jest to, że istnieje większe prawdopodobieństwo znalezienia błędu: program zawiedzie za każdym razem, gdy metoda zostanie wywołana z niewłaściwymi argumentami, podczas gdy z w podejściu 1 błąd pojawi się tylko wtedy, gdy zostanie wykonana instrukcja dereferencyjna dla wskaźnika (np. jeśli wprowadzono prawą gałąź instrukcji if). Zatem podejście 2 oferuje silniejszą formę „wczesnego niepowodzenia”.

W Javie masz mniej możliwości wyboru, ponieważ wszystkie obiekty są przekazywane za pomocą referencji, które zawsze mogą mieć wartość NULL. Ponownie, jeśli wartość null reprezentuje wartość NONE opcjonalnego argumentu, wówczas sprawdzenie wartości null (prawdopodobnie) będzie częścią logiki implementacji.

Jeśli wartość null jest nieprawidłowym wejściem, oznacza to, że wystąpił błąd i zastosowałbym podobne uwagi jak w przypadku wskaźników C ++. Ponieważ w Javie można wychwytywać wyjątki zerowego wskaźnika, powyższe podejście 1 jest równoważne podejściu 2, jeśli metoda foo () odrzuci wszystkie argumenty wejściowe we wszystkich ścieżkach wykonania (co prawdopodobnie występuje bardzo często w przypadku małych metod).

Zreasumowanie

  • Zarówno w C ++, jak i Javie sprawdzanie, czy opcjonalne argumenty są puste, jest częścią logiki programu.
  • W C ++ zawsze sprawdzałbym obowiązkowe argumenty, aby upewnić się, że zgłoszony został odpowiedni wyjątek, aby program mógł go obsłużyć w solidny sposób.
  • W Javie (lub C #) sprawdziłbym obowiązkowe argumenty, jeśli istnieją ścieżki wykonania, które nie zostaną rzucone, aby uzyskać silniejszą formę „wczesnego niepowodzenia”.

EDYTOWAĆ

Dzięki Stilgar za więcej szczegółów.

W twoim przypadku wydaje się, że masz wartość null jako wynik metody. Ponownie myślę, że powinieneś najpierw wyjaśnić (i naprawić) semantykę swoich metod, zanim podejmiesz decyzję.

Tak więc masz metodę m1 () wywołującą metodę m2 (), a m2 () zwraca odwołanie (w Javie) lub wskaźnik (w C ++) do jakiegoś obiektu.

Jaka jest semantyka m2 ()? Czy m2 () zawsze powinno zwracać wynik różny od zera? W takim przypadku wynikiem zerowym jest błąd wewnętrzny (w m2 ()). Jeśli sprawdzisz wartość zwracaną w m1 (), możesz mieć bardziej niezawodną obsługę błędów. Jeśli tego nie zrobisz, prawdopodobnie wcześniej czy później będziesz mieć wyjątek. Może nie. Mam nadzieję, że wyjątek zostanie zgłoszony podczas testowania, a nie po wdrożeniu. Pytanie : jeśli m2 () nigdy nie powinien zwracać wartości null, dlaczego zwraca wartość null? Może zamiast tego powinien rzucić wyjątek? m2 () jest prawdopodobnie wadliwy.

Alternatywą jest to, że zwracanie wartości null jest częścią semantyki metody m2 (), tzn. Ma opcjonalny wynik, który powinien zostać udokumentowany w dokumentacji Javadoc tej metody. W takim przypadku można mieć wartość zerową. Metoda m1 () powinna sprawdzić ją jak każdą inną wartość: tutaj nie ma błędu, ale prawdopodobnie sprawdzenie, czy wynik jest pusty, jest tylko częścią logiki programu.

Giorgio
źródło
W naszym przypadku to nie argument był zerowy. Wywołaliśmy w bazie danych wezwanie do pojawienia się czegoś, co miało być obecne, ale w praktyce nie było. Pytanie brzmi, czy powinniśmy sprawdzić, czy każdy obiekt istnieje, czy też powinniśmy sprawdzić tylko wtedy, gdy spodziewamy się, że go brakuje.
Stilgar
Tak więc wynik zwrócony przez metodę był odwołaniem do obiektu, a wartość null była wartością użytą do przedstawienia wyniku: NIE ZNALEZIONO. Czy dobrze rozumiem?
Giorgio
Zaktualizowałem pytanie o dodatkowe szczegóły.
Stilgar
3

Moje ogólne podejście polega na tym, aby nigdy nie testować warunku błędu, którego nie znasz . Wtedy pytanie, na które należy odpowiedzieć w każdym konkretnym przypadku, brzmi: czy możesz zrobić coś rozsądnego w obecności nieoczekiwanej nullwartości?

Jeśli możesz bezpiecznie kontynuować, zastępując inną wartość (powiedzmy pustą kolekcją lub wartością domyślną lub instancją), to zrób to wszystko. @ Przykład Shahbaza z World of Warcraft dotyczący zamiany jednego obrazu na inny należy do tej kategorii; sam obraz jest prawdopodobnie jedynie ozdobą i nie ma wpływu funkcjonalnego . (W wersji do debugowania użyłbym krzyczącego koloru, aby zwrócić uwagę na fakt, że nastąpiło zastąpienie, ale zależy to w dużej mierze od charakteru aplikacji). Zapisz jednak szczegółowe informacje o błędzie, abyś wiedział, że występuje; w przeciwnym razie ukryjesz błąd, o którym prawdopodobnie chcesz wiedzieć. Ukrywanie go przed użytkownikiem to jedno (ponownie, zakładając, że przetwarzanie może być kontynuowane bezpiecznie); ukrywanie go przed deweloperem to zupełnie inna sprawa.

Jeśli nie możesz bezpiecznie kontynuować w obecności wartości zerowej, to nie powiedz z rozsądnym błędem; ogólnie wolę ponieść porażkę jak najszybciej, gdy jest oczywiste, że operacja nie może się powieść, co oznacza sprawdzenie warunków wstępnych. Są chwile, kiedy nie chcesz od razu zawieść, ale odłóż niepowodzenie na tak długo, jak to możliwe; ta uwaga pojawia się na przykład w aplikacjach związanych z kryptografią, w których ataki z boku kanału mogłyby w innym przypadku ujawnić informacje, o których nie chcesz wiedzieć. Na koniec pozwól, aby błąd pojawił się na ekranie do jakiejś ogólnej procedury obsługi błędów, która z kolei rejestruje istotne szczegóły i wyświetla użytkownikowi miły (jakkolwiek miły) komunikat o błędzie.

Warto również zauważyć, że poprawna odpowiedź może się bardzo różnić między kompilacjami testowania / kontroli jakości / debugowania, a kompilacjami produkcji / wydania. W kompilacjach do debugowania wybrałbym wczesne i trudne awarie z bardzo szczegółowymi komunikatami o błędach, aby całkowicie pokazać, że występuje problem i pozwolić sekcji zwłok ustalić, co należy zrobić, aby to naprawić. Kompilacje produkcyjne, w obliczu błędów, które można bezpiecznie odzyskać , prawdopodobnie powinny sprzyjać odzyskiwaniu.

CVn
źródło
Oczywiście w łańcuchu znajduje się ogólna procedura obsługi błędów. Problem z logami polega na tym, że może nie być nikogo, kto by je sprawdzał przez bardzo długi czas.
Stilgar
@Stilgar, jeśli nie ma nikogo, kto mógłby sprawdzić dzienniki, to nie jest to problem z istnieniem lub brakiem zerowych kontroli.
CVn
2
Tam jest problem. Użytkownicy końcowi mogą nie zauważyć, że system nie działa poprawnie przez długi czas, jeśli istnieją kontrole zerowe, które po prostu ukrywają błąd i zapisują go w dziennikach.
Stilgar
@Stilgar, zależy to całkowicie od charakteru błędu. Jak powiedziałem w poście, tylko jeśli możesz kontynuować bezpiecznie, nieoczekiwane zerowanie powinno zostać zastąpione / zignorowane. Używanie jednego obrazu zamiast drugiego w kompilacji wydania (w kompilacjach debugowania kończy się niepowodzeniem tak wcześnie i tak ciężko, jak to możliwe, aby problem stał się oczywisty) to zupełnie inna bestia niż na przykład zwracanie nieprawidłowych wyników do obliczeń. (Edytowana odpowiedź, aby to uwzględnić.)
CVn
1
Szybko i wcześnie kończyłem się niepowodzeniem, nawet w produkcji, chyba że możesz jakoś zalogować się i zgłosić błąd (bez zbytniego polegania na doświadczonych użytkownikach). Ukrywanie wdrożonych błędów przed użytkownikami to jedno - ukrywanie ich przed sobą jest niebezpieczne. Również umożliwienie użytkownikowi życia z subtelnymi błędami może podważyć zaufanie do Twojej aplikacji. Czasami lepiej jest ugryźć kulę i powiadomić ich, że wystąpił błąd i że szybko go naprawiłeś.
Merlyn Morgan-Graham
2

Oczywiście NULLnie należy się tego spodziewać , ale zawsze pojawiają się błędy!

Uważam, że powinieneś sprawdzić, NULLczy się tego nie spodziewasz. Pozwól, że podam przykłady (chociaż nie są w Javie).

  • W World of Warcraft z powodu błędu obraz jednego z programistów pojawił się na kostce dla wielu obiektów. Wyobraź sobie, że gdyby silnik graficzny nie oczekiwał NULL wskaźników, nastąpiłby awaria, podczas gdy zostałby zmieniony w niezbyt śmiertelne (nawet zabawne) doświadczenie dla użytkownika. Przynajmniej nie stracisz nieoczekiwanie połączenia lub któregoś z punktów zapisu.

    Jest to przykład, w którym sprawdzenie wartości NULL zapobiegło awarii i sprawa została po cichu załatwiona.

  • Wyobraź sobie program bankomatowy. Interfejs wykonuje pewne kontrole i wysyła dane wejściowe do podstawowej części w celu wykonania przelewów pieniężnych. Jeśli rdzeń oczekuje, że nie zostanie odebrany NULL, błąd w interfejsie może spowodować problem w transakcji, być może część pieniędzy w środku zostanie zgubiona (lub, co gorsza, zduplikowana).

    Przez „problem” rozumiem awarię (jak w przypadku awarii (powiedzmy, że program jest napisany w C ++), a nie wyjątek wskaźnika zerowego). W takim przypadku transakcja musi zostać wycofana, jeśli napotka NULL (co oczywiście nie byłoby możliwe, gdybyś nie sprawdził zmiennych pod kątem NULL!)

Jestem pewien, że masz pomysł.

Sprawdzanie poprawności argumentów funkcji nie zaśmieca kodu, ponieważ jest to kilka sprawdzeń u góry funkcji (nie rozmieszczonych w środku) i znacznie zwiększa niezawodność.

Jeśli musisz wybrać opcję „program informuje użytkownika, że ​​nie powiodło się, z wdziękiem wyłącza lub przywraca spójny stan, prawdopodobnie tworząc dziennik błędów” i „Naruszenie dostępu”, czy nie wybrałbyś pierwszego? Oznacza to, że każda funkcja powinna być przygotowana na wywołanie przez błędny kod, zamiast oczekiwać, że wszystko jest prawidłowe, po prostu dlatego, że błędy zawsze istnieją.

Shahbaz
źródło
Drugi przykład obala twój punkt widzenia. W przypadku transakcji bankowej, jeśli coś pójdzie nie tak, transakcja powinna zostać przerwana. Właśnie w przypadku pokrycia błędu pieniądze mogą zostać utracone lub zduplikowane. Sprawdzanie wartości null byłoby jak: „Klient wysyła pieniądze null ... no cóż, ja po prostu wyślę 10 $ (równowartość obrazu programisty)”
Stilgar
@Stilgar, wręcz przeciwnie! Sprawdzanie NULL byłoby przerywane z komunikatem. Brak sprawdzania NULL byłby awarią . Teraz awaria na początku transakcji może nie być zła, ale w środku może być katastrofalna. (Nie powiedziałem, że przelew bankowy powinien poradzić sobie z błędem jak w grze! Sam fakt, że powinien go obsłużyć)
Shahbaz
2
Chodzi o to, że „transakcji” nie można dokończyć do połowy :) Jeśli wystąpi błąd, zostanie on wycofany.
Stilgar
To okropna rada dla wszystkiego, co wymaga tak naprawdę semantyki transakcyjnej. Powinieneś sprawić, aby wszystkie twoje transakcje były atomowe i idempotentne, w przeciwnym razie każdy błąd, który wyeliminujesz, gwarantuje utratę lub zduplikowanie zasobów (pieniędzy). Jeśli masz do czynienia z operacjami nieatomowymi lub niebędącymi idempotentnymi, powinieneś bardzo ostrożnie owinąć je warstwami, które gwarantują zachowanie atomowe i idempotentne (nawet jeśli musisz sam je napisać). Zawsze myśl: co się stanie, jeśli meteor trafi na serwer sieciowy w momencie, gdy transakcja się dzieje?
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, zrobiłem to. Nadzieja, która usuwa zamieszanie.
Shahbaz
2

Jak wskazały inne odpowiedzi Jeśli nie oczekujesz NULL, wyjaśnij to rzucając ArgumentNullException. Moim zdaniem, kiedy debugujesz projekt , pomaga to szybciej wykryć błędy w logice twojego programu.

Będziesz więc wypuszczać swoje oprogramowanie, jeśli przywrócisz te NullRefrenceskontrole, niczego nie przeoczysz w logice oprogramowania, po prostu upewni się, że poważny błąd nie wyskoczy.

Inną kwestią jest to, że jeśli NULLsprawdzanie parametrów funkcji, to możesz zdecydować, czy funkcja jest właściwym miejscem do zrobienia tego, czy nie; jeśli nie, znajdź wszystkie odwołania do funkcji, a następnie przed przekazaniem parametru do funkcji przejrzyj prawdopodobne scenariusze, które mogą prowadzić do pustego odwołania.

Ahmad
źródło
1

Każdy nullw twoim systemie czeka tykająca bomba zegarowa. Sprawdź nullna granicach, na których Twój kod wchodzi w interakcję z kodem, którego nie kontrolujesz, i zabraniaj używania nullwłasnego kodu. To eliminuje problem bez naiwnego ufania obcemu kodowi. Zamiast tego użyj wyjątków lub jakiegoś rodzaju Maybe/ Nothingtype.

Doval
źródło
0

Chociaż wyjątek wskaźnika zerowego zostanie ostatecznie zgłoszony, często będzie wyrzucony daleko od miejsca, w którym uzyskałeś wskaźnik zerowy. Zrób sobie przysługę i skróć czas potrzebny na naprawienie błędu, rejestrując przynajmniej fakt, że jak najszybciej uzyskasz wskaźnik zerowy.

Nawet jeśli nie wiesz, co teraz zrobić, rejestrowanie wydarzenia pozwoli Ci zaoszczędzić czas. I może facet, który dostanie bilet, będzie mógł wykorzystać zaoszczędzony czas, aby wymyślić właściwą odpowiedź.

Jon Strayer
źródło
-1

Ponieważ, Fail early, fail fastjak stwierdził @DeadMG (@empi), nie jest pssible, ponieważ aplikacja internetowa musi dobrze działać w przypadku błędów, należy egzekwować regułę

bez wyjątku łapanie bez logowania

więc jako programiści jesteście świadomi potencjalnych problemów, przeglądając dzienniki.

Jeśli korzystasz ze środowiska rejestrowania, takiego jak log4net lub log4j, możesz skonfigurować dodatkowe specjalne dane wyjściowe rejestrowania (inaczej appender), które wysyłają ci błędy i błędy krytyczne. więc bądź na bieżąco .

[aktualizacja]

jeśli użytkownik strony nie będzie wiedział o błędzie, możesz skonfigurować program dołączający, który będzie wysyłał Ci wiadomości o błędach i błędach, abyś był na bieżąco informowany.

jeśli to jest w porządku, że użytkownik strony widzi tajemnicze błędy, możesz skonfigurować program dołączający, który umieszcza dziennik błędów przetwarzania strony na końcu strony.

Bez względu na to, jak korzystasz z rejestrowania, jeśli połkniesz wyjątek, program kontynuuje pracę z danymi, które mają sens, a połykanie nie jest już cichsze.

k3b
źródło
1
Pytanie brzmi: co dzieje się ze stroną?
Stilgar
Skąd wiemy, że dane mają sens, a system jest w spójnym stanie. W końcu błąd był nieoczekiwany, więc nie wiemy, jaki był jego wpływ.
Stilgar
„Od wczesnego niepowodzenia, szybkie niepowodzenie, jak stwierdził @DeadMG (@empi), nie jest pssible, ponieważ aplikacja internetowa musi dobrze działać w przypadku błędów, należy egzekwować regułę”: Zawsze można było złapać wszystkie wyjątki (catch (Throwable t)) i przekieruj na stronę błędu. Czy to nie byłoby możliwe?
Giorgio
-1

Istnieją już dobre odpowiedzi na to pytanie, może być jednym z nich: wolę twierdzenia w moim kodzie. Jeśli Twój kod może działać tylko z poprawnymi obiektami, ale z nutą o wartości null, powinieneś potwierdzić wartość null.

Twierdzenia w takiej sytuacji pozwoliłyby na niepowodzenie testów jednostkowych i / lub integracyjnych z dokładnym komunikatem, dzięki czemu można dość szybko ustalić problem przed rozpoczęciem produkcji. Jeśli taki błąd prześlizgnie się przez testy i przejdzie do produkcji (gdzie twierdzenia należy dezaktywować), otrzymasz NpEx i poważny błąd, ale jest lepszy niż jakiś drobny błąd, który jest znacznie bardziej niewyraźny i wyskakuje gdzie indziej w kod, a jego naprawienie byłoby droższe.

Co więcej, jeśli ktoś musi pracować z asercjami kodu, mówi mu o twoich założeniach, które podjąłeś podczas projektowania / pisania kodu (w tym przypadku: Hej, ten obiekt musi zostać przedstawiony!), Co ułatwia utrzymanie.

Otrzymać
źródło
Czy mógłbyś napisać coś do głosowania w dół? Byłbym wdzięczny za dyskusję. Thanx
GeT
Nie oddałem głosu i zgadzam się z pańskim ogólnym pomysłem. Język wymaga jednak trochę pracy. Twoje twierdzenia określają umowę interfejsu API, a jeśli te umowy zostaną naruszone, szybko się nie powiedziesz / szybko zawiedziesz. Zrobienie tego na skórze aplikacji poinformuje użytkowników Twojego kodu, że zrobili coś złego w kodzie. Jeśli zobaczą ślady stosu w zakamarkach twojego kodu, prawdopodobnie pomyślą, że Twój kod jest wadliwy - możesz również tak myśleć, dopóki nie dostaniesz solidnego repro i go zdebuguje.
Merlyn Morgan-Graham
-1

Zwykle sprawdzam, czy występują wartości null, ale „radzę sobie” z nieoczekiwanymi wartościami null, zgłaszając wyjątek, który podaje nieco więcej szczegółów na temat dokładnego miejsca wystąpienia wartości null.

Edycja: Jeśli otrzymasz zero, gdy nie spodziewasz się, że coś jest nie tak - program powinien umrzeć.

Loren Pechtel
źródło
-1

To zależy od implementacji wyjątków w języku. Wyjątki pozwalają podjąć pewne działania, aby zapobiec uszkodzeniu danych lub czysto zwolnić zasoby w przypadku, gdy system wejdzie w nieoczekiwany stan lub stan. Różni się to od obsługi błędów, które są warunkiem, którego można się spodziewać. Moja filozofia jest taka, że ​​powinieneś mieć jak najmniej obsługi wyjątków. To jest hałas. Czy Twoja metoda może zrobić coś sensownego, obsługując wyjątek? Czy to da się odzyskać? Czy to może naprawić lub zapobiec dalszym szkodom? Jeśli zamierzasz złapać wyjątek, a następnie powrócić z metody lub zawieść w inny nieszkodliwy sposób, naprawdę nie było sensu łapać wyjątku. Dodalibyście tylko hałas i złożoność do swojej metody, a być może ukryliście źródło błędu w swoim projekcie. W tym przypadku pozwoliłbym, aby usterka wybuchła i została złapana przez zewnętrzną pętlę. Jeśli możesz zrobić coś takiego, jak naprawić obiekt, oznaczyć go jako uszkodzony lub zwolnić niektóre krytyczne zasoby, takie jak plik blokady lub przez czyste zamknięcie gniazda, jest to dobre wykorzystanie wyjątków. Jeśli faktycznie oczekujesz, że NULL będzie często pojawiał się jako poprawny przypadek zbocza, powinieneś poradzić sobie z tym w normalnym przepływie logicznym za pomocą instrukcji if-the-else lub instrukcji case-switch lub cokolwiek innego, bez obsługi wyjątków. Na przykład pole wyłączone w formularzu może być ustawione na NULL, które jest inne niż pusty ciąg reprezentujący pole pozostawione puste. Jest to obszar, w którym należy kierować się zdrowym rozsądkiem i zdrowym rozsądkiem. Nigdy nie słyszałem o dobrych, solidnych regułach dotyczących radzenia sobie z tym problemem w każdej sytuacji. Jeśli możesz zrobić coś takiego, jak naprawić obiekt, oznaczyć go jako uszkodzony lub zwolnić niektóre krytyczne zasoby, takie jak plik blokady lub przez czyste zamknięcie gniazda, jest to dobre wykorzystanie wyjątków. Jeśli faktycznie oczekujesz, że NULL będzie często pojawiał się jako poprawny przypadek zbocza, powinieneś poradzić sobie z tym w normalnym przepływie logicznym za pomocą instrukcji if-the-else lub instrukcji case-switch lub cokolwiek innego, bez obsługi wyjątków. Na przykład pole wyłączone w formularzu może być ustawione na NULL, które jest inne niż pusty ciąg reprezentujący pole pozostawione puste. Jest to obszar, w którym należy kierować się zdrowym rozsądkiem i zdrowym rozsądkiem. Nigdy nie słyszałem o dobrych, solidnych regułach dotyczących radzenia sobie z tym problemem w każdej sytuacji. Jeśli możesz zrobić coś takiego, jak naprawić obiekt, oznaczyć go jako uszkodzony lub zwolnić niektóre krytyczne zasoby, takie jak plik blokady lub przez czyste zamknięcie gniazda, jest to dobre wykorzystanie wyjątków. Jeśli faktycznie oczekujesz, że NULL będzie często pojawiał się jako poprawny przypadek zbocza, powinieneś poradzić sobie z tym w normalnym przepływie logicznym za pomocą instrukcji if-the-else lub instrukcji case-switch lub cokolwiek innego, bez obsługi wyjątków. Na przykład pole wyłączone w formularzu może być ustawione na NULL, które jest inne niż pusty ciąg reprezentujący pole pozostawione puste. Jest to obszar, w którym należy kierować się zdrowym rozsądkiem i zdrowym rozsądkiem. Nigdy nie słyszałem o dobrych, solidnych regułach dotyczących radzenia sobie z tym problemem w każdej sytuacji.

Java nie działa w implementacji obsługi wyjątków z „sprawdzonymi wyjątkami”, gdzie każdy możliwy wyjątek, który może zostać zgłoszony przez obiekty użyte w metodzie, musi zostać wychwycony lub zadeklarowany w klauzuli „throws” metody. Jest to przeciwieństwo C ++ i Python, w których możesz wybrać obsługę wyjątków według własnego uznania. W Javie, jeśli zmodyfikujesz treść metody, aby uwzględnić wywołanie, które może zgłosić wyjątek, staniesz przed wyborem dodania jawnej obsługi wyjątku, którego nie obchodzi, powodując w ten sposób szum i złożoność kodu, lub musisz dodaj ten wyjątek do klauzuli „throws” modyfikowanej metody, która nie tylko dodaje szum i bałagan, ale zmienia sygnaturę metody. Następnie musisz przejść do kodu modyfikacji, gdziekolwiek używana jest metoda, aby obsłużyć nowy wyjątek, który twoja metoda może teraz zgłosić, lub dodać wyjątek do klauzuli „rzuca” tych metod, wywołując w ten sposób efekt domina zmian kodu. „Złap lub określ wymaganie” musi być jednym z najbardziej denerwujących aspektów Java. Wyjątkiem wyjątku dla RuntimeExceptions i oficjalnej racjonalizacji Java dla tego błędu projektowego jest lame.

Ten ostatni akapit miał niewiele wspólnego z odpowiedzią na twoje pytanie. Nienawidzę Java.

- Noah

Noah Spurrier
źródło
ten post jest raczej trudny do odczytania (ściana tekstu). Czy mógłbyś edytować go w lepszym kształcie?
komar