Quando deve essere generata un'eccezione IllegalArgumentException?


98

Sono preoccupato che questa sia un'eccezione di runtime, quindi probabilmente dovrebbe essere usata con parsimonia.
Caso d'uso standard:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Ma sembra che forzerebbe il seguente design:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

Per riportarlo ad essere un'eccezione controllata.

Va bene, ma andiamo con quello. Se dai un input errato, ottieni un errore di runtime. Quindi, in primo luogo, questa è in realtà una politica abbastanza difficile da implementare in modo uniforme, perché potresti dover eseguire la conversione esattamente opposta:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

E peggio ancora: mentre 0 <= pct && pct <= 100ci si può aspettare che il controllo del codice client avvenga in modo statico, non è così per dati più avanzati come un indirizzo e-mail o, peggio, qualcosa che deve essere controllato in un database, quindi in generale il codice client non può pre- convalidare.

Quindi sostanzialmente quello che sto dicendo è che non vedo una politica coerente e significativa per l'uso di IllegalArgumentException. Sembra che non dovrebbe essere utilizzato e dovremmo attenerci alle nostre eccezioni controllate. Qual è un buon caso d'uso per lanciarlo?

Risposte:


80

Il documento API per IllegalArgumentException:

Lanciato per indicare che a un metodo è stato passato un argomento illegale o inappropriato.

Guardando come viene utilizzato nelle librerie JDK , direi:

  • Sembra una misura difensiva lamentarsi di un input ovviamente sbagliato prima che l'input possa entrare nei lavori e causare il fallimento di qualcosa a metà con un messaggio di errore senza senso.

  • Viene utilizzato per i casi in cui sarebbe troppo fastidioso lanciare un'eccezione controllata (sebbene faccia la sua comparsa nel codice java.lang.reflect, dove la preoccupazione per livelli ridicoli di lancio di eccezioni verificate non è altrimenti evidente).

Userei IllegalArgumentExceptionper fare l'ultimo controllo difensivo degli argomenti per le utilità comuni (cercando di rimanere coerente con l'utilizzo di JDK). O dove l'aspettativa è che un cattivo argomento sia un errore del programmatore, simile a un file NullPointerException. Non lo userei per implementare la convalida nel codice aziendale. Certamente non lo userei per l'esempio di posta elettronica.


8
Penso che il consiglio "dove l'aspettativa è che un cattivo argomento sia un errore del programmatore" sia più coerente con come l'ho visto usato, quindi accetta questa risposta.
Djechlin

22

Quando parli di "input errato", dovresti considerare da dove proviene l'input.

È l'input immesso da un utente o da un altro sistema esterno che non controlli, dovresti aspettarti che l'input non sia valido e convalidarlo sempre. È perfettamente corretto lanciare un'eccezione selezionata in questo caso. L'applicazione dovrebbe "recuperare" da questa eccezione fornendo un messaggio di errore all'utente.

Se l'input proviene dal tuo sistema, ad esempio dal tuo database o da altre parti della tua applicazione, dovresti essere in grado di fare affidamento su di esso per essere valido (avrebbe dovuto essere convalidato prima che arrivasse lì). In questo caso è perfettamente corretto lanciare un'eccezione non controllata come IllegalArgumentException, che non dovrebbe essere catturata (in generale non dovresti mai catturare eccezioni non verificate). È un errore del programmatore che il valore non valido sia arrivato lì in primo luogo;) È necessario correggerlo.


2
Perché "non dovresti mai rilevare eccezioni non controllate"?
Koray Tugay

9
Perché un'eccezione non controllata deve essere generata a seguito di un errore di programmazione. Non ci si può ragionevolmente aspettare che il chiamante di un metodo che genera tali eccezioni si riprenda da esso, e quindi in genere non ha senso rilevarle.
Tom il

1
Because an unchecked exception is meant to be thrown as a result of a programming errormi ha aiutato a chiarire un sacco di cose nella mia testa, grazie :)
svarog

14

Lanciare eccezioni di runtime "con parsimonia" non è davvero una buona politica - Effective Java consiglia di utilizzare eccezioni controllate quando ci si può ragionevolmente aspettare che il chiamante si ripristini . (L'errore del programmatore è un esempio specifico: se un caso particolare indica un errore del programmatore, dovresti lanciare un'eccezione non controllata; vuoi che il programmatore abbia una traccia dello stack di dove si è verificato il problema logico, non provare a gestirlo da solo.)

Se non c'è speranza di guarigione, sentiti libero di usare eccezioni non controllate; non ha senso catturarli, quindi va benissimo.

Tuttavia, non è chiaro al 100% dal tuo esempio quale caso questo esempio sia nel tuo codice.


Penso che "ragionevolmente previsto per recuperare" sia weaselly. foo(data)Potrebbe essersi verificata qualsiasi operazione for(Data data : list) foo(data);in cui il chiamante potrebbe desiderare che il maggior numero possibile di operazioni riuscisse anche se alcuni dati non sono corretti. Include anche errori programmatici, se la mia applicazione fallisce significa che una transazione non andrà a buon fine, probabilmente è meglio, se significa che il raffreddamento nucleare va offline, è un male.
Djechlin

StackOverflowErrore tali sono casi da cui non ci si può ragionevolmente aspettare che il chiamante si riprenda. Ma sembra che tutti i dati o il caso del livello logico dell'applicazione debbano essere controllati. Ciò significa che fai i controlli del tuo puntatore nullo!
Djechlin

