¿Cuándo se debe lanzar una IllegalArgumentException?

98

Me preocupa que se trate de una excepción de tiempo de ejecución, por lo que probablemente debería usarse con moderación.
Caso de uso estándar:

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

Pero eso parece que forzaría el siguiente diseño:

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

Para que vuelva a ser una excepción marcada.

Está bien, pero vayamos con eso. Si da una entrada incorrecta, obtendrá un error de tiempo de ejecución. Entonces, en primer lugar, esa es en realidad una política bastante difícil de implementar de manera uniforme, porque podría tener que hacer la conversión totalmente opuesta:

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

Y lo que es peor: si bien 0 <= pct && pct <= 100se puede esperar que la verificación del código del cliente se haga de forma estática, esto no es así para datos más avanzados, como una dirección de correo electrónico, o peor aún, algo que debe verificarse con una base de datos, por lo tanto, en general, el código del cliente no puede validar.

Básicamente, lo que estoy diciendo es que no veo una política coherente significativa para el uso de IllegalArgumentException. Parece que no debería usarse y deberíamos ceñirnos a nuestras propias excepciones marcadas. ¿Cuál es un buen caso de uso para lanzar esto?

djechlin
fuente

Respuestas:

80

El documento API para IllegalArgumentException:

Se lanza para indicar que a un método se le ha pasado un argumento ilegal o inapropiado.

Al observar cómo se usa en las bibliotecas JDK , diría:

  • Parece una medida defensiva quejarse de una entrada obviamente mala antes de que la entrada pueda entrar en funcionamiento y hacer que algo falle a la mitad con un mensaje de error sin sentido.

  • Se usa para casos en los que sería demasiado molesto lanzar una excepción marcada (aunque aparece en el código java.lang.reflect, donde la preocupación por los niveles ridículos de lanzamiento de excepciones marcadas no es aparente).

Solía IllegalArgumentExceptionhacer una última revisión de argumentos defensivos para las utilidades comunes (tratando de mantener la coherencia con el uso de JDK). O donde la expectativa es que un mal argumento sea un error del programador, similar a un NullPointerException. No lo usaría para implementar la validación en el código comercial. Ciertamente no lo usaría para el ejemplo del correo electrónico.

Nathan Hughes
fuente
8
Creo que el consejo "donde la expectativa es que un mal argumento es un error del programador" es más consistente con la forma en que he visto que se usa, por lo que acepto esta respuesta.
djechlin
22

Cuando se habla de "entrada incorrecta", debe considerar de dónde proviene la entrada.

Si la entrada es ingresada por un usuario u otro sistema externo que no controla, debe esperar que la entrada no sea válida y siempre validarla. Está perfectamente bien lanzar una excepción marcada en este caso. Su aplicación debería 'recuperarse' de esta excepción proporcionando un mensaje de error al usuario.

Si la entrada se origina en su propio sistema, por ejemplo, su base de datos, o alguna otra parte de su aplicación, debería poder confiar en que será válida (debería haber sido validada antes de llegar allí). En este caso, está perfectamente bien lanzar una excepción no verificada como una IllegalArgumentException, que no debe detectarse (en general, nunca debe detectar excepciones no verificadas). Es un error del programador que el valor inválido haya llegado allí en primer lugar;) Debe corregirlo.

Tom
fuente
2
¿Por qué "nunca debería detectar excepciones no comprobadas"?
Koray Tugay
9
Porque una excepción sin marcar está destinada a ser lanzada como resultado de un error de programación. No se puede esperar razonablemente que la persona que llama a un método que arroja tales excepciones se recupere de él y, por lo tanto, normalmente no tiene sentido detectarlas.
Tom
1
Because an unchecked exception is meant to be thrown as a result of a programming errorme ayudó a aclarar muchas cosas en mi cabeza, gracias :)
svarog
14

Lanzar excepciones en tiempo de ejecución "con moderación" no es realmente una buena política - Effective Java recomienda que use excepciones marcadas cuando se pueda esperar razonablemente que la persona que llama se recupere . (El error del programador es un ejemplo específico: si un caso particular indica un error del programador, entonces debe lanzar una excepción sin marcar; desea que el programador tenga un seguimiento de la pila de dónde ocurrió el problema lógico, no para tratar de manejarlo usted mismo).

Si no hay esperanza de recuperación, no dude en utilizar excepciones no comprobadas; no tiene sentido atraparlos, así que está perfectamente bien.

Sin embargo, no está 100% claro a partir de su ejemplo en qué caso se encuentra este ejemplo en su código.

Louis Wasserman
fuente
Creo que "razonablemente se espera que se recupere" es una comadreja. Cualquier operación foo(data)podría haber sucedido como parte de la for(Data data : list) foo(data);cual la persona que llama podría querer que la mayor cantidad posible de personas tengan éxito aunque algunos datos estén mal formados. También incluye errores programáticos, si mi aplicación falla significa que una transacción no se realizará, probablemente sea mejor, si significa que el enfriamiento nuclear se desconecta, eso es malo.
djechlin
StackOverflowErrory tales son los casos de los que no es razonable esperar que la persona que llama se recupere. Pero parece que se debe verificar cualquier caso de nivel lógico de aplicación o datos. ¡Eso significa que haga sus comprobaciones de puntero nulo!
djechlin
4
En una aplicación de enfriamiento nuclear, prefiero fallar en las pruebas que permitir que un caso que el programador pensó que era imposible pasar desapercibido.
Louis Wasserman
Boolean.parseBoolean (..), arroja una IllegalArugmentException aunque "es razonable esperar que la persona que llama se recupere". entonces ..... depende de su código manejarlo o fallar a la persona que llama.
Jeryl Cook
5

