¿Por qué se considera que las excepciones son mejores que las pruebas de error explícitas? [duplicar]

43

A menudo me encuentro con publicaciones de blogs acaloradas en las que el autor utiliza el argumento: "excepciones frente a la comprobación explícita de errores" para abogar por su idioma preferido sobre otro idioma. El consenso general parece ser que los idiomas que hacen uso de las excepciones son intrínsecamente mejores / más limpios que los idiomas que dependen en gran medida de la comprobación de errores a través de llamadas a funciones explícitas.

¿Se considera que el uso de excepciones es una mejor práctica de programación que la comprobación explícita de errores? En caso afirmativo, ¿por qué?

    
pregunta Richard Keller 25.09.2012 - 01:46

10 respuestas

62

En mi opinión, el mayor argumento es la diferencia en lo que sucede cuando el programador comete un error. Olvidarse de manejar un error es un error muy común y fácil de cometer.

Si devuelve códigos de error, es posible ignorar un error en silencio. Por ejemplo, si malloc falla, devuelve NULL y establece el errno global. Así que el código correcto debería hacer

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

Pero es muy fácil y conveniente escribir en su lugar:

void* myptr = malloc(1024);
doSomethingWith(myptr);

que inesperadamente pasará NULL a su otro procedimiento y probablemente descartará el errno que se estableció cuidadosamente. No hay nada visible mal con el código para indicar que esto es posible.

En un idioma que usa excepciones, en lugar de eso, escribirías

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

En este ejemplo (Java), el operador new devuelve un objeto inicializado válido o lanza OutOfMemoryError . Si un programador debe manejar esto, pueden atraparlo. En el caso habitual (y convenientemente, también perezoso) en el que se trata de un error fatal, la propagación de excepciones termina el programa de una manera relativamente limpia y explícita.

Esa es una de las razones por las que las excepciones, cuando se usan correctamente, pueden hacer que la escritura de códigos claros y seguros sea mucho más fácil. Este patrón se aplica a muchas, muchas cosas que pueden salir mal, no solo a la asignación de memoria.

    
respondido por el Steven Schlansker 25.09.2012 - 02:03
57

Si bien la respuesta de Steven proporciona una buena explicación, hay otro punto que considero bastante importante. A veces, cuando verifica un código de error, no puede manejar el caso de falla inmediatamente. Debe propagar el error explícitamente a través de la pila de llamadas. Cuando refactoriza una función grande, es posible que tenga que agregar todo el código repetitivo de comprobación de errores a su subfunción.

Con excepciones, solo tienes que cuidar tu flujo principal. Si un fragmento de código lanza algún InvalidOperationError, aún puede mover ese código a una subfunción y se mantendrá la lógica de administración de errores.

Por lo tanto, las excepciones le permiten refactorizar más rápido y evitar la placa de la caldera.

    
respondido por el Simon 25.09.2012 - 03:09
17

Un punto de vista desde un ángulo diferente: la gestión de errores tiene que ver con la seguridad. Un error sin marcar rompe todas las suposiciones y condiciones previas en las que se basó el siguiente código. Esto puede abrir una gran cantidad de vectores de ataque externos: desde una simple DoS sobre el acceso de datos no autorizado hasta la corrupción de datos y la completa infiltración del sistema.

Claro, depende de la aplicación específica, pero cuando se rompen las suposiciones y las condiciones previas, todas las apuestas están desactivadas. Dentro de un software complejo, simplemente no se puede decir con certeza qué es posible a partir de ese momento, y qué se puede y no se puede usar desde el exterior.

Dado lo anterior, hay una observación fundamental: cuando la seguridad se trata como un complemento opcional que se puede adjuntar más adelante, entonces falla con más frecuencia. Funciona mejor cuando ya se consideró en las primeras etapas de diseño básico y se incorporó desde el principio.

Esto es lo que básicamente obtiene con excepciones: una infraestructura de manejo de errores ya integrada que está activa incluso si no le importa. Con las pruebas explícitas, tienes que construirlo tú mismo. Tienes que construirlo como un primer paso. Comenzar a escribir funciones que devuelven códigos de error, sin pensar en el panorama general hasta que esté en las etapas finales del desarrollo de su aplicación, es efectivamente un manejo de errores adicional, condenado al fracaso.

