Dlaczego „odwołanie do obiektu nie jest ustawione na instancję obiektu” nie mówi nam, który obiekt?

39

Wprowadzamy system i czasami otrzymujemy słynny wyjątek NullReferenceExceptionz komunikatem Object reference not set to an instance of an object.

Jednak w metodzie, w której mamy prawie 20 obiektów, posiadanie dziennika, który mówi, że obiekt jest pusty, tak naprawdę w ogóle się nie przydaje. To tak, jakby powiedzieć, kiedy jesteś agentem bezpieczeństwa seminarium, że mężczyzna na 100 uczestników jest terrorystą. To naprawdę nie ma dla ciebie żadnego sensu. Powinieneś uzyskać więcej informacji, jeśli chcesz wykryć, który mężczyzna jest groźny.

Podobnie, jeśli chcemy usunąć błąd, musimy wiedzieć, który obiekt jest pusty.

Teraz coś mnie obsesję na punkcie od kilku miesięcy, a mianowicie:

Dlaczego .NET nie podaje nam nazwy, a przynajmniej rodzaju odwołania do obiektu, które jest puste? . Czy nie może zrozumieć typu z odbicia lub innego źródła?

Jakie są najlepsze praktyki, aby zrozumieć, który obiekt jest pusty? Czy zawsze powinniśmy ręcznie testować zerowanie obiektów w tych kontekstach i rejestrować wynik? Czy jest lepszy sposób?

Aktualizacja: wyjątek The system cannot find the file specifiedma ten sam charakter. Nie możesz znaleźć pliku, dopóki nie zostanie dołączony do procesu i debugowany. Myślę, że tego rodzaju wyjątki mogą stać się bardziej inteligentne. Czy nie byłoby lepiej, gdyby .NET mógł nam powiedzieć c:\temp.txt doesn't exist.zamiast tego ogólnego komunikatu? Jako programista głosuję na tak.

Saeed Neamati
źródło
14
Wyjątek powinien zawierać ślad stosu z numerem wiersza. Zacząłem moje dochodzenie od tego, patrząc na każdy obiekt dostępny w tej linii.
PersonalNexus
2
Poza tym zawsze zastanawiałem się, dlaczego okno dialogowe wyjątku w programie Visual Studio zawiera „pomocną” wskazówkę, której można użyć newdo tworzenia instancji klasy. Kiedy taka wskazówka naprawdę pomaga?
PersonalNexus
1
Jeśli masz łańcuch dziesięciu wywołań obiektów, masz problem ze sprzężeniem w swoim projekcie.
Pete Kirkham
9
Uwielbiam to, jak każda odpowiedź na to pytanie brzmi: „Użyj debugera, zaloguj swoje błędy, sprawdź, czy nie ma, to i tak twoja wina”, które nie odpowiadają na pytanie, ale obwiniają cię. Tylko w przypadku przepełnienia stosu ktoś faktycznie daje ci odpowiedź (która, jak sądzę, mówi, że jest to zbyt duży narzut, aby maszyna wirtualna mogła to kontrolować). Ale tak naprawdę jedynymi osobami, które potrafią poprawnie odpowiedzieć na to pytanie, jest ktoś z Microsoft, który pracował nad frameworkiem.
Rocklan

Odpowiedzi:

28

NullReferenceExceptionZasadzie mówi ci: robisz to źle. Nic dodać nic ująć. Wręcz przeciwnie, nie jest to pełne narzędzie do debugowania. W tym przypadku powiedziałbym, że robisz to źle, ponieważ

  • istnieje wyjątek NullReferenceException
  • nie zapobiegłeś temu w sposób, w jaki wiesz, dlaczego / gdzie to się wydarzyło
  • a także może: metoda wymagająca 20 obiektów wydaje się nieco odległa

Jestem wielkim fanem sprawdzania wszystkiego, zanim coś pójdzie nie tak i zapewniania dobrych informacji deweloperowi. W skrócie: napisz czeki za pomocą ArgumentNullExceptioni polubień i napisz nazwisko. Oto próbka:

void Method(string a, SomeObject b)
{
    if (a == null) throw ArgumentNullException("a");
    if (b == null) throw ArgumentNullException("b");

    // See how nice this is, and what peace of mind this provides? As long as
    // nothing modifies a or b you can use them here and be 100% sure they're not
    // null. Should they be when entering the method, at least you know which one
    // is null.
    var c = FetchSomeObject();
    if(c == null)
    {
        throw InvalidOperationException("Fetching B failed!!");
    }

    // etc.
}

Możesz także zajrzeć do kontraktów kodowych , ma dziwactwa, ale działa całkiem dobrze i oszczędza ci pisania.