Como se especifica en el tutorial oficial de Oracle, establece que:

Si es razonable esperar que un cliente se recupere de una excepción, conviértala en una excepción marcada. Si un cliente no puede hacer nada para recuperarse de la excepción, conviértala en una excepción sin marcar.

Si tengo una aplicación que interactúa con la base de datos JDBC, y tengo un método que toma el argumento como int itemy double price. El priceelemento correspondiente se lee de la tabla de la base de datos. Simplemente multiplico el número total de itemcompras por el pricevalor y devuelvo el resultado. Aunque siempre estoy seguro al final (final de la aplicación) de que el valor del campo de precio en la tabla nunca podría ser negativo. ¿Pero qué pasa si el valor del precio sale negativo ? Muestra que hay un problema grave con la base de datos. Quizás el operador haya introducido un precio incorrecto. Este es el tipo de problema que la otra parte de la aplicación que llama a ese método no puede anticipar y no puede recuperarse. Está BUGen su base de datos. Y entoncesIllegalArguementException() este caso, se debería arrojar que afirmaría quethe price can't be negative.
Espero haber expresado mi punto con claridad.

Vishal K
fuente
No me gusta este consejo (de Oracle) porque el manejo de excepciones se trata de cómo recuperarse, no de si recuperarse. Por ejemplo, una solicitud de usuario con formato incorrecto no vale la pena bloquear un servidor web completo.
djechlin
5

Cualquier API debe verificar la validez de cada parámetro de cualquier método público antes de ejecutarlo:

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

Representan el 99,9% de las veces errores en la aplicación porque está pidiendo operaciones imposibles por lo que al final son bugs que deberían bloquear la aplicación (por lo que es un error no recuperable).

En este caso y siguiendo el enfoque de falla rápida, debe dejar que la aplicación finalice para evitar dañar el estado de la aplicación.

Ignacio Soler García
fuente
Por el contrario, si un cliente API me da una mala entrada, debería no bloquee toda mi servidor de la API.
djechlin
2
Por supuesto, no debería bloquear su servidor API, sino devolver una excepción a la persona que llama. Eso no debería bloquear nada más que el cliente.
Ignacio Soler García
Lo que escribió en el comentario no es lo que escribió en la respuesta.
djechlin
1
Permítanme explicarles, si la llamada a la API con parámetros incorrectos (un error) la realiza un cliente de terceros, entonces el cliente debería fallar. Si es el servidor API el que tiene un error al llamar al método con parámetros incorrectos, entonces el servidor API debería fallar. Verificar: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler García
1

Trate IllegalArgumentExceptioncomo una verificación de las condiciones previas y considere el principio de diseño: un método público debe conocer y documentar públicamente sus propias condiciones previas.

Estoy de acuerdo en que este ejemplo es correcto:

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

Si EmailUtil es opaco , lo que significa que hay alguna razón por la que no se pueden describir las condiciones previas al usuario final, entonces una excepción marcada es correcta. La segunda versión, corregida para este diseño:

import com.someoneelse.EmailUtil;

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

Si EmailUtil es transparente , por ejemplo, tal vez sea un método privado propiedad de la clase en cuestión, IllegalArgumentExceptiones correcto si y solo si sus condiciones previas se pueden describir en la documentación de la función. Esta también es una versión correcta:

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

Este diseño podría ir de cualquier manera.

  • Si las condiciones previas son caras de describir, o si la clase está destinada a clientes que no saben si sus correos electrónicos son válidos, utilice ParseException. El método de nivel superior aquí se nombra, lo scanEmailque sugiere que el usuario final tiene la intención de enviar un correo electrónico no estudiado, por lo que probablemente sea correcto.
  • Si las condiciones previas se pueden describir en la documentación de la función, y la clase no intenta una entrada no válida y, por lo tanto, se indica un error del programador, use IllegalArgumentException. Aunque no está "marcado", el "cheque" se mueve al Javadoc que documenta la función, que se espera que el cliente cumpla. IllegalArgumentExceptiondonde el cliente no puede decir que su argumento es ilegal de antemano está mal.

Una nota sobre IllegalStateException : Esto significa que "el estado interno de este objeto (variables de instancia privadas) no puede realizar esta acción". El usuario final no puede ver el estado privado, por lo que, hablando libremente, tiene prioridad IllegalArgumentExceptionen el caso de que la llamada del cliente no tenga forma de saber que el estado del objeto es inconsistente. No tengo una buena explicación cuando se prefiere a las excepciones marcadas, aunque cosas como inicializar dos veces o perder una conexión de base de datos que no se recupera, son ejemplos.

djechlin
fuente