En cosas de alto nivel, excepciones; en cosas de bajo nivel, códigos de error.
El comportamiento predeterminado de una excepción es desenrollar la pila y detener el programa, si estoy escribiendo un script y busco una clave que no está en un diccionario, probablemente sea un error, y quiero que el programa se detenga y me deje saber todo sobre eso.
Sin embargo, si estoy escribiendo un fragmento de código del que debo conocer el comportamiento en cada situación posible, entonces quiero códigos de error. De lo contrario, tengo que conocer todas las excepciones que pueden lanzar cada línea en mi función para saber qué hará (lea La excepción que fundamenta una aerolínea para tener una idea de lo complicado que es esto). Es tedioso y difícil escribir código que reaccione apropiadamente a cada situación (incluidas las infelices), pero eso se debe a que escribir código sin errores es tedioso y difícil, no porque esté pasando códigos de error.
Tanto Raymond Chen como Joel han presentado algunos argumentos elocuentes en contra del uso de excepciones para todo.
nodiscard
atributo que dará una advertencia al compilador si no se almacena el valor de retorno de una función. Ayuda un poco a detectar la verificación de códigos de error olvidados. Ejemplo: godbolt.org/g/6i6E0BNormalmente prefiero las excepciones, porque tienen más información contextual y pueden transmitir (cuando se usan correctamente) el error al programador de una manera más clara.
Por otro lado, los códigos de error son más ligeros que las excepciones, pero son más difíciles de mantener. La comprobación de errores se puede omitir inadvertidamente. Los códigos de error son más difíciles de mantener porque debe mantener un catálogo con todos los códigos de error y luego activar el resultado para ver qué error se produjo. Los rangos de error pueden ser de ayuda aquí, porque si lo único que nos interesa es si estamos en presencia de un error o no, es más sencillo verificar (por ejemplo, un código de error HRESULT mayor o igual a 0 es éxito y menos de cero es falla). Se pueden omitir inadvertidamente porque no existe una obligación programática de que el desarrollador verifique los códigos de error. Por otro lado, no puede ignorar las excepciones.
Para resumir, prefiero las excepciones a los códigos de error en casi todas las situaciones.
fuente
Prefiero las excepciones porque
fuente
goto
Las personas que llaman a sus funciones pueden ignorar los códigos de error (¡y a menudo lo hacen!). Las excepciones al menos los obligan a lidiar con el error de alguna manera. Incluso si su versión de lidiar con eso es tener un manejador de capturas vacío (suspiro).
fuente
Excepciones sobre los códigos de error, sin duda alguna. Obtiene muchos de los mismos beneficios de las excepciones que con los códigos de error, pero también mucho más, sin las deficiencias de los códigos de error. El único problema con las excepciones es que es un poco más elevado; pero en la actualidad, esa sobrecarga debe considerarse insignificante para casi todas las aplicaciones.
Aquí hay algunos artículos que discuten, comparan y contrastan las dos técnicas:
Hay algunos buenos enlaces en los que pueden darle más información.
fuente
Nunca mezclaría los dos modelos ... es demasiado difícil convertir de uno a otro a medida que se mueve de una parte de la pila que usa códigos de error a una parte superior que usa excepciones.
Las excepciones son para "cualquier cosa que detenga o impida que el método o subrutina haga lo que le pidió que hiciera" ... NO para devolver mensajes sobre irregularidades o circunstancias inusuales, o el estado del sistema, etc. Utilice valores de retorno o ref (o fuera) parámetros para eso.
Las excepciones permiten que los métodos se escriban (y utilicen) con semántica que dependen de la función del método, es decir, un método que devuelve un objeto Empleado o una Lista de empleados se puede escribir para hacer precisamente eso, y puede utilizarlo llamando.
Con los códigos de error, todos los métodos devuelven un código de error, por lo que, para aquellos que necesitan devolver algo más para que lo use el código de llamada, debe pasar una variable de referencia para que se complete con esos datos y probar el valor de retorno para el código de error, y manejarlo, en cada función o llamada de método.
Si codifica para que cada método haga una y solo una cosa simple, entonces debe lanzar una excepción siempre que el método no pueda lograr el objetivo deseado del método. Las excepciones son mucho más ricas y fáciles de usar de esta manera que los códigos de error. Su código es mucho más limpio: el flujo estándar de la ruta del código "normal" se puede dedicar estrictamente al caso en el que el método ES capaz de lograr lo que usted quería que hiciera ... Y luego el código para limpiar, o manejar el Circunstancias "excepcionales" en las que sucede algo malo que impide que el método se complete correctamente pueden aislarse del código normal. Además, si no puede manejar la excepción donde ocurrió y debe pasarla de la pila a una IU (o peor, a través del cable desde un componente de nivel medio a una IU), entonces con el modelo de excepción,
fuente
En el pasado me uní al campo de código de error (hice demasiada programación en C). Pero ahora he visto la luz.
Sí, las excepciones son una carga para el sistema. Pero simplifican el código, reduciendo la cantidad de errores (y WTF).
Así que usa la excepción pero úsalas sabiamente Y serán tus amigos.
Como nota al margen. He aprendido a documentar qué excepción se puede lanzar mediante qué método. Desafortunadamente, esto no es requerido por la mayoría de los idiomas. Pero aumenta las posibilidades de manejar las excepciones correctas en el nivel correcto.
fuente
Puede haber algunas situaciones en las que usar las excepciones de una manera limpia, clara y correcta sea engorroso, pero la gran mayoría de las veces las excepciones son la opción obvia. El mayor beneficio que tiene el manejo de excepciones sobre los códigos de error es que cambia el flujo de ejecución, lo cual es importante por dos razones.
Cuando ocurre una excepción, la aplicación ya no sigue su ruta de ejecución "normal". La primera razón por la que esto es tan importante es que, a menos que el autor del código haga todo lo posible para ser malo, el programa se detendrá y no continuará haciendo cosas impredecibles. Si no se comprueba un código de error y no se toman las acciones adecuadas en respuesta a un código de error incorrecto, el programa seguirá haciendo lo que está haciendo y quién sabe cuál será el resultado de esa acción. Hay muchas situaciones en las que hacer que el programa haga "lo que sea" podría resultar muy caro. Considere un programa que recupera información de desempeño para varios instrumentos financieros que vende una empresa y entrega esa información a corredores / mayoristas. Si algo sale mal y el programa continúa, podría enviar datos de rendimiento erróneos a los corredores y mayoristas. No sé de nadie más, pero no quiero ser el que esté sentado en la oficina de un vicepresidente explicando por qué mi código hizo que la empresa recibiera multas reglamentarias por valor de 7 cifras. En general, es preferible enviar un mensaje de error a los clientes a entregar datos incorrectos que podrían parecer "reales", y esta última situación es mucho más fácil de encontrar con un enfoque mucho menos agresivo como los códigos de error.
La segunda razón por la que me gustan las excepciones y su ruptura con la ejecución normal es que hace que sea mucho más fácil mantener la lógica de 'cosas normales están sucediendo' separada de la lógica de 'algo salió mal'. Para mi, esto:
... es preferible a esto:
Hay otras pequeñas cosas acerca de las excepciones que también son agradables. Tener un montón de lógica condicional para realizar un seguimiento de si alguno de los métodos que se llaman en una función tuvo un código de error devuelto, y devolver ese código de error más arriba es una gran cantidad de placa de caldera. De hecho, es una gran cantidad de placa de caldera la que puede salir mal. Tengo mucha más fe en el sistema de excepciones de la mayoría de los idiomas que en un nido de ratas de declaraciones if-else-if-else que escribió Fred "Recién salido de la universidad", y tengo cosas mucho mejores que hacer con mi tiempo que el código revisando dicho nido de ratas.
fuente
Deberías usar ambos. La cuestión es decidir cuándo usar cada uno .
Hay algunos escenarios en los que las excepciones son la opción obvia :
En algunas situaciones, no puede hacer nada con el código de error , y solo necesita manejarlo en un nivel superior en la pila de llamadas , generalmente solo registra el error, muestra algo al usuario o cierra el programa. En estos casos, los códigos de error requerirían que usted agregue los códigos de error manualmente nivel por nivel, lo que obviamente es mucho más fácil de hacer con las excepciones. El caso es que esto es para situaciones inesperadas e imposibles de manejar .
Sin embargo, en la situación 1 (en la que ocurre algo inesperado e imposible de manejar, simplemente no desea registrarlo), las excepciones pueden ser útiles porque puede agregar información contextual . Por ejemplo, si obtengo una SqlException en mis ayudantes de datos de nivel inferior, querré detectar ese error en el nivel bajo (donde conozco el comando SQL que causó el error) para poder capturar esa información y volver a lanzar con información adicional . Tenga en cuenta la palabra mágica aquí: volver a lanzar y no tragar . La primera regla del manejo de excepciones: no se trague las excepciones . Además, tenga en cuenta que mi captura interna no necesita registrar nada porque la captura externa tendrá todo el seguimiento de la pila y puede registrarlo.
En algunas situaciones, tiene una secuencia de comandos, y si alguno de ellos falla , debe limpiar / eliminar los recursos (*), ya sea que se trate de una situación irrecuperable (que debe lanzarse) o una situación recuperable (en cuyo caso puede manejar localmente o en el código de la persona que llama, pero no necesita excepciones). Obviamente, es mucho más fácil poner todos esos comandos en un solo intento, en lugar de probar los códigos de error después de cada método y limpiarlos / eliminarlos en el bloque final. Tenga en cuenta que si desea que aparezca el error (que es probablemente lo que desea), ni siquiera necesita detectarlo; solo use el finalmente para limpiar / desechar ; solo debe usar catch / retrow si lo desea para agregar información contextual (ver viñeta 2).
Un ejemplo sería una secuencia de sentencias SQL dentro de un bloque de transacciones. Nuevamente, esta también es una situación "inmanejable", incluso si decide detectarla temprano (trátela localmente en lugar de burbujear hasta la parte superior), sigue siendo una situación fatal en la que el mejor resultado es abortar todo o al menos abortar una gran parte del proceso.
(*) Esto es como el
on error goto
que usamos en el antiguo Visual BasicEn los constructores solo puedes lanzar excepciones.
Dicho esto, en todas las demás situaciones en las que está devolviendo información sobre la que la persona que llama PUEDE / DEBE tomar alguna acción , usar códigos de retorno es probablemente una mejor alternativa. Esto incluye todos los "errores" esperados , porque probablemente deberían ser manejados por la persona que llama de inmediato, y difícilmente será necesario que los aumente demasiados niveles en la pila.
Por supuesto, siempre es posible tratar los errores esperados como excepciones y detectarlos inmediatamente un nivel por encima, y también es posible abarcar cada línea de código en un intento, detectar y tomar acciones para cada posible error. En mi opinión, este es un mal diseño, no solo porque es mucho más detallado, sino especialmente porque las posibles excepciones que podrían arrojarse no son obvias sin leer el código fuente, y las excepciones podrían arrojarse desde cualquier método profundo, creando gotos invisibles . Rompen la estructura del código creando múltiples puntos de salida invisibles que dificultan la lectura e inspección del código. En otras palabras, nunca debe usar excepciones como control de flujo, porque sería difícil para otros entender y mantener. Puede resultar incluso difícil comprender todos los posibles flujos de código para realizar pruebas.
Nuevamente: para una limpieza / eliminación correcta, puede usar try-finalmente sin atrapar nada .
La crítica más popular sobre los códigos de retorno es que "alguien podría ignorar los códigos de error, pero en el mismo sentido, alguien también puede aceptar excepciones. El manejo de excepciones incorrectas es fácil en ambos métodos. Pero escribir un buen programa basado en códigos de error es mucho más fácil que escribir un programa basado en excepciones . Y si, por cualquier motivo, uno decide ignorar todos los errores (los antiguos
on error resume next
), puede hacerlo fácilmente con códigos de retorno y no puede hacerlo sin un montón de repetición de intentos y capturas.La segunda crítica más popular sobre los códigos de retorno es que "es difícil hacer burbujas", pero eso se debe a que la gente no entiende que las excepciones son para situaciones no recuperables, mientras que los códigos de error no.
Decidir entre excepciones y códigos de error es un área gris. Incluso es posible que necesite obtener un código de error de algún método comercial reutilizable, y luego decida incluirlo en una excepción (posiblemente agregando información) y dejar que aparezca. Pero es un error de diseño suponer que TODOS los errores deben presentarse como excepciones.
Para resumirlo:
Me gusta usar excepciones cuando tengo una situación inesperada, en la que no hay mucho que hacer y, por lo general, queremos abortar un gran bloque de código o incluso toda la operación o el programa. Esto es como el antiguo "en error goto".
Me gusta usar códigos de retorno cuando tengo situaciones esperadas en las que el código de la persona que llama puede / debería tomar alguna acción. Esto incluye la mayoría de los métodos comerciales, API, validaciones, etc.
Esta diferencia entre excepciones y códigos de error es uno de los principios de diseño del lenguaje GO, que utiliza el "pánico" para situaciones inesperadas fatales, mientras que las situaciones esperadas habituales se devuelven como errores.
Sin embargo, sobre GO, también permite múltiples valores de retorno , que es algo que ayuda mucho al usar códigos de retorno, ya que puede devolver simultáneamente un error y algo más. En C # / Java podemos lograr eso sin parámetros, tuplas o (mi favorito) genéricos, que combinados con enumeraciones pueden proporcionar códigos de error claros para la persona que llama:
Si agrego un nuevo retorno posible en mi método, incluso puedo verificar a todas las personas que llaman si están cubriendo ese nuevo valor en una declaración de cambio, por ejemplo. Realmente no puedes hacer eso con excepciones. Cuando utilice códigos de retorno, normalmente conocerá de antemano todos los posibles errores y los probará. Con excepciones, por lo general, no sabe lo que podría pasar. Envolver enumeraciones dentro de excepciones (en lugar de genéricos) es una alternativa (siempre que esté claro el tipo de excepciones que arrojará cada método), pero en mi opinión, sigue siendo un mal diseño.
fuente
Mi razonamiento sería que si está escribiendo un controlador de bajo nivel que realmente necesita rendimiento, use códigos de error. Pero si está utilizando ese código en una aplicación de nivel superior y puede manejar un poco de sobrecarga, envuelva ese código con una interfaz que verifique esos códigos de error y genere excepciones.
En todos los demás casos, las excepciones son probablemente el camino a seguir.
fuente
Puede que esté sentado en la valla aquí, pero ...
En Python, el uso de excepciones es una práctica estándar y estoy muy feliz de definir mis propias excepciones. En C no hay excepciones en absoluto.
En C ++ (al menos en STL), las excepciones generalmente solo se lanzan para errores verdaderamente excepcionales (virtualmente nunca los veo yo mismo). No veo ninguna razón para hacer algo diferente en mi propio código. Sí, es fácil ignorar los valores de retorno, pero C ++ tampoco lo obliga a detectar excepciones. Creo que solo tienes que acostumbrarte a hacerlo.
La base de código en la que trabajo es principalmente C ++ y usamos códigos de error en casi todas partes, pero hay un módulo que genera excepciones para cualquier error, incluidos los muy poco excepcionales, y todo el código que usa ese módulo es bastante horrible. Pero eso podría deberse a que hemos mezclado excepciones y códigos de error. Es mucho más fácil trabajar con el código que utiliza códigos de error de forma constante. Si nuestro código usara constantemente excepciones, tal vez no sería tan malo. Mezclar los dos no parece funcionar tan bien.
fuente
Como trabajo con C ++ y tengo RAII para que su uso sea seguro, utilizo excepciones casi exclusivamente. Elimina el manejo de errores del flujo normal del programa y hace que la intención sea más clara.
Sin embargo, dejo excepciones para circunstancias excepcionales. Si espero que ocurra un cierto error con frecuencia, verificaré que la operación se realice correctamente antes de realizarla, o llamaré a una versión de la función que use códigos de error en su lugar (Me gusta
TryParse()
)fuente
Las firmas de métodos deben comunicarle lo que hace el método. Algo así como long errorCode = getErrorCode (); podría estar bien, pero largo errorCode = fetchRecord (); es confuso.
fuente
Mi enfoque es que podemos usar ambos, es decir, códigos de Excepciones y Errores al mismo tiempo.
Estoy acostumbrado a definir varios tipos de excepciones (por ejemplo: DataValidationException o ProcessInterruptExcepion) y dentro de cada excepción definir una descripción más detallada de cada problema.
Un ejemplo simple en Java:
De esta manera utilizo Excepciones para definir errores de categoría y códigos de error para definir información más detallada sobre el problema.
fuente
throw new DataValidationException("The input is too small")
? Una de las ventajas de las excepciones es permitir información detallada.Las excepciones son por circunstancias excepcionales , es decir, cuando no forman parte del flujo normal del código.
Es bastante legítimo mezclar excepciones y códigos de error, donde los códigos de error representan el estado de algo, en lugar de un error en la ejecución del código per se (por ejemplo, verificar el código de retorno de un proceso hijo).
Pero cuando ocurre una circunstancia excepcional, creo que las Excepciones son el modelo más expresivo.
Hay casos en los que puede preferir, o debe, usar códigos de error en lugar de Excepciones, y estos ya se han cubierto adecuadamente (aparte de otras restricciones obvias como el soporte del compilador).
Pero yendo en la otra dirección, usar Excepciones le permite construir abstracciones de nivel aún más alto para su manejo de errores, que pueden hacer que su código sea aún más expresivo y natural. Recomiendo encarecidamente leer este artículo excelente, aunque subestimado, del experto en C ++ Andrei Alexandrescu sobre el tema de lo que él llama "Enforcements": http://www.ddj.com/cpp/184403864 . Aunque es un artículo de C ++, los principios son generalmente aplicables y he traducido el concepto de aplicación a C # con bastante éxito.
fuente
Primero, estoy de acuerdo con la respuesta de Tom de que para cosas de alto nivel use excepciones, y para cosas de bajo nivel use códigos de error, siempre que no sea Arquitectura Orientada a Servicios (SOA).
En SOA, donde se pueden llamar métodos en diferentes máquinas, es posible que las excepciones no se pasen por el cable, en su lugar, usamos respuestas de éxito / fracaso con una estructura como la siguiente (C #):
Y usa así:
Cuando se utilizan de forma coherente en las respuestas de su servicio, se crea un patrón muy agradable de manejo de éxitos / fracasos en la aplicación. Esto permite un manejo de errores más fácil en llamadas asíncronas dentro de los servicios, así como entre servicios.
fuente
Preferiría Excepciones para todos los casos de error, excepto cuando una falla es un resultado esperado sin errores de una función que devuelve un tipo de datos primitivo. Por ejemplo, encontrar el índice de una subcadena dentro de una cadena más grande normalmente devolvería -1 si no se encuentra, en lugar de generar una NotFoundException.
No es aceptable devolver punteros no válidos que puedan ser desreferenciados (por ejemplo, causando NullPointerException en Java).
El uso de varios códigos de error numéricos diferentes (-1, -2) como valores de retorno para la misma función suele ser de mal estilo, ya que los clientes pueden hacer una comprobación "== -1" en lugar de "<0".
Una cosa a tener en cuenta aquí es la evolución de las API a lo largo del tiempo. Una buena API permite cambiar y ampliar el comportamiento ante fallos de varias formas sin dañar a los clientes. Por ejemplo, si un controlador de error de cliente verificó 4 casos de error y usted agrega un quinto valor de error a su función, es posible que el controlador de cliente no pruebe esto y se rompa. Si genera Excepciones, esto generalmente facilitará la migración de los clientes a una versión más reciente de una biblioteca.
Otra cosa a considerar es cuando se trabaja en equipo, dónde trazar una línea clara para que todos los desarrolladores tomen esa decisión. Por ejemplo, "Excepciones para material de alto nivel, códigos de error para material de bajo nivel" es muy subjetivo.
En cualquier caso, donde es posible más de un tipo de error trivial, el código fuente nunca debe usar el literal numérico para devolver un código de error o para manejarlo (devuelve -7, si x == -7 ...), pero siempre una constante nombrada (devuelve NO_SUCH_FOO, si x == NO_SUCH_FOO).
fuente
Si trabaja en un proyecto grande, no puede usar solo excepciones o solo códigos de error. En diferentes casos, debe utilizar diferentes enfoques.
Por ejemplo, decide utilizar solo excepciones. Pero una vez que decida utilizar el procesamiento de eventos asíncronos. Es una mala idea utilizar excepciones para el manejo de errores en estas situaciones. Pero usar códigos de error en todas partes de la aplicación es tedioso.
Entonces, en mi opinión, es normal usar tanto las excepciones como los códigos de error simultáneamente.
fuente
Para la mayoría de las aplicaciones, las excepciones son mejores. La excepción es cuando el software tiene que comunicarse con otros dispositivos. El dominio en el que trabajo son los controles industriales. Aquí se prefieren y se esperan códigos de error. Entonces mi respuesta es que depende de la situación.
fuente
Creo que también depende de si realmente necesita información como el seguimiento de la pila del resultado. Si es así, definitivamente opta por Exception, que proporciona un objeto lleno de mucha información sobre el problema. Sin embargo, si solo está interesado en el resultado y no le importa por qué ese resultado, busque el código de error.
Por ejemplo, cuando procesa un archivo y se enfrenta a IOException, el cliente podría estar interesado en saber desde dónde se activó, en abrir un archivo o analizarlo, etc. Así que mejor devuelva IOException o su subclase específica. Sin embargo, en un escenario en el que tiene un método de inicio de sesión y desea saber si fue exitoso o no, simplemente devuelve booleano o para mostrar el mensaje correcto, devuelve el código de error. Aquí el Cliente no está interesado en saber qué parte de la lógica causó ese código de error. Solo sabe si su credencial no es válida o si su cuenta está bloqueada, etc.
Otro caso de uso en el que puedo pensar es cuando los datos viajan por la red. Su método remoto puede devolver solo un código de error en lugar de una excepción para minimizar la transferencia de datos.
fuente
Mi regla general es:
fuente
Los códigos de error tampoco funcionan cuando su método devuelve algo que no sea un valor numérico ...
fuente