La forma moderna de realizar el manejo de errores ...

116

He estado reflexionando sobre este problema durante un tiempo y me encuentro continuamente encontrando advertencias y contradicciones, por lo que espero que alguien pueda llegar a una conclusión sobre lo siguiente:

Favorecer excepciones sobre códigos de error

Por lo que yo sé, desde que trabajé en la industria durante cuatro años, leyendo libros y blogs, etc., la mejor práctica actual para el manejo de errores es lanzar excepciones, en lugar de devolver códigos de error (no necesariamente un código de error). , pero un tipo que representa un error).

Pero, para mí, esto parece contradecir ...

Codificación a interfaces, no implementaciones

Codificamos interfaces o abstracciones para reducir el acoplamiento. No sabemos, o queremos saber, el tipo específico y la implementación de una interfaz. Entonces, ¿cómo podemos saber qué excepciones deberíamos estar tratando de atrapar? La implementación podría lanzar 10 excepciones diferentes, o podría no lanzar ninguna. Cuando detectamos una excepción, seguramente estamos haciendo suposiciones acerca de la implementación.

A menos que la interfaz tenga ...

Especificaciones de excepción

Algunos lenguajes permiten a los desarrolladores afirmar que ciertos métodos producen ciertas excepciones (Java, por ejemplo, usa la palabra clave throws ). Desde el punto de vista del código de llamada, esto parece correcto: sabemos explícitamente qué excepciones deberíamos detectar.

Pero - esto parece sugerir un ...

Abstracción con fugas

¿Por qué debería una interfaz especificar qué excepciones se pueden lanzar? ¿Qué sucede si la implementación no necesita lanzar una excepción o si necesita lanzar otras excepciones? No hay forma, a nivel de interfaz, de saber qué excepciones puede querer lanzar una implementación.

Entonces ...

Para concluir

¿Por qué se prefieren las excepciones cuando parecen (en mi opinión) contradecir las mejores prácticas de software? Y, si los códigos de error son tan malos (y no necesito que me vendan los vicios de los códigos de error), ¿hay otra alternativa? ¿Cuál es el estado de la técnica actual (o pronto lo estará) para el manejo de errores que cumpla con los requisitos de las mejores prácticas como se describe anteriormente, pero no se basa en que el código de llamada verifique el valor de retorno de los códigos de error?

    
pregunta RichK 03.05.2012 - 12:35
fuente

11 respuestas

31

En primer lugar, no estaría de acuerdo con esta afirmación:

  

Favorecer excepciones sobre códigos de error

Este no es siempre el caso: por ejemplo, eche un vistazo a Objective-C (con el marco de Foundation). Allí, NSError es la forma preferida de manejar los errores, a pesar de la existencia de lo que un desarrollador de Java llamaría verdaderas excepciones: @try, @catch, @throw, NSException class, etc.

Sin embargo, es cierto que muchas interfaces pierden sus abstracciones con las excepciones lanzadas. Creo que esto no es culpa del estilo de "excepción" de propagación / manejo de errores. En general, creo que el mejor consejo sobre el manejo de errores es este:

Trate el error / excepción en el nivel más bajo posible, período

Creo que si uno se apega a esa regla de oro, la cantidad de "fugas" de las abstracciones puede ser muy limitada y contenida.

Sobre si las excepciones lanzadas por un método deberían ser parte de su declaración, creo que deberían: son parte del contrato definido por esta interfaz: Este método hace A, o falla con B o C.

Por ejemplo, si una clase es un analizador XML, una parte de su diseño debe ser para indicar que el archivo XML proporcionado es simplemente incorrecto. En Java, normalmente lo hace declarando las excepciones que espera encontrar y agregándolas a la parte throws de la declaración del método. Por otro lado, si uno de los algoritmos de análisis ha fallado, no hay razón para pasar esa excepción por encima de la no controlada.

Todo se reduce a una cosa: Buen diseño de interfaz. Si diseñas tu interfaz lo suficientemente bien, ninguna cantidad de excepciones debería perseguirte. De lo contrario, no son solo las excepciones lo que le molesta.

También, creo que los creadores de Java tenían razones de seguridad muy sólidas para incluir excepciones a una declaración / definición de método.

Una última cosa: algunos idiomas, Eiffel, por ejemplo, tienen otros mecanismos para el manejo de errores y simplemente no incluyen capacidades de lanzamiento. Allí, una "excepción" de clasificación se genera automáticamente cuando no se cumple una condición posterior para una rutina.

    
respondido por el K.Steff 03.05.2012 - 13:23
fuente
27

