Quand une IllegalArgumentException doit-elle être levée?

98

Je crains qu'il ne s'agisse d'une exception d'exécution, donc elle devrait probablement être utilisée avec parcimonie.
Cas d'utilisation standard:

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

Mais cela semble forcer la conception suivante:

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

Pour qu'il redevienne une exception vérifiée.

D'accord, mais allons-y avec ça. Si vous donnez une mauvaise entrée, vous obtenez une erreur d'exécution. Donc, tout d'abord, c'est en fait une politique assez difficile à mettre en œuvre uniformément, car vous pourriez avoir à faire la conversion tout à fait inverse:

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

Et pire - alors que la vérification 0 <= pct && pct <= 100du code client pourrait être censée faire de manière statique, ce n'est pas le cas pour des données plus avancées telles qu'une adresse e-mail, ou pire, quelque chose qui doit être vérifié par rapport à une base de données, donc en général, le code client ne peut pas pré- valider.

Donc, fondamentalement, ce que je dis, c'est que je ne vois pas de politique cohérente significative pour l'utilisation de IllegalArgumentException. Il semble qu'il ne devrait pas être utilisé et que nous devrions nous en tenir à nos propres exceptions vérifiées. Quel est un bon cas d'utilisation pour lancer cela?

Djechlin
la source

Réponses:

80

La documentation de l'API pour IllegalArgumentException:

Lancé pour indiquer qu'une méthode a reçu un argument illégal ou inapproprié.

En regardant comment il est utilisé dans les bibliothèques JDK , je dirais:

  • Cela semble être une mesure défensive de se plaindre d'une entrée manifestement mauvaise avant que l'entrée puisse entrer dans les travaux et provoquer l'échec de quelque chose à mi-chemin avec un message d'erreur absurde.

  • Il est utilisé dans les cas où il serait trop ennuyeux de lancer une exception vérifiée (bien que cela apparaisse dans le code java.lang.reflect, où l'inquiétude concernant les niveaux ridicules de lancement d'exceptions vérifiées n'est pas apparente autrement).

J'utiliserais IllegalArgumentExceptionpour faire la vérification des arguments défensifs de dernière minute pour les utilitaires courants (en essayant de rester cohérent avec l'utilisation du JDK). Ou où l'on s'attend à ce qu'un mauvais argument soit une erreur de programmeur, similaire à un NullPointerException. Je ne l'utiliserais pas pour implémenter la validation dans le code métier. Je ne l'utiliserais certainement pas pour l'exemple de courrier électronique.

Nathan Hughes
la source
8
Je pense que le conseil "où l'on s'attend à ce qu'un mauvais argument soit une erreur de programmeur" est le plus cohérent avec la façon dont j'ai vu cela utilisé, donc accepter cette réponse.
djechlin
22

Lorsque vous parlez de "mauvaise entrée", vous devez considérer d'où provient l'entrée.

Est-ce que l'entrée est entrée par un utilisateur ou un autre système externe que vous ne contrôlez pas, vous devez vous attendre à ce que l'entrée soit invalide et toujours la valider. Il est parfaitement normal de lancer une exception cochée dans ce cas. Votre application doit «récupérer» de cette exception en fournissant un message d'erreur à l'utilisateur.

Si l'entrée provient de votre propre système, par exemple votre base de données, ou d'autres parties de votre application, vous devriez pouvoir compter sur elle pour être valide (elle devrait avoir été validée avant d'y arriver). Dans ce cas, il est parfaitement possible de lancer une exception non vérifiée comme une IllegalArgumentException, qui ne devrait pas être interceptée (en général, vous ne devriez jamais intercepter les exceptions non vérifiées). C'est une erreur du programmeur que la valeur invalide y soit arrivée en premier lieu;) Vous devez la corriger.

À M
la source
2
Pourquoi "vous ne devriez jamais attraper des exceptions non contrôlées"?
Koray Tugay
9
Parce qu'une exception non vérifiée est destinée à être levée à la suite d'une erreur de programmation. On ne peut raisonnablement pas s'attendre à ce que l'appelant d'une méthode lançant de telles exceptions s'en remette, et par conséquent, il n'a généralement aucun sens de les intercepter.
Tom
1
Because an unchecked exception is meant to be thrown as a result of a programming errorm'a aidé à nettoyer beaucoup de choses dans ma tête, merci :)
svarog
14

Lancer des exceptions d'exécution "avec parcimonie" n'est pas vraiment une bonne politique - Effective Java recommande d'utiliser des exceptions vérifiées lorsque l'appelant peut raisonnablement être appelé à récupérer . (L'erreur de programmeur est un exemple spécifique: si un cas particulier indique une erreur de programmeur, vous devez alors lancer une exception non vérifiée; vous voulez que le programmeur ait une trace de pile de l'endroit où le problème de logique s'est produit, pas d'essayer de le gérer vous-même.)

S'il n'y a aucun espoir de récupération, n'hésitez pas à utiliser des exceptions non cochées; il ne sert à rien de les attraper, donc c'est parfaitement bien.

Cependant, il n'est pas clair à 100% dans votre exemple dans quel cas cet exemple est dans votre code.

