¿Son las variables de error un antipatrón o un buen diseño?

44

Para manejar varios posibles errores que no deberían detener la ejecución, tengo una errorvariable 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.

Mikayla Maki
fuente
66
Esto lo try/ catchexiste para. Además, puede colocar su try/ catchmucho 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).
jpmc26
Algo a tener en cuenta: si va a utilizar el manejo basado en excepciones y obtiene una excepción, no desea mostrar demasiada información al usuario. Use un controlador de errores como Elmah o Raygun.io para interceptarlo y mostrar un mensaje de error genérico al usuario. NUNCA muestre un seguimiento de la pila o un mensaje de error específico al usuario, porque divulga información sobre el funcionamiento interno de la aplicación, que puede ser abusado.
Nzall
44
@Nate Su consejo solo es aplicable para aplicaciones críticas de seguridad en las que el usuario no confía por completo. Los mensajes de error vagos son en sí mismos un antipatrón. También lo es enviar informes de errores a través de la red sin el consentimiento expreso del usuario.
piedar
3
@piedar Creé una pregunta separada donde esto se puede discutir más libremente: programmers.stackexchange.com/questions/245255/…
Nzall
26
Un principio general en el diseño de API que lo lleva bastante lejos es mirar siempre lo que PHP está haciendo, y luego hacer exactamente lo contrario.
Philipp

Respuestas:

65

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:

No se pudo recuperar una política de nombre "foo" para la "barra" del usuario, a la que se hizo referencia en el perfil del usuario.

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 ArgumentNullExceptioncual el código de llamada podría envolverse dentro de una UnableToRetrievePolicyExceptionporque 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 se ArgumentNullExceptionha 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).

Omer Iqbal
fuente
55
Windows devuelve los valores HRESULT de las funciones de C ++ para mantener la compatibilidad de C en sus API públicas (y porque comienza a meterse en un mundo de dolor tratando de ordenar las excepciones a través de los límites). No siga ciegamente el modelo del sistema operativo si está escribiendo una aplicación.
Cody gris
2
Lo único que agregaría a esto es la mención del acoplamiento más flojo. Las excepciones le permiten manejar muchas situaciones inesperadas en el lugar más apropiado. Por ejemplo, en una aplicación web, desea devolver un 500 en cualquier excepción para la que su código no esté preparado, en lugar de bloquear la aplicación web. Entonces necesita algún tipo de captura en la parte superior de su código (o en su marco). Una situación similar existe en las interfaces gráficas de escritorio. Pero también puede colocar controladores menos generales en varios lugares del código para manejar una variedad de situaciones de falla de una manera apropiada para el proceso actual que se intenta.
jpmc26
2
@TrentonMaki Si habla de errores de constructores de C ++, la mejor respuesta está aquí: parashift.com/c++-faq-lite/ctors-can-throw.html . En resumen, lanza una excepción, pero recuerda limpiar primero las posibles fugas. No conozco ningún otro idioma en el que simplemente lanzar directamente desde un constructor sea algo malo. Creo que la mayoría de los usuarios de una API preferiría detectar excepciones en lugar de verificar los códigos de error.
Ogre Psalm33
3
"Tales escenarios avanzados no son fáciles de implementar con los valores de retorno". ¡Seguro que puede! Todo lo que tiene que hacer es crear una ErrorStateReturnVariablesuperclase, y una de sus propiedades es InnerErrorState(que es una instancia de ErrorStateReturnVariable), que la implementación de subclases puede configurar para mostrar una cadena de errores ... oh, espera. : p
Brian S
55
El hecho de que un idioma admita excepciones no las convierte en una panacea. Las excepciones introducen rutas de ejecución ocultas y, por lo tanto, sus efectos deben controlarse adecuadamente; try / catch son fáciles de agregar, pero lograr la recuperación correcta es difícil , ...
Matthieu M.
21

Las 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.

Doc Brown
fuente
1
¡Gracias por la explicación! Su respuesta + Omer Iqbal respondió mi pregunta.
Mikayla Maki
Otra forma de manejar las "advertencias" es lanzar una excepción por defecto y tener algún tipo de bandera opcional para evitar que se lance la excepción.
Cyanfish
1
@Cyanfish: sí, pero hay que tener cuidado de no diseñar demasiado tales cosas, especialmente al hacer bibliotecas. Proporcione mejor un mecanismo de advertencia simple y funcional que 2, 3 o más.
Doc Brown
Solo se debe lanzar una excepción cuando se haya experimentado una falla, un escenario desconocido o irrecuperable, circunstancias excepcionales . Usted debe esperar impacto en el rendimiento cuando se construye excepciones
Gusdor
@Gusdor: absolutamente, es por eso que una "advertencia" típica no debería, en mi humilde opinión, lanzar una excepción por defecto. Pero esto también depende un poco del nivel de abstracción. A veces, un servicio o biblioteca simplemente no puede decidir si un evento inusual debe tratarse como una excepción desde el punto de vista de las personas que llaman. Personalmente, en tales situaciones, prefiero la lib solo para establecer un indicador de advertencia (sin excepción), dejar que la persona que llama pruebe esa bandera y arroje una excepción si lo considera apropiado. Eso es lo que tenía en mente cuando escribí arriba "es mejor proporcionar un mecanismo de advertencia en una biblioteca".
Doc Brown
20

