remarks generate example c# defensive-programming

generate - params comments c#



¿Es este código de programación defensiva, o mala práctica? (5)

Después de algunos años de pensar en este problema, uso el siguiente estilo de programación "defensivo": el 95% de mis métodos devuelven la cadena como un resultado exitoso / fallido.

Devuelvo string.Empty para el éxito, y cadena de texto informativo si falla. Mientras devuelvo el texto de error, lo escribo para iniciar sesión al mismo tiempo.

public string Divide(int numA, int numB, out double division) { division = 0; if (numB == 0) return Log.Write(Level.Error, "Divide - numB-[{0}] not valid.", numB); division = numA / numB; return string.Empty; }

Luego lo uso:

private string Process(int numA, int numB) { double division = 0; string result = string.Empty; if (!string.IsNullOrEmpty(result = Divide(numA, numB, out divide)) return result; return SendResult(division); }

Cuando tiene un sistema de monitoreo de registro, permite que el sistema se muestre, pero se lo notifica a la administración.

Tengo este debate con mi colega sobre este código:

var y = null; if (x.parent != null) y = x.parent.somefield;

Mi punto de vista es que en el lugar donde se encuentra el código, x.parent no debe ser POSIBLEMENTE nulo. Y cuando es nulo, tenemos un problema grave y quiero saberlo. Por lo tanto, la comprobación nula no debería estar allí y permitir que se produzca la excepción de flujo descendente.

Mi colega dice que esto es programación defensiva. Y la verificación nula se asegura de que el código no rompa la aplicación.

Mi pregunta es, ¿es esta programación defensiva? ¿O una mala práctica?

Nota: el punto no es quién tiene razón. Estoy tratando de aprender de este ejemplo.


En resumen, yo diría que esto NO es una programación defensiva. Estoy de acuerdo con quienes piensan que este código oculta el error del sistema en lugar de exponerlo y corregirlo. Este código viola el principio de "fallar rápido".

Esto es cierto si y solo si x.parent es una propiedad obligatoria no nula (lo que parece ser obvio en el contexto). Sin embargo, si x.parent es una propiedad opcional (es decir, puede tener un valor nulo), entonces este código Puede estar bien dependiendo de su lógica empresarial expresada.

Siempre estoy considerando el uso de valores vacíos (0, "", objetos vacíos) en lugar de valores nulos que requieren toneladas de declaraciones if irrelevantes.


Interesante pregunta. Desde mi punto de vista, si incluir o no la verificación es una cuestión de qué tan bien se validan los datos, de dónde provienen y qué puede suceder cuando falla la verificación.

"x.parent no debe ser POSIBLEMENTE nulo" es una suposición seria. Necesitas estar muy seguro de ello para jugar seguro, por supuesto que existe el clásico "nunca sucederá" ........ hasta que suceda, por eso creo que es interesante revisar las posibilidades.

Veo dos cosas diferentes para pensar.

¿De dónde vienen los datos?

  • Si proviene de otro método en la misma clase, o de alguna clase relacionada, ya que usted tiene más o menos control al respecto, es lógico relajar sus defensas, ya que puede suponer razonablemente que es muy poco probable que tenga datos erróneos para comenzar. con, o si sucede, es razonablemente fácil detectar el error temprano durante la depuración / prueba e incluso colocar algunas pruebas unitarias para él.

  • El caso contrario sería si se trata de datos ingresados ​​por el usuario, o leídos de un archivo, o de una URL, algo externo en general. Ya que no puede controlar con qué está tratando el programa: por todos los medios, valídelo tan a fondo como pueda antes de usarlo de cualquier manera, ya que está expuesto a información falsa / faltante / incompleta / incorrecta / maliciosa que puede causar problemas. el camino.

  • Un caso intermedio puede ser cuando la entrada proviene de otra capa / nivel dentro del mismo sistema. Es más difícil decidir si realizar una validación completa o darla por sentada, ya que es otra pieza de software interno, pero puede ser reemplazada o modificada de forma independiente más adelante. Mi preferencia va hacia la validación de nuevo al cruzar fronteras.

¿Qué hacer con la validación?

  • Usar un if (como en su muestra) para simplemente saltarse alguna asignación o uso puede estar bien para algunos casos. Por ejemplo, si el usuario ingresa algunos datos y esto solo muestra información sobre herramientas u otra información menor, es posible que se salte. Pero si el fragmento de código hace algo importante, que a su vez cumple con alguna condición obligatoria o ejecuta algún otro proceso, no es el enfoque correcto, ya que causará problemas para la próxima ejecución del código. La cosa es que, cuando omite algún código, debe ser seguro hacerlo, sin efectos secundarios o consecuencias no deseadas, de lo contrario, estaría ocultando algún error, y eso es bastante difícil de depurar en etapas posteriores de desarrollo.

  • Abortar el proceso actual con gracia es una buena opción para las validaciones tempranas, cuando el fallo es totalmente esperado y usted sabe exactamente cómo responder a él. Un ejemplo podría ser la falta de un campo obligatorio, el proceso se interrumpe y se muestra un mensaje al usuario que solicita la información que falta. No está bien simplemente ignorar el error, pero tampoco es lo suficientemente serio como para lanzar una excepción que interrumpa el flujo normal del programa. Por supuesto, aún puede usar una excepción, dependiendo de su arquitectura, pero en cualquier caso, cójala y recupérela con gracia.

  • Lanzar una excepción es siempre una opción cuando realmente sucede lo "imposible". Es en esos casos en los que posiblemente no puede proporcionar una respuesta razonable para continuar con alguna variación o cancelar solo el proceso actual, puede deberse a un error o mala entrada en algún lugar, pero lo importante es que desea saberlo y Todos los detalles al respecto, por lo que la mejor manera posible es hacer que explote lo más alto posible, para que la excepción brille y alcance un controlador global que interrumpe todo, guarde en un archivo de registro / DB / lo que sea, envíe un Informe de fallos a usted y encuentra una manera de reanudar la ejecución, si eso es posible o seguro. Al menos si tu aplicación falla, hazlo de la manera más elegante y deja huellas para un análisis más detallado.

Como siempre, depende de la situación. Pero usar una if para evitar codificar un controlador de excepciones es una mala práctica. Siempre debe estar allí, y luego algún código puede depender de él, ya sea apropiado o no, si no es crítico fallar.


No lo llamaría programación defensiva en absoluto, lo llamaría "la la la la no puedo escucharte" programación :) Porque el código parece estar ignorando efectivamente una posible condición de error.

