¿TDD hace redundante la programación defensiva?

102

Hoy tuve una discusión interesante con un colega.

Soy un programador defensivo. Creo que la regla " una clase debe garantizar que sus objetos tengan un estado válido cuando se interactúa desde fuera de la clase " siempre debe cumplirse. El motivo de esta regla es que la clase no sabe quiénes son sus usuarios y que, como era de esperar, debería fallar cuando interactúa de forma ilegal. En mi opinión, esa regla se aplica a todas las clases.

En la situación específica en la que tuve una discusión hoy, escribí un código que valida que los argumentos de mi constructor son correctos (por ejemplo, un parámetro entero debe ser > 0) y si no se cumple la condición previa, se obtiene una excepción. arrojado Mi colega, por otro lado, cree que dicha verificación es redundante, porque las pruebas unitarias deben detectar cualquier uso incorrecto de la clase. Además, cree que las validaciones de la programación defensiva también deben probarse por unidades, por lo que la programación defensiva agrega mucho trabajo y, por lo tanto, no es óptima para TDD.

¿Es cierto que TDD puede reemplazar la programación defensiva? ¿La validación de parámetros (y no me refiero a la entrada del usuario) es innecesaria como consecuencia? ¿O las dos técnicas se complementan?

    
pregunta user2180613 23.09.2016 - 22:21

14 respuestas

194

Eso es ridículo. TDD obliga al código a pasar las pruebas y obliga a todos los códigos a tener algunas pruebas a su alrededor. No evita que sus consumidores llamen incorrectamente el código, ni tampoco evita mágicamente que los programadores pierdan casos de prueba.

Ninguna metodología puede obligar a los usuarios a usar el código correctamente.

Hay hay un pequeño argumento que se debe a que si hicieras TDD a la perfección, habrías capturado tu > Verifique en un caso de prueba, antes de implementarlo, y solucione esto, probablemente agregando el cheque. Pero si hizo TDD, su requisito (> 0 en el constructor) primero aparecerá como un testcase que falla. Por lo tanto, le damos la prueba después de agregar su cheque.

También es razonable probar algunas de las condiciones defensivas (agregaste lógica, ¿por qué no querrías probar algo tan fácilmente comprobable?). No estoy seguro de por qué pareces estar en desacuerdo con esto.

  

¿O las dos técnicas se complementan entre sí?

TDD desarrollará las pruebas. La implementación de la validación de parámetros los hará pasar.

    
respondido por el enderland 23.09.2016 - 22:37
32

La programación defensiva y las pruebas unitarias son dos formas diferentes de detectar errores y cada una tiene diferentes puntos fuertes. El uso de una sola forma de detectar errores hace que sus mecanismos de detección de errores sean frágiles. El uso de ambos detectará errores que podrían haberse perdido uno u otro, incluso en el código que no es una API pública; por ejemplo, alguien puede haberse olvidado de agregar una prueba de unidad para los datos no válidos pasados a la API pública. Comprobar todo en los lugares apropiados significa más posibilidades de detectar el error.

En seguridad de la información, esto se llama Defensa en profundidad. Tener múltiples capas de defensa asegura que si una falla, aún hay otras para atraparla.

Su colega tiene razón en una cosa: usted debería probar sus validaciones, pero esto no es un "trabajo innecesario". Es lo mismo que probar cualquier otro código, quiere asegurarse de que todos los usos, incluso los no válidos, tengan un resultado esperado.

    
respondido por el Kevin Fee 23.09.2016 - 22:39
30

TDD no reemplaza absolutamente la programación defensiva. En su lugar, puede usar TDD para asegurarse de que todas las defensas estén en su lugar y funcionen como se espera.

En TDD, se supone que no debe escribir código sin escribir primero una prueba; siga religiosamente el ciclo rojo-verde-refactor. Eso significa que si desea agregar validación, primero escriba una prueba que requiera esta validación. Llame al método en cuestión con números negativos y con cero, y espere que arroje una excepción.

