java - todas - ¿Existe una preferencia por bloques anidados de try/catch?
try catch solo letras java (11)
A veces, try-catch anidado no es una preferencia, considere esto:
try{
string s = File.Open("myfile").ReadToEnd(); // my file has a bunch of numbers
// I want to get a total of the numbers
int total = 0;
foreach(string line in s.split("/r/n")){
try{
total += int.Parse(line);
} catch{}
}
catch{}
Este es probablemente un mal ejemplo, pero a veces necesitará try-cactch anidado.
Una de las cosas que siempre me molesta sobre el uso de Readers and Streams en Java es que el método close()
puede arrojar una excepción. Dado que es una buena idea poner el método de cierre en un bloque final, eso requiere una situación un poco incómoda. Usualmente uso esta construcción:
FileReader fr = new FileReader("SomeFile.txt");
try {
try {
fr.read();
} finally {
fr.close();
}
} catch(Exception e) {
// Do exception handling
}
Pero también he visto esta construcción:
FileReader fr = new FileReader("SomeFile.txt");
try {
fr.read()
} catch (Exception e) {
// Do exception handling
} finally {
try {
fr.close();
} catch (Exception e) {
// Do exception handling
}
}
Prefiero la primera construcción porque solo hay un bloque catch y parece más elegante. ¿Hay alguna razón para preferir la segunda o una construcción alternativa?
ACTUALIZACIÓN: ¿Haría una diferencia si señalara que ambos read
y close
solo arrojan IOExceptions? Por lo tanto, me parece probable que, si la lectura falla, el cierre fallará por la misma razón.
La convención estándar que uso es que no debes permitir que las excepciones escapen de un bloque finally.
Esto se debe a que si una excepción ya se está propagando, la excepción lanzada fuera del bloque finally prevalecerá sobre la excepción original (y por lo tanto se perderá).
En el 99% de los casos, esto no es lo que desea, ya que la excepción original es probablemente la fuente de su problema (cualquier excepción secundaria puede ser efectos secundarios de la primera, pero oscurecerá su capacidad de encontrar la fuente de la excepción original y, por lo tanto, la real problema).
Entonces su código básico debería verse así:
try
{
// Code
}
// Exception handling
finally
{
// Exception handling that is garanteed not to throw.
try
{
// Exception handling that may throw.
}
// Optional Exception handling that should not throw
finally()
{}
}
La diferencia, hasta donde puedo ver, es que hay diferentes excepciones y causas en juego en diferentes niveles, y el
catch (Excepción e)
oscurece eso. El único punto de los múltiples niveles es distinguir sus excepciones y lo que hará con ellas:
try
{
try{
...
}
catch(IOException e)
{
..
}
}
catch(Exception e)
{
// we could read, but now something else is broken
...
}
Me temo que hay un gran problema con el primer ejemplo, que es que si ocurre una excepción en o después de la lectura, el bloque finally
se ejecuta. Hasta aquí todo bien. Pero, ¿qué fr.close()
si fr.close()
provoca el fr.close()
otra excepción? Esto "prevalecerá" sobre la primera excepción (un poco como poner el return
en un bloque final ) y perderás toda la información sobre lo que realmente causó que el problema comience.
Tu bloque finalmente debería usar:
IOUtil.closeSilently(fr);
donde este método de utilidad simplemente hace:
public static void closeSilently(Closeable c) {
try { c.close(); } catch (Exception e) {}
}
Segundo enfoque.
De lo contrario, no veo que capture la excepción del constructor de FileReader
http://java.sun.com/j2se/1.5.0/docs/api/java/io/FileReader.html#FileReader(java.lang.String)
public FileReader (String fileName) arroja FileNotFoundException
Por lo tanto, generalmente tengo el constructor dentro del bloque try también. finalmente, el bloque comprueba si el lector NO es nulo antes de intentar cerrarlo.
El mismo patrón va para Datasource, Connection, Statement, ResultSet.
Si tanto la lectura como el cierre arrojan una excepción, la excepción de la lectura se ocultará en la opción 1. Entonces, la segunda opción maneja más errores.
Sin embargo, en la mayoría de los casos, la primera opción seguirá siendo preferida.
- En muchos casos, no puede tratar con excepciones en el método que se generan, pero aún debe encapsular el manejo de la secuencia dentro de esa operación.
- Intente agregar un escritor al código y vea qué detallado es el segundo enfoque.
Si necesita pasar todas las excepciones generadas, se puede hacer .
Usualmente hago lo siguiente. En primer lugar, defina una clase basada en el método de plantilla para lidiar con el lío try / catch
import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public abstract class AutoFileCloser {
private static final Closeable NEW_FILE = new Closeable() {
public void close() throws IOException {
// do nothing
}
};
// the core action code that the implementer wants to run
protected abstract void doWork() throws Throwable;
// track a list of closeable thingies to close when finished
private List<Closeable> closeables_ = new LinkedList<Closeable>();
// mark a new file
protected void newFile() {
closeables_.add(0, NEW_FILE);
}
// give the implementer a way to track things to close
// assumes this is called in order for nested closeables,
// inner-most to outer-most
protected void watch(Closeable closeable) {
closeables_.add(0, closeable);
}
public AutoFileCloser() {
// a variable to track a "meaningful" exception, in case
// a close() throws an exception
Throwable pending = null;
try {
doWork(); // do the real work
} catch (Throwable throwable) {
pending = throwable;
} finally {
// close the watched streams
boolean skip = false;
for (Closeable closeable : closeables_) {
if (closeable == NEW_FILE) {
skip = false;
} else if (!skip && closeable != null) {
try {
closeable.close();
// don''t try to re-close nested closeables
skip = true;
} catch (Throwable throwable) {
if (pending == null) {
pending = throwable;
}
}
}
}
// if we had a pending exception, rethrow it
// this is necessary b/c the close can throw an
// exception, which would remove the pending
// status of any exception thrown in the try block
if (pending != null) {
if (pending instanceof RuntimeException) {
throw (RuntimeException) pending;
} else {
throw new RuntimeException(pending);
}
}
}
}
}
Observe la excepción "pendiente": se ocupa del caso en el que una excepción lanzada durante el cierre enmascararía una excepción que realmente nos interesaría.
Finalmente trata de cerrarse desde el exterior de cualquier secuencia decorada primero, por lo que si tiene un BufferedWriter envolviendo un FileWriter, trataremos de cerrar el BuffereredWriter primero, y si eso falla, aún trate de cerrar el FileWriter.
Puede usar la clase anterior de la siguiente manera:
try {
// ...
new AutoFileCloser() {
@Override protected void doWork() throws Throwable {
// declare variables for the readers and "watch" them
FileReader fileReader = null;
BufferedReader bufferedReader = null;
watch(fileReader = new FileReader("somefile"));
watch(bufferedReader = new BufferedReader(fileReader));
// ... do something with bufferedReader
// if you need more than one reader or writer
newFile(); // puts a flag in the
FileWriter fileWriter = null;
BufferedWriter bufferedWriter = null;
watch(fileWriter = new FileWriter("someOtherFile"));
watch(bufferedWriter = new BufferedWriter(fileWriter));
// ... do something with bufferedWriter
}
};
// .. other logic, maybe more AutoFileClosers
} catch (RuntimeException e) {
// report or log the exception
}
Con este enfoque, nunca tendrá que preocuparse por el try / catch / finally para tratar nuevamente con los archivos de cierre.
Si esto es demasiado pesado para su uso, al menos piense en seguir el enfoque try / catch y variable "pendiente" que utiliza.
Me gusta el enfoque de @Chris Marshall, pero nunca me gusta que las excepciones se traguen en silencio. Creo que es mejor registrar las excepciones, especialmente si lo haces de todos modos.
Siempre utilizo una clase de utilidad para manejar este tipo de excepciones comunes, pero haría que esta respuesta sea muy diferente.
Siempre usaría un registrador (log4j para mí) para registrar errores, etc.
IOUtil.close(fr);
Una ligera modificación en el método de utilidad:
public static void close(Closeable c) {
try {
c.close();
} catch (Exception e) {
logger.error("An error occurred while closing. Continuing regardless", e);
}
}
Siempre iré por el primer ejemplo.
Si se llegara a lanzar una excepción (en la práctica eso nunca sucederá con un FileReader), ¿no sería la forma estándar de manejar lanzar una excepción apropiada para la persona que llama? La excepción más cercana casi definitivamente supera cualquier problema que haya tenido al usar el recurso. El segundo método es probablemente más apropiado si su idea del manejo de excepciones es llamar a System.err.println.
Existe un problema de cuán lejos deberían lanzarse las excepciones. ThreadDeath siempre debería volverse a lanzar, pero cualquier excepción dentro de ella detendría eso. Del mismo modo, Error debería lanzar más allá de RuntimeException y RuntimeException más allá de las excepciones marcadas. Si realmente quisiera, podría escribir código para seguir estas reglas, y luego resumirlo con la expresión "ejecutar alrededor".
En algunos casos, un Try-Catch anidado es inevitable. Por ejemplo, cuando el código de recuperación de error en sí mismo puede lanzar y una excepción. Pero para mejorar la legibilidad del código, siempre puedes extraer el bloque anidado en un método propio. Consulte esta publicación en el blog para obtener más ejemplos sobre bloques anidados de Try-Catch-Finally.
Yo prefiero el segundo. ¿Por qué? Si las excepciones read()
y close()
arrojan excepciones, una de ellas podría perderse. En la primera construcción, la excepción de close()
anula la excepción de read()
, mientras que en la segunda, la excepción de close()
se maneja por separado.
A partir de Java 7, el constructo try-with-resources hace esto mucho más simple. Para leer sin preocuparse por las excepciones:
try (FileReader fr = new FileReader("SomeFile.txt")) {
fr.read();
// no need to close since the try-with-resources statement closes it automatically
}
Con manejo de excepciones:
try (FileReader fr = new FileReader("SomeFile.txt")) {
fr.read();
// no need to close since the try-with-resources statement closes it automatically
} catch (IOException e) {
// Do exception handling
log(e);
// If this catch block is run, the FileReader has already been closed.
// The exception could have come from either read() or close();
// if both threw exceptions (or if multiple resources were used and had to be closed)
// then only one exception is thrown and the others are suppressed
// but can still be retrieved:
Throwable[] suppressed = e.getSuppressed(); // can be an empty array
for (Throwable t : suppressed) {
log(suppressed[t]);
}
}
Solo se necesita un try-catch y todas las excepciones se pueden manejar con seguridad. Todavía puede agregar un bloque finally
si lo desea, pero no es necesario cerrar los recursos.