Esta pregunta está destinada a aplicarse a cualquier lenguaje de programación OO que admita el manejo de excepciones; Estoy usando C # solo con fines ilustrativos.
Por lo general, las excepciones están destinadas a surgir cuando surge un problema que el código no puede manejar de inmediato, y luego quedar atrapado en una catch
cláusula en una ubicación diferente (generalmente un marco de pila externo).
P: ¿Hay situaciones legítimas en las que no se generan y capturan excepciones, sino que simplemente se devuelven de un método y luego se pasan como objetos de error?
Esta pregunta se me ocurrió porque el System.IObserver<T>.OnError
método de .NET 4 sugiere exactamente eso: las excepciones se pasan como objetos de error.
Veamos otro escenario, la validación. Digamos que estoy siguiendo la sabiduría convencional y que, por lo tanto, estoy distinguiendo entre un tipo de objeto de error IValidationError
y un tipo de excepción separado ValidationException
que se utiliza para informar errores inesperados:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(El System.Component.DataAnnotations
espacio de nombres hace algo bastante similar).
Estos tipos podrían emplearse de la siguiente manera:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Ahora me pregunto, ¿no podría simplificar lo anterior a esto?
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
P: ¿Cuáles son las ventajas y desventajas de estos dos enfoques diferentes?
AggregateException
lugar? Buen punto.Respuestas:
Si nunca se lanza, entonces no es una excepción. Es un objeto
derived
de una clase de excepción, aunque no sigue el comportamiento. Llamarlo una excepción es puramente semántica en este punto, pero no veo nada de malo en no lanzarlo. Desde mi punto de vista, no lanzar una excepción es exactamente lo mismo que una función interna que lo atrapa y evita que se propague.¿Es válido que una función devuelva objetos de excepción?
Absolutamente. Aquí hay una breve lista de ejemplos donde esto puede ser apropiado:
¿No es malo tirarlo?
Lanzar una excepción es algo así como: "Ve a la cárcel. No pases ¡Ve!" en el juego de mesa Monopoly. Le dice a un compilador que salte sobre todo el código fuente hasta la captura sin ejecutar nada de ese código fuente. No tiene nada que ver con errores, informar o detener errores. Me gusta pensar en lanzar una excepción como una declaración de "superretorno" para una función. Devuelve la ejecución del código a un lugar mucho más alto que el llamador original.
Lo importante aquí es comprender que el verdadero valor de las excepciones está en el
try/catch
patrón, y no en la instanciación del objeto de excepción. El objeto de excepción es solo un mensaje de estado.En su pregunta, parece estar confundiendo el uso de estas dos cosas: un salto a un controlador de la excepción y el estado de error que representa la excepción. El hecho de que haya tomado un estado de error y lo haya envuelto en una excepción no significa que esté siguiendo el
try/catch
patrón o sus beneficios.fuente
Exception
clase pero no sigue el comportamiento". - Y ese es exactamente el problema: ¿es legítimo usar unException
objeto derivado en una situación en la que no sea arrojado, ni por el productor del objeto, ni por ningún método de llamada; donde solo sirve como un "mensaje de estado" que se transmite, sin el flujo de control de "superretorno"? ¿O estoy violando un contratotry
/ palabracatch(Exception)
tan grave que nunca debería hacer esto, y en su lugar usar un tipo distintoException
?No
que no está rompiendo ningún contrato. Lapopular opinion
sería que no deberías hacer eso. La programación está llena de ejemplos en los que su código fuente no hace nada malo, pero a todos no les gusta. El código fuente siempre debe escribirse para que quede claro cuál es la intención. ¿Realmente estás ayudando a otro programador usando una excepción de esa manera? Si es así, entonces hazlo. De lo contrario, no se sorprenda si no le gusta.Devolver excepciones en lugar de lanzarlas puede tener sentido semántico cuando tiene un método auxiliar para analizar la situación y devolver una excepción apropiada que luego es lanzada por la persona que llama (podría llamar a esto una "fábrica de excepciones"). Lanzar una excepción en esta función del analizador de errores significaría que algo salió mal durante el análisis en sí, mientras que devolver una excepción significa que el tipo de error se analizó con éxito.
Un posible caso de uso podría ser una función que convierta los códigos de respuesta HTTP en excepciones:
Tenga en cuenta que lanzar una excepción significa que el método se usó incorrectamente o tuvo un error interno, mientras que devolver una excepción significa que el código de error se identificó con éxito.
fuente
return
declaraciones superfluas para que el compilador deje de quejarse.System.Exit()
. El código después de la llamada a la función no se ejecuta. Eso no suena como un buen patrón de diseño para una función.Conceptualmente, si el objeto de excepción es el resultado esperado de la operación, entonces sí. Sin embargo, los casos en los que puedo pensar siempre involucran tirar-atrapar en algún momento:
Variaciones en el patrón "Try" (donde un método encapsula un segundo método de lanzamiento de excepciones, pero captura la excepción y en su lugar devuelve un valor booleano que indica éxito). Podría, en lugar de devolver un booleano, devolver cualquier excepción lanzada (nula si tuvo éxito), permitiendo al usuario final más información mientras conserva la indicación de éxito o falla sin captura.
Procesamiento de errores de flujo de trabajo. Similar en la práctica a la encapsulación del método "probar patrón", puede tener un paso de flujo de trabajo abstraído en un patrón de cadena de comando. Si un enlace en la cadena arroja una excepción, a menudo es más claro detectar esa excepción en el objeto de abstracción, luego devolverlo como resultado de dicha operación para que el motor de flujo de trabajo y el código real ejecutado como un paso de flujo de trabajo no requiera un intento extenso -lógica de captura propia.
fuente
OperationResult
clase abstracta , con unabool
propiedad "Exitosa", unGetExceptionIfAny
método y unAssertSuccessful
método (que arrojaría la excepción deGetExceptionIfAny
si no es nulo). TenerGetExceptionIfAny
un método en lugar de una propiedad permitiría el uso de objetos de error inmutables estáticos para casos en los que no había mucho que decir.Digamos que ha realizado alguna tarea para que se ejecute en algún grupo de subprocesos. Si esta tarea arroja una excepción, es un subproceso diferente para que no la atrape. Hilo donde fue ejecutado simplemente muere.
Ahora considere que algo (ya sea código en esa tarea o la implementación del grupo de subprocesos) atrapa esa excepción, almacénelo junto con la tarea y considere la tarea finalizada (sin éxito). Ahora puede preguntar si la tarea ha finalizado (y preguntar si se lanzó o si se puede volver a lanzar en su hilo (o mejor, una nueva excepción con el original como causa)).
Si lo hace manualmente, puede notar que está creando una nueva excepción, lanzándola y atrapándola, luego almacenándola, en diferentes hilos recuperando arrojándola, atrapando y reaccionando sobre ella. Por lo tanto, puede tener sentido omitir lanzar y atrapar y simplemente almacenarlo y finalizar la tarea y luego preguntar si hay una excepción y reaccionar al respecto. Pero esto puede conducir a un código más complicado si en el mismo lugar puede haber realmente excepciones.
PD: esto está escrito con experiencia con Java, donde la información de seguimiento de la pila se crea cuando se crea una excepción (a diferencia de C #, donde se crea al lanzar). Por lo tanto, el enfoque de excepción no lanzada en Java será más lento que en C # (a menos que se cree previamente y se reutilice), pero tendrá disponible información de seguimiento de pila.
En general, me mantendría alejado de crear excepciones y nunca lanzarlas (a menos que la optimización del rendimiento después de la elaboración del perfil lo señale como un cuello de botella). Al menos en Java, donde la creación de excepciones es costosa (seguimiento de pila). En C # es posible, pero IMO es sorprendente y, por lo tanto, debe evitarse.
fuente
Depende de su diseño, por lo general, no devolvería las excepciones a una persona que llama, sino que las lanzaría, atraparía y dejaría así. Por lo general, el código se escribe para fallar temprano y rápido. Por ejemplo, considere el caso de abrir un archivo y procesarlo (Esto es C # PsuedoCode):
En este caso, cometeríamos un error y detendríamos el procesamiento del archivo tan pronto como encontrara un registro incorrecto.
Pero supongamos que el requisito es que queremos continuar procesando el archivo incluso si una o más líneas tienen un error. El código puede comenzar a verse así:
Entonces, en este caso, devolvemos la excepción a la persona que llama, aunque probablemente no devolvería una excepción, sino algún tipo de objeto de error, ya que la excepción se ha detectado y ya se ha solucionado. Esto no es perjudicial al devolver una excepción, pero otras personas que llaman pueden volver a lanzar la excepción, lo que puede no ser deseable ya que los objetos de excepción tienen ese comportamiento. Si desea que las personas que llaman tengan la capacidad de volver a lanzar y luego devolver una excepción, de lo contrario, saque la información del objeto de excepción y construya un objeto más pequeño y liviano y devuélvalo en su lugar. El error rápido suele ser un código más limpio.
Para su ejemplo de validación, probablemente no heredaría de una clase de excepción o lanzaría excepciones porque los errores de validación podrían ser bastante comunes. Sería menos costoso devolver un objeto en lugar de lanzar una excepción si el 50% de mis usuarios no completan un formulario correctamente en el primer intento.
fuente
Si. Por ejemplo, recientemente encontré una situación de situación en la que encontré que las excepciones lanzadas en un servicio web ASMX no incluyen un elemento en el mensaje SOAP resultante, por lo que tuve que generarlo.
Código ilustrativo:
fuente
En la mayoría de los idiomas (afaik), las excepciones vienen con algunos beneficios adicionales. Principalmente, una instantánea de la traza de la pila actual se almacena en el objeto. Esto es bueno para la depuración, pero también puede tener implicaciones de memoria.
Como ya dijo @ThinkingMedia, realmente está utilizando la excepción como un objeto de error.
En su ejemplo de código, parece que lo hace principalmente para reutilizar el código y evitar la composición. Personalmente, no creo que esta sea una buena razón para hacerlo.
Otra posible razón sería engañar al lenguaje para darle un objeto de error con un seguimiento de pila. Esto proporciona información contextual adicional a su código de manejo de errores.
Por otro lado, podemos suponer que mantener el seguimiento de la pila tiene un costo de memoria. Por ejemplo, si comienza a recolectar estos objetos en algún lugar, puede ver un impacto desagradable en la memoria. Por supuesto, esto depende en gran medida de cómo se implementen los rastreos de pila de excepciones en el idioma / motor respectivo.
Entonces, ¿es una buena idea?
Hasta ahora no he visto que se use o recomiende. Pero esto no significa mucho.
La pregunta es: ¿el seguimiento de la pila es realmente útil para el manejo de errores? O, en general, ¿qué información desea proporcionar al desarrollador o administrador?
El patrón típico de lanzar / probar / atrapar hace que sea necesario tener un seguimiento de la pila, porque de lo contrario no tienes idea de dónde viene. Para un objeto devuelto, siempre proviene de la función llamada. El seguimiento de la pila puede contener toda la información que necesita el desarrollador, pero tal vez sea suficiente algo menos pesado y más específico.
fuente
La respuesta es sí. Consulte http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions y abra la flecha de la guía de estilo C ++ de Google sobre excepciones. Puede ver allí los argumentos que solían decidir en contra, pero diga que si tuvieran que hacerlo de nuevo, probablemente lo habrían decidido.
Sin embargo, vale la pena señalar que el lenguaje Go tampoco utiliza excepciones idiomáticamente, por razones similares.
fuente