Perché "riferimento oggetto non impostato su un'istanza di un oggetto" non ci dice quale oggetto?


39

Stiamo avviando un sistema e talvolta riceviamo la famosa eccezione NullReferenceExceptioncon il messaggio Object reference not set to an instance of an object.

Tuttavia, in un metodo in cui abbiamo quasi 20 oggetti, con un registro che dice che un oggetto è nullo, non è affatto utile. È come dirti, quando sei l'agente di sicurezza di un seminario, che un uomo tra i 100 partecipanti è un terrorista. Non ti è affatto utile. Dovresti ottenere maggiori informazioni, se vuoi scoprire quale uomo è l'uomo minaccioso.

Allo stesso modo, se vogliamo rimuovere il bug, dobbiamo sapere quale oggetto è nullo.

Ora, qualcosa ha ossessionato la mia mente per diversi mesi, e cioè:

Perché .NET non ci fornisce il nome o almeno il tipo di riferimento all'oggetto, che è null? . Non capisce il tipo dalla riflessione o da qualsiasi altra fonte?

Inoltre, quali sono le migliori pratiche per capire quale oggetto è nullo? Dovremmo sempre testare la nullità degli oggetti in questi contesti manualmente e registrare il risultato? Esiste un modo migliore?

Aggiornamento: l'eccezione The system cannot find the file specifiedha la stessa natura. Non è possibile trovare quale file fino a quando non si allega al processo e al debug. Immagino che questi tipi di eccezioni possano diventare più intelligenti. Non sarebbe meglio se .NET potesse dirci c:\temp.txt doesn't exist.invece di quel messaggio generale? Come sviluppatore, voto sì.


14
L'eccezione dovrebbe includere una traccia dello stack con il numero di riga. Vorrei iniziare la mia indagine da lì guardando ogni oggetto a cui si accedeva su quella linea.
PersonalNexus,

2
Inoltre, mi sono sempre chiesto perché la finestra di dialogo dell'helper delle eccezioni in Visual Studio includa il suggerimento "utile" da utilizzare newper creare istanze di una classe. Quando un tale suggerimento aiuta davvero?
PersonalNexus,

1
Se hai una catena di dieci chiamate ad oggetto, hai un problema con l'accoppiamento nel tuo progetto.
Pete Kirkham,


9
Adoro il modo in cui ogni singola risposta a questa domanda è sulla falsariga di "Usa un debugger, registra i tuoi errori, verifica la presenza di null, comunque è colpa tua" che non risponde alla domanda ma ti dà la colpa. Solo su StackOverflow qualcuno ti dà effettivamente una risposta (che penso dice che sia un sovraccarico eccessivo per la VM da tenere traccia). Ma davvero, le uniche persone che possono rispondere correttamente a questa domanda sono persone di Microsoft che hanno lavorato al framework.
Rocklan,

Risposte:


28

La NullReferenceExceptionsostanza si dice: si sta facendo male. Niente di più, niente di meno. Al contrario, non è uno strumento di debug completo. In questo caso direi che stai sbagliando entrambi perché

  • c'è una NullReferenceException
  • non l'hai impedito in un modo in cui sai perché / dove è successo
  • e forse anche: un metodo che richiede 20 oggetti sembra un po 'fuori

Sono un grande fan di controllare tutto prima che le cose inizino a andare male e di fornire buone informazioni allo sviluppatore. In breve: scrivere assegni usando ArgumentNullExceptione simili e scrivere il nome da soli. Ecco un esempio:

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

Puoi anche esaminare i Contratti di codice , ha delle stranezze ma funziona abbastanza bene e ti fa risparmiare un po 'di battitura.


17
@SaeedNeamati non stai insinuando che, poiché hai una base di codice di grandi dimensioni, non dovresti fare un controllo degli errori decente, giusto? Più grande è il progetto, più importanti diventano i ruoli o il controllo e la segnalazione degli errori.
Stijn

6
+1 per un buon consiglio, anche se non risponde alla domanda reale (a cui probabilmente solo Anders Hejlsberg può rispondere).
Ross Patterson,

12
+1 per un consiglio eccellente. @SaeedNeamati dovresti ascoltare questo consiglio. Il tuo problema è causato dalla disattenzione e dalla mancanza di professionalità nel codice. Se non hai tempo per scrivere un buon codice, hai problemi molto più grandi ...
MattDavey,