Obviamente, no tenemos idea de lo que sigue en su código, pero como no incluyó una cláusula else , solo puedo asumir que su código continúa, incluso en el caso de que x.parent sea realmente null .

Tenga en cuenta que "no debería ser nulo" y "está absolutamente garantizado que nunca será nulo" no es necesariamente lo mismo; por lo que, en ese caso, podría dar lugar a una excepción en la línea cuando intente quitar la referencia a y .

La pregunta es entonces: qué es más aceptable en el contexto del problema que estás tratando de resolver (el "dominio") y eso depende de lo que quieras hacer con y más adelante.

  • Si el hecho de que sea null después de este código no es un problema (supongamos que luego hace un chequeo defensivo para y!=null ), entonces está bien, aunque personalmente no me gusta ese estilo, termina por revisar defensivamente cada uno de los detalles. referencia porque nunca puede estar completamente seguro de si es una referencia nula lejos de estrellarse ...

  • Si y no puede ser null después del código porque causará una excepción o datos faltantes más adelante, entonces es simplemente una mala idea continuar cuando sepa que un invariante no es correcto.


Parece que su colega no entiende la "programación defensiva" y / o las excepciones.

Programación Defensiva

La programación defensiva se trata de proteger contra ciertos tipos de errores.

En este caso, x.parent == null es un error porque su método necesita usar x.parent.SomeField . Y si el parent fuera nulo, entonces el valor de SomeField sería claramente inválido. Cualquier cálculo o tarea realizada con un valor no válido daría resultados erróneos e impredecibles.

Así que necesitas protegerte contra esta posibilidad. Una muy buena forma de proteger es lanzando una NullPointerException si descubre que x.parent == null . La excepción le impedirá obtener un valor no válido de SomeField . Le impedirá realizar cualquier cálculo o realizar cualquier tarea con el valor no válido. Y abortará todo el trabajo actual hasta que el error se resuelva adecuadamente.

Tenga en cuenta que la excepción no es el error; un valor no válido en el parent que es el error real . La excepción es de hecho un mecanismo de protección. Las excepciones son una técnica de programación defensiva , no son algo que deba evitarse.

Como C # ya lanza una excepción, en realidad no tienes que hacer nada . De hecho, resulta que el esfuerzo de su colega "en nombre de la programación defensiva", en realidad está deshaciendo la programación defensiva incorporada proporcionada por el lenguaje.

Excepciones

He notado que muchos programadores son excesivamente paranoicos acerca de las excepciones. Una excepción en sí misma no es un error, simplemente informa el error.

Su colega dice: "la comprobación nula se asegura de que el código no rompa la aplicación". Esto sugiere que él cree que las excepciones rompen las aplicaciones. Generalmente no "rompen" una aplicación completa.

