¿Buen uso de try catch-blocks?

15

Siempre me encuentro luchando con esto ... tratando de encontrar el equilibrio correcto entre intentar / atrapar y que el código no se convierta en este lío obsceno de pestañas, corchetes y excepciones lanzados de nuevo a la pila de llamadas como una papa caliente. Por ejemplo, tengo una aplicación que estoy desarrollando en este momento que usa SQLite. Tengo una interfaz de base de datos que abstrae las llamadas de SQLite, y un modelo que acepta las cosas que entran / salen de la base de datos ... Entonces, si ocurre una excepción de SQLite, debe lanzarse al modelo (que lo llamó ), quién tiene que pasarlo a quien llamó AddRecord / DeleteRecord / lo que sea ...  

Soy fanático de las excepciones en lugar de devolver códigos de error porque los códigos de error pueden ignorarse, olvidarse, etc., mientras que una Excepción esencialmente tiene que ser manejada (concedido, podría atrapar y seguir de inmediato ...) Estoy seguro de que tiene que haber una mejor manera de lo que tengo ahora.

Editar:  debería haber redactado esto un poco diferente. Entiendo volver a tirar como diferentes tipos y tal, lo redacté mal y es mi culpa. Mi pregunta es ... ¿cómo se mantiene mejor el código limpio al hacerlo? Simplemente comienza a sentirse extremadamente abarrotado para mí después de un tiempo.

trata de atraparlo
fuente
¿Qué lenguaje de programación?
Apalala
2
C # en este momento, pero estoy tratando de pensar en general.
trycatch
C # no fuerza la declaración de excepciones lanzadas, lo que facilita el manejo de excepciones donde sea razonable, y evita que los programadores se vean tentados a atraparlas sin realmente manejarlas. Anders Hejlsberg, diseñador de C #, presenta el caso contra las excepciones comprobadas en este artículo artima.com/intv/handcuffs.html
Apalala

Respuestas:

14

Piénselo en términos de tipeo fuerte, incluso si no está usando un lenguaje fuertemente tipado; si su método no puede devolver el tipo que esperaba, debería arrojar una excepción.

Además, en lugar de lanzar la excepción SQLException hasta el modelo (o peor, UI), cada capa debe capturar excepciones conocidas y envolver / mutar / reemplazarlas con excepciones adecuadas para esa capa:

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

Esto debería ayudar a limitar el número de excepciones que está buscando en cada capa y ayudarlo a mantener un sistema de excepciones organizado.

Nicole
fuente
Hice algunas ediciones, redacté mal la Q original ... mi pregunta es más acerca de cómo mantener el código limpio mientras se manejan todos los intentos y capturas y demás. Comienza a sentirse muy desordenado cuando intenta bloquear bloques en todas partes ...
trycatch
1
@Ed, ¿no te molesta que tu interfaz de usuario o modelo esté captando "SQLException"? Para mí, eso no es muy relevante. A cada uno lo suyo, supongo.
Nicole
1
@Ed, eso podría estar bien en idiomas que no tienen excepciones marcadas, pero en idiomas con excepciones marcadas es realmente feo tener throws SQLExceptionun método que no implique que SQL esté involucrado. ¿Y qué sucede si decides que algunas operaciones deberían ir a un almacén de archivos? Ahora tiene que declarar throws SQLException, IOException, etc. Se saldrá de control.
Mike Daniels
1
@Ed para el usuario final SQLException no significa mucho, especialmente si su sistema puede soportar múltiples tipos de persistencia aparte de las bases de datos relacionales. Como programador de GUI, preferiría tratar con DataNotFoundException que un conjunto completo de excepciones de bajo nivel. Muy a menudo, una excepción a una biblioteca de bajo nivel es solo parte de la vida normal arriba, en este caso son mucho más fáciles de manejar cuando su nivel de abstracción coincide con el de la aplicación.
Newtopian
1
@Newtopian: nunca dije presentar la excepción en bruto al usuario final. Para los usuarios finales, un simple mensaje 'El programa ha dejado de funcionar' es suficiente mientras se registra la Excepción en algún lugar útil para que el soporte pueda recoger. En modo de depuración, es útil mostrar la excepción. No debería preocuparse por todas las excepciones posibles que una API podría generar a menos que tenga un controlador específico para tales Excepciones; solo deje que su receptor de excepciones no manejadas se encargue de todo.
Ed James
9

