In che modo è utile lanciare un ArgumentNullException?


27

Diciamo che ho un metodo:

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

Sono stato addestrato a credere che il lancio ArgumentNullExceptiondell'errore sia "corretto" ma un errore "Riferimento oggetto non impostato su un'istanza di un oggetto" significa che ho un bug.

Perché?

So che se stavo memorizzando nella cache il riferimento someObjecte lo stavo usando in seguito, è meglio verificare la nullità quando viene passato e fallire presto. Tuttavia, se lo sto riferendo alla riga successiva, perché dovremmo fare il controllo? Genererà un'eccezione in un modo o nell'altro.

Modifica :

Mi è appena venuto in mente ... la paura del null dereferenziato proviene da un linguaggio come il C ++ che non controlla per te (cioè cerca solo di eseguire qualche metodo nella posizione di memoria zero + offset del metodo)?


Rendere esplicita l'eccezione aiuta ad affermare che esiste una possibilità di errore, che può essere notata direttamente nella documentazione.
zzzzBov,

Risposte:


34

Suppongo che se stai immediatamente dereferenziando la variabile, potresti discutere in entrambi i modi, ma preferirei comunque ArgumentNullException.
È molto più esplicito su ciò che sta succedendo. L'eccezione contiene il nome della variabile che era null, mentre non una NullReferenceException. Soprattutto a livello di API, una ArgumentNullException chiarisce che alcuni chiamanti non hanno rispettato il contratto di un metodo e il fallimento non è stato necessariamente un errore casuale o un problema più profondo (anche se potrebbe essere ancora così). Dovresti fallire presto.

Inoltre, cosa hanno più probabilità di fare i manutentori successivi? Aggiungi ArgumentNullException se aggiungono codice prima della prima dereference, o semplicemente aggiungono il codice e in seguito consentono di lanciare una NullReferenceException?


Per estensione, se questo fosse un metodo non pubblico o un metodo pubblico su una classe non pubblica, non pensi che ci sia una buona ragione per il controllo null?
Scott Whitlock,

Di solito non aggiungo questi controlli ai metodi privati, ma posso ancora aggiungerli ai metodi pubblici delle classi private per essere coerenti. Per i metodi privati, tendo a supporre che gli argomenti siano stati validati prima a un livello di chiamata pubblica.
Matt H

54

Sono stato addestrato a credere che lanciare ArgumentNullException sia "corretto" ma un errore "Riferimento oggetto non impostato su un'istanza di un oggetto" significa che ho un bug. Perché?

Supponiamo che io chiami il metodo M(x)che hai scritto. Passo null. Ottengo ArgumentNullException con il nome impostato su "x". Quell'eccezione significa inequivocabilmente che ho un bug; Non avrei dovuto passare null per x.

Supponiamo che io chiami un metodo M che hai scritto. Passo null. Ottengo un'eccezione null deref. Come diamine dovrei sapere se ho un bug o se hai un bug o una libreria di terze parti che hai chiamato per mio conto ha un bug? Non so se devo correggere il mio codice o chiamarti per dirti di correggere il tuo.

Entrambe le eccezioni sono indicative di bug; nessuno dei due dovrebbe mai essere gettato nel codice di produzione. La domanda è: quale eccezione comunica chi ha il bug meglio alla persona che sta eseguendo il debug? Null deref exception non ti dice quasi nulla; argomento null exception ti dice molto. Sii gentile con i tuoi utenti; generare eccezioni che consentano loro di imparare dai propri errori.


Non sono sicuro di capire "neither should ever be thrown in production code"Quali altri tipi di codice / situazioni verrebbero utilizzati? Dovrebbero essere compilati come Debug.Assert? O vuoi dire non buttarli fuori da un'API?
StuperUser

6
@StuperUser: Penso che tu abbia capito male cosa intendevo, perché l'ho scritto in modo confuso. Si dovrebbe avere throw new ArgumentNullExceptionnel codice di produzione, e non dovrebbe mai eseguito al di fuori di un banco di prova . L'eccezione non dovrebbe mai essere effettivamente lanciata perché il chiamante non dovrebbe mai avere quel bug.
Eric Lippert,

1
Non solo comunica chi ha il bug, ma comunica dove si trova il bug. Anche se entrambe le aree sono mie, non è sempre facile capire esattamente quale variabile fosse nulla.
Ohad Schneider,

5

Il punto è che il tuo codice dovrebbe fare qualcosa di significativo.