4
In un'applicazione di raffreddamento nucleare, preferirei fallire duramente nei test piuttosto che consentire un caso che il programmatore riteneva impossibile passare inosservato.
Louis Wasserman

Boolean.parseBoolean (..), genera un'eccezione IllegalArugmentException anche se "ci si può ragionevolmente aspettare che il chiamante si riprenda". quindi ... sta al tuo codice gestirlo o non rispondere al chiamante.
Jeryl Cook

5

Come specificato nel tutorial ufficiale di Oracle, afferma che:

Se ci si può ragionevolmente aspettare che un client si riprenda da un'eccezione, renderla un'eccezione controllata. Se un client non può eseguire alcuna operazione per il ripristino dall'eccezione, rendila un'eccezione non controllata.

Se ho un'applicazione che interagisce con il database utilizzando JDBC, e ho un metodo che accetta l'argomento come int iteme double price. L' priceelemento corrispondente viene letto dalla tabella del database. Moltiplico semplicemente il numero totale di itemacquisti per il pricevalore e restituisco il risultato. Anche se sono sempre sicuro alla fine (Fine applicazione) che il valore del campo prezzo nella tabella non potrebbe mai essere negativo, ma cosa succede se il valore del prezzo risulta negativo ? Mostra che c'è un problema serio con il lato database. Forse l'inserimento del prezzo sbagliato da parte dell'operatore. Questo è il tipo di problema che l'altra parte dell'applicazione che chiama quel metodo non può anticipare e non può risolverlo. È BUGnel tuo database. Così eIllegalArguementException()dovrebbe essere lanciato in questo caso che lo affermerebbe the price can't be negative.
Spero di aver espresso chiaramente il mio punto ..


Non mi piace questo consiglio (di Oracle) perché la gestione delle eccezioni riguarda come recuperare, non se ripristinare. Ad esempio, una richiesta utente non valida non vale la pena di bloccare un intero server web.
Djechlin

5

Qualsiasi API dovrebbe verificare la validità di ogni parametro di qualsiasi metodo pubblico prima di eseguirlo:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

Rappresentano il 99,9% delle volte che si verificano errori nell'applicazione perché richiede operazioni impossibili quindi alla fine sono bug che dovrebbero mandare in crash l'applicazione (quindi è un errore non recuperabile).

In questo caso e seguendo l'approccio del fail fast dovresti lasciare che l'applicazione finisca per evitare di danneggiare lo stato dell'applicazione.


Al contrario, se un client API mi dà una brutta entrata, dovrei non in crash tutto il mio server API.
Djechlin

2
Ovviamente, non dovrebbe mandare in crash il tuo server API ma restituire un'eccezione al chiamante che non dovrebbe mandare in crash nient'altro che il client.
Ignacio Soler Garcia

Quello che hai scritto nel commento non è quello che hai scritto nella risposta.
Djechlin

1
Lasciatemi spiegare, se la chiamata all'API con parametri errati (un bug) viene effettuata da un client di terze parti, il client dovrebbe bloccarsi. Se è il server API quello con un bug che chiama il metodo con parametri sbagliati, il server API dovrebbe bloccarsi. Controlla: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia

1

Trattare IllegalArgumentExceptioncome un controllo delle precondizioni e considerare il principio di progettazione: un metodo pubblico dovrebbe conoscere e documentare pubblicamente le proprie precondizioni.

Sono d'accordo che questo esempio sia corretto:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Se EmailUtil è opaco , il che significa che per qualche motivo le precondizioni non possono essere descritte all'utente finale, un'eccezione selezionata è corretta. La seconda versione, corretta per questo design:

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

Se EmailUtil è trasparente , ad esempio forse è un metodo privato di proprietà della classe in questione, IllegalArgumentExceptionè corretto se e solo se le sue precondizioni possono essere descritte nella documentazione della funzione. Anche questa è una versione corretta:

/** @param String email An email with an address in the form abc@xyz.com
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

Questo design potrebbe andare in entrambi i modi.

  • Se le condizioni preliminari sono costose da descrivere, o se la classe è destinata ad essere utilizzata da clienti che non sanno se le loro email sono valide, allora usa ParseException. Il metodo di primo livello qui è denominato scanEmailche suggerisce che l'utente finale intende inviare e-mail non studiate, quindi è probabile che sia corretto.
  • Se le condizioni preliminari possono essere descritte nella documentazione della funzione e la classe non intende fornire input non validi e quindi viene indicato un errore del programmatore, utilizzare IllegalArgumentException. Sebbene non "controllato", il "controllo" si sposta in Javadoc che documenta la funzione, a cui il client dovrebbe aderire. IllegalArgumentExceptiondove il cliente non può dire in anticipo che il suo argomento è illegale è sbagliato.

Una nota su IllegalStateException : Ciò significa che "lo stato interno di questo oggetto (variabili di istanza private) non è in grado di eseguire questa azione". L'utente finale non può vedere lo stato privato così liberamente parlando ha la precedenza IllegalArgumentExceptionnel caso in cui la chiamata del client non ha modo di sapere che lo stato dell'oggetto è incoerente. Non ho una buona spiegazione quando è preferibile rispetto alle eccezioni controllate, anche se cose come l'inizializzazione due volte o la perdita di una connessione al database che non viene ripristinata, sono esempi.

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.