2012-01-26 13 views
12

Soy un programador travieso, y hasta ahora no he manejado los errores correctamente (por ejemplo, simplemente capturando una excepción java.lang.Express, imprimiendo un mensaje de depuración, y moviéndome). Cuando los "manejo", simplemente ha sido cerrar el compilador.Manejo de errores: ¿es este un patrón decente?

Recientemente me enteré del error (jaja) de mis costumbres, y me gustaría empezar a hacerlo bien. Así que lo estoy investigando aquí y en otros lugares (a través de búsquedas en Google).

Supongamos que tengo un bloque de código que hace lo siguiente:

... 
x.method1(); // throws ExceptionTypeA 
    ... 
y.method2(); // throws ExceptionTypeB 
    ... 
z.method3(); // throws ExceptionTypeC 
    ... 
x.method4(); // throws ExceptionTypeA (again) 
    ... 

De lo que he reunido, la forma correcta de manejar esto es:

try { 
     ... 
    x.method1(); // throws ExceptionTypeA 
     ... 
    y.method2(); // throws ExceptionTypeB 
     ... 
    z.method3(); // throws ExceptionTypeC 
     ...  
    x.method4(); // throws ExceptionTypeA (again) 
     ... 
} catch (ExceptionTypeA e) { 
    // do something about condition A 
} catch (ExceptionTypeB e) { 
    // do something about condition B 
} catch (ExceptionTypeC e) { 
    // do something about condition C 
} 

Esto parece bastante sencillo de yo, pero parece desordenado cuando tengo un bloque largo de código que arroja varios errores a lo largo. ¡Parece que terminé con solo un intento/captura gigante en todo mi método! La alternativa parece ser:

try { 
     ... 
    x.method1(); // throws ExceptionTypeA 
     ... 
} catch (ExceptionTypeA e) { 
    // do something about condition A 
} 

try { 
     ... 
    y.method2(); // throws ExceptionTypeB 
     ... 
} catch (ExceptionTypeB e) { 
    // do something about condition A 
} 

try { 
     ... 
    z.method3(); // throws ExceptionTypeC 
     ... 
} catch (ExceptionTypeC e) { 
    // do something about condition C 
} 
try { 
     ... 
    x.method4(); // throws ExceptionTypeA 
     ... 
} catch (ExceptionTypeA e) { 
    // do something about condition A 
} 

Esto se ve realmente desagradable. En casos como este, he considerado hacer algo como lo siguiente:

private void doSomething() throws exceptionTypeA, exceptionTypeB, exceptionTypeC { 
     ... 
    x.method1(); // throws ExceptionTypeA 
     ... 
    y.method2(); // throws ExceptionTypeB 
     ... 
    z.method3(); // throws ExceptionTypeC 
     ... 
    x.method4(); // throws ExceptionTypeA (again) 
     ... 

} 

public void doSomething_andHandleErrors() { 
    try { 
     this.toSomething(); 
    } catch (ExceptionTypeA e) { 
     // do something about condition A 
    } catch (ExceptionTypeB e) { 
     // do something about condition B 
    } catch (ExceptionTypeC e) { 
     // do something about condition C 
    } 
} 

... y luego doSomething_andHandleErrors sólo llamar(); desde afuera. ¿Es esta una buena practica? ¿Estoy cayendo en algún antipatrón?

Gracias!

+0

Me movería esto a http://codereview.stackexchange.com –

Respuesta

8

La principal diferencia entre su primer y segundo ejemplo es cómo maneja el error en sí. ¿Es transaccional? En su primer ejemplo, y.method2() no se ejecutará si x.method1() arroja una excepción. En su segundo ejemplo, es posible dependiendo de lo que haga el manejo de errores.

Ambos son patrones decentes, es una cuestión de caso de negocios que se requiere aquí. ¿Desea que se pase la excepción a la persona que llama para que puedan manejarla? ¿Quieres hacer otra cosa debido al error?

Además, no olvide el bloque finally. Deberá asegurarse de usar uno si está tratando con la administración de recursos (IO Streams, conexiones de bases de datos, por ejemplo) para que pueda hacer la limpieza si es necesario.

+1

De acuerdo, primero o segundo, dependiendo del manejo deseado. Tercero, no tan bueno. – CPerkins

+1

O try-with-resources en lugar de bloques 'finally'. – biziclop

+0

Sí, si está utilizando Java 7, puede usar el try-with-resources en su lugar, como dijo biziclop. No especificó qué versión de Java estaba usando. – Michael

0

Preferiría su primer ejemplo si no hay más de unas líneas entre las llamadas al método. De lo contrario, su segundo ejemplo cabría mejor. Nunca he visto el patrón en el último ejemplo, nunca en nuestra base de código de todos modos.

1

Por lo general, no hay demasiadas cosas diferentes que hacer en un bloque catch, por lo que debe apuntar a tener un bloque para un comportamiento.

Así que si todo lo que estás haciendo es registrar y volver a lanzar la excepción, debes ir a la primera opción. Es muy raro que tenga que manejar cada excepción lanzada desde múltiples llamadas a métodos por separado, y elegir la opción dos cuando no la necesite reduce en gran medida la legibilidad, especialmente si también está asignando valores a variables locales, que debe declarar e inicializar afuera el bloque try.

boolean success = false; 
try { 
    success = doStuff(); 
} catch(...) { 
    ... 
} 