Hay varias formas de señalar un error:

  • una variable de error para verificar: C , Ir , ...
  • una excepción: Java , C # , ...
  • un controlador de "condición": Lisp (¿solo?), ...
  • un retorno polimórfico: Haskell , ML , Rust , ...

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 ben Haskell) son mi solución favorita hasta ahora:

  • explícito: sin ruta oculta de ejecución
  • explícito: completamente documentado en la firma del tipo de función (sin sorpresas)
  • difícil de ignorar: debe coincidir con el patrón para obtener el resultado deseado y manejar el caso de error

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 .

Matthieu M.
fuente
1
¡Buena respuesta! Esperaba poder obtener una lista de formas de manejar los errores.
Mikayla Maki
Arrghhhh! ¿Cuántas veces he visto esto? "El problema de la variable de error es que es fácil olvidar verificar". El problema con las excepciones es que es fácil olvidarlo. Entonces su aplicación se bloquea. Su jefe no quiere pagar para arreglarlo, pero sus clientes dejan de usar su aplicación porque se frustran con los bloqueos. Todo por algo que no habría afectado la ejecución del programa si los códigos de error fueran devueltos e ignorados. La única vez que he visto personas ignorar los códigos de error es cuando no importaba tanto. El código más arriba en la cadena sabe que algo salió mal.
Dunk
1
@Dunk: tampoco creo que mi respuesta haga una disculpa de excepciones; sin embargo, bien podría depender del tipo de código que escriba. Mi experiencia laboral personal tiende a favorecer los sistemas que fallan en presencia de errores porque la corrupción silenciosa de los datos es peor (y no detectada) y los datos en los que trabajo son valiosos para el cliente (por supuesto, también significan soluciones urgentes).
Matthieu M.
No se trata de corrupción silenciosa de datos. Se trata de comprender su aplicación y saber cuándo y dónde debe verificar si una operación fue exitosa o no. En muchos casos, tomar esa determinación y manejarlo puede retrasarse. No debería depender de otra persona decirme cuándo tengo que manejar una operación fallida, que requiere una excepción. Es mi aplicación, sé cuándo y dónde quiero manejar cualquier problema relevante. Si las personas escriben aplicaciones que podrían corromper los datos, entonces eso está haciendo un trabajo realmente pobre. Escribir aplicaciones que se bloquean (que veo mucho) también está haciendo un mal trabajo.
Dunk
12

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:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

O esto:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Prefiero que las acciones arrojen excepciones, por lo que terminas con algo como:

x.doActionA();
x.doActionB();

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):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Nuevo y mejorado:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

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.

mrjink
fuente
1
Sin embargo, hay una situación en la que su 'nuevo y mejorado' perdería el contexto de dónde ocurre inicialmente la excepción. Por ejemplo, en la "versión actual (horrible)" de doActionA (), la cláusula catch tendría acceso a variables de instancia y otra información del objeto adjunto para dar un mensaje más útil.
InformadoA
1
Eso es cierto, pero actualmente no sucede aquí. Y siempre puede detectar la excepción en doActionA y envolverla en otra excepción con un mensaje de estado. Entonces todavía tendrá el seguimiento de la pila y el mensaje útil. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink
Estoy de acuerdo, puede que no suceda en tu situación. Pero no puede "siempre" poner la información en doActionA (). ¿Por qué? La persona que llama a doActionA () podría ser la única que tiene la información que necesita incluir.
InformadoA
2
Por lo tanto, haga que la persona que lo llama lo incluya cuando maneje la excepción. Sin embargo, lo mismo se aplica a la pregunta original. No hay nada que pueda hacer ahora que no pueda hacer con excepciones, y esto conduce a un código más limpio. Prefiero excepciones sobre booleanos devueltos o mensajes de error.
mrjink
Por lo general, se asume que una excepción que ocurre dentro de cualquiera de las "operaciones" se puede manejar y limpiar de la misma manera. Lo que sucede en la vida real es que necesita detectar y manejar excepciones para cada operación. Por lo tanto, no solo lanzas una excepción como en tu ejemplo. Además, el uso de excepciones termina creando un grupo de bloques try-catch anidados o una serie de bloques try-catch. Con frecuencia hace que el código sea mucho menos legible. No estoy en contra de las excepciones, pero uso la herramienta adecuada para la situación específica. Las excepciones son solo 1 herramienta.
Dunk
5

