He estado leyendo el consejo sobre esta pregunta sobre cómo se debe tratar una excepción lo más cerca posible de donde se plantea.
Mi dilema sobre la mejor práctica es si uno debería usar un try / catch / finalmente para devolver una enumeración (o un int que representa un valor, 0 para error, 1 para aceptar, 2 para advertencia, etc., según el caso) para que una respuesta siempre está en orden, o ¿debería uno dejar pasar la excepción para que la parte que llama se ocupe de ella?
De lo que puedo deducir, esto puede ser diferente según el caso, por lo que el consejo original parece extraño.
Por ejemplo, en un servicio web, siempre querrá devolver un estado, por lo que cualquier excepción debe tratarse en el acto, pero digamos que dentro de una función que publica / obtiene algunos datos a través de http, desearía que excepción (por ejemplo, en el caso del 404) para pasar al que lo disparó. Si no lo hace, tendría que crear alguna forma de informar a la parte que llama de la calidad del resultado (error: 404), así como el resultado en sí.
Aunque es posible intentar atrapar la excepción 404 dentro de la función auxiliar que obtiene / publica los datos, ¿debería? ¿Soy solo yo el que usa una letra pequeña para denotar estados en el programa (y los documenta adecuadamente, por supuesto), y luego uso esta información para fines de validación de cordura (todo está bien / manejo de errores) afuera?
Actualización: esperaba una excepción fatal / no fatal para la clasificación principal, pero no quería incluir esto para no perjudicar las respuestas. Permítanme aclarar de qué se trata la pregunta: manejar las excepciones lanzadas, no lanzar excepciones. Cuál es el efecto deseado: detecta un error e intenta recuperarte de él. Si la recuperación no es posible, proporcione los comentarios más significativos.
Nuevamente, con el ejemplo http get / post, la pregunta es, ¿debería proporcionar un nuevo objeto que describa lo que le sucedió a la persona que llamó originalmente? Si este asistente estuviera en una biblioteca que está utilizando, ¿esperaría que le proporcionara un código de estado para la operación, o lo incluiría en un bloque try-catch? Si lo está diseñando , ¿proporcionaría un código de estado o lanzaría una excepción y dejaría que el nivel superior lo traduzca a un código / mensaje de estado?
Sinopsis: ¿Cómo eliges si un código en lugar de producir una excepción, devuelve un código de estado junto con cualquier resultado que pueda producir?
fuente
Respuestas:
Las excepciones deben usarse para condiciones excepcionales. Lanzar una excepción es básicamente hacer la declaración: "No puedo manejar esta condición aquí; ¿puede alguien más arriba en la pila de llamadas captar esto por mí y manejarlo?"
Devolver un valor puede ser preferible, si está claro que la persona que llama tomará ese valor y hará algo significativo con él. Esto es especialmente cierto si lanzar una excepción tiene implicaciones de rendimiento, es decir, puede ocurrir en un ciclo cerrado. Lanzar una excepción lleva mucho más tiempo que devolver un valor (al menos en dos órdenes de magnitud).
Las excepciones nunca deben usarse para implementar la lógica del programa. En otras palabras, no arrojes una excepción para hacer algo; lanzar una excepción para indicar que no se pudo hacer.
fuente
Un buen consejo que leí una vez fue, arroje excepciones cuando no pueda progresar dado el estado de los datos que está tratando, sin embargo, si tiene un método que puede arrojar una excepción, también proporcione, cuando sea posible, un método para afirmar si los datos son realmente válido antes de que se llame al método.
Por ejemplo, System.IO.File.OpenRead () lanzará una FileNotFoundException si el archivo suministrado no existe, sin embargo, también proporciona un método .Exists () que devuelve un valor booleano que indica si el archivo está presente al que debe llamar antes llamando a OpenRead () para evitar cualquier excepción inesperada.
Para responder a la parte de la pregunta "cuándo debería tratar con una excepción", diría donde sea que pueda hacer algo al respecto. Si su método no puede manejar una excepción lanzada por un método al que llama, no la atrape. Deje que suba más arriba en la cadena de llamadas a algo que pueda manejarlo. En algunos casos, esto puede ser solo un registrador que escucha Application.UnhandledException.
fuente
Ese es un diseño terrible. No "enmascare" una excepción traduciéndola a un código numérico. Déjelo como una excepción adecuada e inequívoca.
Para eso están las excepciones.
No es una verdad universal en absoluto. Es una buena idea algunas veces. Otras veces no es tan útil.
fuente
if
declaraciones engorrosas en lugar de (a veces) un manejo de excepciones más simple. La respuesta ("Cómo elegir ...") es simple. No hacerlo . Creo que decir esto se suma a la conversación. Sin embargo, eres libre de ignorar esta respuesta.Estoy de acuerdo con S.Lott . Capturar la excepción lo más cerca posible de la fuente puede ser una buena idea o una mala idea según la situación. La clave para manejar las excepciones es detectarlas solo cuando puede hacer algo al respecto. Atraparlos y devolver un valor numérico a la función de llamada generalmente es un mal diseño. Solo debes dejarlos flotar hasta que puedas recuperarte.
Siempre considero que el manejo de excepciones está a un paso de la lógica de mi aplicación. Me pregunto, si se lanza esta excepción, ¿qué tan atrás de la pila de llamadas debo rastrear antes de que mi aplicación esté en un estado recuperable? En muchos casos, si no hay nada que pueda hacer dentro de la aplicación para recuperarme, eso podría significar que no lo atraparé hasta el nivel superior y solo registraré la excepción, fallaré el trabajo e intentaré cerrarlo limpiamente.
Realmente no hay una regla estricta sobre cuándo y cómo configurar el manejo de excepciones que no sea dejarlos solos hasta que sepa qué hacer con ellos.
fuente
Las excepciones son cosas hermosas. Le permiten producir una descripción clara de un problema de tiempo de ejecución sin recurrir a ambigüedades innecesarias. Las excepciones pueden ser mecanografiadas, subtipadas y pueden ser manejadas por tipo. Se pueden pasar para su manejo en otro lugar, y si no se pueden manejar, se pueden volver a subir para tratar en una capa superior de su aplicación. También volverán automáticamente de su método sin necesidad de invocar mucha lógica loca para tratar con códigos de error ofuscados.
Debe lanzar una excepción inmediatamente después de encontrar datos no válidos en su código. Debería ajustar las llamadas a otros métodos en un intento ... capturar ... finalmente para manejar cualquier excepción que pueda ser lanzada, y si no sabe cómo responder a una excepción dada, la arroja nuevamente para indicar a las capas superiores que hay algo mal que debería manejarse en otro lugar.
Administrar códigos de error puede ser muy difícil. Por lo general, terminas con una gran cantidad de duplicaciones innecesarias en tu código y / o mucha lógica desordenada para lidiar con los estados de error. Ser un usuario y encontrar un código de error es aún peor, ya que el código en sí no será significativo y no proporcionará al usuario un contexto para el error. Una excepción, por otro lado, puede decirle al usuario algo útil, como "Olvidó ingresar un valor" o "ingresó un valor no válido, aquí está el rango válido que puede usar ..." o "No lo hago sepa lo que sucedió, póngase en contacto con el soporte técnico y dígales que acabo de estrellarme, y deles el siguiente seguimiento de la pila ... ".
Entonces, mi pregunta al OP es ¿por qué en la Tierra NO querrías usar excepciones sobre la devolución de códigos de error?
fuente
Todas buenas respuestas. También me gustaría agregar que devolver un código de error en lugar de lanzar una excepción puede hacer que el código de la persona que llama sea más complicado. Decir que el método A llama al método B llama al método C y C encuentra un error. Si C devuelve un código de error, ahora B necesita tener lógica para determinar si puede manejar ese código de error. Si no puede, debe devolverlo a A. Si A no puede manejar el error, ¿qué debe hacer? ¿Lanzar una excepción? En este ejemplo, el código es mucho más limpio si C simplemente lanza una excepción, B no detecta la excepción, por lo que aborta automáticamente sin ningún código adicional necesario para hacerlo y A puede detectar ciertos tipos de excepciones mientras deja que otros continúen la llamada apilar.
Esto trae a la mente una buena regla para codificar:
fuente
Utilicé una combinación de ambas soluciones: para cada función de validación, paso un registro que completo con el estado de validación (un código de error). Al final de la función, si existe un error de validación, lanzo una excepción, de esta manera no lanzo una excepción para cada campo, sino solo una vez.
También aproveché que lanzar una excepción detendrá la ejecución porque no quiero que la ejecución continúe cuando los datos no son válidos.
Por ejemplo
Si se basa únicamente en booleano, el desarrollador que usa mi función debe tener esto en cuenta al escribir:
pero puede que se olvide y solo llame
Validate()
(sé que no debería, pero tal vez sí).Entonces, en el código anterior obtuve las dos ventajas:
fuente
No hay una respuesta aquí, algo así como que no hay un tipo de HttpException.
Tiene mucho sentido que las bibliotecas HTTP subyacentes arrojen una excepción cuando obtienen una respuesta 4xx o 5xx; la última vez que miré las especificaciones HTTP, esos fueron errores.
En cuanto a lanzar esa excepción, o envolverla y volver a lanzarla, creo que realmente es una cuestión de caso de uso. Por ejemplo, si está escribiendo un contenedor para tomar algunos datos de la API y exponerlos a las aplicaciones, podría decidir que semánticamente una solicitud de un recurso inexistente que devuelve un HTTP 404 tendría más sentido para atrapar eso y devolver nulo. Por otro lado, un error 406 (no aceptable) podría valer la pena arrojar un error, ya que eso significa que algo ha cambiado y la aplicación debería fallar, arder y gritar pidiendo ayuda.
fuente