A NullReferenceExceptionnon è particolarmente significativo, perché potrebbe significare tutto. Senza guardare dove è stata generata l'eccezione (e il codice potrebbe non essere disponibile per me) non riesco a capire cosa sia andato storto.

An ArgumentNullExceptionè significativo, perché mi dice: l'operazione non è riuscita perché l'argomento someObjectera null. Non ho bisogno di guardare il tuo codice per capirlo.

Anche sollevare questa eccezione significa che gestisci esplicitamente null, anche se il tuo unico modo per farlo è dire che non puoi gestirlo, mentre lasciare che il crash del codice possa potenzialmente significare che potresti effettivamente essere in grado di gestire null , ma ho dimenticato di implementare il caso.

Il punto di eccezione è comunicare le informazioni in caso di errore, che possono essere gestite in fase di esecuzione per ripristinarsi dall'errore o al momento del debug per comprendere meglio cosa è andato storto.


2

Credo che il solo vantaggio sia che puoi fornire una migliore diagnostica nell'eccezione. Cioè sai, che il chiamante della funzione sta passando null, mentre se lasci che la dereferenza la scarti, non saresti sicuro se il chiamante passa dati errati o c'è un bug nella funzione stessa.

Lo farei se qualcun altro chiamerà la funzione per renderlo più facile da usare (debug se qualcosa va storto) e non farlo nel mio codice interno, perché il runtime lo controllerà comunque.


1

Sono stato addestrato a credere che lanciare ArgumentNullException sia "corretto" ma un errore "Riferimento oggetto non impostato su un'istanza di un oggetto" significa che ho un bug.

Hai un bug se il codice non funziona come previsto. An NullReferenceExceptionviene lanciato dal sistema e questo fatto rende davvero più un bug che una funzionalità. Non solo perché non hai definito l'eccezione specifica da gestire. Anche perché uno sviluppatore che legge il tuo codice potrebbe presumere che tu non abbia tenuto conto della situazione.

Per ragioni analoghe che ApplicationExceptionappartengono alla tua applicazione contro un generale Exceptionche appartiene al sistema operativo. Il tipo di eccezione generata indica l'origine dell'eccezione.

Se hai considerato null, è meglio gestirlo con grazia lanciando un'eccezione specifica o se è un input accettabile, quindi non lanciare un'eccezione perché sono costosi. Gestirlo eseguendo operazioni con valori predefiniti buoni o nessuna operazione (ad es. Nessun aggiornamento richiesto).

Infine, non trascurare la capacità del chiamante di gestire la situazione. Dando loro un'eccezione più specifica, ArgumentExceptionpossono costruire il loro tentativo / cattura in modo da gestire questo errore prima del più generale NullReferenceExceptionche può essere causato da un numero qualsiasi di altri motivi che si verificano altrove, come un fallimento nell'inizializzare una variabile del costruttore.


1

Il controllo rende più esplicito ciò che sta succedendo, ma il vero problema si presenta con un codice come questo esempio (inventato):

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

Qui c'è una catena di chiamate coinvolta e senza un controllo nullo vedi solo l'errore in someOtherObject e non dove il problema si è rivelato per la prima volta, il che significa istantaneamente tempo perso nel debug o nell'interpretazione degli stack di chiamate.

In generale è meglio essere coerenti con questo genere di cose e aggiungere sempre il controllo null, il che rende più facile individuare se manca uno piuttosto che raccogliere e scegliere caso per caso (supponendo che tu sappia che null è sempre non valido).


1

Un altro motivo da considerare è cosa succede se qualche elaborazione si verifica prima che venga utilizzato l'oggetto null?

Supponi di avere un file di dati di ID società associati (Cid) e ID persona (Pid). Viene salvato come flusso di coppie di numeri:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

E questa funzione VB scrive i record nel file:

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

Se Personè riferimento null, la riga File.Write(Person.ID)genererà un'eccezione. Il software può rilevare l'eccezione e ripristinarlo, ma ciò lascia un problema. Un ID azienda è stato scritto senza un ID persona associata. In effetti, quando richiamerai questa funzione, inserirai un ID azienda in cui era previsto l'ID persona precedente. Oltre che il record è errato, ogni record successivo avrà un ID persona seguito da un ID azienda, che è il modo sbagliato.

Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid

Convalidando i parametri all'avvio della funzione, si evita che si verifichi qualsiasi elaborazione quando il metodo ha esito negativo.

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.