3
Se non hai tempo per scrivere un buon codice, sicuramente non hai tempo per scrivere un codice cattivo . La scrittura di codice errato richiede più tempo rispetto alla scrittura di codice valido. A meno che non ti interessi davvero dei bug. E se davvero non ti interessano i bug, perché scrivere il programma?
MarkJ,

5
@SaeedNeamati I only say that checking every object to get sure that it's not null, is not a good methodSeriamente. È il metodo migliore. E non solo null, controlla ogni argomento per valori ragionevoli. Prima si rilevano errori, più è facile trovare la causa. Non è necessario eseguire il backtracking di diversi livelli nella traccia dello stack per trovare la chiamata che causa.
jgauffin,

19

Dovrebbe davvero mostrare esattamente ciò che stai cercando di chiamare. È come dire "C'è un problema. Devi risolverlo. So cosa è. Non ho intenzione di dirtelo. Lo capisci" Un po 'come metà delle risposte su questo Stack Overflow, ironicamente.

Quindi quanto sarebbe utile, ad esempio, se tu avessi questo ...

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

...? Dobbiamo andare nel codice, esaminarlo e capire che la cosa che stai cercando di chiamare nullva bene, ma perché non darci solo una piccola mano?

Sono d'accordo che di solito è utile scorrere il codice per arrivare alla risposta (perché probabilmente ne scoprirai di più), ma spesso un sacco di tempo e frustrazione verrebbero salvati se il NullReferenceExceptiontesto fosse più simile all'esempio sopra.

Sto solo dicendo.


Come si suppone che il runtime sappia cosa è null?
Sarà il

3
Il runtime ha più informazioni di quelle fornite. Qualunque informazione utile abbia, dovrebbe fornire. Questo è tutto ciò che sto dicendo. In risposta alla tua domanda, perché non lo sa? Se conosci la risposta, potresti essere in grado di fornire un commento migliore di quello che hai.
LiverpoolsNumber9

D'accordo, e direi lo stesso per KeyNotFoundExceptione molti altri fastidi ...
sinelaw

In realtà, nel caso del dereferenziamento nullo questo sembra una limitazione nel modo in cui le dereferenze del CLR (grazie al commento di Shahrooz Jefri sulla domanda del PO)
sinelaw,


4

Il registro dovrebbe includere una traccia dello stack, che in genere fornisce un suggerimento su quale riga del metodo presenta il problema. Potrebbe essere necessario che la build di rilascio includa i simboli PDB in modo da avere un'idea della riga su cui si trova l'errore.

Certo, non ti aiuterà in questo caso:

Foo.Bar.Baz.DoSomething()

Il principio " non chiedere " può aiutare a evitare tale codice.

Per quanto riguarda il motivo per cui le informazioni non sono incluse, non sono sicuro - sospetto che almeno in una build di debug, se lo volessero davvero, potrebbero capirlo. Può essere utile eseguire un dump di arresto anomalo e aprirlo in WinDBG.


2

Sono state create eccezioni come strumento per segnalare condizioni non fatali eccezionali nella catena di chiamate. Cioè, non sono progettati come uno strumento di debug.

