Dovresti lanciare un'eccezione se i valori di input di un metodo non sono compresi nell'intervallo? [duplicare]

15

Dovresti lanciare un'eccezione se i valori di input di un metodo non sono compresi nell'intervallo? ad esempio

//no imaginary numbers
public int MySquareRoot(int x)
{
    if (x<0)
    {
    throw new ArgumentOutOfBoundsException("Must be a non-negative integer"); 
    }

    //our implementation here 

}

Ora questo metodo non dovrebbe mai essere chiamato con un numero non negativo, ma i programmatori hey commettono errori. Lanciare eccezioni qui è la cosa giusta da fare?

    
posta dwjohnston 13.05.2014 - 10:08
fonte

6 risposte

20

Sì, credo che dovresti fare un'eccezione, per aiutare i tuoi colleghi programmatori a notare l'errore in fase di compilazione, aggiungendo anche throws ArgumentOutOfBoundsException nella dichiarazione del metodo.

Cioè, a meno che non si utilizzino numeri immaginari nel progetto, dove -1 è un argomento perfettamente valido per il proprio metodo di radice quadrata. :)

    
risposta data 13.05.2014 - 10:30
fonte
15

Se puoi codificare il tuo metodo o la sua firma in modo tale che la condizione di eccezione non possa accadere, farlo è probabilmente l'approccio migliore.

Ad esempio, con l'esempio in particolare, la tua lingua ha un tipo di dati intero senza segno con un intervallo appropriato? In tal caso, basta usare quello invece di un corrispondente numero intero con segno e generare un'eccezione per qualsiasi valore passato che sia inferiore a 0. In tal caso, un valore negativo non può essere utilizzato in quella posizione in un programma ben formato; il compilatore prenderà l'errore molto prima del runtime, e almeno emetterà un avvertimento (per il tipo firmato / non firmato usare la mancata corrispondenza), a meno che non si faccia in modo di ignorare esplicitamente tale rete di sicurezza; e se stai forzando una variabile firmata in una funzione la cui firma del metodo dice solo un intero senza segno, assicurandoti che il valore sia all'interno di un intervallo accettabile per quella funzione, allora direi che il programmatore che lo fa merita di ottenere un'eccezione generata in faccia.

Come altro esempio, se solo un insieme di valori ragionevolmente piccolo è valido come input, forse quelli possono essere espressi come un'enumerazione con un insieme specifico di valori piuttosto che usare un valore intero semplice per codificare ogni possibile valore? Anche se sotto il cofano l'enum collassa a un int, usando le enumerazioni protegge dagli errori del programmatore.

Se non puoi scrivere la firma del metodo in modo tale che la condizione di eccezione non possa accadere in un programma ben formato, allora direi che questo è esattamente ciò che ArgumentOutOfRangeException , IllegalArgumentException e per i quali sono previsti tipi di eccezioni simili. Questo può anche essere banalmente lavorato in un flusso di lavoro TDD. Assicurati solo di chiarire quale sia l'errore , in particolare se non hai un facile accesso al codice sorgente, ottenere un ArgumentOutOfRangeException non specifico restituito a te può essere molto frustrante. Nota la documentazione per questi tipi di eccezioni:

ArgumentOutOfRangeException (.NET): "The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method." (as seen here)

IllegalArgumentException (Java): "Thrown to indicate that a method has been passed an illegal or inappropriate argument." (as seen here)

Scrivere il metodo per rendere impossibile l'inizio della condizione di eccezione aiuterà molto di più che lanciare un'eccezione in fase di runtime, ma dobbiamo riconoscere che talvolta ciò non è possibile nella pratica. Tuttavia, sarà necessario verificare gli invarianti non direttamente correlati all'intervallo di un singolo valore del parametro in fase di runtime; ad esempio, se x + y > 20 è un errore, ma xey possono essere qualsiasi cosa < = 20, non penso che ci sia un modo semplice per esprimerlo nella firma del metodo.

    
risposta data 13.05.2014 - 14:52
fonte
9

Comprendo la tua domanda come " Dovresti convalidare i dati di input? ", invece di "Dovresti usare Eccezioni invece dei codici di errore?".

Quando accetti i parametri di input dall'esterno, che si tratti di un metodo con parametri di chiamata, dati di input XML da una chiamata di servizio o una pagina web con campi utente, hai fondamentalmente due scelte :

  1. effettua rigorus controlla se i dati di input corrispondono al formato previsto
  2. fidati del tuo chiamante solo per chiamarti con dati corretti

L'aggiunta di controlli aggiunge complessità

  1. lo sviluppatore iniziale deve pensare di tutti i valori validi (o "eventualmente non validi") e aggiungere controlli appropriati. Idealmente questo viene fatto all'inizio del metodo, anche se a volte deve essere fatto in seguito (ad esempio se "valore di input valido" è "l'ID di un oggetto effettivamente esistente nel database")
  2. altri sviluppatori che cercano di capire la reale logica del metodo, poiché aggiunge più linee per investigare.
  3. overhead di runtime per controllare tutte le ipotesi

D'altro canto, la convalida dell'input offre preziosi vantaggi :

  1. Ti consente di rendere le ipotesi più in profondità nella codifica, poiché sono già verificate
  2. altri sviluppatori potrebbero avere più cose da leggere, ma fare delle ipotesi sui dati di input espliciti anche aiuta a capire la logica , e quei casi d'angolo sono presi in considerazione.
  3. Assicura che nessuno stato di programma corrotto (arresti anomali, corruzione dei dati, vulnerabilità della sicurezza) possa verificarsi.