Además, no olvides el paso "refactor". Si bien TDD está controlado por , esto no significa que solo se prueba . Aún debe aplicar un diseño adecuado y escribir código sensible. Escribir código defensivo es un código sensible, ya que hace que las expectativas sean más explícitas y su código en general sea más sólido: detectar errores posibles hace que sean más fáciles de depurar.

¿Pero no se supone que usemos pruebas para localizar errores? Las afirmaciones y pruebas son complementarias. Una buena estrategia de prueba mezclará varios enfoques para asegurarse de que el software sea robusto. Solo las pruebas unitarias o solo las pruebas de integración o solo las afirmaciones en el código son todas insatisfactorias, necesita una buena combinación para alcanzar un grado suficiente de confianza en su software con un esfuerzo aceptable.

Luego hay un malentendido conceptual muy grande de su compañero de trabajo: las pruebas unitarias nunca pueden probar usos de su clase, solo que la clase en sí funciona como se espera en aislamiento. Usaría pruebas de integración para verificar que la interacción entre varios componentes funciona, pero la explosión combinatoria de posibles casos de prueba hace que sea imposible probar todo. Por lo tanto, las pruebas de integración deben limitarse a un par de casos importantes. Las pruebas más detalladas que también cubren casos extremos y casos de error son más adecuadas para pruebas unitarias.

    
respondido por el amon 23.09.2016 - 22:40
16

Las pruebas están ahí para apoyar y garantizar la programación defensiva

La programación defensiva protege la integridad del sistema en tiempo de ejecución.

Las pruebas son herramientas de diagnóstico (en su mayoría estáticas). En el tiempo de ejecución, sus pruebas no están a la vista. Son como los andamios que se usan para levantar un alto muro de ladrillos o una cúpula de roca. No deja partes importantes fuera de la estructura porque tiene un andamio que la sostiene durante la construcción. Usted tiene un andamio que lo sostiene durante la construcción para facilitar colocar todas las piezas importantes.

EDITAR: una analogía

¿Qué pasa con una analogía a los comentarios en código?

Los comentarios tienen su propósito, pero pueden ser redundantes o incluso dañinos. Por ejemplo, si pone conocimiento intrínseco sobre el código en comentarios , luego cambia el código, los comentarios se vuelven irrelevantes en el mejor de los casos y dañinos en el peor.

Entonces, digamos que usted pone mucho conocimiento intrínseco de su base de código en las pruebas, como que MethodA no puede tomar un nulo y el argumento de MethodB debe ser > 0 . Entonces el código cambia. Nulo está bien para A ahora, y B puede tomar valores tan pequeños como -10. Las pruebas existentes ahora son funcionalmente incorrectas, pero continuarán pasando.

Sí, debería actualizar las pruebas al mismo tiempo que actualiza el código. También debe actualizar (o eliminar) los comentarios al mismo tiempo que actualiza el código. Pero todos sabemos que estas cosas no siempre suceden, y que se cometen errores.

Las pruebas verifican el comportamiento del sistema. Ese comportamiento real es intrínseco al sistema en sí mismo, no intrínseco a las pruebas.

¿Qué podría salir mal?

El objetivo con respecto a las pruebas es idear todo lo que podría salir mal, escribir una prueba que verifique el comportamiento correcto, luego diseñar el código de tiempo de ejecución para que pase todas las pruebas.

Lo que significa que la programación defensiva es el punto .

TDD acciona la programación defensiva, si las pruebas son exhaustivas.

Más pruebas, conduciendo más programación defensiva

Cuando los errores se encuentran inevitablemente, se escriben más pruebas para modelar las condiciones que manifiestan el error. Luego el código se corrige, con el código para hacer que se pasen las pruebas de esas , y las nuevas pruebas permanezcan en la suite de pruebas.

