Quali eccezioni devono essere generate per parametri non validi o imprevisti in .NET?


163

Quali tipi di eccezioni devono essere generati per parametri non validi o imprevisti in .NET? Quando dovrei scegliere uno anziché un altro?

Azione supplementare:

Quale eccezione useresti se hai una funzione in attesa di un numero intero corrispondente a un mese e hai passato '42'? Questo rientrerebbe nella categoria "fuori portata" anche se non è una collezione?

Risposte:


249

Mi piace usare: ArgumentException, ArgumentNullException, e ArgumentOutOfRangeException.

Esistono anche altre opzioni che non si concentrano molto sull'argomento stesso, ma piuttosto giudicano la chiamata nel suo insieme:

  • InvalidOperationException- L'argomento potrebbe essere OK, ma non nello stato corrente dell'oggetto. Il merito va a STW (precedentemente Yoooder). Vota anche la sua risposta .
  • NotSupportedException- Gli argomenti passati sono validi, ma non supportati in questa implementazione. Immagina un client FTP e passi un comando in quanto il client non supporta.

Il trucco è lanciare l'eccezione che esprime meglio il motivo per cui il metodo non può essere chiamato così com'è. Idealmente, l'eccezione dovrebbe essere dettagliata di ciò che è andato storto, perché è sbagliato e come risolverlo.

Adoro quando i messaggi di errore indicano aiuto, documentazione o altre risorse. Ad esempio, Microsoft ha fatto un buon primo passo con i suoi articoli KB, ad esempio "Perché ricevo un messaggio di errore" Operazione interrotta "quando visito una pagina Web in Internet Explorer?" . Quando si verifica l'errore, ti indicano l'articolo KB nel messaggio di errore. Quello che non fanno bene è che non te lo dicono, perché nello specifico è fallito.

Grazie ancora a STW (ex Yoooder) per i commenti.


In risposta al tuo seguito, lancerei un ArgumentOutOfRangeException. Guarda cosa dice MSDN su questa eccezione:

ArgumentOutOfRangeExceptionviene generato quando viene richiamato un metodo e almeno uno degli argomenti passati al metodo non è riferimento null ( Nothingin Visual Basic) e non contiene un valore valido.

Quindi, in questo caso, stai passando un valore, ma questo non è un valore valido, poiché l'intervallo è compreso tra 1 e 12. Tuttavia, il modo in cui lo documenti chiarisce cosa genera l'API. Perché anche se potrei dire ArgumentOutOfRangeException, potrebbe dire un altro sviluppatore ArgumentException. Semplifica e documenta il comportamento.


Psst! Sono d'accordo che tu abbia risposto alla sua domanda specifica esattamente bene, ma vedi la mia risposta di seguito per aiutare a completare la codifica difensiva e i parametri di convalida ;-D
STW

+1 ma documentando quale eccezione viene generata e perché è più importante che scegliere quella "giusta".
pipTheGeek,

@pipTheGeek - Penso che sia davvero un punto controverso. Mentre la documentazione è sicuramente importante, si aspetta anche che lo sviluppatore utilizzatore sia proattivo o difensivo e che legga effettivamente la documentazione in dettaglio. Opterei per un errore descrittivo / amichevole su una buona documentazione poiché l'utente finale ha la possibilità di vederne uno e non l'altro; c'è una migliore possibilità che un utente finale comunichi un errore descrittivo a un programmatore scadente rispetto a un programmatore scadente che legge tutti i documenti
STW

4
Nota, se catturi ArgumentException, catturerà anche ArgumentOutOfRange.
Steven Evers,

Che ne dite FormatException: l'eccezione che viene generata quando il formato di un argomento non è valido o quando una stringa di formato composito non è ben formata.
Anthony,

44

Ho votato a favore della risposta di Josh , ma vorrei aggiungerne un'altra alla lista:

System.InvalidOperationException deve essere generato se l'argomento è valido, ma l'oggetto si trova in uno stato in cui l'argomento non deve essere utilizzato.

Aggiornamento preso da MSDN:

InvalidOperationException viene utilizzato nei casi in cui la mancata invocazione di un metodo è causata da motivi diversi dagli argomenti non validi.

