Para manejar varios posibles errores que no deberían detener la ejecución, tengo una error
variable que los clientes pueden verificar y usar para lanzar excepciones. ¿Es esto un antipatrón? ¿Hay una mejor manera de manejar esto? Para ver un ejemplo de esto en acción, puede ver la API mysqli de PHP . Suponga que los problemas de visibilidad (accesores, ámbito público y privado, ¿es la variable en una clase o global?) Se manejan correctamente.
44
try
/catch
existe para. Además, puede colocar sutry
/catch
mucho más arriba en la pila en una ubicación más adecuada para manejarlo (lo que permite una mayor separación de las preocupaciones).Respuestas:
Si un lenguaje admite inherentemente excepciones, entonces se prefiere lanzar excepciones y los clientes pueden detectar la excepción si no quieren que resulte en una falla. De hecho, los clientes de su código esperan excepciones y se encontrarán con muchos errores porque no verificarán los valores de retorno.
Hay bastantes ventajas al usar excepciones si tiene una opción.
Mensajes
Las excepciones contienen mensajes de error legibles por el usuario que los desarrolladores pueden usar para depurar o incluso mostrar a los usuarios si así lo desean. Si el código consumidor no puede manejar la excepción, siempre puede registrarlo para que los desarrolladores puedan revisar los registros sin tener que detenerse en cualquier otro rastro para descubrir cuál era el valor de retorno y mapearlo en una tabla para descubrir cuál era excepción real
Con los valores de retorno, no hay información adicional que pueda proporcionarse fácilmente. Algunos idiomas admitirán la realización de llamadas a métodos para obtener el último mensaje de error, por lo que esta preocupación se disipa un poco, pero eso requiere que la persona que llama realice llamadas adicionales y, a veces, requerirá acceso a un 'objeto especial' que lleve esta información.
En el caso de mensajes de excepción, proporciono la mayor cantidad de contexto posible, como:
Compare esto con un código de retorno -85. ¿Cuál preferirías?
Pilas de llamadas
Las excepciones generalmente también tienen pilas de llamadas detalladas que ayudan a depurar el código más rápido y más rápido, y también pueden registrarse mediante el código de llamada si así lo desea. Esto permite a los desarrolladores identificar el problema generalmente en la línea exacta y, por lo tanto, es muy poderoso. Una vez más, compare esto con un archivo de registro con valores de retorno (como -85, 101, 0, etc.), ¿cuál preferiría?
Enfoque fallido sesgado rápido
Si se llama a un método en algún lugar que falla, arrojará una excepción. El código de llamada tiene que suprimir la excepción explícitamente o fallará. He descubierto que esto es realmente sorprendente porque durante el desarrollo y las pruebas (e incluso en la producción) el código falla rápidamente, lo que obliga a los desarrolladores a solucionarlo. En el caso de los valores de retorno, si se omite la comprobación de un valor de retorno, el error se ignora silenciosamente y el error aparece en algún lugar inesperado, generalmente con un costo mucho mayor para depurar y corregir.
Envolviendo y desenvolviendo excepciones
Las excepciones pueden envolverse dentro de otras excepciones y luego desenvolverse si es necesario. Por ejemplo, su código podría arrojar el
ArgumentNullException
cual el código de llamada podría envolverse dentro de unaUnableToRetrievePolicyException
porque esa operación había fallado en el código de llamada. Si bien es posible que se muestre al usuario un mensaje similar al ejemplo que proporcioné anteriormente, algunos códigos de diagnóstico pueden desenvolver la excepción y descubrir que seArgumentNullException
ha producido el problema, lo que significa que es un error de codificación en el código de su consumidor. Esto podría activar una alerta para que el desarrollador pueda arreglar el código. Tales escenarios avanzados no son fáciles de implementar con los valores de retorno.Simplicidad de código
Este es un poco más difícil de explicar, pero aprendí a través de esta codificación tanto con valores de retorno como con excepciones. El código que se escribió utilizando valores de retorno generalmente haría una llamada y luego verificaría cuál era el valor de retorno. En algunos casos, llamaría a otro método y ahora tendrá otra serie de comprobaciones para los valores de retorno de ese método. Con excepciones, el manejo de excepciones es mucho más simple en la mayoría, si no en todos los casos. Tiene un bloque try / catch / finally, con el tiempo de ejecución haciendo todo lo posible para ejecutar el código en los bloques finalmente para la limpieza. Incluso los bloques anidados try / catch / finally son relativamente más fáciles de seguir y mantener que los valores anidados if / else y los valores de retorno asociados de múltiples métodos.
Conclusión
Si la plataforma que está utilizando admite excepciones (especialmente, como Java o .NET), entonces definitivamente debe suponer que no hay otra forma, excepto lanzar excepciones porque estas plataformas tienen pautas para lanzar excepciones, y sus clientes esperan entonces. Si estuviera usando su biblioteca, no me molestaría en verificar los valores de retorno porque espero que se produzcan excepciones, así es el mundo en estas plataformas.
Sin embargo, si fuera C ++, sería un poco más difícil determinarlo porque ya existe una gran base de código con códigos de retorno, y una gran cantidad de desarrolladores están sintonizados para devolver valores en lugar de excepciones (por ejemplo, Windows está plagado de HRESULT) . Además, en muchas aplicaciones, también puede ser un problema de rendimiento (o al menos percibido).
fuente
ErrorStateReturnVariable
superclase, y una de sus propiedades esInnerErrorState
(que es una instancia deErrorStateReturnVariable
), que la implementación de subclases puede configurar para mostrar una cadena de errores ... oh, espera. : pLas variables de error son una reliquia de lenguajes como C, donde las excepciones no estaban disponibles. Hoy, debe evitarlos, excepto cuando está escribiendo una biblioteca que se utiliza potencialmente desde un programa en C (o un lenguaje similar sin manejo de excepciones).
Por supuesto, si tiene un tipo de error que podría clasificarse mejor como "advertencia" (= su biblioteca puede entregar un resultado válido y la persona que llama puede ignorar la advertencia si cree que no es importante), entonces un indicador de estado en forma de una variable puede tener sentido incluso en idiomas con excepciones. Pero cuidado. Las personas que llaman de la biblioteca tienden a ignorar tales advertencias incluso si no deberían hacerlo. Así que piénselo dos veces antes de introducir tal construcción en su lib.
fuente
Hay varias formas de señalar un error:
El problema de la variable de error es que es fácil olvidar verificar.
El problema de las excepciones es que crea rutas ocultas de ejecuciones y, aunque try / catch es fácil de escribir, garantizar una recuperación adecuada en la cláusula catch es realmente difícil de lograr (no es compatible con los sistemas de tipo / compiladores).
El problema de los controladores de condiciones es que no se componen bien: si tiene una ejecución dinámica de código (funciones virtuales), entonces es imposible predecir qué condiciones deben manejarse. Además, si se puede plantear la misma condición en varios puntos, no se sabe si se puede aplicar una solución uniforme cada vez, y rápidamente se vuelve desordenada.
Los retornos polimórficos (
Either a b
en Haskell) son mi solución favorita hasta ahora:El único problema es que potencialmente pueden conducir a una verificación excesiva; los idiomas que los usan tienen modismos para encadenar las llamadas de funciones que los usan, pero aún puede requerir un poco más de tipeo / desorden. En Haskell esto sería mónadas ; sin embargo, esto es mucho más aterrador de lo que parece, vea Programación orientada al ferrocarril .
fuente
Creo que es horrible Actualmente estoy refactorizando una aplicación Java que usa valores de retorno en lugar de excepciones. Aunque es posible que no esté trabajando con Java, creo que esto se aplica de todos modos.
Terminas con un código como este:
O esto:
Prefiero que las acciones arrojen excepciones, por lo que terminas con algo como:
Puede envolverlo en un intento de captura y obtener el mensaje de la excepción, o puede elegir ignorar la excepción, por ejemplo, cuando está eliminando algo que ya puede haber desaparecido. También conserva su rastro de pila, si tiene uno. Los métodos mismos también se vuelven más fáciles. En lugar de manejar las excepciones ellos mismos, simplemente arrojan lo que salió mal.
Código actual (horrible):
Nuevo y mejorado:
Strack trace se conserva y el mensaje está disponible en la excepción, en lugar del inútil "¡Algo salió mal!".
Por supuesto, puede proporcionar mejores mensajes de error, y debería. Pero esta publicación está aquí porque el código actual en el que estoy trabajando es un fastidio, y no debes hacer lo mismo.
fuente
throw new Exception("Something went wrong with " + instanceVar, ex);
"Para manejar varios posibles errores que suceden, eso no debería detener la ejecución"
Si quiere decir que los errores no deberían detener la ejecución de la función actual, sino que deberían informarse a la persona que llama de alguna manera, entonces tiene algunas opciones que realmente no se han mencionado. Este caso es realmente más una advertencia que un error. Lanzar / Devolver no es una opción, ya que finaliza la función actual. Un solo parámetro de mensaje de error o retorno solo permite que ocurra uno de estos errores como máximo.
Dos patrones que he usado son:
Una colección de error / advertencia, ya sea pasada o mantenida como una variable miembro. A lo que agregas cosas y simplemente continúas procesando Personalmente, no me gusta mucho este enfoque, ya que siento que le quita el poder a la persona que llama.
Pase un objeto controlador de error / advertencia (o configúrelo como una variable miembro). Y cada error llama a una función miembro del controlador. De esta manera, la persona que llama puede decidir qué hacer con tales errores que no terminan.
Lo que pasa a estas colecciones / controladores debe contener suficiente contexto para que el error se maneje "correctamente": una cadena suele ser demasiado pequeña, pasarle alguna instancia de Excepción a menudo es razonable, pero a veces está mal visto (como un abuso de Excepciones) .
El código típico que usa un controlador de errores podría verse así
fuente
warnings
paquete de Python , que ofrece otro patrón para este problema.A menudo no hay nada de malo en usar este patrón o ese patrón, siempre que use el patrón que todos los demás usan. En el desarrollo de Objective-C , el patrón preferido es pasar un puntero donde el método que se llama puede depositar un objeto NSError. Las excepciones están reservadas para errores de programación y conducen a un bloqueo (a menos que tenga programadores Java o .NET escribiendo su primera aplicación para iPhone). Y esto funciona bastante bien.
fuente
La pregunta ya está respondida, pero no puedo evitarlo.
Realmente no puede esperar que Exception proporcione una solución para todos los casos de uso. ¿Martillar a alguien?
Hay casos en que las Excepciones no son el final de todo y son todas, por ejemplo, si un método recibe una solicitud y es responsable de validar todos los campos pasados, y no solo el primero, entonces debe pensar que debería ser posible indique la causa del error para más de un campo. También debería ser posible indicar si la naturaleza de la validación impide que el usuario vaya más allá o no. Un ejemplo de eso sería una contraseña no segura. Puede mostrar un mensaje al usuario que indique que la contraseña ingresada no es muy segura, pero que es lo suficientemente segura.
Se podría argumentar que todas estas validaciones se podrían lanzar como una excepción al final del módulo de validación, pero serían códigos de error en cualquier cosa menos en el nombre.
Entonces, la lección aquí es: las excepciones tienen su lugar, al igual que los códigos de error. Elije sabiamente.
fuente
Validator
(interfaz) inyectada en el método en cuestión (o el objeto detrás de él). Dependiendo de lo inyectado,Validator
el método procederá con contraseñas malas, o no. El código circundante podría probar aWeakValidator
si el usuario lo solicitó después, por ejemplo, aWeakPasswordException
fue arrojado por el inicialmente intentadoStrongValidator
.MiddlyStrongValidator
o algo. Y si eso realmente no interrumpió su flujo, seValidator
debe haber llamado de antemano, es decir, antes de continuar el flujo mientras el usuario aún ingresa su contraseña (o similar). Pero en primer lugar, la validación no era parte del método en cuestión. :) Probablemente sea una cuestión de gustos después de todo ...AggregateException
(o similarValidationException
) y pondré excepciones específicas para cada problema de validación en InnerExceptions. Por ejemplo, podría serBadPasswordException
: "La contraseña del usuario es menor que la longitud mínima de 6" oMandatoryFieldMissingException
: "Se debe proporcionar el nombre del usuario", etc. Esto no es equivalente a los códigos de error. Todos estos mensajes se pueden mostrar al usuario de una manera que lo entiendan, y si en su lugarNullReferenceException
se arrojó uno, entonces tenemos un error.Hay casos de uso donde los códigos de error son preferibles a las excepciones.
Si su código puede continuar a pesar del error, pero necesita un informe, entonces una excepción es una mala elección porque las excepciones terminan el flujo. Por ejemplo, si está leyendo un archivo de datos y descubre que contiene datos incorrectos no terminales, podría ser mejor leer el resto del archivo e informar el error en lugar de fallar directamente.
Las otras respuestas han cubierto por qué las excepciones deberían preferirse a los códigos de error en general.
fuente
AcknowledgePossibleCorruption
método. .Definitivamente no hay nada de malo en no usar excepciones cuando las excepciones no se ajustan bien.
Cuando no se debe interrumpir la ejecución del código (por ejemplo, actuar sobre la entrada del usuario que puede contener múltiples errores, como un programa para compilar o un formulario para procesar), encuentro que recopilar errores en variables de error como
has_errors
yerror_messages
es, de hecho, un diseño mucho más elegante que lanzar Una excepción en el primer error. Permite encontrar todos los errores en la entrada del usuario sin obligarlo a volver a enviarlo innecesariamente.fuente
En algunos lenguajes de programación dinámicos puede usar tanto valores de error como manejo de excepciones . Esto se hace devolviendo el objeto de excepción no lanzado en lugar del valor de retorno ordinario, que puede verificarse como un valor de error, pero arroja una excepción si no se verifica.
En Perl 6 se hace a través de
fail
, que si dentro delno fatal;
alcance devuelve unFailure
objeto de excepción especial no lanzado .En Perl 5 puede usar Contextual :: Return con el que puede hacer esto
return FAIL
.fuente
A menos que haya algo muy específico, creo que tener una variable de error para la validación es una mala idea. El propósito parece ser ahorrar tiempo dedicado a la validación (solo puede devolver el valor de la variable)
Pero luego, si cambia algo, debe volver a calcular ese valor de todos modos. Sin embargo, no puedo decir más sobre la detención y el lanzamiento de excepciones.
EDITAR: No me di cuenta de que esta es una cuestión de paradigma de software en lugar de un caso específico.
Permítanme aclarar más mis puntos en un caso particular mío en el que mi respuesta tendría sentido
Hay dos tipos de errores:
En la capa de servicio, no hay otra opción que usar el objeto Resultado como contenedor, que es la variable de equivalencia de error. Es posible simular una excepción a través de una llamada de servicio en un protocolo como http, pero definitivamente no es algo bueno. No estoy hablando de este tipo de error y no pensé que este es el tipo de error que se hace en esta pregunta.
Estaba pensando en el segundo tipo de error. Y mi respuesta es sobre este segundo tipo de errores. En los objetos de entidad, hay opciones para nosotros, algunos de ellos son
Usar la variable de validación es lo mismo que tener un único método de validación para cada objeto de entidad. En particular, el usuario puede establecer el valor de una manera que mantenga al setter como un setter puro, sin efectos secundarios (esto es a menudo una buena práctica) o uno puede incorporar la validación en cada setter y luego guardar el resultado en una variable de validación. La ventaja de esto sería ahorrar tiempo, el resultado de la validación se almacena en caché en la variable de validación para que cuando el usuario llame a validación () varias veces, no haya necesidad de hacer una validación múltiple.
Lo mejor que se puede hacer en este caso es usar un único método de validación sin siquiera usar ningún error de validación para validar en caché. Esto ayuda a mantener el setter como solo setter.
fuente