No es que un sistema incorporado ayude de ninguna manera. La mayoría de los programadores no saben cómo manejar el error. la mayoría de las veces es demasiado complejo.

  • Hay tantos errores que pueden ocurrir, y cada error requiere su propio manejo y sus propias acciones y reacciones.

  • Incluso el mismo error puede requerir diferentes acciones, según el contexto. ¿Qué pasa con un archivo no encontrado o sin memoria?

    • FNF: ¿Se necesita una biblioteca de terceros para que su aplicación se ejecute? Terminar.
    • FNF: ¿el archivo de configuración de inicio de su aplicación? Comience con la configuración predeterminada.
    • FNF: ¿un archivo de datos en una red compartida que el usuario desea que abra su aplicación? Los archivos que desaparecen pueden ocurrir en cualquier momento, incluso en los microsegundos entre Exists () y Open (). Ni siquiera es una "excepción" en el significado literal de la palabra.
    • OOM - ¿Para una asignación de 2GB? No es de extrañar.
    • OOM - ¿Para una asignación de 100 bytes? Estás en serios problemas.
  • Error al manejar las fugas a través de todas sus capas de abstracción cuidadosamente separadas. Un error en el nivel más bajo puede requerir notificar al usuario con un mensaje GUI. Puede requerir una decisión del usuario sobre qué hacer ahora. Puede que necesite registro. Puede necesitar operaciones de recuperación en otra parte, por ej. Base de datos abierta o conexiones de red. Etc.

  • ¿Qué pasa con el estado? Cuando llama a un método en un objeto que invoca cambios de estado y arroja un error:

    • ¿Está el objeto en un estado incoherente y necesita una reconstrucción?
    • ¿Es consistente el estado del objeto pero (parcialmente) cambiado y necesita una reversión adicional o una reconstrucción?
    • ¿Se realiza una reversión dentro del objeto y permanece sin cambios para la persona que llama?
    • ¿Dónde es más sensato hacer qué?

Solo muéstrame un libro para aprendices en el que el manejo de errores se diseñe rigurosamente desde el principio y, en consecuencia, se use a través de todos los ejemplos, sin dejar de lado la brevedad y la legibilidad y como ejercicio para el lector. Si esto es aplicable desde un punto de vista educativo es otra pregunta, pero no es de extrañar que el manejo de errores sea a menudo un segundo o tercer pensamiento cuando debería ser el primero.

    
respondido por el Secure 25.09.2012 - 10:05
14

Me gustaría considerar las excepciones como ...

Le permiten escribir código de la forma en que normalmente se escribe

Al escribir código, hay varias cosas que el cerebro humano necesita para hacer malabares:

  • ¿Cuál es la lógica que estoy tratando de expresar aquí?
  • ¿Cómo puedo lograr esto en el lenguaje XXXX?
  • ¿Cómo encaja este código en el resto de la aplicación?
  • ¿Por qué sigo recibiendo estos errores de compilación?
  • ¿Estoy olvidando las condiciones de los límites?
  • ¿Es este código lo suficientemente legible y fácil de mantener?
  • ¿Qué sucede si falla algo en este código?
  • ¿Estoy produciendo código con un estilo coherente y uso las convenciones de codificación correctas?

Cada una de estas balas requiere una cierta cantidad de concentración y la concentración es un recurso limitado. Esta es la razón por la que las personas que son nuevas en un proyecto a menudo se olvidan de las cosas al final de la lista. No son personas malas, pero debido a que muchas cosas pueden ser nuevas para ellos (lenguaje, proyecto, errores extraños, etc.), simplemente no tienen la capacidad de lidiar con otras viñetas.

He hecho mi parte de revisiones de código y esta tendencia es muy evidente. Una persona con menos experiencia olvidará más cosas. Estas cosas incluyen el estilo y, a menudo, el manejo de errores. Cuando las personas tienen dificultades para hacer que el código funcione, el manejo de errores tiende a estar en el fondo de sus mentes. A veces regresan y agregan algunas declaraciones if aquí y allá, pero pueden estar seguros de que olvidarán un montón de cosas.

El manejo de excepciones le permite escribir código donde se escribe la esencia de su lógica como si no hubiera errores. Realiza una llamada de función y la siguiente línea puede simplemente asumir que la línea anterior fue exitosa. Esto tiene una ventaja adicional de producir código que es mucho más fácil de leer. ¿Alguna vez ha visto una referencia de API con ejemplos de código que tenían un comentario, "// Error de manejo omitido para mayor claridad"? Si eliminan el manejo de errores en la documentación para hacer que el ejemplo sea más fácil de leer, ¿no sería fantástico si pudiera hacer lo mismo en el código de producción y hacerlo también más fácil de leer? Eso es exactamente lo que las excepciones te permiten hacer.

Ahora al menos una persona que lea este post dirá: "pffttt algunos noob no saben cómo codificar. Nunca olvido el manejo de errores". a) Bien por ti, pero lo que es más importante, b) como dije anteriormente, la codificación requiere que tu cerebro se concentre y solo hay mucho cerebro por recorrer; Eso se aplica a TODOS. Si su código es fácil de leer, es menos concentración. Si puede poner try / catch alrededor de una gran porción y luego simplemente codificar la lógica de la aplicación, eso es menos concentración. Ahora puede usar esa capacidad adicional para cosas más importantes, como hacer el trabajo real mucho más rápido.

    
respondido por el DXM 25.09.2012 - 05:40
6