Quindi la scelta di aggiungere controlli dovrebbe essere eseguita nel contesto dove viene utilizzato il tuo metodo.

  • È esposto a dati esterni non attendibili, come siti Web o API pubbliche? Quindi esegui definitivamente la validazione.
  • è un metodo privato in un cosiddetto inner-loop, protetto dalla codifica esterna? Quindi probabilmente non vuoi controlli ridondanti. Potresti voler documentare le tue aspettative (ad es. JavaDoc), anche se questo è raramente fatto in pratica.
  • alcune lingue forniscono asserzioni compilate condizionatamente, che sono abilitate nelle build di debug per rilevare errori di logica del programma, ma disabilitate per le prestazioni in uso produttivo
  • per le librerie devi decidere di quanto ti fidi dei chiamanti. Meglio sbagliare sul lato della cautela e aggiungere controlli, specialmente se non vuoi che i tuoi invarianti di libreria siano compromessi. Per le funzioni relative alla velocità, tuttavia, puoi decidere in modo diverso (e documentarlo).
  • di solito viene eseguita una combinazione: buona convalida sui metodi di fronte esterno, alcune convalida sui livelli di interfaccia, pochissimi (spesso solo NullPointerCheck) nel funzionamento interno di un programma.

Se la tua domanda tuttavia era " Le eccezioni sono il modo giusto per segnalare i dati di input non validi? ", la risposta è: molto probabilmente sì .

Le

eccezioni impongono un sovraccarico sulle prestazioni del runtime, ma rendono il ragionamento sul flusso del programma drasticamente più semplice. Ciò riduce la programmazione errata (errori semantici), soprattutto perché ti costringe a gestirli - essi "falliscono in modo sicuro" chiudendo il programma se vengono ignorati. Sono ideali per "situazioni che non dovrebbero accadere". Inoltre possono trasportare i metadati come uno stacktrace.

I codici di errore , d'altra parte, sono leggeri, veloci, ma costringono il chiamante del metodo a controllarli esplicitamente. In caso contrario, i difetti del programma, che possono variare da un danneggiamento silenzioso dei dati, buchi di sicurezza, a dei fuochi d'artificio se il tuo programma è in esecuzione all'interno di un razzo spaziale.

Un esempio in cui personalmente avrei preferito i codici di errore invece delle eccezioni è il metodo String.parseInt () in Java. Ottenere una stringa non digitata da una sorgente di input (ad es. Un utente) non è del tutto inaspettato e devo affrontarlo in ogni caso - a volte semplice come usare un valore predefinito. Come chiamante, non ho modo di controllare i dati se provocherebbe un'eccezione (a meno di implementare la logica di validazione da solo), generando così l'eccezione come parte del normale flusso di programma e l'utilizzo di blocchi try-catch è l'unica scelta qui .

Tieni presente che i codici di errore potrebbero essere sia in-band che out-of-band:

  • In-band : un valore speciale (magico) viene dichiarato come marker per la condizione di errore. Spesso qui viene utilizzato null o -1, ad es. nei metodi di ricerca delle stringhe
  • Out-of-Band : viene restituito un valore separato, che indica "nessun errore" (in genere 0 o null) o l'errore specifico. Questo può essere fatto come / un parametro di ritorno aggiuntivo (per le lingue che supportano più valori di ritorno, ad esempio Googles GO lo supporta in particolare per gli errori) o come requisito per chiamare un metodo speciale "getError ()".
risposta data 13.05.2014 - 17:58
fonte
5

Se chiamo una funzione, mi aspetto che faccia qualcosa. Se non può fare ciò che ho richiesto, dovrei essere informato - la funzione non dovrebbe fare nulla o fare qualcosa di sbagliato.

Posso essere avvisato da esso restituendo un valore di ritorno specifico (ad esempio, una funzione sqrt in virgola mobile potrebbe restituire NaN per un numero errato), impostare una variabile di errore esterna o generare un'eccezione. (Il primo o il terzo sono preferiti.)

Nel tuo caso, la tua funzione non può fare ciò che viene richiesto quando x è minore di zero. Lanciare un'eccezione dicendo che l'argomento è fuori portata è perfettamente accettabile e ben informato.

    
risposta data 13.05.2014 - 16:14
fonte
4

Vorrei aggiungere un altro motivo per cui lanciare un'eccezione è la scelta migliore qui: unit test e TDD.

Ogni framework di testing maturo dovrebbe essere in grado di affermare che un determinato metodo genera quando si fornisce un determinato input. Scrivendo un test unitario che verifica tale eccezione, puoi documentare l'input valido e non valido per il metodo e allo stesso tempo convalidare che l'input non valido verrà effettivamente rifiutato.

Questo è molto più difficile quando si usano asserzioni (o qualche altro meccanismo).

    
risposta data 13.05.2014 - 13:56
fonte
4

Dovresti fare qualcosa, come questa risposta dice , le eccezioni sono utili, è buono per fallire -velocemente ecc. ecc. In effetti aggiungerei che aiutano nel tempo di lettura, direi che documentano meglio l'input desiderato rispetto al commento principale nel tuo esempio.

Tuttavia ci sono alternative. Se ho ipotizzato correttamente le tue lingue: Debug.Assert(x > 0) e Contract.Requires( x > 0 );

Tuttavia, il consenso sembra essere che usare le eccezioni in una build di rilascio è l'approccio migliore.

Gli avvisi sembrano destinati al debugging ed è più utile spedire con le eccezioni e che i Contracts non sono ancora abbastanza e non è buono per i metodi pubblici. Non è banale e non si presta a un rapido riassunto. Suggerisco di leggere quella pagina.

Nel complesso, tenderei a privilegiare le eccezioni, ma le affermazioni possono essere una scelta decente anche per i metodi interni.

    
risposta data 13.05.2014 - 11:02
fonte

Leggi altre domande sui tag