Se un'eccezione con puntatore null fosse uno strumento di debug, interromperebbe l'esecuzione del programma direttamente sul posto, consentendo a un debugger di connettersi, puntandolo verso la linea incriminante. Ciò darebbe al programmatore tutte le informazioni sul contesto disponibili. (Il che è praticamente ciò che fa un Segfault a causa di un accesso puntatore nullo in C, anche se un po 'rozzamente.)

Tuttavia, l'eccezione del puntatore null è progettata come una condizione di runtime valida che può essere generata e rilevata nel normale flusso del programma. Di conseguenza, è necessario prendere in considerazione considerazioni sulle prestazioni. E qualsiasi personalizzazione del messaggio di eccezione richiede la creazione, la concatenazione e la distruzione di oggetti stringa durante l'esecuzione. Come tale, un messaggio statico è indiscutibilmente più veloce.

Non sto dicendo che il tempo di esecuzione non possa essere programmato in un modo che darebbe il nome del riferimento incriminante, comunque. Questo potrebbe essere fatto. Renderebbe le eccezioni ancora più lente di loro. Se qualcuno si preoccupasse abbastanza, una tale funzionalità potrebbe anche essere commutabile, in modo da non rallentare il codice di produzione, ma consentire un debug più semplice; ma per qualche motivo nessuno sembra essersi preoccupato abbastanza.


1

Credo che Cromulent abbia colpito l'unghia sulla testa, tuttavia c'è anche il punto ovvio che se stai ottenendo un NullReferenceExceptionhai variabili non inizializzate. L'argomentazione secondo cui si passano circa 20 oggetti in un metodo non può essere considerata una mitigazione: come creatore di un pezzo di codice devi essere responsabile delle sue azioni, che include la sua conformità al resto di una base di codice, come così come il corretto e corretto utilizzo delle variabili ecc.

è oneroso, noioso e talvolta noioso, ma le ricompense alla fine ne valgono la pena: molte sono le volte in cui ho dovuto cercare tra i file di registro del peso di diversi gigabyte e sono quasi sempre utili. Tuttavia, prima di arrivare a quello stadio, il debugger può aiutarti, e prima di quello stadio una buona pianificazione farà risparmiare molto dolore (e non intendo neanche un approccio completamente ingegnerizzato alla tua soluzione di codice: semplici schizzi e alcune note possono e lo faranno essere meglio di niente).

Per quanto riguarda Object reference not set to an instance of an objectil codice, non possiamo indovinare i valori che ci potrebbero piacere: quello è il nostro lavoro di programmatori e significa semplicemente che hai passato una variabile non inizializzata.


Ti rendi conto che le persone erano solite offrire giustificazioni come questa per la scrittura in linguaggio assembly? E non stai usando la garbage collection automatica? E riuscire a fare l'aritmetica - in esadecimale? "oneroso, noioso e talvolta noioso" è il modo in cui descriviamo i lavori che dovrebbero essere automatizzati.
Spike0xff,

0

Impara a usare il debugger. Questo è esattamente il tipo di cosa per cui è stato progettato. Imposta un punto di interruzione sul metodo in questione e via.

Basta scorrere il codice e vedere esattamente quali sono i valori di tutte le variabili in determinati punti.

Modifica: Francamente sono scioccato dal fatto che nessun altro abbia mai menzionato l'utilizzo del debugger.


2
Quindi stai dicendo che tutte le eccezioni possono essere facilmente riprodotte quando si utilizza un debugger?
Jgauffin,

@jgauffin Sto dicendo che puoi usare un debugger per capire perché viene generata un'eccezione nel codice del mondo reale piuttosto che nei test di unità sintetici che potrebbero non testare completamente il codice in questione o che i test di unità stessi potrebbero avere bug in cui le cause loro per perdere i bug nel vero codice. Un debugger batte praticamente qualsiasi altro strumento che mi viene in mente (tranne forse cose come Valgrind o DTrace).
Cromulent,

1
Quindi stai dicendo che abbiamo sempre accesso al computer in errore e che il debug è migliore dei test unitari?
Jgauffin,

@jgauffin Sto dicendo che se hai una scelta, vai con il debugger rispetto ad altri strumenti. Ovviamente se non hai questa scelta, allora è un po 'un non-principiante. Chiaramente questo non è il caso di questa domanda, motivo per cui ho dato la risposta che ho fatto. Se la domanda fosse stata come risolvere questo problema su un computer client senza alcun modo per eseguire il debug remoto (o il debug locale) la mia risposta sarebbe stata diversa. Sembra che tu stia cercando di deformare la mia risposta in modi che non hanno alcuna rilevanza per la domanda in corso.
Cromulent,

0

Se si desidera seguire la risposta di @ stijn e inserire controlli nulli nel codice, questo frammento di codice dovrebbe essere di aiuto. Ecco alcune informazioni sugli snippet di codice . Una volta impostato questo, basta digitare argnull, premere due volte la scheda e quindi riempire lo spazio vuoto.

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

nota: c # 6 ora ha nameofcosì il tuo frammento che potrebbe avere throw new ArgumentNullException(nameof($argument$))i vantaggi di non includere costanti magiche, essere controllato dal compilatore e lavorare meglio con gli strumenti di refactoring
stijn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.