Powiedzmy, że mam metodę:
public void DoSomething(ISomeInterface someObject)
{
if(someObject == null) throw new ArgumentNullException("someObject");
someObject.DoThisOrThat();
}
Nauczono mnie wierzyć, że rzucanie ArgumentNullException
jest „poprawne”, ale błąd „Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu” oznacza, że mam błąd.
Czemu?
Wiem, że jeśli buforowałem odwołanie do niego someObject
i korzystałem z niego później, lepiej sprawdzić, czy jest nieważny, gdy został przekazany, i zakończyć się wcześnie. Jeśli jednak odsyłam to do następnej linii, dlaczego powinniśmy to sprawdzić? W ten czy inny sposób wygeneruje wyjątek.
Edytuj :
Właśnie przyszło mi do głowy ... czy strach przed wyzerowanym zeriem pochodzi z języka takiego jak C ++, który cię nie sprawdza (tj. Po prostu próbuje wykonać jakąś metodę w miejscu zero pamięci + przesunięcie metody)?
Odpowiedzi:
Podejrzewam, że jeśli natychmiast odsuwasz zmienną, możesz debatować w obie strony, ale nadal wolałbym wyjątek ArgumentNullException.
Jest o wiele bardziej wyraźne o tym, co się dzieje. Wyjątek zawiera nazwę zmiennej, która była null, podczas gdy wyjątek NullReferenceException nie. Szczególnie na poziomie API wyjątek ArgumentNullException wyjaśnia, że niektóre osoby wywołujące nie dotrzymały kontraktu metody, a niepowodzenie niekoniecznie było przypadkowym błędem lub głębszym problemem (choć może tak być nadal). Powinieneś wcześnie zawieść.
Ponadto, co są bardziej skłonni opiekunowie później? Dodaj ArgumentNullException, jeśli dodają kod przed pierwszym dereferencją, lub po prostu dodaj kod, a następnie zezwalają na zgłoszenie wyjątku NullReferenceException?
źródło
Załóżmy, że wywołuję metodę
M(x)
, którą napisałeś. Przechodzę na zero. Otrzymuję ArgumentNullException z nazwą ustawioną na „x”. Ten wyjątek jednoznacznie oznacza, że mam błąd; Nie powinienem był przyjmować wartości null dla x.Załóżmy, że nazywam napisaną przez Ciebie metodę M. Przechodzę na zero. Dostaję wyjątek zerowy deref. Skąd, do cholery, mam wiedzieć, czy mam błąd, czy masz błąd lub biblioteka innej firmy, do której zadzwoniłeś w moim imieniu, ma błąd? Nic nie wiem na temat tego, czy muszę naprawić mój kod, czy zadzwonić, aby powiedzieć, aby naprawić Twój kod.
Oba wyjątki wskazują na błędy; żaden z nich nigdy nie powinien zostać wrzucony do kodu produkcyjnego. Pytanie brzmi, który wyjątek informuje, kto ma błąd lepiej dla osoby wykonującej debugowanie? Wyjątek zerowy deref mówi ci prawie nic; wyjątek argument zerowy wiele mówi. Bądź miły dla swoich użytkowników; zgłaszać wyjątki, które pozwalają im uczyć się na błędach.
źródło
"neither should ever be thrown in production code"
Jakie inne typy kodu / sytuacji byłyby używane? Czy powinny być one skompilowane jakDebug.Assert
? Czy masz na myśli, że nie wyrzucasz ich z interfejsu API?throw new ArgumentNullException
w kodzie produkcyjnym, a ona nigdy nie powinna pracować poza przypadku testowego . Wyjątek nigdy nie powinien zostać zgłoszony, ponieważ dzwoniący nigdy nie powinien mieć tego błędu.Chodzi o to, że Twój kod powinien zrobić coś znaczącego.
A
NullReferenceException
nie jest szczególnie znaczące, ponieważ może znaczyć wszystko. Nie patrząc na to, gdzie został zgłoszony wyjątek (a kod może być dla mnie niedostępny), nie mogę zrozumieć, co poszło nie tak.Ma
ArgumentNullException
to znaczenie, ponieważ mówi mi: operacja nie powiodła się, ponieważ argumentsomeObject
byłnull
. Nie muszę patrzeć na twój kod, żeby to rozgryźć.Również zgłoszenie tego wyjątku oznacza, że jawnie sobie radzisz
null
, nawet jeśli jedynym sposobem, aby to zrobić, jest powiedzenie, że nie możesz sobie z tym poradzić, a po prostu pozwolenie na awarię kodu może potencjalnie oznaczać, że możesz być w stanie obsłużyć wartość null , ale zapomniałem zaimplementować sprawę.Wyjątkiem jest przekazywanie informacji w przypadku awarii, którą można obsłużyć w czasie wykonywania w celu odzyskania po awarii lub w czasie debugowania, aby lepiej zrozumieć, co poszło nie tak.
źródło
Uważam, że jedyną zaletą jest to, że możesz zapewnić lepszą diagnostykę w wyjątku. To znaczy, że wywołujący funkcję przechodzi na zero, a jeśli pozwolisz, aby dereferencja go rzuciła, nie byłbyś pewien, czy wywołujący przekazuje nieprawidłowe dane, czy też w samej funkcji jest błąd.
Zrobiłbym to, jeśli ktoś inny wywoła funkcję, aby ułatwić jej użycie (debugować, jeśli coś pójdzie nie tak) i nie robić tego w moim wewnętrznym kodzie, ponieważ środowisko wykonawcze i tak to sprawdzi.
źródło
Masz błąd, jeśli kod nie działa zgodnie z przeznaczeniem. System
NullReferenceException
jest rzucany przez system, a ten fakt naprawdę powoduje więcej błędów niż funkcji. Nie tylko dlatego, że nie zdefiniowałeś konkretnego wyjątku do obsłużenia. Także dlatego, że programista czytający Twój kod może założyć, że nie uwzględniłeś sytuacji.Z podobnych powodów, że
ApplicationException
należy do Twojej aplikacji, a ogólneException
do systemu operacyjnego. Rodzaj zgłaszanego wyjątku wskazuje, skąd pochodzi wyjątek.Jeśli uwzględniłeś wartość NULL, lepiej radzisz sobie z nią z wdziękiem, zgłaszając określony wyjątek lub jeśli jest to akceptowalny wkład, więc nie rzucaj wyjątku, ponieważ są one drogie. Obsługuj go, wykonując operacje z dobrymi ustawieniami domyślnymi lub bez operacji (np. Nie jest wymagana aktualizacja).
Wreszcie, nie zaniedbuj zdolności dzwoniącego do poradzenia sobie z sytuacją. Dając im bardziej szczegółowy wyjątek,
ArgumentException
mogą skonstruować swoje try / catch w taki sposób, aby poradzić sobie z tym błędem przed bardziej ogólnym,NullReferenceException
który może być spowodowany dowolną liczbą innych przyczyn, które występują gdzie indziej, takich jak niepowodzenie inicjalizacji zmiennej konstruktora.źródło
Sprawdzenie wyjaśnia, co się dzieje, ale prawdziwy problem pochodzi z kodu takiego jak (wymyślony) przykład:
Tutaj zaangażowany jest łańcuch połączeń i bez sprawdzania wartości zerowej widzisz błąd tylko w innym obiekcie, a nie w miejscu, w którym problem po raz pierwszy się ujawnił - co natychmiast oznacza marnowanie czasu na debugowanie lub interpretowanie stosów połączeń.
Zasadniczo lepiej jest zachować spójność z tego rodzaju rzeczami i zawsze dodawać kontrolę zerową - co ułatwia wykrycie braku, a nie wybieranie i wybieranie poszczególnych przypadków (zakładając, że wiesz, że wartość null to zawsze nieważne).
źródło
Innym powodem do rozważenia jest to, co się stanie, jeśli pewne przetwarzanie nastąpi przed użyciem obiektu zerowego?
Załóżmy, że masz plik danych powiązanych identyfikatorów firmy (Cid) i identyfikatorów osób (Pid). Jest zapisywany jako strumień par liczb:
Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid
I ta funkcja VB zapisuje rekordy do pliku:
Jeśli
Person
jest odwołaniem zerowym, liniaFile.Write(Person.ID)
zgłosi wyjątek. Oprogramowanie może wychwycić wyjątek i odzyskać, ale pozostawia to problem. Identyfikator firmy został napisany bez powiązanego identyfikatora osoby. Jeśli faktem, kiedy następnym razem wywołasz tę funkcję, wstawisz identyfikator firmy w miejscu, w którym oczekiwany był identyfikator poprzedniej osoby. Oprócz tego, że ten rekord jest niepoprawny, każdy kolejny rekord będzie miał identyfikator osoby, a następnie identyfikator firmy, co jest niewłaściwe.Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid
Sprawdzając poprawność parametrów na początku funkcji, zapobiega się przetwarzaniu, gdy gwarantowana jest niepowodzenie metody.
źródło