Pregunta

Acabo de tener una experiencia de resolución de problemas bastante dolorosa al solucionar algunos códigos que parecían a esto:

try {
   doSomeStuff()
   doMore()
} finally {
   doSomeOtherStuff()
}

El problema era difícil de solucionar porque doSomeStuff () lanzó una excepción, lo que a su vez hizo que doSomeOtherStuff () también lanzara una excepción. La segunda excepción (lanzada por el bloque finally) se incluyó en mi código, pero no tuvo un identificador en la primera excepción (lanzada desde doSomeStuff ()), que fue la verdadera causa raíz del problema.

Si el código hubiera dicho esto en su lugar, el problema habría sido evidente:

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Entonces, mi pregunta es esta:

¿Se utiliza un bloque finally sin ningún bloque catch en un conocido anti-patrón de java? (Ciertamente, parece ser una subclase que no es fácilmente aparente del anti-patrón obviamente conocido "¡No engullir excepciones! & Quot;)

¿Fue útil?

Solución

En general, no, esto no es un anti-patrón. El punto de los bloques finalmente es asegurarse de que las cosas se limpien, ya sea que se lance una excepción o no. El punto principal del manejo de excepciones es que, si no puede lidiar con eso, deja que crezca a alguien que pueda hacerlo, a través de la relativamente limpia limpieza que proporciona el manejo de excepciones de señalización fuera de banda. Si necesita asegurarse de que las cosas se limpien si se lanza una excepción, pero no puede manejar adecuadamente la excepción en el alcance actual, entonces esto es exactamente lo que debe hacer. Es posible que desee tener un poco más de cuidado para asegurarse de que su bloque final no se lance.

Otros consejos

Creo que el real " anti-patrón " Aquí está haciendo algo en un bloque finalmente que puede lanzar, no teniendo una trampa.

En absoluto.

Lo que está mal es el código dentro de la finalmente.

Recuerde que, finalmente, siempre se ejecutará, y es arriesgado (como acaba de presenciar) poner algo que pueda generar una excepción allí.

No hay absolutamente nada de malo en intentarlo con un final y no hay problema. Considera lo siguiente:

InputStream in = null;
try {
    in = new FileInputStream("file.txt");
    // Do something that causes an IOException to be thrown
} finally {
    if (in != null) {
         try {
             in.close();
         } catch (IOException e) {
             // Nothing we can do.
         }
    }
}

Si se lanza una excepción y este código no sabe cómo lidiar con ella, entonces la excepción debería aumentar la pila de llamadas para la persona que llama. En este caso, todavía queremos limpiar el flujo, así que creo que tiene mucho sentido tener un bloque try sin un catch.

Creo que está lejos de ser un anti-patrón y es algo que hago con mucha frecuencia cuando es crítico desasignar los recursos obtenidos durante la ejecución del método.

Una cosa que hago cuando trato con los manejadores de archivos (para escribir) es vaciar el flujo antes de cerrarlo usando el método IOUtils.closeQuietly, que no produce excepciones:


OutputStream os = null;
OutputStreamWriter wos = null;
try { 
   os = new FileOutputStream(...);
   wos = new OutputStreamWriter(os);
   // Lots of code

   wos.flush();
   os.flush();
finally {
   IOUtils.closeQuietly(wos);
   IOUtils.closeQuietly(os);
}

Me gusta hacerlo de esa manera por las siguientes razones:

  • No es completamente seguro ignorar una excepción al cerrar un archivo. Si hay bytes que aún no se escribieron en el archivo, es posible que el archivo no esté en el estado que esperaría el llamante;
  • Por lo tanto, si se genera una excepción durante el método flush (), se propagará a la persona que llama pero aún así me aseguraré de que todos los archivos estén cerrados. El método IOUtils.closeQuietly (...) es menos detallado que el correspondiente try ... catch ... ignore me block;
  • Si usa múltiples flujos de salida, el orden para el método flush () es importante. Los flujos que se crearon al pasar otros flujos como constructores deben vaciarse primero. Lo mismo es válido para el método close (), pero el flush () es más claro en mi opinión.

Yo diría que un bloque try sin un bloque catch es un anti-patrón. Decir " No tener un finalmente sin un catch " es un subconjunto de " No intente sin una captura " ;.

Utilizo try / finalmente en el siguiente formulario:

try{
   Connection connection = ConnectionManager.openConnection();
   try{
       //work with the connection;
   }finally{
       if(connection != null){
          connection.close();           
       }
   }
}catch(ConnectionException connectionException){
   //handle connection exception;
}

Prefiero esto a try / catch / finally (+ try / catch anidado en el finally). Creo que es más conciso y no duplico la captura (Excepción).

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Tampoco hagas eso ... simplemente escondiste más errores (bueno, no exactamente los ocultamos ... pero hizo que sea más difícil lidiar con ellos. Cuando detectas Exception, también detectas cualquier tipo de RuntimeException (como NullPointer y ArrayIndexOutOfBounds).

En general, capture las excepciones que tiene que atrapar (excepciones verificadas) y trate con los demás en el momento de la prueba. Las RuntimeExceptions están diseñadas para ser utilizadas para errores de programador, y los errores de programador son cosas que no deberían suceder en un programa debidamente depurado.

En mi opinión, es más cierto que finalmente con un catch indica algún tipo de problema. El lenguaje del recurso es muy simple:

acquire
try {
    use
} finally {
    release
}

En Java puedes tener una excepción desde casi cualquier lugar. A menudo, la adquisición arroja una excepción marcada, la manera sensata de manejar eso es poner un retén alrededor del lote. No intentes realizar una comprobación de nulos horribles.

Si ibas a ser realmente anal, debes tener en cuenta que hay excepciones implícitas entre las excepciones. Por ejemplo, ThreadDeath debería saturar a todos, ya se trate de adquirir / use / release. Manejar estas prioridades correctamente es antiestético.

Por lo tanto, abstraiga su manejo de recursos con el lenguaje Execute Around.

Probar / Finalmente es una forma de liberar recursos adecuadamente. El código en el bloque finally NUNCA se debe lanzar, ya que solo debe actuar sobre los recursos o el estado que se adquirió ANTES de ingresar al bloque try.

A un lado, creo que log4J es casi un anti-patrón.

SI DESEA INSPECCIONAR UN PROGRAMA EN EJECUCIÓN, USE UNA HERRAMIENTA DE INSPECCIÓN CORRECTA (es decir, un depurador, IDE o, en un sentido extremo, un tejedor de código de bytes, pero NO PONGA ESTADOS DE REGISTRO EN CUALQUIER LÍNEA).

En los dos ejemplos que presentas, el primero parece correcto. El segundo incluye el código del registrador e introduce un error. En el segundo ejemplo, suprime una excepción si una de las dos primeras declaraciones la lanza (es decir, la captura y la registra pero no la vuelve a generar. Esto es algo que encuentro muy común en el uso de log4j y es un problema real del diseño de la aplicación. Básicamente con su cambio, hace que el programa falle de una manera que sería muy difícil de manejar para el sistema, ya que básicamente avanza hacia adelante como si nunca hubiera tenido una excepción (como VB Basic en el resumen de errores, siguiente construcción).

try-finally puede ayudarlo a reducir el código de copiar y pegar en caso de que un método tenga varias declaraciones de return . Considere el siguiente ejemplo (Android Java):

boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
    Cursor cursor = db.rawQuery("SELECT * FROM table", null);
    if (cursor != null) { 
        try {
            if (cursor.getCount() == 0) { 
                return false;
            }
        } finally {
            // this will get executed even if return was executed above
            cursor.close();
        }
    }
    // database had rows, so do something...
    return true;
}

Si no había una cláusula finally , es posible que tenga que escribir cursor.close () dos veces: justo antes de devolver false y también después la cláusula if circundante

Creo que intentar sin captura es anti-patrón. El uso de try / catch para manejar las condiciones excepcionales (errores de IO de archivo, tiempo de espera de socket, etc.) no es un antipatrón.

Si está usando try / finally para la limpieza, considere usar un bloque en su lugar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top