Louis Wasserman
la source
Je pense que "on s'attend raisonnablement à ce qu'il récupère" est ridicule. Toute opération foo(data)aurait pu se produire dans le cadre de for(Data data : list) foo(data);laquelle l'appelant pourrait souhaiter que le plus grand nombre réussisse, même si certaines données sont mal formées. Inclut également les erreurs de programmation, si l'échec de mon application signifie qu'une transaction ne sera pas exécutée, c'est probablement mieux, si cela signifie que le refroidissement nucléaire est hors ligne, c'est mauvais.
djechlin
StackOverflowErroret tels sont des cas dont on ne peut raisonnablement s'attendre à ce que l'appelant récupère. Mais il semble que tout cas de niveau logique de données ou d'application devrait être vérifié. Cela signifie faire vos vérifications de pointeur nul!
djechlin
4
Dans une application de refroidissement nucléaire, je préfère échouer dur dans les tests plutôt que de laisser passer un cas que le programmeur pensait impossible de passer inaperçu.
Louis Wasserman
Boolean.parseBoolean (..), lève une IllegalArugmentException même si «on peut raisonnablement s'attendre à ce que l'appelant récupère». so ..... c'est à votre code de le gérer ou de revenir à l'appelant.
Jeryl Cook
5

Comme spécifié dans le didacticiel officiel d'Oracle, il indique que:

Si l'on peut raisonnablement s'attendre à ce qu'un client récupère d'une exception, faites-en une exception vérifiée. Si un client ne peut rien faire pour récupérer de l'exception, faites-en une exception non cochée.

Si j'ai une application interagissant avec la base de données à l'aide de JDBC, Et j'ai une méthode qui prend l'argument comme le int itemet double price. L' priceélément correspondant pour est lu à partir de la table de base de données. Je multiplie simplement le nombre total d' itemachat par la pricevaleur et renvoie le résultat. Bien que je sois toujours sûr de mon côté (côté application) que la valeur du champ de prix dans le tableau ne peut jamais être négative. Mais que se passe-t-il si la valeur du prix est négative ? Cela montre qu'il y a un problème sérieux avec la base de données. Peut-être une mauvaise entrée de prix par l'opérateur. C'est le genre de problème que l'autre partie de l'application appelant cette méthode ne peut pas anticiper et ne peut pas y remédier. C'est un BUGdans votre base de données. Alors, etIllegalArguementException()devrait être jeté dans ce cas qui indiquerait que the price can't be negative.
J'espère avoir exprimé clairement mon point de vue.

Vishal K
la source
Je n'aime pas ce conseil (oracle) car la gestion des exceptions concerne la manière de récupérer, pas de récupérer. Par exemple, une requête utilisateur mal formée ne vaut pas la peine de planter un serveur Web entier.
djechlin
5

Toute API doit vérifier la validité de chaque paramètre de toute méthode publique avant de l'exécuter:

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");
    }
}

Ils représentent 99,9% des fois des erreurs dans l'application car elle demande des opérations impossibles donc à la fin ce sont des bogues qui devraient planter l'application (c'est donc une erreur non récupérable).

Dans ce cas et en suivant l'approche de l'échec rapide, vous devez laisser l'application se terminer pour éviter de corrompre l'état de l'application.

Ignacio Soler Garcia
la source
Au contraire, si un client API me donne une mauvaise entrée, je ne devrais pas planter tout mon serveur API.
djechlin le
2
Bien sûr, il ne devrait pas planter votre serveur API mais renvoyer une exception à l'appelant. Cela ne devrait pas planter autre chose que le client.
Ignacio Soler Garcia
Ce que vous avez écrit dans le commentaire n'est pas ce que vous avez écrit dans la réponse.
djechlin
1
Laissez-moi vous expliquer, si l'appel à l'API avec des paramètres incorrects (un bogue) est effectué par un client tiers, le client devrait planter. Si c'est le serveur API celui avec un bogue appelant la méthode avec des paramètres incorrects, le serveur API devrait planter. Vérifiez: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia
2

Traitez IllegalArgumentExceptioncomme une vérification des conditions préalables et considérez le principe de conception: une méthode publique devrait à la fois connaître et documenter publiquement ses propres conditions préalables.

Je conviens que cet exemple est correct:

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

Si EmailUtil est opaque , ce qui signifie qu'il y a une raison pour laquelle les conditions préalables ne peuvent pas être décrites à l'utilisateur final, alors une exception vérifiée est correcte. La deuxième version, corrigée pour cette conception:

import com.someoneelse.EmailUtil;

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

Si EmailUtil est transparent , par exemple c'est peut-être une méthode privée appartenant à la classe en question, elle IllegalArgumentExceptionest correcte si et seulement si ses conditions préalables peuvent être décrites dans la documentation de la fonction. Ceci est également une version correcte:

/** @param String email An email with an address in the form [email protected]
 * 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");
  }
}

Cette conception pourrait aller dans les deux sens.

  • Si les conditions préalables sont coûteuses à décrire, ou si la classe est destinée à être utilisée par des clients qui ne savent pas si leurs e-mails sont valides, utilisez ParseException. La méthode de niveau supérieur ici est nommée, scanEmailce qui indique que l'utilisateur final a l'intention d'envoyer des e-mails non étudiés, ce qui est probablement correct.
  • Si les conditions préalables peuvent être décrites dans la documentation des fonctions, et que la classe n'a pas l'intention de saisir une entrée non valide et qu'une erreur de programmeur est donc indiquée, utilisez IllegalArgumentException. Bien que non "coché", le "chèque" se déplace vers le Javadoc documentant la fonction, à laquelle le client est censé adhérer. IllegalArgumentExceptionoù le client ne peut pas dire à l'avance que son argument est illégal est faux.

Une note sur IllegalStateException : Cela signifie que "l'état interne de cet objet (variables d'instance privée) n'est pas en mesure d'effectuer cette action." L'utilisateur final ne peut pas voir l'état privé si vaguement qu'il a la priorité sur IllegalArgumentExceptionle cas où l'appel client n'a aucun moyen de savoir que l'état de l'objet est incohérent. Je n'ai pas une bonne explication quand elle est préférée aux exceptions vérifiées, bien que des choses comme l'initialisation deux fois ou la perte d'une connexion à une base de données non récupérée en soient des exemples.

Djechlin
la source