Jak pomaga zgłoszenie wyjątku ArgumentNullException?

27

Powiedzmy, że mam metodę:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

Nauczono mnie wierzyć, że rzucanie ArgumentNullExceptionjest „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 someObjecti 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)?

Scott Whitlock
źródło
Wyraźne określenie wyjątku pomaga potwierdzić, że istnieje możliwość wystąpienia błędu, który można odnotować bezpośrednio w dokumentacji.
zzzzBov

Odpowiedzi:

34

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?

Matt H.
źródło
W związku z tym, jeśli byłaby to metoda niepubliczna lub metoda publiczna w klasie niepublicznej, nie uważasz, że istnieje dobry powód do sprawdzenia wartości zerowej?
Scott Whitlock,
Zazwyczaj nie dodam tych testów do metod prywatnych, ale nadal mogę dodawać je do publicznych metod klas prywatnych, aby zachować spójność. W przypadku metod prywatnych zwykle zakładam, że argumenty są sprawdzane wcześniej na pewnym poziomie wywołania publicznego.
Matt H
54

Nauczono mnie wierzyć, że zgłoszenie wyjątku ArgumentNullException jest „prawidłowe”, ale błąd „Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu” oznacza, że ​​mam błąd. Czemu?

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.

Eric Lippert
źródło
Nie jestem pewien, czy rozumiem "neither should ever be thrown in production code"Jakie inne typy kodu / sytuacji byłyby używane? Czy powinny być one skompilowane jak Debug.Assert? Czy masz na myśli, że nie wyrzucasz ich z interfejsu API?
StuperUser,
6
@StuperUser: Myślę, że źle zrozumiałeś, co miałem na myśli, ponieważ napisałem to w mylący sposób. Państwo powinno mieć throw new ArgumentNullExceptionw 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.
Eric Lippert,
1
Nie tylko komunikuje, kto ma błąd, ale także informuje, gdzie jest błąd. Nawet jeśli oba obszary są moje, nie zawsze łatwo jest dokładnie zrozumieć, która zmienna była pusta.
Ohad Schneider,
5

Chodzi o to, że Twój kod powinien zrobić coś znaczącego.

A NullReferenceExceptionnie 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 ArgumentNullExceptionto znaczenie, ponieważ mówi mi: operacja nie powiodła się, ponieważ argument someObjectbył 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.

back2dos
źródło
2

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.

Jan Hudec
źródło
1

Nauczono mnie wierzyć, że zgłoszenie wyjątku ArgumentNullException jest „prawidłowe”, ale błąd „Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu” oznacza, że ​​mam błąd.

Masz błąd, jeśli kod nie działa zgodnie z przeznaczeniem. System NullReferenceExceptionjest 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 ApplicationExceptionnależy do Twojej aplikacji, a ogólne Exceptiondo 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, ArgumentExceptionmogą skonstruować swoje try / catch w taki sposób, aby poradzić sobie z tym błędem przed bardziej ogólnym, NullReferenceExceptionktóry może być spowodowany dowolną liczbą innych przyczyn, które występują gdzie indziej, takich jak niepowodzenie inicjalizacji zmiennej konstruktora.

P.Brian.Mackey
źródło
1

Sprawdzenie wyjaśnia, co się dzieje, ale prawdziwy problem pochodzi z kodu takiego jak (wymyślony) przykład:

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

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).

FinnNk
źródło
1

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:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

Jeśli Personjest odwołaniem zerowym, linia File.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.

Hand-E-Food
źródło