TL; DR
Premisa
- Se deben generar excepciones de tiempo de ejecución cuando el error es irrecuperable: cuando el error está en el código y no depende del estado externo (por lo tanto, la recuperación estaría corrigiendo el código).
- Se deben generar excepciones marcadas cuando el código es correcto, pero el estado externo no es el esperado: no hay conectividad de red, no se encuentra el archivo o está dañado, etc.
Conclusión
Podemos volver a lanzar una excepción marcada como una excepción de tiempo de ejecución si el código de propagación o interfaz supone que la implementación subyacente depende del estado externo, cuando claramente no lo hace.
Esta sección discute el tema de cuándo se debe lanzar cualquiera de las excepciones. Puede pasar a la siguiente barra horizontal si solo desea leer una explicación más detallada de la conclusión.
¿Cuándo es apropiado lanzar una excepción de tiempo de ejecución? Lanza una excepción de tiempo de ejecución cuando está claro que el código es incorrecto y que la recuperación es apropiada modificando el código.
Por ejemplo, es apropiado lanzar una excepción de tiempo de ejecución para lo siguiente:
float nan = 1/0;
Esto arrojará una división por cero excepción de tiempo de ejecución. Esto es apropiado porque el código es defectuoso.
O, por ejemplo, aquí hay una parte del HashMap
constructor de:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// more irrelevant code...
}
Para corregir la capacidad inicial o el factor de carga, es apropiado que edite el código para asegurarse de que se pasan los valores correctos. No depende de que algún servidor lejano esté activo, del estado actual del disco, un archivo u otro programa. Ese constructor que se llama con argumentos no válidos depende de la exactitud del código de llamada, ya sea un cálculo incorrecto que condujo a los parámetros no válidos o al flujo inapropiado que omitió un error.
¿Cuándo es apropiado lanzar una excepción marcada? Lanza una excepción marcada cuando el problema es recuperable sin cambiar el código. O para decirlo en términos diferentes, arroja una excepción marcada cuando el error está relacionado con el estado mientras el código es correcto.
Ahora la palabra "recuperar" puede ser difícil aquí. Podría significar que encuentra otra forma de lograr el objetivo: por ejemplo, si el servidor no responde, debe probar el siguiente servidor. Si ese tipo de recuperación es posible para su caso, entonces eso es genial, pero eso no es lo único que significa la recuperación: la recuperación podría simplemente mostrar un diálogo de error al usuario que explica lo que sucedió, o si esa es una aplicación de servidor, entonces podría ser enviando un correo electrónico al administrador, o incluso simplemente registrando el error de manera adecuada y concisa.
Tomemos el ejemplo que se mencionó en la respuesta de mrmuggles:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
Esta no es la forma correcta de manejar la excepción marcada. La mera incapacidad para manejar la excepción en el alcance de este método no significa que la aplicación deba bloquearse. En cambio, es apropiado propagarlo a un alcance superior de esta manera:
public Data dataAccessCode() throws SQLException {
// some code that communicates with the database
}
Lo que permite la posibilidad de recuperación por parte de la persona que llama:
public void loadDataAndShowUi() {
try {
Data data = dataAccessCode();
showUiForData(data);
} catch(SQLException e) {
// Recover by showing an error alert dialog
showCantLoadDataErrorDialog();
}
}
Las excepciones marcadas son una herramienta de análisis estático, dejan en claro para un programador lo que podría salir mal en una determinada llamada sin requerirles que aprendan la implementación o pasen por un proceso de prueba y error. Esto facilita asegurar que no se ignorará ninguna parte del flujo de error. Volver a lanzar una excepción marcada como una excepción de tiempo de ejecución funciona en contra de esta función de análisis estático que ahorra trabajo.
También vale la pena mencionar que la capa de llamada tiene un mejor contexto del esquema más amplio de las cosas como se ha demostrado anteriormente. Podría haber muchas causas para que dataAccessCode
se llamara, la razón específica de la llamada solo es visible para la persona que llama, por lo que puede tomar una mejor decisión en la recuperación correcta en caso de falla.
Ahora que tenemos esta distinción clara, podemos proceder a deducir cuándo está bien volver a lanzar una excepción marcada como una excepción de tiempo de ejecución.
Dado lo anterior, ¿cuándo es apropiado volver a lanzar una excepción marcada como RuntimeException? Cuando el código que está utilizando asume dependencia del estado externo, pero puede afirmar claramente que no depende del estado externo.
Considera lo siguiente:
StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
throw new IllegalStateException(e);
}
En este ejemplo, el código se propaga IOException
porque la API de Reader
está diseñada para acceder al estado externo, sin embargo, sabemos que la StringReader
implementación no accede al estado externo. En este ámbito, donde ciertamente podemos afirmar que las partes involucradas en la llamada no acceden a E / S ni a ningún otro estado externo, podemos volver a lanzar la excepción de manera segura como una excepción de tiempo de ejecución sin sorprender a los colegas que desconocen nuestra implementación (y posiblemente asumiendo que el código de acceso IO arrojará un IOException
).
La razón para mantener estrictamente marcadas las excepciones dependientes del estado externo es que no son deterministas (a diferencia de las excepciones dependientes de la lógica, que previsiblemente se reproducirán cada vez para una versión del código). Por ejemplo, si intenta dividir por 0, siempre producirá una excepción. Si no divide entre 0, nunca producirá una excepción, y no tiene que manejar ese caso de excepción, porque nunca sucederá. Sin embargo, en el caso de acceder a un archivo, tener éxito una vez no significa que tendrá éxito la próxima vez: el usuario podría haber cambiado los permisos, otro proceso podría haberlo eliminado o modificado. Entonces, siempre tiene que manejar ese caso excepcional, o es probable que tenga un error.