stijn
źródło
17
@ SaeedNeamati nie sugerujesz, że ponieważ masz dużą bazę kodów, nie powinieneś sprawdzać poprawności błędów, prawda? Im większy projekt, tym ważniejsze stają się role lub sprawdzanie błędów i raportowanie.
stijn
6
+1 za dobrą radę, nawet jeśli nie odpowiada na rzeczywiste pytanie (na które prawdopodobnie może odpowiedzieć tylko Anders Hejlsberg).
Ross Patterson
12
+1 za doskonałą poradę. @SaeedNeamati powinieneś posłuchać tej rady. Twój problem jest spowodowany niedbalstwem i brakiem profesjonalizmu w kodzie. Jeśli nie masz czasu na pisanie dobrego kodu, masz o wiele większe problemy ...
MattDavey
3
Jeśli nie masz czasu na napisanie dobrego kodu, to na pewno nie masz czasu na napisanie złego kodu. Pisanie złego kodu trwa dłużej niż pisanie dobrego kodu. Chyba że tak naprawdę nie obchodzą Cię błędy. A jeśli naprawdę nie przejmujesz się błędami, po co w ogóle pisać program?
MarkJ
5
@SaeedNeamati I only say that checking every object to get sure that it's not null, is not a good methodPoważnie. To najlepsza metoda. I nie tylko zero, sprawdź każdy argument pod kątem rozsądnych wartości. Im wcześniej wykryjesz błędy, tym łatwiej będzie znaleźć przyczynę. Nie trzeba cofać się o kilka poziomów w śladzie stosu, aby znaleźć wywołanie wywołujące.
jgauffin
19

Naprawdę powinien pokazywać dokładnie to, co próbujesz zadzwonić. To tak, jakby powiedzieć: „Jest problem. Musisz go naprawić. Wiem, co to jest. Nie powiem ci. Idź to rozgryźć” Trochę jak połowa odpowiedzi na temat przepełnienia stosu, jak na ironię.

Jak przydatne byłoby, na przykład, jeśli masz to ...

Object reference (HttpContext.Current) not set to instance of an object

... Aby wejść do kodu, przejrzeć go i przekonać się, że to, co próbujesz wywołać, nulljest w porządku, ale dlaczego nie po prostu podać nam pomocną dłoń?

Zgadzam się, że zwykle przydatne jest przechodzenie przez kod, aby uzyskać odpowiedź (ponieważ prawdopodobnie dowiesz się więcej), ale często zaoszczędzono by dużo czasu i frustracji, gdyby NullReferenceExceptiontekst był bardziej podobny do powyższego przykładu.

Tylko mówię.

LiverpoolsNumber9
źródło
Skąd środowisko wykonawcze powinno wiedzieć, co jest zerowe?
Czy
3
Środowisko wykonawcze zawiera więcej informacji niż zapewnia. Niezależnie od użytecznych informacji, powinien je dostarczyć. To wszystko co mówię. W odpowiedzi na twoje pytanie, dlaczego nie wie? Jeśli znasz odpowiedź na to pytanie, być może możesz podać lepszy komentarz niż masz.
LiverpoolsNumber9
Zgodził się, i powiedziałbym to samo KeyNotFoundExceptioni wiele innych niedogodności ...
sinelaw
W rzeczywistości, w przypadku zerowej dereferencji wygląda to na ograniczenie w sposobie, w jaki dereferencje CLR (dzięki komentarzowi Shahrooz Jefri do pytania OP)
sinelaw
4

Twój dziennik powinien zawierać ślad stosu - zwykle daje wskazówkę, która linia w metodzie ma problem. Może być konieczne, aby kompilacja wydania zawierała symbole PDB, abyś miał pomysł, w której linii wystąpił błąd.

To prawda, że ​​nie pomoże ci w tym przypadku:

Foo.Bar.Baz.DoSomething()

Zasada „ nie pytaj ” może pomóc uniknąć takiego kodu.

Co do tego, dlaczego informacje nie są zawarte, nie jestem pewien - podejrzewam, że przynajmniej w kompilacji debugowania, jeśli naprawdę chcieliby, mogliby to zrozumieć. Pomoże Ci zrobienie zrzutu awaryjnego i otwarcie w WinDBG.

Paul Stovell
źródło
2

Stworzono wyjątki jako narzędzie do sygnalizowania wyjątkowych, nie kończących się śmiercią warunków w łańcuchu połączeń. Oznacza to, że nie zostały one zaprojektowane jako narzędzie do debugowania.

Gdyby wyjątek wskaźnika zerowego był narzędziem do debugowania, przerywałby on wykonywanie programu natychmiast, umożliwiając debugerowi połączenie się, kierując go bezpośrednio na linię obciążającą. Dałoby to programiście wszystkie dostępne informacje kontekstowe. (Co jest mniej więcej tym, co robi Segfault ze względu na zerowy dostęp do wskaźnika w C, choć trochę z grubsza.)

Jednak wyjątek wskaźnika zerowego został zaprojektowany jako poprawny warunek działania, który może zostać wygenerowany i przechwycony podczas normalnego przepływu programu. W związku z tym należy wziąć pod uwagę względy wydajności. Każde dostosowanie komunikatu wyjątku wymaga tworzenia, łączenia i niszczenia obiektów łańcuchowych w czasie wykonywania. Jako taki, statyczny komunikat jest bezsprzecznie szybszy.