"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í

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
Michael Anderson
fuente
3
+1 por notar que el usuario quiere una advertencia, en lugar de un error. Vale la pena mencionar el warningspaquete de Python , que ofrece otro patrón para este problema.
James_pic
¡Gracias por la gran respuesta! Esto es más de lo que quería ver, patrones adicionales para manejar errores donde el try / catch tradicional podría no ser suficiente.
Mikayla Maki
Puede ser útil mencionar que un objeto de devolución de llamada de error pasado puede generar una excepción cuando ocurren ciertos errores o advertencias, de hecho, ese podría ser el comportamiento predeterminado, pero también puede ser útil para los medios por los cuales puede preguntar La función de llamada para hacer algo. Por ejemplo, un método de "error de análisis de manejo" podría darle al llamador un valor que el análisis debería considerarse como devuelto.
supercat
5

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.

gnasher729
fuente
4

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.

Alexandre Santos
fuente
Creo que esto más bien implica un mal diseño. Un método que tome argumentos debería ser capaz de manejarlos o no, nada en el medio. Su ejemplo debe tener una Validator(interfaz) inyectada en el método en cuestión (o el objeto detrás de él). Dependiendo de lo inyectado, Validatorel método procederá con contraseñas malas, o no. El código circundante podría probar a WeakValidatorsi el usuario lo solicitó después, por ejemplo, a WeakPasswordExceptionfue arrojado por el inicialmente intentado StrongValidator.
jhr
Ah, pero no dije que esto no podría ser una interfaz o un validador. Ni siquiera mencioné JSR303. Y, si lees detenidamente, me aseguré de no decir una contraseña débil, sino que no era muy segura. Una contraseña débil sería una razón para detener el flujo y solicitar al usuario una contraseña más segura.
Alexandre Santos
¿Y qué harías con una contraseña medianamente fuerte pero no realmente débil? Interrumpiría el flujo y le mostraría al usuario un mensaje que indica que la contraseña ingresada no es muy segura. Entonces ten un MiddlyStrongValidatoro algo. Y si eso realmente no interrumpió su flujo, se Validatordebe 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 ...
jhr
@jhr En los validadores que he escrito, normalmente crearé un AggregateException(o similar ValidationException) y pondré excepciones específicas para cada problema de validación en InnerExceptions. Por ejemplo, podría ser BadPasswordException: "La contraseña del usuario es menor que la longitud mínima de 6" o MandatoryFieldMissingException: "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 lugar NullReferenceExceptionse arrojó uno, entonces tenemos un error.
Omer Iqbal
4

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.

Jack Aidley
fuente
Si es necesario registrar una advertencia o algo así, que así sea. Pero si un error "necesita informe", por lo que supongo que se refiere al informe al usuario, no hay forma de garantizar que el código circundante leerá su código de retorno y realmente lo informará.
jhr
No, me refiero a informar a la persona que llama. Es responsabilidad de la persona que llama decidir si el usuario necesita saber sobre el error o no, al igual que con una excepción.
Jack Aidley
1
@jhr: ¿Cuándo hay alguna garantía de algo? El contrato para una clase puede especificar que los clientes tienen ciertas responsabilidades; Si los clientes cumplen con el contrato, harán las cosas que el contrato requiere. Si no lo hacen, cualquier consecuencia será culpa del código del cliente. Si uno desea protegerse contra errores accidentales por parte del cliente y tiene control sobre el tipo devuelto por un método de serialización, uno podría hacer que incluya un indicador de "posible corrupción no reconocida" y que no permita que los clientes lean datos sin llamar a un AcknowledgePossibleCorruptionmétodo. .
supercat
1
... pero tener una clase de objeto para contener información sobre problemas puede ser más útil que lanzar excepciones o devolver un código de error de aprobación / error. Correspondería a la aplicación utilizar esa información de manera adecuada (por ejemplo, al cargar el archivo "Foo", informar al usuario que los datos pueden no ser confiables y pedirle al usuario que elija un nuevo nombre al guardar).
supercat
1
Hay una garantía si se usan excepciones: si no las detecta, arrojan más arriba, el peor de los casos hasta la interfaz de usuario. No existe tal garantía si utiliza códigos de retorno que nadie lee. Claro, sigue la API si quieres usarla. ¡Estoy de acuerdo! Pero lamentablemente hay margen de error ...
jhr
2

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_errorsy error_messageses, 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.

ikh
fuente
Interesante toma de la pregunta. Creo que el problema con mi pregunta, y mi comprensión, es una terminología poco clara. Lo que describe no es excepcional, pero es un error. ¿Cómo deberíamos llamar eso?
Mikayla Maki
1

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 del no fatal;alcance devuelve un Failureobjeto de excepción especial no lanzado .

En Perl 5 puede usar Contextual :: Return con el que puede hacer esto return FAIL.

Jakub Narębski
fuente
-1

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

  1. Tengo una colección de objetos de entidad.
  2. Tengo servicios web de estilo de procedimiento que funcionan con estos objetos de entidad

Hay dos tipos de errores:

  1. Los errores cuando el procesamiento ocurre en la capa de servicio
  2. Los errores porque hay inconsistencias en los objetos de la entidad.

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

  • usando una variable de validación
  • lanzar una excepción inmediatamente cuando un campo se establece incorrectamente desde el colocador

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.

InformadoA
fuente
Ya veo de lo que estás hablando. Enfoque interesante Me alegro de que sea parte del conjunto de respuestas.
Mikayla Maki