Uso de una validación try-finally (sin captura) vs enum-state

9

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?

Mihalis Bagos
fuente
1
Eso no está tratando con el error que está cambiando la forma de manejo de errores que se está utilizando. En el caso 404, lo dejarías pasar porque no puedes manejarlo.
stonemetal
"un int que representa un valor, 0 para error, 1 para aceptar, 2 para advertencia, etc." ¡Por favor, diga que este fue un ejemplo inesperado! Usar 0 para indicar OK es definitivamente el estándar ...
anaximander

Respuestas:

15

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.

Robert Harvey
fuente
Gracias por la respuesta, es la más informativa, pero mi enfoque está en el manejo de excepciones y no en el lanzamiento de excepciones. ¿Debería atrapar la excepción 404 tan pronto como la reciba o debería dejarla ir más arriba en la pila?
Mihalis Bagos
Veo tu edición, pero no cambia mi respuesta. Convierta la excepción a un código de error si eso es significativo para la persona que llama. Si no es así, maneje la excepción; déjalo subir por la pila; o atraparlo, hacer algo con él (como escribirlo en un registro u otra cosa) y volver a lanzarlo.
Robert Harvey
1
+1: para una explicación razonable y equilibrada. Se debe utilizar una excepción para manejar casos excepcionales. De lo contrario, una función o método debería devolver un valor significativo (enumeración o tipo de opción) y la persona que llama debería manejarlo correctamente. Tal vez se podría mencionar una tercera alternativa que es popular en la programación funcional, es decir, continuaciones.
Giorgio
4

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.

Trevor Pilley
fuente
Muchas personas, especialmente los programadores de Python , prefieren EAFP, es decir, "Es más fácil pedir perdón que obtener permiso"
Mark Booth
1
+1 para comentarios sobre cómo evitar excepciones como con .Exists ().
codingoutloud
+1 Esto sigue siendo un buen consejo. En el código que escribo / administro, una Excepción es "Excepcional", 9/10 veces una Excepción está destinada a que un desarrollador la vea, dice: ¡oye, deberías estar defendiendo la programación! La otra vez, es algo con lo que no podemos lidiar, lo registramos y salimos lo mejor que podemos. La consistencia es importante, por ejemplo, por convención, normalmente tendríamos una respuesta falsa verdadera y mensajes internos para la tarifa / procesamiento estándar. Las API de terceros que parecen arrojar excepciones para todo pueden manejarse en la llamada y devolverse utilizando el proceso estándar acordado.
Gavin Howden
3

use try / catch / finally 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 la respuesta siempre esté en orden,

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.

¿o debería dejarse pasar la excepción para que la parte que llama se ocupe de ella?

Para eso están las excepciones.

tratado lo más cerca posible de donde se levanta

No es una verdad universal en absoluto. Es una buena idea algunas veces. Otras veces no es tan útil.

S.Lott
fuente
No es un diseño terrible. Microsoft lo implementa en muchos lugares, es decir, en el proveedor de membresía asp.NET predeterminado. Aparte de eso, no puedo ver cómo esta respuesta contribuye a la conversación
Mihalis Bagos
@MihalisBagos: Todo lo que puedo hacer es sugerir que el enfoque de Microsoft no es aceptado por todos los lenguajes de programación. En idiomas sin excepciones, devolver un valor es esencial. C es el ejemplo más notable. En idiomas con excepciones, devolver "valores de código" para indicar errores es un diseño terrible. Conduce a (a veces) ifdeclaraciones 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.
S.Lott
No digo que tu opinión no cuente, pero digo que tu opinión no está desarrollada. Con ese comentario, supongo que el razonamiento es que ¿dónde podemos usar excepciones, deberíamos, solo porque podemos? No veo el código de estado como enmascaramiento, en lugar de una categorización / organización de los casos de flujo de código
Mihalis Bagos
1
La excepción ya es una categorización / organización. No veo el valor en reemplazar una excepción inequívoca con un valor de retorno que puede confundirse fácilmente con valores de retorno "normales" o "no excepcionales". Los valores de retorno siempre deben ser no excepcionales. Mi opinión no está muy desarrollada: es muy simple. No transforme una excepción en un código numérico. Ya era un objeto de datos perfectamente bueno que sirve todos los casos de uso perfectamente buenos que podamos imaginar.
S.Lott
3

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.

DwayneAllen
fuente
1

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?

S.Robins
fuente
0

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:

Las líneas de código son como balas doradas. Desea utilizar la menor cantidad posible para realizar el trabajo.

Raymond Saltrelli
fuente
0

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

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Si se basa únicamente en booleano, el desarrollador que usa mi función debe tener esto en cuenta al escribir:

if not Validate() then
  DoNotContinue();

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:

  1. Solo una excepción en la función de validación.
  2. La excepción, incluso no detectada, detendrá la ejecución y aparecerá en el momento de la prueba.
Bahaa
fuente
0

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.

Wyatt Barnett
fuente