Ventajas de la excepción al devolver el código de error:

  • El valor de retorno de una función se puede usar para lo que fue diseñado : devolver el resultado de la llamada a la función, no un código que represente el éxito o una de las muchas fallas dentro de la función. Esto crea un código más limpio, más elegante; menos parámetros de salida / referencia, se realizan asignaciones a cosas que realmente le interesan y no a códigos de estado desechables, etc.
  • Las excepciones están orientadas a objetos, por lo que los tipos de excepción tienen un significado conceptual reutilizable . ¿Qué significa un código de retorno de -2? Tienes que buscarlo en la documentación (lo que es mejor es bueno o estás obligado a rastrear el código, y si no tienes el código fuente, estás realmente jodido). ¿Qué significa una NullPointerException? Que intentó llamar a un método en una variable que no hace referencia a una instancia. Además, las excepciones encapsulan los datos, por lo que pueden decirle exactamente cómo y dónde se equivocó su programa con medios fácilmente legibles por el ser humano. Un código de retorno solo puede indicarle el método equivocado.
  • De forma predeterminada, los códigos de retorno se ignoran; las excepciones no son . Si no almacena y verifica el código de retorno, su programa continúa alegremente, corrompiendo los datos, arruinando los punteros y generalmente arrugándose a sí mismo en la basura. Si no detecta una excepción, se envía al sistema operativo que finaliza su programa. "Fallo rápido".
  • Las excepciones se estandarizan más fácilmente . Debido a que las excepciones crean más códigos de auto-documentación, y debido a que la mayoría de nosotros no escribimos todos nuestros programas al 100% desde cero, los tipos de excepción que cubren una amplia variedad de casos generales ya están disponibles. La solución más barata es la que ya tiene, por lo que estos tipos de excepción incorporados se utilizan en situaciones similares a las que se crearon originalmente. Ahora, una InvalidOperationException significa que trató de hacer algo inconsistente con el estado actual de un objeto (exactamente lo que fue se detallará en el mensaje de error), independientemente de la función de la biblioteca de terceros a la que intentaba llamar.

    Contraste eso con los códigos de retorno; para un método, -1 podría ser un "error de puntero nulo", mientras que -2 podría ser un "error de operación no válida". En el siguiente método, la condición de "operación no válida" se verificó primero y el error obtuvo el código de retorno -1, mientras que el "puntero nulo" obtuvo -2. Un número es un número, y usted es libre de crear cualquier estándar para asignar números a los errores, y también lo es todo el mundo, lo que lleva a MUCHOS estándares en competencia.

  • Las excepciones te permiten ser más optimista . En lugar de verificar de forma pesimista todo lo que podría salir mal con una declaración antes de ejecutar la declaración, simplemente puede intentar ejecutar la declaración y capturar cualquier Excepciones que fueron generadas por el mismo. Si puede resolver la causa del error e intentarlo de nuevo, excelente, si no, bien; Probablemente no podrías haber hecho nada al respecto si lo hubieras sabido de antemano.

    El manejo de errores basado en excepciones crea un código más eficiente al asumir que funcionará hasta que no lo haga. Declaraciones de guardia que devuelven tiempo de procesador de costo temprano; por lo tanto, deben utilizarse principalmente cuando los beneficios de la verificación por adelantado superan los costos; si puede determinar en unos pocos relojes que la entrada nunca producirá una salida válida, pero tomará varios segundos para que el cuerpo principal de la función llegue a la misma conclusión, entonces, por todos los medios, coloque una cláusula de seguridad. Sin embargo, si hay una docena de cosas que podrían salir mal, ninguna de las cuales es particularmente probable y la mayoría requiere una ejecución costosa, el "camino feliz" de la ejecución normal del programa será varias veces más rápido si simplemente intenta / captura.

respondido por el KeithS 25.09.2012 - 17:55
5

La diferencia fundamental para mí es leer frente a escribir:

El manejo del código de error tiende a saturar el intento : el código basado en excepciones suele ser más fácil de leer, ya que la fuente se centra en las cosas que debería suceder, en lugar de lo que < em> podría .

OTOH, mucho código basado en excepciones es más difícil de escribir, porque para un análisis de corrección tiene que discutir sobre detalles "no relacionados", como orden de construcción / destrucción, objetos parcialmente construidos, recolección de basura, etc.

¿Qué tan difícil? Esto es difícil.

La generación de excepciones significativas también puede saturar la fuente, tengo bases de código en las que virtualmente cada línea de código es seguida por dos o tres líneas que crean una excepción de alfabetización.