Un buen conjunto de pruebas pasará argumentos buenos y malos a una función / método y esperará resultados consistentes. Esto, a su vez, significa que el componente probado utilizará controles de condición previa (programación defensiva) para confirmar los argumentos que se le pasaron.

Hablando genéricamente ...

Por ejemplo, si un argumento nulo para un procedimiento en particular no es válido, entonces al menos una prueba pasará un nulo y esperará una excepción / error de "argumento nulo no válido" de algún tipo.

Por lo menos otra prueba pasará un argumento válido , por supuesto, o pasará a través de una gran matriz y pasará innumerables argumentos válidos, y confirmará que el estado resultante es adecuado.

Si un test no pasa ese argumento nulo y recibe una bofetada con la excepción esperada (y esa excepción fue lanzada porque el código verificó el estado pasado a la defensiva), entonces el nulo puede terminar asignados a una propiedad de una clase o enterrados en una colección de algún tipo donde no debería estar.

Esto podría causar un comportamiento inesperado en una parte completamente diferente del sistema a la que se pasa la instancia de la clase, en algún lugar geográfico distante después de que el software se haya enviado . Y ese es el tipo de cosas que estamos tratando de evitar, ¿no?

Incluso podría ser peor. La instancia de clase con el estado no válido podría ser serializada y almacenada, solo para causar una falla cuando se reconstituye para su uso posterior. Caray, no sé, quizás sea un sistema de control mecánico de algún tipo que no se puede reiniciar después de un apagado porque no puede deserializar su propio estado de configuración persistente. O la instancia de la clase se podría serializar y pasar a un sistema completamente diferente creado por alguna otra entidad, y ese sistema podría fallar.

Especialmente si los programadores de ese otro sistema no codificaron a la defensiva.

    
respondido por el Craig 24.09.2016 - 08:54
9

En lugar de TDD, hablemos de "pruebas de software" en general, y en lugar de "programación defensiva" en general, hablemos de mi forma favorita de hacer programación defensiva, que es mediante el uso de aserciones.

Entonces, ya que hacemos pruebas de software, deberíamos dejar de colocar declaraciones de afirmación en el código de producción, ¿verdad? Déjame contar las formas en que esto está mal:

  1. Las aserciones son opcionales, por lo tanto, si no te gustan, simplemente ejecuta tu sistema con las aserciones deshabilitadas.

  2. Las aserciones comprueban lo que las pruebas no pueden (y no deberían). Se supone que las pruebas tienen una vista de caja negra de su sistema, mientras que las aserciones tienen una vista de caja blanca. (Por supuesto, ya que viven en él).

  3. Las afirmaciones son una excelente herramienta de documentación. Ningún comentario fue, ni nunca será, tan inequívoco como una pieza de código que afirma lo mismo. Además, la documentación tiende a quedar desactualizada a medida que el código evoluciona, y no es de ninguna manera exigible para el compilador.

  4. Las aserciones pueden detectar errores en el código de prueba. ¿Alguna vez se ha encontrado con una situación en la que falla una prueba y no sabe quién está equivocado: el código de producción o la prueba?

  5. Las afirmaciones pueden ser más pertinentes que las pruebas. Las pruebas verifican lo que prescriben los requisitos funcionales, pero el código a menudo tiene que hacer ciertas suposiciones que son mucho más técnicas que eso. Las personas que escriben documentos de requisitos funcionales rara vez piensan en la división por cero.

  6. Las aserciones señalan errores que las pruebas solo apuntan en términos generales. Por lo tanto, su prueba establece algunas condiciones previas extensas, invoca un trozo de código largo, recopila los resultados y encuentra que no son como se esperaba. Teniendo en cuenta la suficiente solución de problemas, eventualmente encontrará exactamente dónde las cosas salieron mal, pero las aseveraciones generalmente lo encontrarán primero.

  7. Las aserciones reducen la complejidad del programa. Cada línea de código que escriba aumenta la complejidad del programa. Las aserciones y la palabra clave final ( readonly ) son las únicas dos construcciones que conozco que realmente reducen la complejidad del programa. Eso no tiene precio.

  8. Las aserciones ayudan al compilador a entender mejor su código. Por favor, intente esto en casa: void foo( Object x ) { assert x != null; if( x == null ) { } } su compilador debería emitir una advertencia que le dice que la condición x == null siempre es falsa. Eso puede ser muy útil.