Las excepciones permiten escribir código más limpio porque la mayor parte se ocupa del caso normal, y los casos excepcionales se pueden manejar más adelante, incluso en un contexto diferente.

La regla para manejar (atrapar) excepciones es que debe hacerse por contexto que realmente puede hacer algo al respecto. Pero eso tiene una excepción:

Las excepciones deben detectarse en los límites del módulo (especialmente los límites de la capa) e incluso para encerrarlos y lanzar una excepción de nivel superior que tenga significado para la persona que llama. Cada módulo y capa debe ocultar sus detalles de implementación incluso con respecto a las excepciones (un módulo de montón puede arrojar HeapFull pero nunca ArrayIndexOutOfBounds).

En su ejemplo, es poco probable que las capas superiores puedan hacer algo con respecto a una excepción SQLite (si lo hacen, entonces está todo tan acoplado a SQLite que no podrá cambiar la capa de datos a otra cosa). Hay un puñado de razones previsibles para que cosas como Agregar / Eliminar / Actualizar fallen, y algunas de ellas (cambios incompatibles en transacciones concurrentes) son imposibles de recuperar incluso en la capa de datos / persistencia (violación de las reglas de integridad, fi). La capa de persistencia debe traducir las excepciones a algo significativo en los términos de la capa del modelo para que las capas superiores puedan decidir si volver a intentarlo o fallar con gracia.

Apalala
fuente
Estoy de acuerdo, y edité mi pregunta para reflejar que donde la había redactado mal. Quería que esto fuera más una cuestión de cómo evitar que el intento y la captura desordenen las cosas.
trycatch
Puede aplicar los principios que @Ed James y yo mencionamos dentro de una capa o módulo. En lugar de llamar a SQLite directamente desde cualquier lugar, tenga algunos métodos / funciones que hablen con SQLite y se recuperen de las excepciones o las traduzcan a otras más genéricas. Si una transacción involucra varias consultas y actualizaciones, no necesita manejar todas las excepciones posibles en cada una: un solo try-catch externo puede traducir las excepciones, y una interna puede encargarse de las actualizaciones parciales con una reversión. También puede mover las actualizaciones a su propia función para un manejo de excepciones más simple.
Apalala
1
esto sería más fácil de entender con ejemplos de código ..
Haga clic Upvote
Esta fue probablemente una pregunta más adecuada para stackoverflow desde el principio.
Apalala
5

Como regla general, solo debe detectar Excepciones específicas (por ejemplo, IOException), y solo si tiene algo específico que hacer una vez que haya detectado la Excepción.

De lo contrario, a menudo es mejor dejar que las Excepciones salgan a la superficie para que puedan ser expuestas y tratadas. Algunas personas llaman a esto falla rápida.

Debería tener algún tipo de controlador en la raíz de su aplicación para detectar Excepciones no manejadas que han surgido desde abajo. Esto le brinda la oportunidad de presentar, informar o administrar la excepción de manera adecuada.

Ajustar excepciones es útil cuando necesita lanzar una excepción en un sistema distribuido y el cliente no tiene la definición de la falla del lado del servidor.