Me gustaría señalar que las excepciones y los códigos de error no son la única forma de tratar los errores y las rutas alternativas de código.

Fuera de la parte superior de la mente, puede tener un enfoque como el de Haskell, donde los errores se pueden señalar a través de tipos de datos abstractos con múltiples constructores (piense enumas discriminadas, o punteros nulos, pero tipografía segura y con la posibilidad de agregar funciones sintácticas o de azúcar para hacer que el flujo de código se vea bien).

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail es una función que devuelve un valor envuelto en Maybe. Funciona como un puntero anulable, pero la notación do garantiza que todo se evalúa como nulo si alguno de a, b o c falla. (y el compilador lo protege de hacer una excepción NullPointerException accidental)

Otra posibilidad es pasar un objeto de controlador de errores como un argumento adicional a cada función que llame. Este controlador de errores tiene un método para cada posible "excepción" que puede ser señalada por la función a la que se le pasa, y puede ser utilizada por esa función para tratar las excepciones donde ocurren, sin tener necesariamente que rebobinar la pila a través de excepciones.

Common LISP hace esto, y lo hace viable al contar con soporte sintético (argumentos implícitos) y tener las funciones integradas que siguen este protocolo.

    
respondido por el hugomg 03.05.2012 - 17:53
fuente
8

Sí, las excepciones pueden causar abstracciones con fugas. Pero, ¿los códigos de error no son aún peores a este respecto?

Una forma de resolver este problema es hacer que la interfaz especifique exactamente qué excepciones se pueden lanzar en qué circunstancias y declarar que las implementaciones deben asignar su modelo de excepción interno a esta especificación, capturando, convirtiendo y volviendo a lanzar excepciones si es necesario. . Si quieres una interfaz "prefecta", ese es el camino a seguir.

En la práctica, generalmente es suficiente especificar excepciones que forman parte lógica de la interfaz y que un cliente puede querer detectar y hacer algo al respecto. Generalmente se entiende que puede haber otras excepciones cuando ocurren errores de bajo nivel o se manifiesta un error, y que un cliente solo puede manejar generalmente mostrando un mensaje de error y / o cerrando la aplicación. Al menos la excepción puede contener información que ayude a diagnosticar el problema.

De hecho, con los códigos de error, prácticamente sucede lo mismo, solo de una manera más implícita, y con muchas más probabilidades de que la información se pierda y la aplicación termine en un estado inconsistente.

    
respondido por el Michael Borgwardt 03.05.2012 - 12:50
fuente
5

Aquí hay muchas cosas buenas, me gustaría agregar que todos debemos tener cuidado con el código que usa excepciones como parte del flujo de control normal. A veces las personas se meten en esa trampa donde cualquier cosa que no sea el caso habitual se convierte en una excepción. Incluso he visto una excepción utilizada como condición de terminación de bucle.

Las excepciones significan que "algo que no puedo manejar aquí sucedió, necesito ir a otra persona para averiguar qué hacer". Un usuario que escribe una entrada no válida no es una excepción (que debe ser manejada localmente por la entrada preguntando nuevamente, etc.).

Otro caso degenerado de uso de excepciones que he visto es el de personas cuya primera respuesta es "lanzar una excepción". Esto casi siempre se hace sin escribir la captura (regla general: escriba la captura primero, luego la declaración de lanzamiento). En aplicaciones grandes, esto se vuelve problemático cuando una excepción no detectada brota de las regiones inferiores y explota el programa.

No estoy en contra de las excepciones, pero parecen singletes de hace unos años: se usan con demasiada frecuencia y de manera inapropiada. Son perfectos para el uso previsto, pero ese caso no es tan amplio como algunos piensan.

    
respondido por el anon 05.05.2012 - 16:51
fuente
4
  

Abstracción con fugas

     

¿Por qué debería una interfaz especificar qué excepciones pueden   ser arrojado? ¿Qué pasa si la implementación no necesita lanzar un   ¿Excepción, o necesita lanzar otras excepciones? No hay manera, en una   nivel de interfaz, para saber qué excepciones desea una implementación   lanzar.

No. Las especificaciones de excepción están en el mismo grupo que los tipos de retorno y argumento, son parte de la interfaz. Si no puede cumplir con esa especificación, entonces no implemente la interfaz. Si nunca tiras, entonces está bien. No hay nada importante en especificar excepciones en una interfaz.