Para transferir las excepciones significativas al nivel superior, a menudo es necesario volver a empaquetarlas. Cuando hago clic en "rebicker cells", un mensaje de error como "Compartir violación en% tmp% \ foo \ file536821" no es mucho más útil que "Error 77" .

Realmente no tengo una preferencia, ambas opciones son igualmente feas. Lo que es peor: cuál es mejor parece depender mucho del proyecto, en formas difíciles de predecir. En mi experiencia, la interacción de hardware de bajo nivel es más suave con los códigos de error (podrían ser los clientes ...), el acceso a datos de bajo nivel es mejor con excepciones.

    
respondido por el peterchen 25.09.2012 - 08:51
3

Dejemos de lado los problemas de implementación de muchos idiomas. Las excepciones de beneficios tienen que es que pueden centralizarse para manejar todo tipo (en teoría) de estados excepcionales del programa. El problema que enfrentan es que las cosas excepcionales suceden y casi nunca se pueden predecir. Lo que es genial (en teoría) con las excepciones es que usted está seguro siempre y cuando:

  • Escriba el código de excepción de seguridad en el mayor grado posible
  • Capture los estados excepcionales a través de excepciones
  • Manejar las excepciones en consecuencia
respondido por el zxcdw 25.09.2012 - 02:50
2

El código basado en excepciones desplaza el manejo de errores fuera del flujo principal del programa. En su lugar, el manejo de errores se mueve al destructor de objetos o al constructo catch.

La mayor ventaja y también el mayor inconveniente con excepción es que no te obliga a pensar sobre el manejo de errores. Con excepción, a menudo solo escribiría un código directo y dejaría que el destructor haga su trabajo, y se olvidó de ese pequeño escenario que el destructor no maneja y debe hacerse manualmente (lo cual está claramente documentado y por lo tanto es su culpa para olvidar). El mismo escenario podría ocurrir con el código de error, pero al escribir código con código de error, se ve obligado a consultar el manual / documentación / fuente todo el tiempo y es menos probable que se olvide de esa pequeña condición.

Sí, estas excepciones significan que a veces sea más difícil escribir el código correcto.

Escribir un código incorrecto con un código de error es fácil, escribir un código bueno con un código de error es difícil. Escribir un código decente en excepción es fácil (porque cualquier error termina el programa o es detectado por un manejador de muy alto nivel que podría advertir sobre la inminente muerte, en lugar de pasarlo en silencio), escribir un buen código con excepción es realmente difícil estás manejando los errores a una distancia del lugar donde ocurre el error).

    
respondido por el Lie Ryan 25.09.2012 - 11:03
1
  • Códigos de salida fáciles de olvidar
  • Código duplicado. Si revisa el código de error y devuelve el código de error cuando se produce un error, muchos de los códigos duplicados se manejarán y los lotes cambiarán si cambia el tipo de devolución. También, ¿qué hacer cuando diferentes tipos pueden causar un error?
  • Las excepciones se ejecutan fuera de la ruta del código, lo que significa que no hay trabajo que verifique el valor de retorno. Pero hay trabajo en el binario en algún lugar cuando se ejecuta el código de excepción.
  • Es fácil ver qué funciones manejan qué tipo de errores. No tiene que mirar un montón de código, solo desplácese por el retén.
  • Más detalles (en ciertos idiomas). Ha incorporado información tal como qué línea, función, etc. Poner eso en una clase de error sería tedioso. Es probable que solo escriba el nombre de la función y, con suerte, el tipo o la causa del error.
  • Los depuradores entienden que las excepciones son errores y pueden pausarse cuando llega a uno mientras se está depurando. Herramientas de ayuda :)
respondido por el user2528 25.09.2012 - 03:25
-1

Si ambos se implementan perfectamente, las diferencias no son tan grandes, sin embargo, por lo general, nada es perfecto. Si falta el manejo de algunos errores, en caso de excepciones, el programa se detiene con un error visible, mientras que el programa con prueba de errores sigue ejecutándose con un comportamiento indefinido.

Si desarrolla un programa y tiene un error que no notó, ¿qué preferiría tener?

  1. El programa falla para que comiences a buscar y encontrar el error (o el programa se bloquea en los probadores beta, o tal vez en el usuario para que puedan enviar un informe de error) (manejo de excepciones)
  2. El programa se sigue ejecutando, mientras tanto, corrompe silenciosamente algunas variables sin causar ningún efecto visible en su computadora. Sin embargo, eliminará todos los datos de los usuarios cuando lo ejecuten, sin que se den cuenta de cuándo sucede. ( prueba de error )
respondido por el vsz 25.09.2012 - 10:13

Lea otras preguntas en las etiquetas