Mi pregunta es qué prefieren la mayoría de los desarrolladores para el manejo de errores, excepciones o códigos de retorno de errores. Sea específico del idioma (o familia de idiomas) y por qué prefiere uno sobre el otro.
Te pregunto esto por curiosidad. Personalmente, prefiero los códigos de retorno de error, ya que son menos explosivos y no obligan al código de usuario a pagar la penalización de rendimiento de excepción si no lo desean.
actualización: ¡gracias por todas las respuestas! Debo decir que aunque no me gusta la imprevisibilidad del flujo de código con excepciones. La respuesta sobre el código de retorno (y los identificadores de su hermano mayor) agrega mucho ruido al código.
language-agnostic
exception
Robert Gould
fuente
fuente
Respuestas:
Para algunos lenguajes (es decir, C ++), la fuga de recursos no debería ser una razón
C ++ está basado en RAII.
Si tiene un código que podría fallar, devolver o lanzar (es decir, el código más normal), entonces debe tener su puntero envuelto dentro de un puntero inteligente (asumiendo que tiene una muy buena razón para no tener su objeto creado en la pila).
Los códigos de retorno son más detallados
Son detallados y tienden a convertirse en algo como:
Al final, tu código es una colección de instrucciones identificadas (vi este tipo de código en el código de producción).
Este código bien podría traducirse en:
Que separan limpiamente el código y el procesamiento de errores, lo que puede ser algo bueno .
Los códigos de retorno son más frágiles
Si no es alguna advertencia oscura de un compilador (ver el comentario de "phjr"), pueden ignorarse fácilmente.
Con los ejemplos anteriores, suponga que alguien se olvida de manejar su posible error (esto sucede ...). El error se ignora cuando se "devuelve" y posiblemente explote más tarde (es decir, un puntero NULL). El mismo problema no ocurrirá con excepción.
El error no se ignorará. Sin embargo, a veces quieres que no explote ... Así que debes elegir con cuidado.
Los códigos de devolución a veces deben traducirse
Digamos que tenemos las siguientes funciones:
¿Cuál es el tipo de retorno de do TryToDoSomethingWithAllThisMess si falla una de sus funciones llamadas?
Los códigos de retorno no son una solución universal
Los operadores no pueden devolver un código de error. Los constructores de C ++ tampoco pueden.
Códigos de retorno significa que no puede encadenar expresiones
El corolario del punto anterior. ¿Y si quiero escribir?
No puedo, porque el valor de retorno ya se usa (y, a veces, no se puede cambiar). Entonces, el valor de retorno se convierte en el primer parámetro, enviado como referencia ... O no.
Se escriben excepciones
Puede enviar diferentes clases para cada tipo de excepción. Las excepciones de recursos (es decir, sin memoria) deben ser ligeras, pero cualquier otra cosa podría ser tan pesada como sea necesario (me gusta la excepción de Java que me da toda la pila).
Luego, cada captura puede especializarse.
Nunca uses catch (...) sin volver a lanzar
Por lo general, no debe ocultar un error. Si no vuelve a lanzar, como mínimo, registre el error en un archivo, abra un cuadro de mensaje, lo que sea ...
La excepción son ... NUKE
El problema con la excepción es que usarlos en exceso producirá un código lleno de intentos / capturas. Pero el problema está en otra parte: ¿Quién intenta / captura su código usando el contenedor STL? Aún así, esos contenedores pueden enviar una excepción.
Por supuesto, en C ++, nunca deje que una excepción salga de un destructor.
La excepción es ... sincrónica
Asegúrese de atraparlos antes de que saquen su hilo de rodillas o se propaguen dentro de su bucle de mensajes de Windows.
¿La solución podría ser mezclarlos?
Entonces supongo que la solución es lanzar cuando algo no debería suceder. Y cuando algo pueda suceder, utilice un código de retorno o un parámetro para permitir que el usuario reaccione.
Entonces, la única pregunta es "¿qué es algo que no debería suceder?"
Depende del contrato de tu función. Si la función acepta un puntero, pero especifica que el puntero no debe ser NULL, entonces está bien lanzar una excepción cuando el usuario envía un puntero NULL (la pregunta es, en C ++, cuándo el autor de la función no usó referencias en su lugar de punteros, pero ...)
Otra solución sería mostrar el error.
A veces, tu problema es que no quieres errores. Usar excepciones o códigos de retorno de error es genial, pero ... Quieres saberlo.
En mi trabajo, utilizamos una especie de "Afirmar". Dependiendo de los valores de un archivo de configuración, sin importar las opciones de compilación de depuración / liberación:
Tanto en el desarrollo como en las pruebas, esto permite al usuario identificar el problema exactamente cuando se detecta y no después (cuando algún código se preocupa por el valor de retorno o dentro de una captura).
Es fácil de agregar al código heredado. Por ejemplo:
conduce una especie de código similar a:
(Tengo macros similares que están activas solo en la depuración).
Tenga en cuenta que en producción, el archivo de configuración no existe, por lo que el cliente nunca ve el resultado de esta macro ... Pero es fácil activarlo cuando sea necesario.
Conclusión
Cuando codifica utilizando códigos de retorno, se está preparando para fallar y espera que su fortaleza de pruebas sea lo suficientemente segura.
Cuando codifica utilizando una excepción, sabe que su código puede fallar y, por lo general, coloca la captura de contrafuego en la posición estratégica elegida en su código. Pero por lo general, su código se trata más de "lo que debe hacer" que de "lo que temo que suceda".
Pero cuando codifica, debe usar la mejor herramienta a su disposición y, a veces, es "Nunca esconda un error y muéstrelo lo antes posible". La macro que hablé anteriormente sigue esta filosofía.
fuente
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
mejor porque si necesito agregar if / while / etc. declaraciones para fines de ejecución normal, no quiero que se mezclen con if / while / etc. declaraciones agregadas para propósitos excepcionales ... Y como la regla real sobre el uso de excepciones es lanzar , no atrapar , las declaraciones try / catch generalmente no son invasivas.Yo uso ambos en realidad.
Utilizo códigos de retorno si se trata de un posible error conocido. Si es un escenario que sé que puede suceder, y que sucederá, entonces hay un código que se envía de vuelta.
Las excepciones se usan únicamente para cosas que NO espero.
fuente
De acuerdo con el Capítulo 7 titulado "Excepciones" en Pautas de diseño de marcos: convenciones, expresiones idiomáticas y patrones para bibliotecas .NET reutilizables , se dan numerosos fundamentos de por qué el uso de excepciones sobre los valores de retorno es necesario para marcos de OO como C #.
Quizás esta sea la razón más convincente (página 179):
"Las excepciones se integran bien con los lenguajes orientados a objetos. Los lenguajes orientados a objetos tienden a imponer restricciones en las firmas de miembros que no son impuestas por funciones en lenguajes que no son OO. Por ejemplo, en el caso de constructores, sobrecargas de operadores y propiedades, el desarrollador no tiene opción en el valor de retorno. Por esta razón, no es posible estandarizar el reporte de errores basado en el valor de retorno para marcos orientados a objetos. Un método de reporte de errores, como excepciones, que está fuera de la banda de la firma del método es la única opción " .
fuente
Mi preferencia (en C ++ y Python) es usar excepciones. Las facilidades proporcionadas por el lenguaje lo convierten en un proceso bien definido para generar, capturar y (si es necesario) volver a lanzar excepciones, lo que hace que el modelo sea fácil de ver y usar. Conceptualmente, es más limpio que los códigos de retorno, ya que las excepciones específicas pueden definirse por sus nombres y tener información adicional que las acompañe. Con un código de retorno, está limitado solo al valor de error (a menos que desee definir un objeto ReturnStatus o algo así).
A menos que el código que está escribiendo sea crítico en cuanto al tiempo, la sobrecarga asociada con el desenrollado de la pila no es lo suficientemente significativa como para preocuparse.
fuente
Las excepciones solo deben devolverse cuando sucede algo que no esperaba.
El otro punto de las excepciones, históricamente, es que los códigos de retorno son intrínsecamente propietarios, a veces se puede devolver un 0 de una función C para indicar éxito, a veces -1, o cualquiera de ellos para un error con 1 para un éxito. Incluso cuando se enumeran, las enumeraciones pueden ser ambiguas.
Las excepciones también pueden proporcionar mucha más información, y específicamente deletrear bien 'Algo salió mal, esto es lo que, un seguimiento de pila y alguna información de apoyo para el contexto'
Dicho esto, un código de retorno bien enumerado puede ser útil para un conjunto conocido de resultados, un simple 'aquí hay n resultados de la función, y simplemente se ejecutó de esta manera'
fuente
En Java, uso (en el siguiente orden):
Diseño por contrato (garantizar que se cumplan las condiciones previas antes de intentar cualquier cosa que pueda fallar). Esto detecta la mayoría de las cosas y devuelvo un código de error para esto.
Devolver códigos de error mientras se procesa el trabajo (y realizar una reversión si es necesario).
Excepciones, pero se usan solo para cosas inesperadas.
fuente
assert
cosas de C, ya que no detectará problemas en el código de producción a menos que ejecute con aserciones todo el tiempo. Tiendo a ver las afirmaciones como una forma de detectar problemas durante el desarrollo, pero solo para cosas que afirmarán o no afirmarán consistentemente (como cosas con constantes, no cosas con variables o cualquier otra cosa que pueda cambiar en tiempo de ejecución).Los códigos de retorno fallan casi siempre en la prueba del " pozo del éxito ".
En cuanto al rendimiento:
fuente
Un gran consejo que recibí de The Pragmatic Programmer fue algo así como "su programa debería poder realizar todas sus funciones principales sin utilizar excepciones".
fuente
Escribí una publicación de blog sobre esto hace un tiempo.
La sobrecarga de rendimiento de lanzar una excepción no debería influir en su decisión. Si lo está haciendo bien, después de todo, una excepción es excepcional .
fuente
No me gustan los códigos de retorno porque hacen que el siguiente patrón se propague a lo largo de su código
CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; }
Pronto, una llamada a método que consta de 4 llamadas a funciones se llena con 12 líneas de manejo de errores. Algunas de las cuales nunca sucederán. Abundan los casos y los interruptores.
Las excepciones son más limpias si las usa bien ... para señalar eventos excepcionales ... después de los cuales la ruta de ejecución no puede continuar. A menudo son más descriptivos e informativos que los códigos de error.
Si tiene varios estados después de una llamada a un método que deben manejarse de manera diferente (y no son casos excepcionales), use códigos de error o params. Aunque Personaly, he encontrado que esto es raro ...
He buscado un poco sobre el contraargumento de la "penalización de rendimiento" ... más en el mundo C ++ / COM, pero en los lenguajes más nuevos, creo que la diferencia no es tanta. En cualquier caso, cuando algo explota, las preocupaciones sobre el rendimiento quedan relegadas a un segundo plano :)
fuente
Con cualquier compilador decente o entorno de ejecución, las excepciones no conllevan una penalización significativa. Es más o menos como una declaración GOTO que salta al controlador de excepciones. Además, tener excepciones detectadas por un entorno de ejecución (como la JVM) ayuda a aislar y corregir un error mucho más fácilmente. Tomaré una NullPointerException en Java sobre una segfault en C cualquier día.
fuente
finally
bloques y destructores para objetos asignados a la pila.Tengo un conjunto simple de reglas:
1) Use códigos de retorno para las cosas a las que espera que reaccione su interlocutor inmediato.
2) Utilice excepciones para errores que tengan un alcance más amplio y que se pueda esperar razonablemente que sean manejados por algo que esté a muchos niveles por encima de la persona que llama, de modo que el conocimiento del error no tenga que filtrarse a través de muchas capas, haciendo que el código sea más complejo.
En Java, solo usé excepciones no verificadas, las excepciones verificadas terminan siendo solo otra forma de código de retorno y, en mi experiencia, la dualidad de lo que podría ser "devuelto" por una llamada a un método fue generalmente más un impedimento que una ayuda.
fuente
Utilizo Excepciones en Python tanto en circunstancias excepcionales como en circunstancias no excepcionales.
A menudo es bueno poder usar una excepción para indicar que "no se pudo realizar la solicitud", en lugar de devolver un valor de error. Significa que usted / siempre / sabe que el valor de retorno es del tipo correcto, en lugar de arbitrariamente None o NotFoundSingleton o algo así. Aquí hay un buen ejemplo de dónde prefiero usar un controlador de excepciones en lugar de un condicional en el valor de retorno.
El efecto secundario es que cuando se ejecuta un datastore.fetch (obj_id), nunca tienes que verificar si su valor de retorno es Ninguno, obtienes ese error inmediatamente de forma gratuita. Esto es contrario al argumento, "su programa debería poder realizar toda su funcionalidad principal sin usar excepciones en absoluto".
Aquí hay otro ejemplo de dónde las excepciones son 'excepcionalmente' útiles, para escribir código para tratar con el sistema de archivos que no está sujeto a condiciones de carrera.
Una llamada al sistema en lugar de dos, sin condición de carrera. Este es un mal ejemplo porque obviamente esto fallará con un OSError en más circunstancias de las que el directorio no existe, pero es una solución 'suficientemente buena' para muchas situaciones estrictamente controladas.
fuente
Creo que los códigos de retorno aumentan el ruido del código. Por ejemplo, siempre odié el aspecto del código COM / ATL debido a los códigos de retorno. Tenía que haber una verificación HRESULT para cada línea de código. Considero que el código de retorno de error es una de las malas decisiones tomadas por los arquitectos de COM. Esto dificulta la agrupación lógica del código, por lo que la revisión del código se vuelve difícil.
No estoy seguro de la comparación de rendimiento cuando hay una verificación explícita del código de retorno en cada línea.
fuente
Prefiero usar excepciones para el manejo de errores y devolver valores (o parámetros) como el resultado normal de una función. Esto proporciona un esquema de manejo de errores fácil y consistente y, si se hace correctamente, hace que el código se vea mucho más limpio.
fuente
Una de las grandes diferencias es que las excepciones lo obligan a manejar un error, mientras que los códigos de retorno de error pueden quedar sin marcar.
Los códigos de retorno de error, si se usan mucho, también pueden causar un código muy feo con muchas pruebas if similares a esta forma:
Personalmente, prefiero usar excepciones para errores que DEBEN o DEBEN ser atendidos por el código de llamada, y solo uso códigos de error para "fallas esperadas" donde devolver algo es realmente válido y posible.
fuente
Hay muchas razones para preferir las excepciones al código de retorno:
fuente
Las excepciones no son para el manejo de errores, en mi opinión. Las excepciones son solo eso; eventos excepcionales que no esperabas. Use con precaución digo.
Los códigos de error pueden estar bien, pero devolver 404 o 200 de un método es malo, en mi opinión. Use enums (.Net) en su lugar, eso hace que el código sea más legible y más fácil de usar para otros desarrolladores. Además, no es necesario mantener una tabla sobre números y descripciones.
También; el patrón de intentar-atrapar-finalmente es un anti-patrón en mi libro. Intentar-finalmente puede ser bueno, probar-atrapar también puede ser bueno, pero probar-atrapar-finalmente nunca es bueno. try-finalmente a menudo puede ser reemplazado por una declaración "using" (patrón IDispose), que es mejor en mi opinión. Y Try-catch donde realmente detecta una excepción que puede manejar es bueno, o si hace esto:
Entonces, siempre que deje que la excepción continúe burbujeando, está bien. Otro ejemplo es este:
Aquí realmente manejo la excepción; lo que hago fuera del try-catch cuando falla el método de actualización es otra historia, pero creo que mi punto ya está claro. :)
¿Por qué entonces try-catch-finalmente es un anti-patrón? Este es el por qué:
¿Qué sucede si el objeto db ya se ha cerrado? ¡Se lanza una nueva excepción y debe manejarse! Esta es mejor:
O, si el objeto db no implementa IDisposable, haga esto:
¡De todos modos, esos son mis 2 centavos! :)
fuente
Solo uso excepciones, no códigos de retorno. Estoy hablando de Java aquí.
La regla general que sigo es que si tengo un método llamado
doFoo()
, se deduce que si no "hace foo", por así decirlo, entonces ha sucedido algo excepcional y se debe lanzar una excepción.fuente
Una cosa que temo sobre las excepciones es que lanzar una excepción arruinará el flujo de código. Por ejemplo si lo haces
O peor aún, ¿qué pasa si eliminé algo que no debería haber hecho, pero me lanzaron a atrapar antes de hacer el resto de la limpieza? Lanzar puso mucho peso en el pobre usuario en mi humilde opinión.
fuente
Mi regla general en el argumento de excepción frente a código de retorno:
fuente
No encuentro que los códigos de retorno sean menos feos que las excepciones. Con la excepción, tiene el
try{} catch() {} finally {}
dónde como con los códigos de retorno que tieneif(){}
. Solía temer las excepciones por las razones dadas en el post; no sabes si el puntero necesita ser borrado, ¿qué tienes? Pero creo que tienes los mismos problemas cuando se trata de códigos de retorno. No conoce el estado de los parámetros a menos que conozca algunos detalles sobre la función / método en cuestión.Independientemente, debe manejar el error si es posible. Puede permitir que una excepción se propague al nivel superior con la misma facilidad que ignorar un código de retorno y dejar que el programa siga por defecto.
Me gusta la idea de devolver un valor (¿enumeración?) Para los resultados y una excepción para un caso excepcional.
fuente
Para un lenguaje como Java, iría con Exception porque el compilador da un error de tiempo de compilación si no se manejan las excepciones. Esto obliga a la función de llamada a manejar / lanzar las excepciones.
Para Python, estoy más en conflicto. No hay un compilador, por lo que es posible que la persona que llama no maneje la excepción generada por la función que genera excepciones en tiempo de ejecución. Si usa códigos de retorno, es posible que tenga un comportamiento inesperado si no se maneja correctamente y si usa excepciones, es posible que obtenga excepciones en tiempo de ejecución.
fuente
Generalmente prefiero los códigos de retorno porque permiten que la persona que llama decida si la falla es excepcional .
Este enfoque es típico en el lenguaje Elixir.
La gente mencionó que los códigos de retorno pueden hacer que tenga muchas
if
declaraciones anidadas , pero eso se puede manejar con una mejor sintaxis. En Elixir, lawith
declaración nos permite separar fácilmente una serie de valor de retorno de ruta feliz de cualquier falla.Elixir todavía tiene funciones que generan excepciones. Volviendo a mi primer ejemplo, podría hacer cualquiera de estos para generar una excepción si el archivo no se puede escribir.
Si yo, como persona que llama, sé que quiero generar un error si falla la escritura, puedo elegir llamar en
File.write!
lugar deFile.write
. O puedo elegir llamarFile.write
y manejar cada una de las posibles razones de falla de manera diferente.Por supuesto, siempre es posible hacer
rescue
una excepción si queremos. Pero en comparación con el manejo de un valor de retorno informativo, me parece incómodo. Si sé que una llamada de función puede fallar o incluso debería fallar, su falla no es un caso excepcional.fuente