Recientemente comencé a trabajar en un lugar con algunos desarrolladores mucho más antiguos (más de 50 años). Han trabajado en aplicaciones críticas relacionadas con la aviación donde el sistema no puede fallar. Como resultado, el programador anterior tiende a codificar de esta manera.
Tiende a poner un booleano en los objetos para indicar si se debe lanzar una excepción o no.
Ejemplo
public class AreaCalculator
{
AreaCalculator(bool shouldThrowExceptions) { ... }
CalculateArea(int x, int y)
{
if(x < 0 || y < 0)
{
if(shouldThrowExceptions)
throwException;
else
return 0;
}
}
}
(En nuestro proyecto, el método puede fallar porque estamos tratando de usar un dispositivo de red que no puede estar presente en ese momento. El ejemplo del área es solo un ejemplo del indicador de excepción)
Para mí, esto parece un olor a código. Escribir pruebas unitarias se vuelve un poco más complejo ya que debe probar la marca de excepción cada vez. Además, si algo sale mal, ¿no le gustaría saberlo de inmediato? ¿No debería ser responsabilidad de quien llama determinar cómo continuar?
Su lógica / razonamiento es que nuestro programa necesita hacer 1 cosa, mostrar datos al usuario. Cualquier otra excepción que no nos impida hacerlo debe ser ignorada. Estoy de acuerdo en que no deberían ignorarse, sino que deberían burbujear y ser manejados por la persona adecuada, y no tener que lidiar con banderas para eso.
¿Es esta una buena forma de manejar las excepciones?
Editar : solo para dar más contexto sobre la decisión de diseño, sospecho que es porque si este componente falla, el programa aún puede funcionar y hacer su tarea principal. Por lo tanto, no querríamos lanzar una excepción (¿y no manejarla?) Y hacer que elimine el programa cuando para el usuario funcione bien
Edición 2 : para dar aún más contexto, en nuestro caso se llama al método para restablecer una tarjeta de red. El problema surge cuando la tarjeta de red se desconecta y se vuelve a conectar, se le asigna una dirección IP diferente, por lo que Restablecer arrojará una excepción porque estaríamos intentando restablecer el hardware con la antigua IP.
Respuestas:
El problema con este enfoque es que, si bien nunca se lanzan excepciones (y, por lo tanto, la aplicación nunca se bloquea debido a excepciones no detectadas), los resultados devueltos no son necesariamente correctos y el usuario puede nunca saber que hay un problema con los datos (o cuál es ese problema y cómo corregirlo).
Para que los resultados sean correctos y significativos, el método de llamada debe verificar el resultado en busca de números especiales, es decir, valores de retorno específicos utilizados para denotar problemas que surgieron al ejecutar el método. Los números negativos (o cero) que se devuelven para cantidades positivas definidas (como área) son un buen ejemplo de esto en el código anterior. Sin embargo, si el método de llamada no sabe (¡u olvida!) Verificar estos números especiales, el procesamiento puede continuar sin darse cuenta de un error. Luego, los datos se muestran al usuario mostrando un área de 0, que el usuario sabe que es incorrecta, pero no tiene ninguna indicación de qué salió mal, dónde o por qué. Luego se preguntan si alguno de los otros valores está mal ...
Si se lanzara la excepción, el procesamiento se detendría, el error (idealmente) se registraría y el usuario podría ser notificado de alguna manera. El usuario puede arreglar lo que está mal y volver a intentarlo. El manejo adecuado de las excepciones (¡y las pruebas!) Asegurará que las aplicaciones críticas no se bloqueen o terminen en un estado no válido.
fuente
No, creo que esta es una práctica bastante mala. Lanzar una excepción versus devolver un valor es un cambio fundamental en la API, cambiar la firma del método y hacer que el método se comporte de manera bastante diferente desde una perspectiva de interfaz.
En general, cuando diseñamos clases y sus API, debemos considerar que
puede haber varias instancias de la clase con diferentes configuraciones flotando en el mismo programa al mismo tiempo, y
Debido a la inyección de dependencia y a muchas otras prácticas de programación, un cliente consumidor puede crear los objetos y entregarlos a otro para que los use, por lo que a menudo tenemos una separación entre los creadores de objetos y los usuarios de objetos.
Considere ahora qué debe hacer el llamador del método para hacer uso de una instancia que le han entregado, por ejemplo, para llamar al método de cálculo: la persona que llama tendría que verificar si el área es cero y detectar excepciones, ¡ay! Las consideraciones de prueba van no solo a la clase en sí, sino también al manejo de errores de quienes llaman ...
Siempre debemos hacer las cosas lo más fácil posible para el cliente consumidor; Esta configuración booleana en el constructor que cambia la API de un método de instancia es lo opuesto a hacer que el programador cliente consumidor (tal vez usted o su colega) caiga en el pozo del éxito.
Para ofrecer ambas API, es mucho mejor y más normal proporcionar dos clases diferentes: una que siempre arroja un error y otra que siempre devuelve 0 en caso de error, o bien, proporciona dos métodos diferentes con una sola clase. De esta manera, el cliente consumidor puede saber fácilmente cómo verificar y manejar los errores.
Usando dos clases diferentes o dos métodos diferentes, puede usar los IDE para encontrar usuarios de métodos y funciones de refactorización, etc. mucho más fácilmente ya que los dos casos de uso ya no se combinan. La lectura, escritura, mantenimiento, revisiones y pruebas de código también es más simple.
En otra nota, personalmente siento que no deberíamos tomar parámetros de configuración booleanos, donde los llamadores reales simplemente pasan una constante . Dicha parametrización de la configuración combina dos casos de uso separados sin beneficio real.
¡Eche un vistazo a su base de código y vea si alguna vez se usa una variable (o expresión no constante) para el parámetro de configuración booleana en el constructor! Lo dudo.
Y una consideración adicional es preguntar por qué la computación del área puede fallar. Lo mejor podría ser lanzar el constructor, si no se puede hacer el cálculo. Sin embargo, si no sabe si el cálculo se puede hacer hasta que el objeto se inicialice más, entonces quizás considere usar diferentes clases para diferenciar esos estados (no está listo para calcular el área frente a listo para calcular el área).
Leí que su situación de falla está orientada hacia la comunicación remota, por lo que es posible que no se aplique; solo algo de comida para pensar.
Sí estoy de acuerdo. Parece prematuro que la persona que llama decida que el área de 0 es la respuesta correcta en condiciones de error (especialmente porque 0 es un área válida, por lo que no hay forma de distinguir entre el error y el 0 real, aunque puede no aplicarse a su aplicación).
fuente
Esa es una introducción interesante, que me da la impresión de que la motivación detrás de este diseño es evitar arrojar excepciones en algunos contextos "porque el sistema podría fallar" . Pero si el sistema "puede fallar debido a una excepción", esta es una clara indicación de que
Entonces, si el programa que usa el
AreaCalculator
está defectuoso, su colega prefiere no hacer que el programa se "bloquee temprano", sino devolver algún valor incorrecto (esperando que nadie lo note, o esperando que nadie haga algo importante con él). En realidad, eso está enmascarando un error y, en mi experiencia, tarde o temprano conducirá a errores de seguimiento para los que se hace difícil encontrar la causa raíz.En mi humilde opinión, escribir un programa que no se bloquee bajo ninguna circunstancia, pero muestre datos incorrectos o resultados de cálculo, generalmente no es mejor que dejar que el programa se bloquee. El único enfoque correcto es darle a la persona que llama la oportunidad de notar el error, lidiar con él, dejarle decidir si el usuario debe ser informado sobre el comportamiento incorrecto y si es seguro continuar con el procesamiento o si es más seguro para detener el programa por completo. Por lo tanto, recomendaría uno de los siguientes:
hace que sea difícil pasar por alto el hecho de que una función puede generar una excepción. La documentación y los estándares de codificación son su amigo aquí, y las revisiones periódicas de código deben respaldar el uso correcto de los componentes y el manejo adecuado de las excepciones.
capacite al equipo para que espere y maneje excepciones cuando usen componentes de "caja negra" y tenga en mente el comportamiento global del programa.
si por alguna razón cree que no puede obtener el código de llamada (o los desarrolladores que lo escriben) para usar el manejo de excepciones correctamente, entonces, como último recurso, puede diseñar una API con variables de salida de error explícitas y sin excepciones, como
por lo que es muy difícil para la persona que llama pasar por alto la función que podría fallar. Pero esto es en mi humilde opinión extremadamente feo en C #; Es una vieja técnica de programación defensiva de C donde no hay excepciones, y normalmente no debería ser necesario trabajar en eso hoy en día.
fuente
try
/catch
dentro de un ciclo si necesita continuar el procesamiento si falla un elemento.Cualquier función con n parámetros será más difícil de probar que una con parámetros n-1 . Extienda eso hasta lo absurdo y el argumento se convierte en que las funciones no deberían tener parámetros porque eso las hace más fáciles de probar.
Si bien es una gran idea escribir código que sea fácil de probar, es una idea terrible poner la simplicidad de prueba por encima de la escritura de código útil para las personas que tienen que llamarlo. Si el ejemplo en la pregunta tiene un interruptor que determina si se produce o no una excepción, es posible que las personas que llaman que desean ese comportamiento merezcan agregarlo a la función. Donde está la línea entre lo complejo y lo demasiado complejo es un juicio; cualquier persona que trate de decirle que hay una línea brillante que se aplica en todas las situaciones debe mirar con desconfianza.
Eso depende de tu definición de error. El ejemplo en la pregunta define mal como "dada una dimensión de menos de cero y
shouldThrowExceptions
es verdadera". Recibir una dimensión si menos de cero no está mal cuandoshouldThrowExceptions
es falso porque el interruptor induce un comportamiento diferente. Eso no es, simplemente, una situación excepcional.El verdadero problema aquí es que el conmutador tenía un nombre incorrecto porque no es descriptivo de lo que hace que la función haga. Si se le hubiera dado un nombre mejor
treatInvalidDimensionsAsZero
, ¿habría hecho esta pregunta?La persona que llama determina cómo continuar. En este caso, lo hace con anticipación configurando o borrando
shouldThrowExceptions
y la función se comporta de acuerdo con su estado.El ejemplo es patológicamente simple porque hace un solo cálculo y devuelve. Si lo hace un poco más complejo, como calcular la suma de las raíces cuadradas de una lista de números, lanzar excepciones puede dar problemas a las personas que llaman que no pueden resolver. Si paso una lista de
[5, 6, -1, 8, 12]
y la función arroja una excepción sobre el-1
, no tengo forma de decirle a la función que continúe porque ya habrá abortado y tirado la suma. Si la lista es un conjunto de datos enorme, generar una copia sin ningún número negativo antes de llamar a la función podría no ser práctico, por lo que me veo obligado a decir de antemano cómo se deben tratar los números no válidos, ya sea en forma de "simplemente ignórelos". "cambiar o tal vez proporcionar una lambda que sea llamada para tomar esa decisión.Una vez más, no existe una solución única para todos. En el ejemplo, la función fue, presumiblemente, escrita con una especificación que dice cómo lidiar con las dimensiones negativas. Lo último que quiere hacer es reducir la relación señal / ruido de sus registros llenándolos con mensajes que digan "normalmente, se lanzaría una excepción aquí, pero la persona que llama dijo que no se molestara".
Y como uno de esos programadores mucho más viejos, le pediría que amablemente se aleje de mi césped. ;-)
fuente
our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignored
mentalidad puede llevar al usuario a tomar decisiones basadas en los datos incorrectos (porque en realidad el programa debe hacerlo 1 cosa: ayudar al usuario a tomar decisiones informadas) 2. casos similares, como por ejemplo,bool ExecuteJob(bool throwOnError = false)
son extremadamente propensos a errores y conducen a un código difícil de razonar con solo leerlo.El código de seguridad crítico y 'normal' puede conducir a ideas muy diferentes de cómo se ve la 'buena práctica'. Hay mucha superposición: algunas cosas son arriesgadas y deben evitarse en ambas, pero aún existen diferencias significativas. Si agrega un requisito para garantizar que responda, estas desviaciones se vuelven bastante sustanciales.
Estos a menudo se relacionan con cosas que esperarías:
Para git, la respuesta incorrecta podría ser muy mala en relación con: tomar demasiado tiempo / abortar / colgar o incluso estrellarse (que efectivamente no son problemas en relación con, digamos, alterar accidentalmente el código facturado).
Sin embargo: para un panel de instrumentos que tiene un cálculo de la fuerza g se estanca y evita que se haga un cálculo de la velocidad del aire puede ser inaceptable.
Algunos son menos obvios:
Si ha probado mucho , los resultados de primer orden (como las respuestas correctas) no son una preocupación tan grande en términos relativos. Sabes que tus pruebas habrán cubierto esto. Sin embargo, si hubo un estado oculto o flujo de control, no sabe que esto no será la causa de algo mucho más sutil. Esto es difícil de descartar con las pruebas.
Ser demostrablemente seguro es relativamente importante. No muchos clientes pensarán si la fuente que están comprando es segura o no. Si estás en el mercado de la aviación por otro lado ...
¿Cómo se aplica esto a su ejemplo?
No lo sé. Hay una serie de procesos de pensamiento que pueden tener reglas principales como "Nadie arroja código de producción" que se adopta en un código crítico de seguridad que sería bastante tonto en situaciones más habituales.
Algunos se relacionarán con la integración, algunos de seguridad y tal vez otros ... Algunos son buenos (se necesitaban límites de rendimiento / memoria ajustados) algunos son malos (no manejamos las excepciones correctamente, así que mejor no arriesgarnos). La mayoría de las veces, incluso sabiendo por qué lo hicieron, realmente no responderá la pregunta. Por ejemplo, si tiene que ver con la facilidad de auditar el código más que mejorarlo, ¿es una buena práctica? Realmente no puedes decirlo. Son animales diferentes y necesitan un trato diferente.
Dicho todo esto, me parece un poco sospechoso PERO :
El software crítico de seguridad y las decisiones de diseño de software probablemente no deberían ser tomadas por extraños en el intercambio de pila de ingeniería de software. Puede haber una buena razón para hacerlo, incluso si es parte de un mal sistema. No leas demasiado a nada de esto más que como "alimento para el pensamiento".
fuente
A veces, lanzar una excepción no es el mejor método. No menos debido al desbobinado de la pila, sino a veces porque atrapar una excepción es problemático, particularmente a lo largo del lenguaje o las costuras de la interfaz.
Una buena manera de manejar esto es devolver un tipo de datos enriquecido. Este tipo de datos tiene suficiente estado para describir todas las rutas felices y todas las rutas infelices. El punto es que si interactúa con esta función (miembro / global / de otro modo) se verá obligado a manejar el resultado.
Dicho esto, este tipo de datos enriquecido no debería forzar la acción. Imagine en su área, por ejemplo, algo así
var area_calc = new AreaCalculator(); var volume = area_calc.CalculateArea(x, y) * z;
. Parece útilvolume
debe contener el área multiplicada por la profundidad, que podría ser un cubo, un cilindro, etc.¿Pero qué pasa si el servicio area_calc no funciona? Luego
area_calc .CalculateArea(x, y)
devolvió un rico tipo de datos que contiene un error. ¿Es legal multiplicar eso porz
? Es una buena pregunta. Puede obligar a los usuarios a manejar la verificación de inmediato. Sin embargo, esto rompe la lógica con el manejo de errores.vs
La lógica esencialmente se extiende sobre dos líneas y se divide por manejo de errores en el primer caso, mientras que el segundo caso tiene toda la lógica relevante en una línea seguida por el manejo de errores.
En ese segundo caso
volume
es un tipo de datos rico. No es solo un número. Esto hace que el almacenamiento sea más grande yvolume
aún deberá investigarse para detectar una condición de error. Además,volume
puede alimentar otros cálculos antes de que el usuario decida manejar el error, permitiéndole manifestarse en varias ubicaciones dispares. Esto puede ser bueno o malo dependiendo de los detalles de la situación.Alternativamente,
volume
podría ser solo un tipo de datos simple, solo un número, pero ¿qué sucede con la condición de error? Podría ser que el valor se convierta implícitamente si está en una condición feliz. Si está en una condición infeliz, puede devolver un valor predeterminado / de error (para el área 0 o -1 puede parecer razonable). Alternativamente, podría arrojar una excepción en este lado del límite de la interfaz / idioma.vs.
Al pasar un valor incorrecto o posiblemente incorrecto, el usuario tiene una gran responsabilidad para validar los datos. Esta es una fuente de errores, porque en lo que respecta al compilador, el valor de retorno es un entero legítimo. Si no se revisó algo, lo descubrirá cuando las cosas salgan mal. El segundo caso combina lo mejor de ambos mundos al permitir excepciones para manejar rutas infelices, mientras que las rutas felices siguen el procesamiento normal. Desafortunadamente, obliga al usuario a manejar sabiamente las excepciones, lo cual es difícil.
Para ser claros, una ruta infeliz es un caso desconocido para la lógica de negocios (el dominio de la excepción), no validar es una ruta feliz porque sabes cómo manejar eso mediante las reglas de negocio (el dominio de las reglas).
La solución definitiva sería una que permita todos los escenarios (dentro de lo razonable).
Algo como:
fuente
Contestaré desde el punto de vista de C ++. Estoy bastante seguro de que todos los conceptos básicos son transferibles a C #.
Parece que su estilo preferido es "siempre lanzar excepciones":
Esto puede ser un problema para el código C ++ porque el manejo de excepciones es pesado : hace que el caso de falla se ejecute lentamente y hace que el caso de falla asigne memoria (que a veces ni siquiera está disponible), y generalmente hace que las cosas sean menos predecibles. El peso pesado de EH es una de las razones por las que escuchas a la gente decir cosas como "No uses excepciones para controlar el flujo".
Entonces, algunas bibliotecas (como
<filesystem>
) usan lo que C ++ llama una "API dual", o lo que C # llama elTry-Parse
patrón (¡gracias Peter por el consejo!)Puede ver el problema con las "API duales" de inmediato: mucha duplicación de código, sin orientación para los usuarios sobre qué API es la "correcta" para usar, y el usuario debe tomar una decisión difícil entre mensajes de error útiles (
CalculateArea
) y speed (TryCalculateArea
) porque la versión más rápida toma nuestra útil"negative side lengths"
excepción y la aplana en un inútilfalse
: "algo salió mal, no me preguntes qué o dónde". (Algunas API duales utilizan un tipo de error más expresivo, comoint errno
o C ++ 'sstd::error_code
, pero que todavía no le dice dónde se produjo el error - sólo que lo hizo aparecer en alguna parte.)Si no puede decidir cómo debe comportarse su código, ¡siempre puede pasar la decisión a la persona que llama!
Esto es esencialmente lo que está haciendo su compañero de trabajo; excepto que está factorizando el "controlador de errores" en una variable global:
Mover parámetros importantes de parámetros de funciones explícitas al estado global es casi siempre una mala idea. No lo recomiendo. (El hecho de que no sea un estado global en su caso, sino simplemente un estado miembro de toda la instancia mitiga un poco la maldad, pero no mucho).
Además, su compañero de trabajo está limitando innecesariamente el número de posibles comportamientos de manejo de errores. En lugar de permitir cualquier lambda de manejo de errores, decidió solo dos:
Este es probablemente el "punto amargo" de cualquiera de estas posibles estrategias. Le ha quitado toda la flexibilidad al usuario final al obligarlo a usar una de sus dos devoluciones de llamada para el manejo de errores; y tienes todos los problemas del estado global compartido; y todavía estás pagando por esa rama condicional en todas partes.
Finalmente, una solución común en C ++ (o cualquier lenguaje con compilación condicional) sería forzar al usuario a tomar la decisión de todo su programa, globalmente, en tiempo de compilación, para que la ruta de código no tomada se pueda optimizar por completo:
Un ejemplo de algo que funciona de esta manera es la
assert
macro en C y C ++, que condiciona su comportamiento en la macro del preprocesadorNDEBUG
.fuente
std::optional
desdeTryCalculateArea()
, es sencillo unificar la implementación de ambas partes de la interfaz dual en una única plantilla de función con un indicador de tiempo de compilación.std::expected
. Con solostd::optional
, a menos que malinterprete su solución propuesta, aún sufriría por lo que dije: el usuario debe tomar una decisión difícil entre mensajes de error útiles y velocidad, porque la versión más rápida toma nuestra"negative side lengths"
excepción útil y la aplana en un inútilfalse
- " algo salió mal, no me preguntes qué o dónde ".std::error_code *ec
a través de todos los niveles de la API, y luego en la parte inferior hace el equivalente moral deif (ec == nullptr) throw something; else *ec = some error code
. (Resume lo realif
en algo llamadoErrorHandler
, pero es la misma idea básica.)Creo que debería mencionarse de dónde obtuvo su patrón su colega.
Hoy en día, C # tiene el patrón TryGet
public bool TryThing(out result)
. Esto le permite obtener su resultado, sin dejar de saber si ese resultado es incluso un valor válido. (Por ejemplo, todos losint
valores son resultados válidos paraMath.sum(int, int)
, pero si el valor se desbordara, este resultado particular podría ser basura). Sin embargo, este es un patrón relativamente nuevo.Antes de la
out
palabra clave, tenía que lanzar una excepción (costosa, y la persona que llama tenía que atraparla o matar todo el programa), crear una estructura especial (clase antes de clase o genéricos eran realmente una cosa) para que cada resultado representara el valor y posibles errores (toma tiempo y tiempo para crear e inflar el software), o devolver un valor predeterminado de "error" (que podría no haber sido un error).El enfoque que utiliza su colega les brinda la posibilidad de fallar al principio de las excepciones al probar / depurar nuevas características, al tiempo que les brinda la seguridad y el rendimiento en tiempo de ejecución (el rendimiento fue un problema crítico todo el tiempo ~ 30 años atrás) de solo devolver un valor de error predeterminado. Ahora, este es el patrón en el que se escribió el software y el patrón esperado para avanzar, por lo que es natural seguir haciéndolo de esta manera, aunque ahora hay mejores formas. Lo más probable es que este patrón se haya heredado de la edad del software, o un patrón que sus universidades nunca crecieron (los viejos hábitos son difíciles de romper).
Las otras respuestas ya cubren por qué esto se considera una mala práctica, por lo que terminaré recomendando que lea sobre el patrón TryGet (tal vez también la encapsulación de lo que promete un objeto a su llamador).
fuente
out
palabra clave, escribiría unabool
función que tome un puntero al resultado, es decir, unref
parámetro. Podría hacerlo en VB6 en 1998. Laout
palabra clave simplemente le compra la seguridad en tiempo de compilación de que el parámetro se asigna cuando la función regresa, eso es todo. Sin embargo, es un patrón agradable y útil.Hay momentos en que desea hacer su enfoque, pero no consideraría que sean la situación "normal". La clave para determinar en qué caso se encuentra es:
Consulta los requisitos. Si sus requisitos realmente dicen que tiene un trabajo, que es mostrar datos al usuario, entonces tiene razón. Sin embargo, en mi experiencia, la mayoría de las veces al usuario también le importa qué datos se muestran. Quieren los datos correctos. Algunos sistemas solo quieren fallar en silencio y dejar que un usuario descubra que algo salió mal, pero los consideraría la excepción a la regla.
La pregunta clave que haría después de una falla es "¿Está el sistema en un estado donde las expectativas del usuario y los invariantes del software son válidos?" Si es así, entonces, por supuesto, solo regrese y continúe. Prácticamente hablando, esto no es lo que sucede en la mayoría de los programas.
En cuanto al indicador en sí, el indicador de excepciones generalmente se considera olor de código porque un usuario necesita saber de alguna manera en qué modo se encuentra el módulo para comprender cómo funciona la función. Si está en
!shouldThrowExceptions
el modo, el usuario tiene que saber que ellos son responsables de la detección de errores y el mantenimiento de las expectativas e invariantes cuando se producen. También son responsables en ese mismo momento, en la línea donde se llama la función. Una bandera como esta suele ser muy confusa.Sin embargo, sucede. Considere que muchos procesadores permiten cambiar el comportamiento del punto flotante dentro del programa. Un programa que desea tener estándares más relajados puede hacerlo simplemente cambiando un registro (que es efectivamente una bandera). El truco es que debes ser muy cauteloso para evitar pisar accidentalmente los dedos de los demás. El código a menudo verificará el indicador actual, lo configurará en la configuración deseada, realizará operaciones y luego lo restablecerá. De esa manera, nadie se sorprende con el cambio.
fuente
Este ejemplo específico tiene una característica interesante que puede afectar las reglas ...
Lo que veo aquí es una verificación de precondición . Una comprobación de precondición fallida implica un error más arriba en la pila de llamadas. Por lo tanto, la pregunta es si este código es responsable de informar errores ubicados en otro lugar.
Parte de la tensión aquí es atribuible al hecho de que esta interfaz exhibe una obsesión primitiva ,
x
yy
se supone que representa mediciones reales de longitud. En un contexto de programación donde los tipos específicos de dominios son una opción razonable, en efecto, acercaríamos la verificación de precondición a la fuente de los datos; en otras palabras, dejaríamos la responsabilidad de la integridad de los datos más arriba en la pila de llamadas, donde tenemos un mejor sentido para el contexto.Dicho esto, no veo nada fundamentalmente malo en tener dos estrategias diferentes para administrar un cheque fallido. Mi preferencia sería usar la composición para determinar qué estrategia está en uso; el indicador de característica se usaría en la raíz de la composición, en lugar de en la implementación del método de la biblioteca.
La Junta Nacional de Tráfico y Seguridad es realmente buena; Podría sugerir técnicas de implementación alternativas a las barbas grises, pero no estoy dispuesto a discutir con ellas sobre el diseño de mamparos en el subsistema de informe de errores.
En términos más generales: ¿cuál es el costo para el negocio? Es mucho más barato bloquear un sitio web que un sistema vital.
fuente
Los métodos manejan excepciones o no, no hay necesidad de banderas en lenguajes como C #.
Si algo sale mal en ... el código, entonces la persona que llama tendrá que manejar esa excepción. Si nadie maneja el error, el programa terminará.
En este caso, si sucede algo malo en ... el código, el Método 1 está manejando el problema y el programa debe continuar.
Donde maneja las excepciones depende de usted. Ciertamente puedes ignorarlos atrapando y sin hacer nada. Pero, me aseguraría de que solo esté ignorando ciertos tipos específicos de excepciones que puede esperar que ocurran. Ignorar (
exception ex
) es peligroso porque algunas excepciones que no desea ignorar, como las excepciones del sistema con respecto a la falta de memoria, etc.fuente
Este enfoque rompe la filosofía de "falla rápido, falla duro".
Por qué quieres fallar rápido:
Inconvenientes de no fallar rápido y duro:
Finalmente, el manejo real de errores basado en excepciones tiene el beneficio de que puede dar un mensaje u objeto de error "fuera de banda", puede enganchar fácilmente el registro de errores, alertas, etc. sin escribir una sola línea adicional en su código de dominio .
Por lo tanto, no solo hay razones técnicas simples, sino también razones de "sistema" que hacen que sea muy útil fallar rápidamente.
Al final del día, no fallar duro y rápido en nuestros tiempos actuales, donde el manejo de excepciones es liviano y muy estable es solo medio criminal. Entiendo perfectamente de dónde viene el pensamiento de que es bueno suprimir las excepciones, pero ya no es aplicable.
Especialmente en su caso particular, donde incluso da la opción de lanzar excepciones o no: esto significa que la persona que llama tiene que decidir de todos modos . Por lo tanto, no hay inconveniente en absoluto para que la persona que llama detecte la excepción y la maneje adecuadamente.
Un punto que aparece en un comentario:
Fallar rápido y duro no significa que toda la aplicación se bloquee. Significa que en el punto donde ocurre el error, está fallando localmente. En el ejemplo del OP, el método de bajo nivel que calcula alguna área no debe reemplazar silenciosamente un error por un valor incorrecto. Debería fallar claramente.
Obviamente, alguien que llama en la cadena tiene que detectar ese error / excepción y manejarlo adecuadamente. Si este método se usó en un avión, esto probablemente debería provocar que se encienda un LED de error, o al menos para mostrar "área de cálculo de error" en lugar de un área incorrecta.
fuente