Las excepciones podrían romper una aplicación si un manejo de excepciones deficiente pone a la aplicación en un estado incoherente. ( Pero esto es aún más probable si los errores están ocultos ). También pueden romper una aplicación si una excepción "escapa" a un hilo. ( Escapar del hilo principal obviamente implica que su programa ha finalizado de manera poco honesta. Pero incluso escapar de un hilo secundario es lo suficientemente malo como para que la mejor opción para un sistema operativo sea la aplicación GPF ).

Sin embargo, las excepciones romperán (abortarán) la operación actual. Y esto es algo que deben hacer. Porque si codificas un método llamado DoSomething que llama a DoStep1 ; un error en DoStep1 significa que DoSomething no puede hacer su trabajo correctamente . No tiene sentido seguir llamando a DoStep2 .

Sin embargo, si en algún momento puede resolver por completo un error particular, entonces por todos los medios: hágalo. Pero tenga en cuenta el énfasis en "resolver plenamente"; Esto no significa simplemente ocultar el error. Además, solo el registro del error suele ser insuficiente para resolverlo . Significa llegar al punto donde: si otro método llama a su método y lo usa correctamente, el ''error resuelto'' no afectará negativamente la capacidad de la persona que llama para hacer su trabajo correctamente . ( No importa cuál sea la persona que llama ).

Quizás el mejor ejemplo de resolución completa de un error sea el ciclo de procesamiento principal de una aplicación. Su trabajo es: esperar un mensaje en la cola, sacar el siguiente mensaje de la cola y llamar al código apropiado para procesar el mensaje. Si se generó una excepción y no se resolvió antes de volver al ciclo de mensajes principal, debe resolverse. De lo contrario, la excepción escapará del hilo principal y la aplicación finalizará.

Muchos idiomas proporcionan un controlador de excepciones predeterminado (con un mecanismo para que el programador lo invalide / intercepte) en su marco estándar. El controlador predeterminado normalmente solo mostrará un mensaje de error al usuario y luego tragará la excepción.

¿Por qué? Debido a que siempre que no haya implementado un manejo deficiente de las excepciones, su programa estará en un estado consistente. El mensaje actual fue abortado, y el siguiente mensaje puede ser procesado como si nada estuviera mal. Por supuesto, puede anular este controlador para:

  • Escribir en el archivo de registro.
  • Enviar información de la pila de llamadas para la solución de problemas.
  • Ignorar ciertas clases de excepción. (Por ejemplo, Abort podría implicar que ni siquiera necesita decirle al usuario, tal vez porque previamente mostró un mensaje).
  • etc.

Manejo de excepciones

Si puede resolver el error sin que se haya planteado una excepción, está más limpio hacerlo. Sin embargo, a veces el error no se puede resolver donde aparece por primera vez o no se puede detectar por adelantado. En estos casos, se debe generar / lanzar una excepción para informar el error, y usted lo resuelve implementando un controlador de excepciones (bloque catch en C #).

NOTA: Los servidores de controladores de excepciones tienen dos propósitos distintos: primero le proporcionan un lugar para realizar la limpieza (o código de reversión ) específicamente porque hubo un error / excepción. En segundo lugar, proporcionan un lugar para resolver un error y para tragar la excepción. NB : Es muy importante en el primer caso que la excepción se vuelva a generar / lanzar porque no se ha resuelto .

En un comentario sobre lanzar una excepción y manejarla, dijiste: " Quería hacer eso, pero me dijeron que creaba un código de manejo de excepciones en todas partes ".

Este es otro concepto erróneo. Según la nota al margen anterior, solo necesita un manejo de excepciones donde:

  • Puede resolver un error, en cuyo caso ya está hecho.
  • O donde necesita implementar el código de retroceso.

La preocupación puede deberse a un análisis defectuoso de causa y efecto. No necesita el código de reversión solo porque está lanzando excepciones. Hay muchas otras razones por las que se pueden lanzar excepciones. Se requiere un código de reversión porque el método debe realizarse una limpieza si se produce un error. En otras palabras, el código de manejo de excepciones sería requerido en cualquier caso. Esto sugiere que la mejor defensa contra el manejo excesivo de excepciones es diseñar de tal manera que se reduzca la necesidad de limpieza en caso de errores.

Así que no " no lance excepciones " para evitar el manejo excesivo de excepciones. Estoy de acuerdo en que el manejo excesivo de excepciones es malo (consulte la consideración de diseño anterior). Pero es mucho peor no retroceder cuando deberías, porque ni siquiera sabías que hubo un error.