Ed James
fuente
Capturar y silenciar excepciones es algo terrible. Si el programa no funciona, debería bloquearse con una excepción y un rastreo. Debería confundirse con el registro silencioso de las cosas, pero haciendo un desastre con los datos.
S.Lott
1
@ S.Lott Estoy de acuerdo en que no se debe silenciar la excepción porque les resulta molesto, pero simplemente bloquear una aplicación es un poco extremo. Hay muchos casos en los que es posible detectar la excepción y restablecer el sistema en un estado conocido, en tales casos, el controlador global de todo es bastante útil. Sin embargo, si el proceso de reinicio falla a su vez de una manera que no se puede tratar con seguridad, entonces sí, dejar que se bloquee es mucho mejor que meter la cabeza en la arena.
Newtopian
2
Una vez más, todo depende de lo que esté construyendo, pero generalmente no estoy de acuerdo con dejarlos burbujear, ya que crea fugas de abstracción. Cuando uso una API, prefiero ver que expone excepciones que están en sintonía con el modelo de la API. También odio las sorpresas, así que odio cuando una API solo deja que la excepción relacionada con su implementación se vea sin previo aviso. Si no tengo idea de lo que viene, ¿cómo puedo reaccionar? Demasiado a menudo uso redes de captura mucho más amplias para evitar que las sorpresas bloqueen mi aplicación sin previo aviso.
Newtopian
@Newtopian: las excepciones de "ajuste" y "reescritura" reducen la filtración de abstracciones. Todavía burbujean apropiadamente. "Si no tengo idea de lo que viene hacia mí, ¿cómo puedo reaccionar? Demasiado a menudo uso redes de captura mucho más amplias". Es lo que te sugerimos que dejes de hacer. No necesitas atrapar todo. El 80% del tiempo, lo correcto es no atrapar nada. El 20% de las veces hay una respuesta significativa.
S.Lott 01 de
1
@Newtopian, creo que necesitamos distinguir entre las excepciones que normalmente lanzará un objeto y, por lo tanto, deben envolverse, y las excepciones que surgen debido a errores en el código del objeto y no deben envolverse.
Winston Ewert
4

Imagina que estás escribiendo una clase de pila. No incluye ningún código de manejo de excepciones en la clase, como resultado, podría producir las siguientes excepciones.

  1. ArrayIndexError: se genera cuando el usuario intenta estallar desde una pila vacía
  2. NullPtrException: provocada debido a un error en la implementación que causa un intento de hacer referencia a una referencia nula

Un enfoque simplista para ajustar las excepciones podría decidir ajustar ambas excepciones en una clase de excepción StackError. Sin embargo, esto realmente pierde el punto de envolver excepciones. Si un objeto arroja una excepción de bajo nivel que debería significar que el objeto está roto. Sin embargo, hay un caso en el que esto es aceptable: cuando el objeto está de hecho roto.

El punto de envolver excepciones es que el objeto debe dar excepciones apropiadas para errores normales. La pila debería elevar StackEmpty, no ArrayIndexError cuando aparece desde una pila vacía. La intención no es evitar lanzar otras excepciones si el objeto o el código están rotos.

Lo que realmente queremos evitar es detectar excepciones de bajo nivel que se hayan pasado a través de objetos de alto nivel. Una clase de pila que arroja un ArrayIndexError cuando emerge de una pila vacía es un problema menor. Si realmente capta ese ArrayIndexError, entonces tenemos un problema grave. La propagación de errores de bajo nivel es un pecado mucho menos grave que atraparlos.

Para traer esto de vuelta a su ejemplo de SQLException: ¿por qué obtiene SQLExceptions? Una razón es porque está pasando consultas no válidas. Sin embargo, si su capa de acceso a datos está generando malas consultas, está rota. No debe intentar volver a envolver su rotura en una excepción DataAccessFailure.

Sin embargo, una excepción SQLException también podría surgir debido a una pérdida de conexión a la base de datos. Mi estrategia en ese punto es detectar la excepción en la última línea de defensa, informar al usuario que se perdió la conectividad de la base de datos y cerrarla. Dado que la aplicación tiene pérdida de acceso a la base de datos, realmente no hay mucho más que se pueda hacer.

No sé cómo se ve tu código. Pero parece que podría estar traduciendo ciegamente todas las excepciones en excepciones de nivel superior. Solo debe hacerlo en un número relativamente pequeño de casos. La mayoría de las excepciones de nivel inferior indican errores en su código. Atraparlos y envolverlos es contraproducente.

Winston Ewert
fuente