Cómo lidiar con excepciones comprobadas que nunca se pueden lanzar

35

Ejemplo:

foobar = new InputStreamReader(p.getInputStream(), "ISO-8859-1");

Dado que la codificación está codificada y es correcta, el constructor nunca arrojará la excepción UnsupportedEncodingException declarada en la especificación (a menos que la implementación de Java esté rota, en cuyo caso me perderé de todos modos). De todos modos, Java me obliga a lidiar con esa excepción de todos modos.

Actualmente, se ve así

try {
    foobar = new InputStreamReader(p.getInputStream(), "ISO-8859-1");
}
catch(UnsupportedEncodingException e) { /* won't ever happen */ }

¿Alguna idea de cómo mejorarlo?

usuario281377
fuente
44
Para un verdadero desafío, escriba una prueba unitaria para asegurarse de que esta captura realmente funcione.
Jay Elston el
1
`lanzar una nueva ImpossibleException (" Reiniciar el universo. Las cosas están en mal estado ", e);
Chris Cudmore
1
"Nunca sucederá" será ...
Thorbjørn Ravn Andersen: puede que después de cambiar el programa, pero al menos en su estado actual, ningún conjunto de datos de entrada pueda desencadenar la excepción.
usuario281377
En su ejemplo específico anterior, se produce una excepción porque el método no sabe si se llamará con una codificación compatible o no compatible. Una forma de evitar esto sería si hubiera otro constructor que asumiera que se proporcionó un juego de caracteres estándar, que no necesitaría declarar que se lanzaría una excepción.
Jordania

Respuestas:

27

Mi hábito es, solo estar en el lado seguro, poner un assertbloque de captura. Alguien podría cambiar el contenido del trybloque más tarde, y usted desea saber si el código falla, ¿no?

Péter Török
fuente
Buena idea; un simple assert false;no agrega demasiado desorden y deja en claro que supongo que el bloque catch nunca se ingresará.
usuario281377
55
@ammoQ, o incluso se puede añadir un mensaje para que su intención absolutamente claro: assert false : "should never happen".
Péter Török
13
Aún mejor, "Nunca debería suceder porque Java requiere que el juego de caracteres ISO-8859-1 sea compatible".
dan04
3
assertimplica que las aserciones están habilitadas. Lanzo un UnexpectedException(que también tiene el beneficio de permitirme tener un seguimiento de la pila ...).
Chop
35

Si me hubieran dado un centavo por cada vez que he visto un registro / error, "Esto nunca debería suceder", tendría ... bueno, dos centavos. Pero aún...

Los bloques de captura vacíos hacen que mis sentidos de araña tiemblen y la mayoría de las buenas herramientas de análisis de código se quejan. Evitaría a toda costa dejarlos vacíos. Claro, ahora sabe que el error nunca puede suceder, pero dentro de un año alguien realiza una búsqueda global de "ISO-8859-1" y de repente puede tener un error extremadamente difícil de encontrar.

La assert falsesugerencia es buena, pero dado que las afirmaciones se pueden deshabilitar en tiempo de ejecución, no son garantía. Yo usaría un RuntimeExceptionen su lugar. No tendrá que atraparlos llamando a clases y si alguna vez ocurren, tendrá un seguimiento de la pila para proporcionar información completa.

Fredrik
fuente
28

Siempre lo he hecho así:

try {
    foobar = new InputStreamReader(p.getInputStream(), "ISO-8859-1");
} catch(UnsupportedEncodingException e) {
    throw new AssertionError(e);
}

Puede ser un poco detallado (Java es ...), pero al menos obtendrá un error de aserción cuando ocurra lo imposible.

Si la implementación de Java está rota, querrás obtener el mejor mensaje de error posible, lo más rápido posible, en lugar de ignorar lo imposible. E incluso si la implementación de Java no está rota, alguien podría haber cambiado su código a "UTF8"(¡vaya! ¿Debería haberlo estado "UTF-8"?).

Esto debería haber sido una excepción en tiempo de ejecución en primer lugar. JDK está lleno de este tipo de elecciones equivocadas.

Joonas Pulakka
fuente
44
La verdadera mala decisión (u omisión si lo desea) es que no hay instancias de Charset predefinidas para esos 6 charsets que requiere Java. foobar = new InputStreamReader(p.getInputStream(), Charset.ISO_8859_1);- ¿No sería eso mejor y evitar cualquier error de una vez y para siempre?
usuario281377
3
La biblioteca de guayaba tiene toneladas de estas cosas. Hace la vida más fácil.
3
Java 1.7+ tiene constantes de juego de caracteres definidas: docs.oracle.com/javase/7/docs/api/java/nio/charset/… . Úselos así: StandardCharsets.UTF_8.displayName ()
Michael
4

Si usted es el único desarrollador que podrá ver este código, entonces diría que está bien, pero si no lo es, lo trataría como una posibilidad real o al menos cambiaría el comentario "nunca sucederá" a Algo más útil.

Thanos Papathanasiou
fuente
4

La parte de estas excepciones que más me molesta es que perjudica la cobertura de mi código.

Cuando me vuelva compulsivo acerca de la cobertura, enrollaré el try / catch que "nunca puede suceder" (... o solo si estoy usando una JVM mutante que de alguna manera olvidó incluir "US-ASCII") en una clase y un método que encapsula ese try / catch y reemplaza la excepción marcada en una de las formas mencionadas aquí (por lo general, arroja una excepción no verificada con un mensaje sarcástico).

Luego, la cobertura de mi código se ve afectada en la clase de utilidad, pero no en todas las referencias a esa operación dispersas por mi código.

A veces me tomo el tiempo de pasar como operaciones en una clase que en realidad tiene una semántica coherente. Pero dado que es bastante obvio para mis compañeros de equipo lo que está sucediendo, generalmente lo mantengo lo más simple posible y no me preocupo tanto por el mejor diseño posible.

Sin embargo, como se menciona en un comentario, Guava y otras bibliotecas tienen formas de mitigar este dolor, pero esa es básicamente la misma estrategia. Mueva la molestia fuera del escenario para que su código principal no se vea afectado en la cobertura.

Robar
fuente