Lo anterior fue un resumen de una publicación de mi blog, 2014-09-21 "Assertions y pruebas "

    
respondido por el Mike Nakis 23.09.2016 - 23:00
5

Creo que a la mayoría de las respuestas les falta una distinción crítica: depende de cómo se vaya a utilizar su código.

¿El módulo en cuestión será utilizado por otros clientes independientemente de la aplicación que está probando? Si proporciona una biblioteca o API para uso de terceros, no tiene forma de asegurarse de que solo llamen a su código con una entrada válida. Tienes que validar todas las entradas.

Pero si el módulo en cuestión solo lo usa el código que controlas, entonces tu amigo puede tener un punto. Puede usar pruebas unitarias para verificar que el módulo en cuestión solo se llame con una entrada válida. Las verificaciones de condición previa aún se pueden considerar una buena práctica, pero es una compensación: usted desecha el código que verifica la condición que sabe nunca puede surgir, simplemente oculta la intención del código.

No estoy de acuerdo con que las verificaciones de precondición requieran más pruebas unitarias. Si decide que no necesita probar algunas formas de entrada no válida, entonces no debería importar si la función contiene comprobaciones de condición previa o no. Recuerde que las pruebas deben verificar el comportamiento, no los detalles de la implementación.

    
respondido por el JacquesB 24.09.2016 - 11:46
3

Este tipo de argumento me desconcierta, porque cuando comencé a practicar TDD, mi unidad comprueba la forma "el objeto responde < de cierta manera > cuando < entrada no válida >" Incremento 2 o 3 veces. Me pregunto cómo se las arregla su colega para pasar con éxito esos tipos de pruebas unitarias sin que sus funciones hagan la validación.

El caso inverso, que las pruebas de unidad muestran que nunca está produciendo salidas malas que pasarán a los argumentos de otras funciones, es mucho más difícil de probar. Al igual que en el primer caso, depende en gran medida de la cobertura completa de los casos perimetrales, pero tiene el requisito adicional de que todas las entradas de sus funciones deben provenir de las salidas de otras funciones cuyas salidas ha probado la unidad y no de, por ejemplo, la entrada del usuario o Módulos de terceros.

En otras palabras, lo que hace TDD no impide que necesite el código de validación, sino que lo ayuda a evitar que lo olvide .

    
respondido por el Karl Bielefeldt 23.09.2016 - 23:54
2

Creo que interpreto las observaciones de su colega de manera diferente a la mayoría de las demás respuestas.

Me parece que el argumento es:

  • Todo nuestro código está probado en la unidad.
  • Todo el código que usa su componente es nuestro código, o si no lo hace, otra persona lo prueba (no se establece explícitamente, pero es lo que entiendo de "las pruebas unitarias deben detectar cualquier uso incorrecto de la clase").
  • Por lo tanto, para cada persona que llama a su función, hay una prueba de unidad en algún lugar que simula su componente, y la prueba falla si la persona que llama pasa un valor no válido a esa simulación.
  • Por lo tanto, no importa lo que haga su función cuando se pasa un valor no válido, porque nuestras pruebas dicen que no puede suceder.

Para mí, este argumento tiene cierta lógica, pero confía demasiado en las pruebas unitarias para cubrir todas las situaciones posibles. El hecho simple es que el 100% de cobertura de línea / rama / ruta no ejerce necesariamente cada valor que la persona que llama puede pasar, mientras que el 100% de cobertura de todos los estados posibles de la persona que llama (es decir, , todos los valores posibles de sus entradas y variables) no son computables.

