2009-03-02 19 views
43

acabo de tener una experiencia de resolución de problemas muy doloroso para solucionar un código que se veía así:¿Es un bloque finalmente sin bloque de captura un antipatrón de Java?

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

El problema fue difícil de solucionar porque doSomeStuff() inició una excepción, que a su vez causó doSomeOtherStuff() para lanzar también una excepción. La segunda excepción (lanzada por el bloque finally) fue arrojada a mi código, pero no tenía un control sobre la primera excepción (lanzada desde doSomeStuff()), que era la verdadera causa raíz del problema.

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

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

lo tanto, mi pregunta es la siguiente:

es un bloque finally utilizar sin ningún bloque catch un bien conocido java anti-patrón? (Ciertamente parece ser una subclase que no es fácilmente aparente del obviamente anti-patrón "¡No tragues excepciones!")

+0

Véase también una pregunta similar en [try-finally without catch in C#] (http://stackoverflow.com/questions/128818/why-istry-finally-good-try-catch-bad) - the Se aplican los mismos argumentos. – avandeursen

Respuesta

61

En general, no, esto no es un antipatrón. El objetivo de los bloques finalmente es asegurarse de que las cosas se limpien independientemente de si se lanza una excepción o no. El objetivo de la gestión de excepciones es que, si no se puede manejar, se lo permite a alguien que puede, a través de la gestión de excepción de señalización relativamente limpia fuera de banda que proporciona. Si necesita asegurarse de que las cosas se limpian 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 ser un poco más cuidadoso para asegurarse de que su bloque finalmente no arroje.

+0

Pero en este caso, no se permitió que la excepción de causa raíz se disparara, porque finalmente lo pisó (lo que provocó que se engulló). – Jared

+0

Ok, pero eso es un caso de esquina. Estás preguntando si try-finally/catch no es un anti-patrón en general. – dsimcha

+1

De acuerdo, dsimcha. Espero que haya consenso en que, en general, esto no es un antipatrón. Tal vez un antipatrón es, "Finalmente bloquea arrojando excepciones"? – Jared

0

Yo diría que un bloque de prueba sin un bloque de captura es un anti-patrón patrón. Decir "No tener un finalmente sin una captura" es un subconjunto de "No lo intentes sin atrapar".

+0

Estoy de acuerdo, no veo por qué esto sería downvoted. – BobbyShaftoe

+2

no vale la pena un voto negativo, pero no es un anti-patrón. un bloque finally permite que siempre se ejecute una cierta porción de código, como cerrar recursos o soltar bloqueos, que no arrojen ninguna excepción. – Chii

+0

Mi punto era que el Catch faltante era el anti-patrón en el anterior, independientemente de si finalmente estaba allí o no. Las excepciones de engullimiento es lo que causó el problema de depuración, exacerbado por el código de excepción posible en el último. – billjamesdev

10

No hay absolutamente nada de malo en probar con un final y sin trampas. Considere 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 produce una excepción y este código no sabe cómo tratar con él, entonces la excepción debe propagarse por la pila de llamadas a la persona que llama. En este caso, todavía queremos limpiar la secuencia, así que creo que tiene sentido tener un bloque de prueba sin trampas.

+0

Si quita la captura (anidada en el final) de este ejemplo, si la prueba arroja una IOException y cierra close() también, no puede decir desde el seguimiento de pila que la causa raíz del problema es la IOException original. ¿Es solo uno de esos casos "Sí, esto siempre será difícil"? – Jared

+1

@Jared: Sí, esto siempre será difícil. :) –

+0

FYI - cuando eliminaste la captura del bloque finally, básicamente dijiste que no te importaba de dónde provenía la excepción ... –

26

Creo que el verdadero "antipatrón" aquí está haciendo algo en un bloque finally que puede arrojar, no sin tener una trampa.

1

utilizo try/finally de la siguiente forma:

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

prefiero esto al try/catch/finally (+ anidada try/catch en el fin). Creo que es más conciso y no duplico la captura (Excepción).

