En Test Driven Development (TDD), comienza con una solución subóptima y luego produce iterativamente mejores soluciones agregando casos de prueba y refactorizando. Se supone que los pasos son pequeños, lo que significa que cada nueva solución estará de alguna manera cerca de la anterior.
Esto se asemeja a los métodos matemáticos de optimización local como el descenso de gradiente o la búsqueda local. Una limitación bien conocida de tales métodos es que no garantizan encontrar el óptimo global, o incluso un óptimo local aceptable. Si su punto de partida está separado de todas las soluciones aceptables por una gran región de soluciones malas, es imposible llegar allí y el método fallará.
Para ser más específico: estoy pensando en un escenario en el que ha implementado una serie de casos de prueba y luego encuentro que el siguiente caso de prueba requeriría un enfoque completamente diferente. Tendrá que tirar su trabajo anterior y comenzar de nuevo.
Este pensamiento se puede aplicar a todos los métodos ágiles que proceden en pequeños pasos, no solo a TDD. ¿Esta analogía propuesta entre TDD y la optimización local tiene alguna falla grave?
fuente
Respuestas:
Para hacer su comparación más adecuada: para algún tipo de problemas, es muy probable que los algoritmos de optimización iterativa produzcan óptimos locales buenos, para algunas otras situaciones, pueden fallar.
Me imagino una situación en la que esto puede suceder en realidad: cuando eliges la arquitectura incorrecta de una manera que necesitas recrear todas tus pruebas existentes nuevamente desde cero. Digamos que comienza a implementar sus primeros 20 casos de prueba en el lenguaje de programación X en el sistema operativo A. Desafortunadamente, el requisito 21 incluye que todo el programa debe ejecutarse en el sistema operativo B, donde X no está disponible. Por lo tanto, debe tirar la mayor parte de su trabajo y reimplementarlo en el lenguaje Y. (Por supuesto, no tiraría el código por completo, sino que lo transferiría al nuevo lenguaje y sistema).
Esto nos enseña que, incluso cuando se usa TDD, es una buena idea hacer un análisis y diseño general de antemano. Esto, sin embargo, también es cierto para cualquier otro tipo de enfoque, por lo que no veo esto como un problema TDD inherente. Y, para la mayoría de las tareas de programación del mundo real, puede elegir una arquitectura estándar (como el lenguaje de programación X, el sistema operativo Y, el sistema de base de datos Z en el hardware XYZ), y puede estar relativamente seguro de que una metodología iterativa o ágil como TDD no te llevará a un callejón sin salida.
Citando a Robert Harvey: "No se puede desarrollar una arquitectura a partir de pruebas unitarias". O pdr: "TDD no solo me ayuda a llegar al mejor diseño final, sino que me ayuda a lograrlo en menos intentos".
Entonces, en realidad lo que escribiste
podría convertirse en realidad: cuando elige una arquitectura incorrecta, es probable que no llegue a la solución requerida desde allí.
Por otro lado, cuando hace una planificación general de antemano y elige la arquitectura correcta, usar TDD debería ser como comenzar un algoritmo de búsqueda iterativo en un área donde puede esperar alcanzar el "máximo global" (o al menos un máximo lo suficientemente bueno) ) en unos pocos ciclos.
fuente
No creo que TDD tenga un problema de máximos locales. El código que escribes podría, como lo has notado correctamente, pero es por eso que la refactorización (reescribir el código sin cambiar la funcionalidad) está en su lugar. Básicamente, a medida que aumentan las pruebas, puede reescribir porciones significativas de su modelo de objetos si lo necesita, manteniendo el comportamiento sin cambios gracias a las pruebas. Las pruebas indican verdades invariables sobre su sistema que, por lo tanto, deben ser válidas tanto en máximos locales como absolutos.
Si está interesado en problemas relacionados con TDD, puedo mencionar tres diferentes en los que a menudo pienso:
El problema de integridad : ¿cuántas pruebas son necesarias para describir completamente un sistema? ¿Es la "codificación de casos de ejemplo" una manera completa de describir un sistema?
El problema de endurecimiento : cualquiera que sea la interfaz de prueba, debe tener una interfaz inmutable. Las pruebas representan verdades invariantes , recuerda. Desafortunadamente, estas verdades no se conocen en absoluto para la mayoría del código que escribimos, en el mejor de los casos solo para objetos externos.
El problema del daño de la prueba : para que las aserciones sean comprobables, es posible que necesitemos escribir un código subóptimo (menos eficaz, por ejemplo). ¿Cómo escribimos pruebas para que el código sea tan bueno como sea posible?
Editado para abordar un comentario: aquí hay un ejemplo de extracción de un máximo local para una función "doble" mediante refactorización
Prueba 1: cuando la entrada es 0, devuelve cero
Implementación:
Refactorización: no necesaria
Prueba 2: cuando la entrada es 1, devuelve 2
Implementación:
Refactorización: no necesaria
Prueba 3: cuando la entrada es 2, devuelve 4
Implementación:
Refactorización:
fuente
Lo que estás describiendo en términos matemáticos es lo que llamamos pintar en un rincón. Este hecho no es exclusivo de TDD. En la cascada, puede reunir y verter los requisitos durante meses con la esperanza de poder ver el máximo global solo para llegar allí y darse cuenta de que hay una mejor idea solo en la próxima colina.
La diferencia está en un entorno ágil que nunca esperó que fuera perfecto en este momento, por lo que está más que listo para lanzar la vieja idea y pasar a la nueva idea.
Más específicamente para TDD, existe una técnica para evitar que esto le suceda a medida que agrega funciones en TDD. Es la premisa de prioridad de transformación . Donde TDD tiene una forma formal de refactorizar, esta es una forma formal de agregar funciones.
fuente
En su respuesta , @Sklivvz ha argumentado convincentemente que el problema no existe.
Quiero argumentar que no importa: la premisa fundamental (y la razón de ser) de las metodologías iterativas en general y Agile y especialmente TDD en particular, es que no solo el óptimo global, sino los óptimos locales tampoco lo son ' t conocido. En otras palabras: incluso si eso fuera un problema, de todos modos no hay forma de hacerlo de forma iterativa. Asumiendo que aceptas la premisa básica.
fuente
¿Pueden TDD y las prácticas ágiles prometer producir una solución óptima? (¿O incluso una "buena" solución?)
No exactamente. Pero, ese no es su propósito.
Estos métodos simplemente proporcionan un "paso seguro" de un estado a otro, reconociendo que los cambios son lentos, difíciles y riesgosos. Y el objetivo de ambas prácticas es garantizar que la aplicación y el código sean viables y se demuestre que cumplen los requisitos de manera más rápida y regular.
TDD se centra en garantizar que cada "fragmento" de código satisfaga los requisitos. En particular, ayuda a garantizar que el código cumpla con los requisitos preexistentes, en lugar de permitir que los requisitos sean controlados por una codificación deficiente. Pero no promete que la implementación sea "óptima" de ninguna manera.
En cuanto a los procesos ágiles:
Agility no busca una solución óptima ; solo una solución de trabajo , con la intención de optimizar el ROI . Promete una solución de trabajo más temprano que tarde ; no uno "óptimo".
Pero, eso está bien, porque la pregunta está mal.
Los óptimos en el desarrollo de software son objetivos difusos y móviles. Los requisitos generalmente están cambiando y plagados de secretos que solo surgen, para su vergüenza, en una sala de conferencias llena de los jefes de su jefe. Y la "bondad intrínseca" de la arquitectura y la codificación de una solución está clasificada por las opiniones divididas y subjetivas de sus pares y la de su jefe administrativo, ninguno de los cuales podría saber realmente nada sobre un buen software.
Como mínimo, las prácticas TDD y Agile reconocen las dificultades e intentan optimizar para dos cosas que son objetivas y medibles: Trabajar v. No trabajar y Sooner v. Más tarde.
E, incluso si tenemos "trabajar" y "antes" como métricas objetivas, su capacidad para optimizarlas depende principalmente de la habilidad y experiencia de un equipo.
Las cosas que podría interpretar como esfuerzos producen soluciones óptimas incluyen cosas como:
etc.
¡Si cada una de esas cosas realmente produce soluciones óptimas sería otra gran pregunta para hacer!
fuente
Una cosa que nadie ha agregado hasta ahora es que el "Desarrollo TDD" que está describiendo es muy abstracto y poco realista. Puede ser así en una aplicación matemática en la que está optimizando un algoritmo, pero eso no sucede mucho en las aplicaciones comerciales en las que trabajan la mayoría de los codificadores.
En el mundo real, sus pruebas básicamente están ejerciendo y validando Reglas de Negocio:
Por ejemplo: si un cliente es un no fumador de 30 años con una esposa y dos hijos, la categoría premium es "x", etc.
No va a cambiar iterativamente el motor de cálculo premium hasta que sea correcto durante mucho tiempo, y casi con certeza no mientras la aplicación esté activa;).
Lo que realmente ha creado es una red de seguridad, de modo que cuando se agrega un nuevo método de cálculo para una categoría particular de clientes, todas las viejas reglas no se rompen repentinamente y dan la respuesta incorrecta. La red de seguridad es aún más útil si el primer paso de la depuración es crear una prueba (o una serie de pruebas) que reproduzca el error antes de escribir el código para corregir el error. Luego, un año después, si alguien recrea accidentalmente el error original, la prueba de la unidad se rompe antes de que el código sea revisado. Sí, una cosa que permite TDD es que ahora puede hacer una gran refactorización y ordenar las cosas con confianza. pero no debería ser una parte masiva de tu trabajo.
fuente
No creo que se interponga en el camino. La mayoría de los equipos no tienen a nadie que sea capaz de encontrar una solución óptima, incluso si la escribiste en su pizarra. TDD / Agile no se interpondrá en su camino.
Muchos proyectos no requieren soluciones óptimas y aquellos que sí lo hacen, el tiempo necesario, la energía y el enfoque se harán en esta área. Como todo lo demás, tendemos a construir, primero, hacer que funcione. Entonces hazlo rápido. Podría hacer esto con algún tipo de prototipo si el rendimiento es tan importante y luego reconstruir todo con la sabiduría adquirida a través de muchas iteraciones.
Esto podría suceder, pero lo que es más probable que ocurra es el miedo a cambiar partes complejas de la aplicación. No tener ninguna prueba puede crear una mayor sensación de miedo en esta área. Una de las ventajas de TDD y de tener un conjunto de pruebas es que creó este sistema con la idea de que será necesario cambiarlo. Cuando se te ocurre esta solución optimizada monolítica desde el principio, puede ser muy difícil cambiarla.
Además, coloque esto en el contexto de su preocupación por la suboptimización, y no puede evitar perder tiempo optimizando cosas que no debería tener y creando soluciones inflexibles porque estaba muy concentrado en su rendimiento.
fuente
Puede ser engañoso aplicar un concepto matemático como "óptimo local" al diseño de software. El uso de tales términos hace que el desarrollo de software parezca mucho más cuantificable y científico de lo que realmente es. Incluso si existiera "óptimo" para el código, no tenemos forma de medirlo y, por lo tanto, no hay forma de saber si lo hemos alcanzado.
El movimiento ágil fue realmente una reacción contra la creencia de que el desarrollo de software podría planificarse y predecirse con métodos matemáticos. Para bien o para mal, el desarrollo de software se parece más a un oficio que a una ciencia.
fuente
fibonacci
, lo que he visto como ejemplo / tutorial de TDD, es más o menos una mentira. Estoy dispuesto a apostar que nadie "descubrió" Fibonacci o cualquier serie similar al hacer TDD. Todo el mundo comienza por saber fibonacci, que es hacer trampa. Si intenta descubrir esto al hacer TDD, es probable que llegue al callejón sin salida por el que el OP estaba preguntando: nunca podrá generalizar la serie simplemente escribiendo más pruebas y refactorizando; debe aplicar matemática ¡razonamiento!