Los códigos de error son más que malos. Son terribles. Debe recordar manualmente verificarlos y propagarlos, cada vez, para cada llamada. Esto viola DRY, para empezar, e infla masivamente su código de manejo de errores. Esta repetición es un problema mucho más grande que cualquier excepción enfrentada. Nunca se puede ignorar una excepción en silencio, pero las personas pueden ignorar los códigos de retorno y, de hecho, lo hacen en silencio, sin duda algo malo.

    
respondido por el DeadMG 03.05.2012 - 18:37
fuente
2

Bueno, el manejo de excepciones puede tener su propia implementación de interfaz. Dependiendo del tipo de excepción lanzada, realice los pasos deseados.

La solución a su problema de diseño es tener dos implementaciones de interfaz / abstracción. Uno para la funcionalidad y el otro para manejo de excepciones. Y dependiendo del tipo de Excepción capturada, llame a la clase de tipo de excepción apropiada.

La implementación de códigos de error es una forma ortodoxa de manejar las excepciones. Es como el uso de cadena contra generador de cadena.

    
respondido por el Estefany Velez 02.05.2012 - 14:35
fuente
2

Las excepciones de IM-very-HO se deben evaluar caso por caso, ya que al interrumpir el flujo de control aumentarán la complejidad real y percibida de su código, en muchos casos innecesariamente. Dejando a un lado la discusión relacionada con el lanzamiento de excepciones dentro de sus funciones, que en realidad puede mejorar su flujo de control, si se trata de observar las excepciones de lanzamiento a través de los límites de llamadas, considere lo siguiente:

Permitir que una persona llamada rompa su flujo de control puede no proporcionar ningún beneficio real, y puede que no haya una manera significativa de lidiar con la excepción. Para un ejemplo directo, si uno está implementando el patrón Observable (en un lenguaje como C # donde tiene eventos en todas partes y no hay throws explícito en la definición), no hay ninguna razón real para permitir que el Observador interrumpa su flujo de control si choques, y no hay una manera significativa de lidiar con sus cosas (por supuesto, un buen vecino no debe lanzar cuando observa, pero nadie es perfecto).

La observación anterior se puede extender a cualquier interfaz acoplada libremente (como usted señaló); Creo que en realidad es una norma que, después de subir de 3 a 6 cuadros de pila, es probable que una excepción no detectada termine en una sección de código que:

  • es demasiado abstracto para lidiar con la excepción de cualquier manera significativa, incluso si la excepción en sí está invalidada;
  • está realizando una función genérica (no podría importarle menos el motivo por el que falló, como una bomba de mensajes o el observable);
  • es específico, pero con una responsabilidad diferente, y realmente no debería preocuparse;

Teniendo en cuenta lo anterior, decorar interfaces con throws semántica es solo una ganancia funcional marginal, porque a muchas personas que llaman a través de los contratos de interfaz solo les importaría si fallase, no por qué.

Yo diría que luego se convierte en una cuestión de gusto y conveniencia: su enfoque principal es recuperar con gracia su estado tanto en la persona que llama como en la persona que llama después de una "excepción", por lo tanto, si tiene mucha experiencia en mover códigos de error alrededor (proveniente de un fondo en C), o si estás trabajando en un entorno donde las excepciones pueden volverse malvadas (C ++), no creo que lanzar cosas sea tan importante para un OOP limpio y agradable que no puedas confiar en tus viejos patrones si te sientes incómodo con eso. Especialmente si conduce a romper SoC.

Desde una perspectiva teórica, creo que una manera de manejar las excepciones de SoC kosher se puede derivar directamente de la observación de que la mayoría de las veces a la persona que llama directamente solo le importa que usted haya fallado, no por qué. Los lanzamientos de la persona que llama, alguien muy cerca de arriba (2-3 cuadros) atrapa una versión mejorada, y la excepción real siempre se transfiere a un controlador de errores especializado (incluso si solo se realiza el rastreo); aquí es donde el AOP sería útil, ya que estos controladores es probable que sean horizontales.

    
respondido por el vski 03.05.2012 - 13:28
fuente
1
  

Favorecer excepciones sobre códigos de error

  • Ambos deben coexistir.

  • Devuelve el código de error cuando anticipas cierto comportamiento.

  • Devuelve la excepción cuando no anticipaste algún comportamiento.

  • Los códigos de error normalmente se asocian con un solo mensaje, cuando el tipo de excepción permanece, pero un mensaje puede variar

  • La excepción tiene un seguimiento de pila, cuando el código de error no lo hace. No utilizo códigos de error para depurar el sistema roto.

  

Codificación para interfaces no implementaciones

Esto puede ser específico de JAVA, pero cuando declaro mis interfaces no especifico qué excepciones puede generar una implementación de esa interfaz, simplemente no tiene sentido.

  

Cuando detectamos una excepción, seguramente estamos haciendo suposiciones sobre el   implementación?

Esto es totalmente de usted. Puede intentar capturar un tipo de excepción muy específico y luego capturar un Exception más general. ¿Por qué no dejar que la excepción se propague por la pila y luego la maneje? Alternativamente, puede ver la programación de aspectos donde el manejo de excepciones se convierte en un aspecto "conectable".

  

¿Qué pasa si la implementación no necesita lanzar una excepción, o   ¿Necesita lanzar otras excepciones?

No entiendo por qué es un problema para ti. Sí, puede tener una implementación que nunca falla o lanza excepciones y puede tener una implementación diferente que falla constantemente y lanza excepciones. Si ese es el caso, entonces no especifique ninguna excepción en la interfaz y se solucionará su problema.

¿Cambiaría algo si en lugar de una excepción su implementación devolviera un objeto de resultado? Este objeto contendría el resultado de su acción junto con cualquier error / falla en su caso. A continuación, puede interrogar ese objeto.

    
respondido por el CodeART 04.05.2012 - 00:40
fuente
1
  

Abstracción con fugas

     

¿Por qué debería una interfaz especificar qué excepciones pueden   ser arrojado? ¿Qué pasa si la implementación no necesita lanzar un   ¿Excepción, o necesita lanzar otras excepciones? No hay manera, en una   nivel de interfaz, para saber qué excepciones desea una implementación   lanzar.

En mi experiencia, el código que recibe el error (ya sea a través de una excepción, código de error o cualquier otra cosa) normalmente no se preocupa por la causa exacta del error; informe del error (ya sea un diálogo de error o algún tipo de registro); y este informe se realizaría de forma ortogonal al código que llamó al procedimiento fallido. Por ejemplo, este código podría pasar el error a otra parte del código que sabe cómo informar errores específicos (por ejemplo, formatear una cadena de mensaje), posiblemente adjuntando información de contexto.

Por supuesto, en algunos casos, es necesario para adjuntar semántica específica a los errores y reaccionar de manera diferente según lo que ocurrió el error. Tales casos deben ser documentados en la especificación de la interfaz. Sin embargo, la interfaz todavía puede reservarse el derecho de lanzar otras excepciones sin un significado específico.

    
respondido por el Ambroz Bizjak 04.05.2012 - 02:35
fuente
1

Encuentro que las excepciones permiten escribir un código más estructurado y conciso para informar y manejar errores: el uso de códigos de error requiere la comprobación de la devolución valores después de cada llamada y decidir qué hacer en caso de un resultado inesperado.

Por otro lado, estoy de acuerdo en que las excepciones revelan detalles de implementación que deberían estar ocultos al código que invoca una interfaz. Dado que no es posible saber a priori qué parte del código puede generar qué excepciones (a menos que estén declaradas en la firma del método como en Java), mediante el uso de excepciones estamos introduciendo dependencias implícitas muy complejas entre las diferentes partes del código, que es Contra el principio de minimizar dependencias.

Resumiendo:

  • Creo que las excepciones permiten un código más limpio y un enfoque más agresivo para probar y depurar porque las excepciones no detectadas son mucho más visibles y difíciles de ignorar que los códigos de error (fallan pronto).
  • Por otro lado, los errores de excepción no detectados que no se descubren durante las pruebas pueden aparecer en un entorno de producción en forma de bloqueo. En ciertas circunstancias, este comportamiento no es aceptable y, en este caso, creo que usar códigos de error es un enfoque más sólido.
respondido por el Giorgio 05.05.2012 - 00:23
fuente
-1

O bien sesgado a la derecha.

No se puede ignorar, debe manejarse, es completamente transparente. Y SI usa el tipo de error correcto para zurdos, transmite toda la misma información que una excepción de Java.

¿Lado negativo? El código con el manejo adecuado de errores parece desagradable (es verdad en todos los mecanismos).

    
respondido por el Keynan 22.12.2014 - 02:53
fuente

Lea otras preguntas en las etiquetas