Perché non dobbiamo lanciare queste eccezioni?


111

Mi sono imbattuto in questa pagina MSDN che afferma:

Non generare intenzionalmente eccezioni , SystemException , NullReferenceException o IndexOutOfRangeException dal codice sorgente.

Purtroppo non si preoccupa di spiegare perché. Posso indovinare i motivi, ma spero che qualcuno più autorevole in materia possa offrire il proprio spunto.

I primi due hanno un senso ovvio, ma gli ultimi due sembrano quelli che vorresti impiegare (e in effetti, l'ho fatto).

Inoltre, queste sono le uniche eccezioni da evitare? Se ce ne sono altri, cosa sono e perché dovrebbero essere evitati anche loro?


22
Da msdn.microsoft.com/en-us/library/ms182338.aspx : se lanci un tipo di eccezione generale, come Eccezione o SystemException in una libreria o framework, costringe i consumatori a rilevare tutte le eccezioni, incluse le eccezioni sconosciute che fanno non so come gestire.
Henrik

3
Perché dovresti lanciare un'eccezione NullReferenceException?
Rik

5
@Rik: simile a NullArgumentExceptioncui alcune persone potrebbero confondere entrambi.
Musulmano Ben Dhaou

2
Metodi di estensione @Rik, come da mia risposta
Marc Gravell

3
Un altro che non dovresti lanciare èApplicationException
Matthew Watson

Risposte:


98

Exceptionè il tipo base per tutte le eccezioni, e come tale terribilmente non specifico. Non dovresti mai lanciare questa eccezione perché semplicemente non contiene informazioni utili. Chiamare il rilevamento del codice per le eccezioni non è stato in grado di disambiguare l'eccezione lanciata intenzionalmente (dalla logica) da altre eccezioni di sistema che sono del tutto indesiderate e indicano difetti reali.

Lo stesso motivo vale anche per SystemException. Se guardi l'elenco dei tipi derivati, puoi vedere un numero enorme di altre eccezioni con semantica molto diversa.

NullReferenceExceptione IndexOutOfRangeExceptionsono di tipo diverso. Queste sono eccezioni molto specifiche, quindi lanciarle potrebbe andare bene. Tuttavia, non vorrai ancora lanciarli, poiché di solito significano che ci sono alcuni errori reali nella tua logica. Ad esempio, l'eccezione di riferimento nullo significa che stai tentando di accedere a un membro di un oggetto che è null. Se questa è una possibilità nel tuo codice, dovresti sempre controllare esplicitamente nulle lanciare invece un'eccezione più utile (ad esempio ArgumentNullException). Allo stesso modo, IndexOutOfRangeExceptions si verificano quando si accede a un indice non valido (su array, non su elenchi). Dovresti sempre assicurarti di non farlo in primo luogo e controllare prima i confini, ad esempio, di un array.

Ci sono alcune altre eccezioni come quelle due, ad esempio InvalidCastExceptiono DivideByZeroException, che vengono lanciate per errori specifici nel tuo codice e di solito indicano che stai facendo qualcosa di sbagliato o che non stai prima controllando alcuni valori non validi. Lasciandoli consapevolmente dal tuo codice, stai solo rendendo più difficile per il codice chiamante determinare se sono stati lanciati a causa di qualche errore nel codice, o semplicemente perché hai deciso di riutilizzarli per qualcosa nella tua implementazione.

Naturalmente, ci sono alcune eccezioni (hah) a queste regole. Se stai creando qualcosa che potrebbe causare un'eccezione che corrisponde esattamente a una esistente, sentiti libero di usarlo, specialmente se stai cercando di abbinare un comportamento integrato. Assicurati solo di scegliere un tipo di eccezione molto specifico allora.

In generale, tuttavia, a meno che non trovi un'eccezione (specifica) che soddisfi le tue esigenze, dovresti sempre considerare di creare i tuoi tipi di eccezione per eccezioni previste specifiche. Soprattutto quando si scrive il codice della libreria, questo può essere molto utile per separare le origini delle eccezioni.


3
La terza parte ha poco senso per me. Certo, dovresti piuttosto evitare di causare questi errori all'inizio, ma quando ad esempio scrivi IListun'implementazione, non è in tuo potere influenzare gli indici richiesti, è l' errore logico del chiamante quando un indice non è valido e puoi solo informarlo di questo errore logico generando un'eccezione appropriata. Perché IndexOutOfRangeExceptionnon è appropriato?

7
@delnan Se stai implementando IList, lancerai un ArgumentOutOfRangeExceptioncome suggerisce la documentazione dell'interfaccia . IndexOutOfRangeExceptionè per gli array e, per quanto ne so, non è possibile reimplementare gli array.
colpisci il

2
Ciò che potrebbe anche essere in qualche modo correlato, di NullReferenceExceptionsolito viene lanciato internamente come un caso speciale di un AccessViolationException(IIRC il test è qualcosa di simile cmp [addr], addr, cioè cerca di dereferenziare il puntatore e se fallisce con una violazione di accesso, gestisce la differenza tra NRE e AVE nel gestore di interrupt risultante). Quindi, a parte le ragioni semantiche, sono coinvolti anche alcuni imbrogli. Potrebbe anche aiutarti a scoraggiarti dal controllare nullmanualmente quando non è utile: se hai comunque intenzione di lanciare un NRE, perché non lasciare che .NET lo faccia?
Luaan

Riguardo alla tua ultima affermazione, sulle eccezioni personalizzate: non ho mai sentito il bisogno di farlo. Forse mi sto perdendo qualcosa. In quali condizioni sarebbe necessario creare un tipo di eccezione personalizzato al posto di qualcosa dal framework?
DonBoitnott

1
Bene, per applicazioni più piccole potrebbe non esserci bisogno. Ma non appena si crea qualcosa di più complesso in cui le singole parti funzionano come "componenti" indipendenti, spesso ha senso introdurre eccezioni personalizzate per situazioni di errore personalizzate. Ad esempio, se si dispone di un livello di controllo dell'accesso e si tenta di eseguire un servizio anche se non si è autorizzati a farlo, si potrebbe lanciare una "eccezione di accesso negato" personalizzata o qualcosa del genere. Oppure, se hai un parser che analizza alcuni file, potresti avere i tuoi errori del parser da segnalare all'utente.
colpisci il

36

Sospetto che l'intento con gli ultimi 2 sia quello di evitare confusione con eccezioni integrate che hanno un significato previsto. Tuttavia, sono dell'opinione che se si preserva l'intento esatto dell'eccezione : è quello corretto throw. Ad esempio, se stai scrivendo una raccolta personalizzata, sembra del tutto ragionevole da usare IndexOutOfRangeException: più chiaro e più specifico, IMO, di ArgumentOutOfRangeException. E anche se List<T>potresti scegliere quest'ultimo, ce ne sono almeno 41 posti (per gentile concessione del riflettore) nel BCL (esclusi gli array) che lanciano su misura un po 'utili nei metodi di estensione, se vuoi preservare la semantica che:IndexOutOfRangeException - nessuno dei quali è "di basso livello" abbastanza da meritare un'esenzione speciale. Quindi sì, penso che tu possa giustamente sostenere che quella linea guida è sciocca. Allo stesso modo,NullReferenceException

obj.SomeMethod(); // this is actually an extension method

lancia un NullReferenceExceptionquando objè null.


2
"se stai preservando l'esatto intento dell'eccezione" - sicuramente se così fosse l'eccezione verrebbe lanciata senza che tu debba provarla in primo luogo? E se l'hai già provato, non è davvero un'eccezione?
PugFugly

5
@PugFugly dedica 2 secondi a guardare l'esempio del metodo di estensione: no, non verrà lanciato senza che tu debba provarlo. Se SomeMethod()non è necessario eseguire l'accesso ai membri, non è corretto forzarlo. Allo stesso modo: prendi questo punto con i 41 posti nella BCL che creano custom IndexOutOfRangeException, e i 16 posti che creano customNullReferenceException
Marc Gravell

16
Direi che un metodo di estensione dovrebbe comunque lanciare un ArgumentNullExceptioninvece di un NullReferenceException. Anche se lo zucchero sintattico dei metodi di estensione consente la stessa sintassi del normale accesso ai membri, funziona comunque in modo molto diverso. E ottenere un NRE da MyStaticHelpers.SomeMethod(obj)sarebbe semplicemente sbagliato.
colpisci il

1
@PugFugly BCL è una "libreria di classi di base", fondamentalmente l'elemento centrale di .NET.
colpisci il

1
@PugFugly: Ci sono molti scenari in cui il mancato rilevamento preventivo di una condizione comporterebbe la generazione di un'eccezione in un momento "scomodo". Se un'operazione non riesce, è meglio lanciare un'eccezione in anticipo che avviare l'operazione, arrivare a metà e quindi dover ripulire il disordine parzialmente elaborato risultante.
supercat

5

Come sottolineato, nell'articolo Creazione e generazione di eccezioni (Guida alla programmazione C #) sotto l'argomento Cose da evitare quando si generano eccezioni , Microsoft elenca effettivamente System.IndexOutOfRangeExceptioncome un tipo di eccezione che non dovrebbe essere lanciato intenzionalmente dal proprio codice sorgente.

Al contrario, tuttavia, nell'articolo throw (C # Reference) , Microsoft sembra violare le proprie linee guida. Ecco un metodo che Microsoft ha incluso nel suo esempio:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Quindi, la stessa Microsoft non è coerente in quanto dimostra il lancio di IndexOutOfRangeExceptionnella sua documentazione per throw!

Questo mi porta a credere che almeno nel caso di IndexOutOfRangeException, ci possano essere occasioni in cui quel tipo di eccezione può essere lanciato dal programmatore ed essere considerato una pratica accettabile.


1

Quando ho letto la tua domanda, mi sono chiesto in quali condizioni si vorrebbe lanciare i tipi di eccezione NullReferenceException, InvalidCastExceptiono ArgumentOutOfRangeException.

A mio parere, quando incontro uno di questi tipi di eccezioni, io (lo sviluppatore) mi sento preoccupato per l'avvertimento nel senso che il compilatore sta parlando con me. Quindi, consentire a te (lo sviluppatore) di lanciare tali tipi di eccezione equivale a (il compilatore) a vendere la responsabilità. Ad esempio, questo suggerisce che il compilatore dovrebbe ora consentire allo sviluppatore di decidere se un oggetto è null. Ma fare una tale determinazione dovrebbe essere davvero il lavoro del compilatore.

PS: Dal 2003 ho sviluppato le mie eccezioni in modo da poterle lanciare come desidero. Penso che sia considerata una buona pratica farlo.


Punti buoni. Tuttavia, penso che sarebbe più accurato dire che il programmatore dovrebbe lasciare che il runtime .NET Framework generi questi tipi di eccezioni (e che il programmatore dovrebbe gestirli in modo appropriato).
DavidRR

0

Mettendo la discussione su NullReferenceExceptione IndexOutOfBoundsExceptionda parte:

Che ne dici di prendere e lanciare System.Exception. Ho lanciato molto questo tipo di eccezione nel mio codice e non ne sono mai stato fregato. Allo stesso modo, molto spesso prendo il Exceptiontipo non specifico e ha funzionato abbastanza bene anche per me. Allora, perché è così?

Di solito gli utenti sostengono che dovrebbero essere in grado di distinguere le cause di errore. Dalla mia esperienza, ci sono pochissime situazioni in cui vorresti gestire diversi tipi di eccezioni in modo diverso. Per quei casi, in cui ci si aspetta che gli utenti gestiscano gli errori a livello di codice, è necessario generare un tipo di eccezione più specifico. Per altri casi, non sono convinto dalle linee guida generali sulle migliori pratiche.

Quindi, per quanto riguarda il lancio Exception, non vedo un motivo per vietarlo in tutti i casi.

EDIT: anche dalla pagina MSDN:

Le eccezioni non devono essere utilizzate per modificare il flusso di un programma come parte dell'esecuzione ordinaria. Le eccezioni devono essere utilizzate solo per segnalare e gestire le condizioni di errore.

Neanche esagerare con le clausole catch con logica individuale per diversi tipi di eccezione.


Il messaggio di eccezione è ancora in vigore per raccontare cosa è successo. Non riesco a immaginare che tu vada e crei un nuovo tipo di eccezione per ogni errore diverso, nel caso in cui un consumatore possa voler distinguere quegli errori, a livello di programmazione.
uebe

Cosa è successo al mio commento precedente?
DonBoitnott
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.