Los analizadores de código estático como Fortify "se quejan" cuando se puede lanzar una excepción dentro de un finally
bloque, diciendo eso Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Normalmente estoy de acuerdo con esto. Pero recientemente me encontré con este código:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Ahora, si writer
no se puede cerrar correctamente, el writer.close()
método arrojará una excepción. Se debe lanzar una excepción porque (muy probablemente) el archivo no se guardó después de la escritura.
Podría declarar una variable adicional, configurarla si hubo un error al cerrar writer
y lanzar una excepción después del bloque finalmente. Pero este código funciona bien y no estoy seguro de si cambiarlo o no.
¿Cuáles son los inconvenientes de lanzar una excepción dentro del finally
bloque?
Respuestas:
Básicamente, las
finally
cláusulas están ahí para garantizar la liberación adecuada de un recurso. Sin embargo, si se lanza una excepción dentro del bloque finalmente, esa garantía desaparece. Peor aún, si su bloque principal de código arroja una excepción, la excepción generada en elfinally
bloque lo ocultará. Parece que el error fue causado por la llamada aclose
, no por la razón real.Algunas personas siguen un patrón desagradable de manejadores de excepciones anidados, tragando cualquier excepción lanzada en el
finally
bloque.En versiones anteriores de Java, puede "simplificar" este código envolviendo recursos en clases que hacen esta limpieza "segura" por usted. Un buen amigo mío crea una lista de tipos anónimos, cada uno de los cuales proporciona la lógica para limpiar sus recursos. Luego, su código simplemente recorre la lista y llama al método de eliminación dentro del
finally
bloque.fuente
Lo que dijo Travis Parks es cierto que las excepciones en el
finally
bloque consumirán cualquier valor de retorno o excepciones de lostry...catch
bloques.Sin embargo, si está utilizando Java 7, el problema se puede resolver utilizando un bloque de prueba con recursos . Según los documentos, siempre que su recurso se implemente
java.lang.AutoCloseable
(la mayoría de los escritores / lectores de bibliotecas lo hacen ahora), el bloque de prueba con recursos lo cerrará por usted. El beneficio adicional aquí es que cualquier excepción que ocurra mientras se cierra se suprimirá, permitiendo que el valor de retorno original o la excepción pase.Desde
A
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
fuente
close()
. "se suprimirá cualquier excepción que ocurra mientras se cierra, permitiendo que el valor de retorno original o excepción pase", esto simplemente no es cierto . Una excepción delclose()
método se suprimirá solo cuando se arroje otra excepción desde el bloque try / catch. Entonces, si eltry
bloque no arroja ninguna excepción, se lanzará la excepción delclose()
método (y no se devolverá el valor de retorno). Incluso se puede atrapar desde elcatch
bloque actual .Creo que esto es algo que tendrá que abordar caso por caso. En algunos casos, lo que dice el analizador es correcto, ya que el código que tiene no es tan bueno y necesita un replanteamiento. Pero puede haber otros casos en los que lanzar o incluso volver a lanzar sea lo mejor. No es algo que puedas ordenar.
fuente
Esta será una respuesta más conceptual a por qué estas advertencias pueden existir incluso con enfoques de prueba con recursos. Desafortunadamente, tampoco es el tipo de solución fácil que esperas lograr.
La recuperación de errores no puede fallar
finally
modela un flujo de control posterior a la transacción que se ejecuta independientemente de si una transacción tiene éxito o falla.En el caso de falla,
finally
captura la lógica que se ejecuta en medio de la recuperación de un error, antes de que se haya recuperado completamente (antes de llegar a nuestrocatch
destino).Imagine el problema conceptual que presenta al encontrarse con un error en medio de la recuperación de un error.
Imagine un servidor de base de datos donde estamos tratando de confirmar una transacción, y falla a la mitad (digamos que el servidor se quedó sin memoria en el medio). Ahora el servidor quiere revertir la transacción a un punto como si nada hubiera pasado. Sin embargo, imagine que encuentra otro error en el proceso de retroceso. Ahora terminamos teniendo una transacción medio comprometida con la base de datos: la atomicidad y la naturaleza indivisible de la transacción ahora se rompe, y la integridad de la base de datos ahora se verá comprometida.
Este problema conceptual existe en cualquier lenguaje que trate con errores, ya sea C con propagación de código de error manual, o C ++ con excepciones y destructores, o Java con excepciones y
finally
.finally
no puede fallar en lenguajes que lo proporcionan de la misma manera que los destructores no pueden fallar en C ++ en el proceso de encontrar excepciones.La única forma de evitar este problema conceptual y difícil es asegurarse de que el proceso de deshacer las transacciones y liberar recursos en el medio no pueda encontrar una excepción / error recursivo.
Entonces, el único diseño seguro aquí es un diseño donde
writer.close()
posiblemente no puede fallar. Por lo general, hay formas en el diseño para evitar escenarios en los que tales cosas pueden fallar en medio de la recuperación, lo que lo hace imposible.Desafortunadamente, es la única forma: la recuperación de errores no puede fallar. La forma más fácil de garantizar esto es hacer que esos tipos de funciones de "liberación de recursos" y "efectos secundarios inversos" no puedan fallar. No es fácil: la recuperación adecuada de errores es difícil y desafortunadamente difícil de probar. Pero la forma de lograrlo es asegurarse de que las funciones que "destruyen", "cierren", "apaguen", "retrocedan", etc., no pueden encontrar un error externo en el proceso, ya que tales funciones a menudo necesitarán ser llamado en medio de la recuperación de un error existente.
Ejemplo: registro
Digamos que quieres registrar cosas dentro de un
finally
bloque. Esto a menudo será un gran problema a menos que el registro no pueda fallar . El registro casi seguro puede fallar, ya que es posible que desee agregar más datos al archivo, y eso puede encontrar fácilmente muchas razones para fallar.Entonces, la solución aquí es hacerlo para que cualquier función de registro utilizada en los
finally
bloques no pueda arrojarse a la persona que llama (podría fallar, pero no arrojará). ¿Cómo podríamos hacer eso? Si su idioma permite el lanzamiento dentro del contexto de que finalmente haya un bloque anidado de prueba / captura, esa sería una forma de evitar lanzar al interlocutor tragándose excepciones y convirtiéndolas en códigos de error, por ejemplo, tal vez el registro se puede hacer en un sitio separado proceso o subproceso que puede fallar por separado y fuera de una pila de recuperación de errores existente desenrollar Siempre que pueda comunicarse con ese proceso sin la posibilidad de encontrarse con un error, eso también sería seguro para las excepciones, ya que el problema de seguridad solo existe en este escenario si estamos lanzando recursivamente desde el mismo hilo.En este caso, podemos salir con la falla de registro siempre y cuando no se lance, ya que no iniciar sesión y no hacer nada no es el fin del mundo (no está filtrando ningún recurso o no revierte los efectos secundarios, por ejemplo).
De todos modos, estoy seguro de que ya puedes comenzar a imaginar lo increíblemente difícil que es realmente hacer que un software sea seguro para las excepciones. Es posible que no sea necesario buscar esto en el mayor grado posible, excepto en el software más crítico. Pero vale la pena tomar nota de cómo lograr realmente la seguridad de las excepciones, ya que incluso los autores de bibliotecas de propósito muy general a menudo pierden el control y destruyen toda la seguridad de las excepciones de su aplicación usando la biblioteca.
SomeFileWriter
Si
SomeFileWriter
puede arrojar dentroclose
, entonces diría que generalmente es incompatible con el manejo de excepciones, a menos que nunca intente cerrarlo en un contexto que implique recuperarse de una excepción existente. Si el código está fuera de su control, podríamos ser SOL, pero valdría la pena notificar a los autores de este flagrante problema de seguridad de excepción. Si está bajo su control, mi recomendación principal es asegurarse de que cerrarlo no pueda fallar por los medios necesarios.Imagínese si un sistema operativo realmente no puede cerrar un archivo. Ahora, cualquier programa que intente cerrar un archivo al apagarse no podrá cerrarse . ¿Qué se supone que debemos hacer ahora, simplemente mantener la aplicación abierta y en el limbo (probablemente no), simplemente filtrar el recurso del archivo e ignorar el problema (tal vez está bien si no es tan crítico)? El diseño más seguro: haga que sea imposible no cerrar un archivo.
fuente