Por lo tanto, tiendo a preferir realizar pruebas unitarias de las personas que llaman para garantizar que (en lo que respecta a las pruebas) nunca pasen valores incorrectos, y adicionalmente para exigir que su componente falle en algunos forma reconocible cuando se pasa un valor incorrecto (al menos en la medida en que es posible reconocer valores incorrectos en el idioma que elija). Esto ayudará a la depuración cuando ocurran problemas en las pruebas de integración, y también ayudará a cualquier usuario de su clase que sea menos riguroso en aislar su unidad de código de esa dependencia.

Tenga en cuenta, sin embargo, que si documenta y prueba el comportamiento de su función cuando se pasa un valor < = 0, entonces los valores negativos ya no serán válidos (al menos, no más inválido que cualquier argumento en absoluto para throw , ya que eso también está documentado para lanzar una excepción!). Las personas que llaman tienen derecho a confiar en ese comportamiento defensivo. Si el idioma lo permite, puede ser que en cualquier caso este sea el mejor escenario: la función no tiene "entradas inválidas", pero las personas que llaman que esperan no provocar la función para lanzar una excepción deben ser la unidad- probado lo suficiente para garantizar que no pasen ningún valor que cause eso.

A pesar de pensar que su colega está algo menos equivocado que la mayoría de las respuestas, llego a la misma conclusión, que es que las dos técnicas se complementan entre sí. Programe a la defensiva, documente sus controles defensivos y pruébelos. El trabajo solo es "innecesario" si los usuarios de su código no pueden beneficiarse de mensajes de error útiles cuando cometen errores. En teoría, si prueban a fondo todo su código antes de integrarlo con el suyo, y nunca hay errores en sus pruebas, nunca verán los mensajes de error. En la práctica, incluso si están realizando TDD y la inyección de dependencia total, aún podrían explorar durante el desarrollo o podría haber un lapso en sus pruebas. ¡El resultado es que llaman a tu código antes de que su código sea perfecto!

    
respondido por el Steve Jessop 24.09.2016 - 00:23
1

Las interfaces públicas pueden y serán mal utilizadas

La reclamación de su compañero de trabajo "las pruebas unitarias deben detectar cualquier uso incorrecto de la clase" es estrictamente falsa para cualquier interfaz que no sea privada. Si se puede llamar a una función pública con argumentos enteros, entonces se puede y se llamará con cualquier argumentos enteros, y el código debe comportarse de manera apropiada. Si una firma de función pública acepta, por ejemplo, Java doble, luego nulo, NaN, MAX_VALUE, -Inf son todos valores posibles. Sus pruebas unitarias no pueden detectar usos incorrectos de la clase porque esas pruebas no pueden probar el código que usará esta clase, ya que el código aún no está escrito, es posible que no lo haya hecho usted y que definitivamente esté fuera del alcance de tus pruebas unitarias

Por otra parte, este enfoque puede ser válido para las propiedades privadas (con suerte mucho más numerosas), si una clase puede garantizar que algún hecho siempre es cierto (por ejemplo, la propiedad X nunca puede ser nula , la posición del entero no excede la longitud máxima, cuando se llama a la función A, todas las estructuras de datos de requisitos previos están bien formadas), entonces puede ser apropiado evitar verificar esto una y otra vez por razones de rendimiento, y confiar en las pruebas unitarias en su lugar.

    
respondido por el Peteris 24.09.2016 - 10:19
1

La defensa contra el uso indebido es una característica , desarrollada debido a un requisito para ello. (No todas las interfaces requieren controles rigurosos contra el uso indebido; por ejemplo, los internos son muy limitados).

La función requiere pruebas: ¿realmente funciona la defensa contra el uso indebido? El objetivo de probar esta función es tratar de demostrar que no es así: crear un mal uso del módulo que no se ha detectado en sus comprobaciones.