Diciamo che il tuo oggetto ha un metodo PerformAction (azione enmSomeAction), enmSomeActions valide sono Open e Close. Se si chiama PerformAction (enmSomeAction.Open) due volte di seguito, la seconda chiamata dovrebbe generare InvalidOperationException (poiché l'arugment era valido, ma non per lo stato corrente del controllo)

Dato che stai già facendo la cosa giusta programmando in modo difensivo, ho un'altra eccezione da menzionare è ObjectDisposedException. Se il tuo oggetto implementa IDisposable, dovresti sempre avere una variabile di classe che tiene traccia dello stato disposto; se l'oggetto è stato eliminato e viene chiamato un metodo su di esso, è necessario aumentare ObjectDisposedException:

public void SomeMethod()
{
    If (m_Disposed) {
          throw new ObjectDisposedException("Object has been disposed")
     }
    // ... Normal execution code
}

Aggiornamento: per rispondere al tuo follow-up: è un po 'una situazione ambigua ed è resa un po' più complicata da un tipo di dati generico (non nel senso di .NET Generics) utilizzato per rappresentare un insieme specifico di dati; un enum o un altro oggetto fortemente tipizzato sarebbe un adattamento più ideale, ma non abbiamo sempre quel controllo.

Personalmente mi sporgerei verso ArgumentOutOfRangeException e fornire un messaggio che indica che i valori validi sono 1-12. Il mio ragionamento è che quando parli di mesi, supponendo che tutte le rappresentazioni intere dei mesi siano valide, ti aspetti un valore compreso tra 1 e 12. Se solo determinati mesi (come i mesi che avevano 31 giorni) fossero validi, allora non avresti a che fare con un intervallo di per sé e lancerei una ArgumentException generica che indicava i valori validi e li documenterei anche nei commenti del metodo.


1
Buon punto. Questo potrebbe spiegare la differenza tra input non valido e non previsto. +1
Daniel Brückner,

Psst, sono d'accordo con te che non ti avrebbe rubato il tuono. Ma da quando l'hai sottolineato, ho aggiornato la mia risposta
JoshBerke,

38

A seconda del valore effettivo e dell'eccezione più adatta:

Se ciò non è abbastanza preciso, basta derivare la propria classe di eccezione ArgumentException.

La risposta di Yoooder mi ha illuminato. Un input non è valido se non è valido in qualsiasi momento, mentre un input è imprevisto se non è valido per lo stato corrente del sistema. Quindi in un secondo momento una InvalidOperationExceptionè una scelta ragionevole.


6
Tratto dalla pagina MSDN su InvalidOperationException: "InvalidOperationException viene utilizzato nei casi in cui la mancata invocazione di un metodo è causata da motivi diversi dagli argomenti non validi."
STW,


3

ArgumentException :

ArgumentException viene generato quando viene invocato un metodo e almeno uno degli argomenti passati non soddisfa la specifica del parametro del metodo chiamato. Tutte le istanze di ArgumentException devono contenere un messaggio di errore significativo che descriva l'argomento non valido, nonché l'intervallo di valori previsto per l'argomento.

Esistono anche alcune sottoclassi per tipi specifici di invalidità. Il collegamento contiene riepiloghi dei sottotipi e quando devono essere applicati.


1

Risposta breve:
Nemmeno

Risposta più lunga: l'
utilizzo dell'argomento Argomento * Eccezione (ad eccezione di una libreria che è un prodotto acceso, come una libreria di componenti) è un odore. Le eccezioni riguardano la gestione di situazioni eccezionali, non i bug e le carenze degli utenti (ovvero i consumatori API).

Risposta più lunga:
generare eccezioni per argomenti non validi è scortese, a meno che non si scriva una libreria.
Preferisco usare asserzioni, per due (o più) ragioni:

  • Non è necessario testare le asserzioni, mentre lo fanno le asserzioni, e test contro ArgumentNullException sembra ridicolo (provalo).
  • Le asserzioni comunicano meglio l'uso previsto dell'unità ed è più vicina alla documentazione eseguibile che a una specifica di comportamento della classe.
  • È possibile modificare il comportamento di violazione dell'asserzione. Ad esempio nella compilazione del debug una finestra di messaggio va bene, in modo che il tuo QA ti colpirà immediatamente con esso (ottieni anche il tuo IDE che si interrompe sulla linea in cui accade), mentre nel test unitario puoi indicare un fallimento di asserzione come un fallimento del test .

Ecco come appare la gestione dell'eccezione nulla (essendo sarcastico, ovviamente):

try {
    library.Method(null);
}
catch (ArgumentNullException e) {
    // retry with real argument this time
    library.Method(realArgument);
}

Si devono usare eccezioni quando la situazione è prevista ma eccezionale (succedono cose che sono al di fuori del controllo del consumatore, come un errore di I / O). Argomento * L'eccezione è un'indicazione di un bug e deve essere (a mio avviso) gestita con test e assistita da Debug.

A proposito: in questo caso particolare, avresti potuto usare il tipo Month, invece di int. C # non è all'altezza quando si tratta di digitare safety (Aspect # rulez!) Ma a volte è possibile prevenire (o intercettare in fase di compilazione) tutti questi bug tutti insieme.

E sì, MicroSoft ha torto al riguardo.


6
IMHO, le eccezioni dovrebbero anche essere lanciate quando il metodo chiamato non può ragionevolmente procedere. Ciò include il caso in cui il chiamante ha superato argomenti fasulli. Cosa faresti invece? Ritorno -1?
John Saunders,

1
Se argomenti non validi provocherebbero il fallimento di una funzione interna, quali sono i pro e i contro di testare gli argomenti per verificarne la validità, anziché catturare un InvalidArgumentException dalla funzione interna e racchiuderlo in uno più informativo? Quest'ultimo approccio sembrerebbe migliorare le prestazioni nel caso comune, ma non l'ho visto fare molto.
supercat

Una rapida ricerca su Google intorno a questa domanda indica che lanciare l'eccezione generale è la peggiore pratica di tutte. Per quanto riguarda un'affermazione dell'argomento, vedo il merito in questo su piccoli progetti personali, ma non su applicazioni aziendali in cui argomenti probabilmente non validi sono probabilmente dovuti a una configurazione errata o a una scarsa comprensione dell'applicazione.
Max

0

Esiste una ArgumentException standard che è possibile utilizzare oppure è possibile eseguire la sottoclasse e crearne una propria. Esistono diverse classi ArgumentException specifiche:

http://msdn.microsoft.com/en-us/library/system.argumentexception(VS.71).aspx

Qualunque sia la migliore.


1
Non sono d'accordo per quasi tutti i casi; le classi di eccezioni .NET Argument * fornite sono molto comunemente utilizzate e offrono la possibilità di fornire informazioni specifiche sufficienti per informare il consumatore del problema.
STW,

Per chiarire: non sono d'accordo per quasi tutti i casi derivanti dalle classi Argomenti * Eccezioni. L'uso di una delle eccezioni di argomenti .NET e un messaggio descrittivo e chiaro fornisce dettagli sufficienti per più o meno ogni situazione in cui gli argomenti non sono validi.
STW,

d'accordo, ma stavo solo descrivendo le opzioni disponibili. Avrei sicuramente dovuto essere più chiaro sul metodo "preferito".
Scott M.,
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.