exception handling - ¿Envolver todo en bloques de prueba/captura constituye una programación defensiva?
exception-handling defensive-programming (12)
He estado programando durante los últimos 3 años. Cuando programo, utilizo para manejar todas las excepciones conocidas y alertar al usuario con gracia. Recientemente he visto un código que tiene casi todos los métodos incluidos en los bloques try / catch. El autor dice que es parte de la programación defensiva. Me pregunto, ¿esta es una programación realmente defensiva? ¿Recomiendas poner todo su código en bloques de prueba?
¿Puedo decir aquí que cada vez que uno de mis compañeros escribe una firma de método con "throws Exception" en lugar de enumerar los tipos de excepciones que realmente arroja el método, quiero ir y dispararles en la cabeza? El problema es que después de un tiempo tienes 14 niveles de llamadas que dicen "lanza excepción", por lo que refactorizar para que declaren lo que realmente lanzan es un ejercicio importante.
Capturar excepciones al azar es malo. ¿Entonces que?
- ¿Ingnóralos? Excelente. Déjame saber cómo funciona eso para ellos.
- Entrarlos y seguir corriendo? Yo creo que no.
- ¿Lanzar una excepción diferente como parte de un bloqueo? Buena suerte depurando eso.
Es bueno captar excepciones para las que realmente puede hacer algo significativo. Estos casos son fáciles de identificar y mantener.
El término "programación defensiva" significa escribir código de tal manera que pueda recuperarse de situaciones de error o que evite por completo la situación de error. Por ejemplo:
private String name;
public void setName(String name) {
}
¿Cómo se maneja el nombre == nulo? ¿Lanzas una excepción o la aceptas? Si no tiene sentido tener un objeto sin nombre, entonces debe lanzar una excepción. ¿Qué pasa con el nombre == ""?
Pero ... luego escribes un editor. Mientras configura la IU, encuentra que hay situaciones en las que un usuario puede decidir quitar el nombre o el nombre puede quedar vacío mientras el usuario lo edita.
Otro ejemplo:
public boolean isXXX (String s) {
}
Aquí, la estrategia de defensa a menudo es falsa cuando s == null (evita los NPE cuando puedas).
O:
public String getName() {
}
Un programador defensivo podría devolver "" si name == null para evitar NPE en el código de llamada.
En C ++, la única razón para escribir muchos bloques try / catch es obtener un rastro de la pila donde se lanzó la excepción. Lo que usted hace es escribir un try / catch en todas partes, y (suponiendo que no se encuentre en el lugar correcto para tratar con la excepción) hacer que el catch capture alguna información de rastreo y luego volver a lanzar la excepción. De esta forma, si una excepción sube todo y hace que el programa finalice, tendrá un rastro de pila completa donde todo comenzó a ir mal (si no lo hace, entonces una excepción de C ++ no controlada ha desenrollado útilmente la pila y ha erradicado cualquier posibilidad de que usted averigüe de dónde vino).
Me imagino que en cualquier idioma con un mejor manejo de excepciones (es decir, las excepciones no detectadas te dicen de dónde vienen), solo querrás detectar excepciones si pudieras hacer algo al respecto. De lo contrario, solo harás que tu programa sea difícil de leer.
Existe el proceso de "demasiado", y atrapando todas las excepciones, kindof derrota el punto. Específicamente para C ++, la declaración catch (...) captura todas las excepciones pero no puede procesar el contenido de esa excepción porque no conoce el tipo de la excepción (y podría ser cualquier cosa).
Debería captar las excepciones que puede manejar total o parcialmente, volviendo a lanzar las excepciones parciales. No debería captar ninguna excepción que no pueda manejar, porque eso solo ofuscará los errores que pueden (o más bien) le morderán más adelante.
Mi regla básica es: a menos que pueda solucionar el problema que causó la excepción, no la atrape, permita que suba hasta un nivel en el que pueda solucionarse .
En mi experiencia, el 95% de todos los bloques catch ignoran la excepción ( catch {}
) o simplemente registran el error y vuelven a lanzar la excepción. Esto último puede parecer lo correcto, pero en la práctica, cuando se hace en todos los niveles, simplemente terminas con tu registro lleno de cinco copias del mismo mensaje de error. Por lo general, estas aplicaciones tienen una "captura de ignorar" en el nivel más alto (ya que "tenemos try / catch en todos los niveles inferiores"), lo que resulta en una aplicación muy lenta con muchas excepciones perdidas y un registro de error demasiado largo para cualquiera que esté dispuesto a revisarlo.
No, no es "programación defensiva". Su compañero de trabajo está tratando de racionalizar su mal hábito mediante el empleo de una palabra de moda para un buen hábito.
Lo que está haciendo debe llamarse "barrerlo debajo de la alfombra". Es como de manera uniforme (void)
el valor de retorno del estado de error de las llamadas a métodos.
Recomendaría contra esta práctica. Poner código en bloques de prueba cuando conoces los tipos de excepciones que se pueden arrojar es una cosa. Le permite, como indicó, recuperar y / o alertar graciosamente al usuario sobre el error. Sin embargo, poner todo su código dentro de dichos bloques donde no se sabe cuál es el error que puede ocurrir es usar excepciones para controlar el flujo del programa, que es un gran no-no.
Si está escribiendo un código bien estructurado, sabrá sobre cada excepción que pueda ocurrir, y puede detectarlas específicamente. Si no sabe cómo se puede lanzar una excepción en particular, no la capte, por si acaso. Cuando sucede, puede averiguar la excepción, qué lo causó y luego atraparlo.
Si va a manejar excepciones aleatorias, trátelas solo en un lugar: en la parte superior de la aplicación, para los fines de:
- presentando un mensaje amigable al usuario, y
- guardando los diagnósticos.
Para todo lo demás, desea el colapso más inmediato y específico de la ubicación, para que pueda detectar estas cosas lo antes posible; de lo contrario, el manejo de excepciones se convierte en una forma de ocultar el código y el diseño descuidado.
En la mayoría de los casos en que la excepción es predecible, es posible realizar pruebas de antemano, para la condición que captará el manejador de excepciones.
En general, si ... Else es mucho mejor que Try ... Catch.
Supongo que la verdadera respuesta es "depende". Si los bloques de prueba capturan excepciones muy genéricas, yo diría que es programación defensiva de la misma manera que nunca salir de su vecindario es conducir a la defensiva. Un try-catch (imo) se debe adaptar a excepciones específicas.
De nuevo, esta es solo mi opinión, pero mi concepto de programación defensiva es que necesitas menos / más pequeños bloques de prueba, no más / más grandes. Su código debería estar haciendo todo lo posible para asegurarse de que una condición de excepción nunca pueda existir en primer lugar.
Uso extensivo de Try ... Catch no es programación defensiva, es solo clavar el cadáver en posición vertical .
Prueba ... Finalmente, se puede usar ampliamente para la recuperación frente a excepciones inesperadas. Solo si espera una excepción y ahora cómo manejarla debería usar Try..Catch en su lugar.
A veces veo Try ... Capturar System.Exception, donde el bloque catch simplemente registra la excepción y vuelve a lanzar. Hay al menos 3 problemas con ese enfoque:
- Retiro supone una excepción no controlada y, por lo tanto, el programa debe finalizar porque se encuentra en un estado desconocido. Pero catch causa la ejecución de los bloques Finally debajo del bloque Catch. En una situación indefinida, el código en estos bloques Finally podría empeorar el problema.
- El código en esos bloques Finally cambiará el estado del programa. Por lo tanto, cualquier registro no capturará el estado real del programa cuando se lanzó originalmente la excepción. Y la investigación será más difícil porque el estado ha cambiado.
- Te da una experiencia de depuración miserable, porque el depurador se detiene en el nuevo lanzamiento, no el lanzamiento original.
He encontrado que los bloques de "prueba" de "captura" son muy útiles, especialmente si se usa algo en tiempo real (como acceder a una base de datos).
¿Demasiados? Ojo del espectador.
Descubrí que copiar un registro a Word y buscar con "buscar" - si el lector de registro no tiene "buscar" o "buscar" como parte de sus herramientas incluidas - es una forma simple pero excelente de avanzar registros detallados.
Ciertamente parece "defensivo" en el sentido ordinario de la palabra.
He descubierto, a través de la experiencia, seguir lo que haga su gerente, líder de equipo o compañero de trabajo. Si solo está programando para usted, utilícelos hasta que el código sea "estable" o en compilaciones de depuración, y luego elimínelos cuando haya terminado.