Nie twierdzę jednak, że środowiska wykonawczego nie można zaprogramować w sposób, który dałby nazwę obciążającego odwołania. Można to zrobić. Sprawiłoby to, że wyjątki byłyby nawet wolniejsze niż są. Jeśli komuś na to zależy, taka funkcja może nawet zostać przełączona, aby nie spowalniała kodu produkcyjnego, ale pozwalała na łatwiejsze debugowanie; ale z jakiegoś powodu nikt nie dbał wystarczająco.

cmaster
źródło
1

Cromulent uderzył w gwóźdź w głowę, ale myślę, że jest też oczywisty punkt, że jeśli dostajesz NullReferenceExceptionniezainicjowaną zmienną (zmienne). Argument, że masz około 20 obiektów przekazywanych do metody, nie może być uznany za złagodzenie: jako twórca fragmentu kodu musisz być odpowiedzialny za jego działania, w tym jego zgodność z resztą bazy kodu, ponieważ a także właściwe i prawidłowe wykorzystanie zmiennych itp.

jest uciążliwy, żmudny i czasem nudny, ale nagrody na końcu są tego warte: w wielu przypadkach musiałem przeszukiwać pliki dzienników ważące kilka gigabajtów i prawie zawsze są pomocne. Jednak zanim przejdziesz do tego etapu, debugger może ci pomóc, a przed tym etapem dobre planowanie zaoszczędzi wiele bólu (i nie mam też na myśli w pełni skonstruowanego podejścia do twojego kodu: proste szkice i niektóre notatki mogą i będą być lepszym niż nic).

Jeśli chodzi o Object reference not set to an instance of an objectkod, nie możemy zgadnąć, jakie wartości mogą nam się spodobać: to nasza praca jako programistów i oznacza to po prostu, że przekazałeś niezainicjowaną zmienną.

GMasucci
źródło
Zdajesz sobie sprawę, że ludzie zwykli oferować takie uzasadnienia pisania w języku asemblera? I nie używasz automatycznego usuwania śmieci? I będąc w stanie robić arytmetykę - szesnastkowo? „uciążliwe, nużące, a czasem nudne” to sposób opisywania zadań, które powinny zostać zautomatyzowane.
Spike0xff
0

Naucz się korzystać z debugera. To jest dokładnie to, do czego jest przeznaczony. Ustaw punkt przerwania dla danej metody i gotowe.

Wystarczy przejść przez kod i zobaczyć dokładnie, jakie są wartości wszystkich zmiennych w określonych punktach.

Edycja: Szczerze mówiąc jestem zszokowany, że nikt jeszcze nie wspomniał o użyciu debuggera.

Cromulent
źródło
2
Mówisz więc, że wszystkie wyjątki można łatwo odtworzyć podczas korzystania z debugera?
jgauffin
@ jgauffin Mówię, że można użyć debugera, aby zobaczyć, dlaczego wyjątek jest generowany w kodzie świata rzeczywistego zamiast syntetycznych testów jednostkowych, które mogą nie w pełni przetestować dany kod lub same testy jednostkowe mogą zawierać błędy, które powodują aby przegapić błędy w prawdziwym kodzie. Debuger przebija każde inne narzędzie, o którym mogę pomyśleć (z wyjątkiem rzeczy takich jak Valgrind lub DTrace).
Cromulent
1
Mówisz więc, że zawsze mamy dostęp do uszkodzonego komputera i że debugowanie jest lepsze niż testy jednostkowe?
jgauffin
@ jgauffin Mówię, że jeśli masz wybór, to idź z debuggerem zamiast innych narzędzi. Oczywiście, jeśli nie masz takiego wyboru, to jest to trochę non-starter. Oczywiście nie ma to miejsca w przypadku tego pytania, dlatego udzieliłem odpowiedzi, której udzieliłem. Gdyby pytanie dotyczyło sposobu rozwiązania tego problemu na komputerze klienta bez możliwości przeprowadzenia zdalnego debugowania (lub debugowania lokalnego), moja odpowiedź byłaby inna. Wygląda na to, że próbujesz wypaczać moją odpowiedź w sposób, który nie ma związku z danym pytaniem.
Cromulent
0

Jeśli chcesz skorzystać z odpowiedzi @ stijn i umieścić w kodzie kody zerowe, ten fragment kodu powinien pomóc. Oto kilka informacji o fragmentach kodu . Po skonfigurowaniu wystarczy wpisać argnull, dwukrotnie nacisnąć klawisz Tab, a następnie wypełnić puste pole.

<CodeSnippet Format="1.0.0">
  <Header>
    <Title>EnsureArgNotNull</Title>
    <Shortcut>argnull</Shortcut>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>argument</ID>
        <ToolTip>The name of the argument that shouldn't be null</ToolTip>
        <Default>arg</Default>
      </Literal>
    </Declarations>
    <Code Language="CSharp">
      <![CDATA[if ($argument$ == null) throw new ArgumentNullException("$argument$");$end$]]>
    </Code>
  </Snippet>
</CodeSnippet>
user2023861
źródło
uwaga: c # 6 ma teraz nameoftaki fragment throw new ArgumentNullException(nameof($argument$))kodu, który może mieć tę zaletę, że nie uwzględnia magicznych stałych, jest sprawdzany przez kompilator i działa lepiej z narzędziami do refaktoryzacji
stijn