Este es un patrón de código bastante horrible y trato de evitarlo si es posible.La forma de hacerlo es darse cuenta de que apilar los bloques catch (opción dos) solo tiene sentido si esos bloques catch finalizan normalmente (es decir, no se arroja ninguna excepción). Pero en ese caso puedes mover todo el bloque al método que se está llamando.

private boolean doStuff() { 
    try { 
     ...do stuff... 
     return true; 
    } catch(SomeException ex) { 
     ...fidget around... 
     return false; 
    } 
} 

Y sólo lo llaman así:

boolean success = doStuff(); 

Como acotación al margen, Java 7 le ayuda con la gestión de excepciones mucho, se puede catch multiple exceptions in one catch block, or catch and rethrow neatly. También le ayuda a do away with catch blocks altogether para cosas como cerrar las conexiones. Si no hay otro factor que lo frene, consideraría cambiarlo.

0

Es bueno que intente crear código limpio.

En mi humilde opinión, lo que está haciendo es levemente excesivo. Suponiendo que tiene que manejar las excepciones, todo lo que está haciendo es crear otra llamada a método. Aún necesitas un bloque try/catch. Simplemente haría lo que denominó "la forma correcta" de manejarlo.

Si no necesita manejar las excepciones y representan fallas de las que no puede recuperar, puede crear excepciones de tiempo de ejecución, lo que detendrá su programa (suponiendo que no las capte).

0

El último bloque de código que tiene se ve bien, aparte de una cosa. Es mejor si las excepciones se extienden desde RuntimeException. De esta forma, no tiene que informar qué excepciones doSomething() arroja.

Mantener el manejo de errores separado del resto de su código es generalmente una buena práctica. Mantiene el otro código limpio.

+0

¿Puedes aclarar a qué te refieres con "si las excepciones se extienden desde RuntimeException"? – loneboat

+0

Las excepciones comprobadas deben publicitarse en todos los métodos en los que puedan arrojarse, lo que tiende a saturar mucho el código. Las excepciones de tiempo de ejecución no necesitan publicidad, por lo que si lanza algunas excepciones personalizadas en sus métodos, me aseguraré de que sean excepciones de tiempo de ejecución. – fivedigit

+0

Cool, gracias!(texto de relleno) – loneboat

0

Tanto el primer como el tercer ejemplo me parecen la mejor manera de manejar este tipo de situaciones, y son la forma en que generalmente trato de manejar la enormida cantidad de excepciones que mi código puede arrojar. Personalmente, prefiero la tercera opción, es mucho más organizada y concisa, y no cae en ningún antipatrón que conozco. La segunda opción es simplemente desagradable, y debe evitarse, es solo una gran pérdida de espacio, ya que no necesita una gran cantidad de cláusulas de prueba para hacer las cosas.

2

El objetivo de manejar las excepciones es que debe poder avanzar aún cuando se encuentre con una excepción. La primera y la tercera forma son básicamente las mismas. Si se produce una excepción en el method1(), saldrá directamente del método padre completo sin siquiera intentar ejecutar method2() y otros .. El segundo método puede parecer desordenado al principio, pero en realidad es la forma en que debe hacerse.

Incluso mejor que eso sería manejar las excepciones que espera que arroje en el método mismo y devolver un tipo de valor predeterminado que permita una ejecución posterior sin romper la lógica comercial o causar incoherencias.

EDIT:

Ejemplo de ventaja cuando se utiliza el método 2:

Supongamos que usted está haciendo un programa de análisis de texto y esperar un date en el formato DD-MM-YYYY. Pero mientras realiza el análisis, encuentra que obtiene date en el formato DD-MON-YYYY. Este tipo de excepciones de análisis se pueden manejar y aún permiten una ejecución posterior.

0

Si hay algunas excepciones que son arrojados una sola vez por un solo bloque de código y si el lugar de manejo de excepciones no afecta a la lógica de negocio, prefiero manejarlos en el acto.

Pero si la misma excepción puede ser lanzado desde múltiples lugares, me gusta manejar la situación al final, al igual que su forma correcta.

Agregar una llamada de método adicional me parece un poco torpe, porque no está evitando el problema, todo lo que hace es colocarlo en otro lugar.

Una cosa importante que falta aquí es el finalmente el bloque, que creo que es necesario independientemente del estilo de manejo de excepciones.

Por supuesto, esta es una elección personal, no puede haber respuestas correctas o incorrectas, supongo.

1

Esto realmente depende de dónde (en qué nivel) desea catch esa excepción.

Un método que arroja una excepción simplemente significa que "No quiero tratar con esta excepción/problema, que alguien más la atrape". El código limpio debe venir después de esta forma de pensar; debería atrapar aquí o no ...

En su último caso si vuelve a lanzar esas excepciones, esto significaría que no necesitará los objetos x, y, z durante el manejo de errores ya que probablemente estarían fuera del alcance .

+0

Buen punto sobre el alcance en el último ejemplo. ¡Gracias! – loneboat

1

El punto de lanzar una excepción es informar a su interlocutor de que no pudo completar la acción solicitada, el punto de atrapar una excepción es tomar la acción adecuada en respuesta a esa falla. Si su acción no difiere en función del tipo de excepción, no es útil capturar un tipo específico.

Como llamador de una excepción detectada, su trabajo es o bien recuperarse de la falla (intente con métodos alternativos, use valores predeterminados, lo que sea), o para gestionar la falla (limpieza, registro).

Si el tipo de excepción es útil para hacer cualquiera de las dos cosas, obtenga el tipo, si no está haciendo ninguna de las dos opciones, deje que suba a uno de sus llamadores que sí lo haga.