Si las verificaciones específicas son una característica requerida, de hecho no tiene sentido afirmar que la existencia de algunas pruebas las hace innecesarias. Si es una característica de alguna función que (digamos) lanza una excepción cuando el parámetro tres es negativo, entonces eso no es negociable; hará eso.

Sin embargo, sospecho que su colega realmente tiene sentido desde el punto de vista de una situación en la que no hay un requisito para controles específicos en las entradas, con respuestas específicas a entradas incorrectas: una situación en el que solo hay un requisito general comprendido de robustez.

Las verificaciones al ingresar a alguna función de nivel superior están ahí, en parte, para proteger a algunos códigos internos débiles o mal probados de combinaciones inesperadas de parámetros (por ejemplo, si el código está bien probado, los chequeos no son necesarios: el código puede simplemente "capear" los parámetros incorrectos).

Hay una verdad en la idea del colega, y lo que él probablemente quiere decir es esto: si construimos una función a partir de piezas muy robustas de nivel inferior que están codificadas a la defensiva y probadas individualmente contra todo mal uso, entonces es posible para el nivel más alto La función es robusta sin tener sus propias autocomprobaciones extensas.

Si se viola su contrato, entonces se traducirá en un uso incorrecto de las funciones de nivel inferior, tal vez lanzando excepciones o lo que sea.

El único problema con eso es que las excepciones de nivel inferior no son específicas de la interfaz de nivel superior. Si eso es un problema depende de cuáles son los requisitos. Si el requisito es simplemente "la función será robusta contra el uso indebido y lanzará algún tipo de excepción en lugar de fallar, o continuará calculando con datos de basura", de hecho podría estar cubierta por toda la robustez de las piezas de nivel inferior en las que se encuentra. construido.

Si la función tiene un requisito para informes de errores muy específicos y detallados relacionados con sus parámetros, las verificaciones de nivel inferior no satisfacen esos requisitos por completo. Solo se aseguran de que la función explote de alguna manera (no continúa con una mala combinación de parámetros, lo que produce un resultado de basura). Si el código del cliente se escribe para detectar específicamente ciertos errores y manejarlos, es posible que no funcione correctamente. El propio código de cliente podría estar obteniendo, como entrada, los datos en los que se basan los parámetros, y podría estar esperando que la función los compruebe y que traduzca los valores erróneos a los errores específicos documentados (para que pueda manejarlos). errores correctamente) en lugar de otros errores que no se manejan y tal vez detengan la imagen del software.

TL; DR: tu colega probablemente no es un idiota; simplemente están hablando uno al lado del otro con diferentes perspectivas en torno a lo mismo, porque los requisitos no están totalmente definidos y cada uno de ustedes tiene una idea diferente de lo que son los "requisitos no escritos". Piensa que cuando no hay requisitos específicos en la verificación de parámetros, debe codificar la verificación detallada de todos modos; el colega piensa, simplemente deje que el robusto código de nivel inferior explote cuando los parámetros estén equivocados. Es un tanto improductivo discutir sobre requisitos no escritos a través del código: reconozca que no está de acuerdo sobre los requisitos en lugar del código. Su forma de codificación refleja lo que cree que son los requisitos; El camino del colega representa su visión de los requisitos. Si lo ves de esa manera, está claro que lo que está bien o mal no está en el código en sí; El código es solo un proxy para su opinión de lo que debería ser la especificación.

    
respondido por el Kaz 27.09.2016 - 01:23
1

Las pruebas definen el contrato de tu clase.

Como corolario, la ausencia de una prueba define un contrato que incluye comportamiento no definido . Por lo tanto, cuando pasa null a Foo::Frobnicate(Widget widget) , y se producen innumerables estragos en el tiempo de ejecución, todavía está dentro del contrato de su clase.