+0

¿No sufre eso el mismo problema de una excepción en el último engullir? una excepción en el intento? – Jared

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

No hagas que sea ... sólo escondiste más errores (no así los había escondido exactamente ... pero lo hizo más difícil de tratar con ellos. Cuando se captura la excepción también es la captura de cualquier tipo de RuntimeException (como NullPointer y ArrayIndexOutOfBounds)

En general, atrapa las excepciones que tienes que atrapar (excepciones marcadas) y trata con los demás en el momento de la prueba. RuntimeExceptions está diseñado para usarse para errores del programador y los errores del programador son cosas eso no debería suceder en un programa correctamente depurado.

+0

Sí ... de acuerdo. Esto continúa apuntando a que el anti-patrón real es finalmente el que arroja una excepción. – Jared

-1

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

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

16

No, en absoluto.

Lo que está mal es el código dentro del final.

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

5

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 se trata de identificadores de archivo (por escrito) está vaciando la corriente antes de cerrarlo usando el método IOUtils.closeQuietly, que no generan 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 camino para las siguientes razones:

  • no es completamente seguro para ignorar una excepción al cerrar un archivo - si hay bytes que no se escriben en el archivo todavía, entonces el archivo no puede estar en el estado de la persona que llama esperar;
  • Por lo tanto, si se produce una excepción durante el método flush(), se propagará a la persona que llama, pero igual me aseguraré de que todos los archivos estén cerrados. El método IOUtils.closeQuietly (...) es menos detallado que el try correspondiente ... catch ... ignore me block;
  • Si se utilizan múltiples flujos de salida, el orden para el método flush() es importante. Las corrientes que se crearon al pasar otras secuencias como constructores se deben enjuagar primero. Lo mismo es válido para el método close(), pero el flush() es más claro en mi opinión.
1

En mi opinión, es más el caso de que finally con un catch indican algún tipo de problema. El idioma del recurso es muy simple:

acquire 
try { 
    use 
} finally { 
    release 
} 

En Java puede tener una excepción desde prácticamente cualquier lugar. A menudo, la adquisición arroja una excepción marcada, la forma sensata de manejar eso es poner un poco de todo el lote. No intentes alguna horrible comprobación nula.

Si fueras realmente anal, debes tener en cuenta que hay prioridades implícitas entre las excepciones. Por ejemplo, ThreadDeath debería destruir todo, ya sea que se trate de adquirir/usar/liberar. Manejar estas prioridades correctamente es desagradable.

Por lo tanto, abstraiga el manejo de sus recursos con el modismo Execute Around.

0

Try/Finally es una forma de liberar recursos correctamente. El código en el bloque finally NUNCA debe arrojarse, ya que solo debe actuar sobre los recursos o el estado que se adquirió ANTES de la entrada en el bloque try.

Como un aparte, creo que log4J es casi un anti-patrón.

SI DESEA INSPECCIONAR UN PROGRAMA DE FUNCIONAMIENTO USE UNA HERRAMIENTA DE INSPECCIÓN CORRECTA (es decir, un depurador, IDE o, en un sentido extremo, un tejedor de códigos de bytes, ¡pero NO PONGA LAS DECLARACIONES DE REGISTRO EN CIERTAS LÍNEAS!).

En los dos ejemplos que presenta, 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 las dos primeras afirmaciones arrojan una (es decir, la capta y la registra, pero no la vuelve a lanzar. 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. con su cambio hace que el programa falle de una manera que sería muy difícil para el sistema, ya que básicamente marcha hacia adelante como si nunca hubiera tenido una excepción (como VB básico en caso de error reanudar el siguiente constructo).

0

try-finally puede ayudar a reducir código de copiar y pegar en el caso de un método tiene varios return declaraciones Consideremos 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 había no finally cláusula, puede que tenga que escribir cursor.close() dos veces: justo antes de return false y también después de la cláusula if circundante.

Cuestiones relacionadas