Más tarde, usted decide: "no queremos la posibilidad de un comportamiento indefinido", que es una opción sensata. Eso significa que tienes que tener un comportamiento esperado para pasar null a Foo::Frobnicate(Widget widget) .

Y usted documenta esa decisión al incluir un

[Test]
void Foo_FrobnicatesANullWidget_ThrowsInvalidArgument() 
{
    Given(Foo foo);
    When(foo.Frobnicate(null));
    Then(Expect_Exception(InvalidArgument));
}
    
respondido por el Caleth 24.05.2018 - 17:44
1

Un buen conjunto de pruebas ejercerá la interfaz externa de su clase y garantizará que dichos usos incorrectos generen la respuesta correcta (una excepción, o lo que sea que defina como "correcto"). De hecho, el primer caso de prueba que escribo para una clase es llamar a su constructor con argumentos fuera de rango.

El tipo de programación defensiva que tiende a eliminarse con un enfoque totalmente probado por unidades es la validación innecesaria de invariantes internos que no pueden ser violados por un código externo.

Una idea útil que a veces utilizo es proporcionar un método que pruebe las invariantes del objeto; su método de desmontaje puede llamarlo para validar que sus acciones externas en el objeto nunca rompen las invariantes.

    
respondido por el Toby Speight 26.09.2016 - 13:12
0

Las pruebas de TDD detectarán errores durante el desarrollo del código .

La verificación de límites que describe como parte de la programación defensiva detectará errores durante el uso del código .

Si los dos dominios son iguales, es decir, el código que está escribiendo solo se usa internamente en este proyecto específico, entonces puede ser cierto que TDD excluirá la necesidad de los límites de programación defensivos que usted describe, pero solo si esos tipos de verificación de límites se realizan específicamente en las pruebas TDD .

Como ejemplo específico, suponga que se desarrolló una biblioteca de código financiero utilizando TDD. Una de las pruebas podría afirmar que un valor particular nunca puede ser negativo. Eso garantiza que los desarrolladores de la biblioteca no hagan un mal uso accidental de las clases al implementar las características.

Pero después de que se publica la biblioteca y la estoy usando en mi propio programa, esas pruebas TDD no me impiden asignar un valor negativo (suponiendo que esté expuesto). La verificación de límites lo haría.

Mi punto es que, si bien una afirmación de TDD podría abordar el problema del valor negativo si el código solo se usa internamente como parte del desarrollo de una aplicación más grande (bajo TDD), si va a ser una biblioteca utilizada por otros programadores sin el marco y las pruebas de TDD , la verificación de límites es importante.

    
respondido por el Blackhawk 24.09.2016 - 01:21
0

TDD y la programación defensiva van de la mano. Usar ambos no es redundante, sino complementario. Cuando tiene una función, desea asegurarse de que la función funciona como se describe y escribir pruebas para ella; Si no cubre lo que sucede cuando en el caso de una entrada incorrecta, un rendimiento incorrecto, un estado incorrecto, etc., no está escribiendo sus pruebas con la suficiente firmeza y su código será frágil incluso si todas sus pruebas están pasando.

Como ingeniero integrado, me gusta usar el ejemplo de escribir una función para simplemente agregar dos bytes y devolver el resultado de esta manera:

uint8_t AddTwoBytes(uint8_t a, uint8_t b, uint8_t *sum); 

Ahora, si simplemente hicieras *(sum) = a + b , funcionaría, pero solo con algunas entradas. a = 1 y b = 2 harían sum = 3 ; sin embargo, debido a que el tamaño de la suma es un byte, a = 100 y b = 200 harían sum = 44 debido al desbordamiento. En C, devolvería un error en este caso para indicar que la función falló; lanzar una excepción es lo mismo en su código. No considerar las fallas o probar cómo manejarlas no funcionará a largo plazo, ya que si se producen esas condiciones, no se manejarán y podrían causar muchos problemas.

    
respondido por el Dom 24.09.2016 - 04:07

Lea otras preguntas en las etiquetas