¿Es esta limitación de Test Driven Development (y Agile en general) prácticamente relevante?

30

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?

Frank Puffer
fuente
¿Te refieres a la sub-técnica TDD llamada triangulación ? Por "solución aceptable", ¿quiere decir una correcta o una mantenible / elegante / legible?
guillaume31
66
Creo que este es un problema real. Como es solo mi opinión, no escribiré una respuesta. Pero sí, dado que TDD se promociona como una práctica de diseño , es una falla que puede conducir a máximos locales o ninguna solución. Yo diría que, en general, TDD NO es adecuado para el diseño algorítmico. Vea la discusión relacionada sobre las limitaciones de TDD: Resolver Sudoku con TDD , en el que Ron Jeffries se burla de sí mismo mientras corre en círculos y "hace TDD", mientras que Peter Norvig proporciona la solución real al conocer realmente el tema,
Andres F.
55
En otras palabras, ofrecería la declaración (con suerte) incontrovertible de que TDD es bueno para minimizar la cantidad de clases que escribe en problemas "conocidos", por lo tanto, produce un código más limpio y simple, pero no es adecuado para problemas algorítmicos o problemas complejos donde En realidad, mirar el panorama general y tener conocimiento específico del dominio es más útil que escribir pruebas parciales y "descubrir" el código que debe escribir.
Andres F.
2
El problema existe, pero no se limita a TDD o incluso Agile. Los requisitos cambiantes que significan que el diseño de software previamente escrito debe cambiar todo el tiempo.
RemcoGerlich
@ guillaume31: no necesariamente triangulación, sino cualquier técnica que use iteraciones a nivel de código fuente. Por solución aceptable me refiero a una que pase todas las pruebas y se pueda mantener razonablemente bien ..
Frank Puffer

Respuestas:

8

Una limitación bien conocida de tales métodos es que no garantizan encontrar el óptimo global, o incluso un óptimo local aceptable.

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.

Estoy pensando en un escenario en el que ha implementado una serie de casos de prueba y luego encuentro que el próximo caso de prueba requeriría un enfoque completamente diferente. Tendrá que tirar su trabajo anterior y comenzar de nuevo.

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

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

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.

Doc Brown
fuente
20

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:

  1. 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?

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

  3. 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:

function double(x) {
  return 0; // simplest possible code that passes tests
}

Refactorización: no necesaria

Prueba 2: cuando la entrada es 1, devuelve 2

Implementación:

function double(x) {
  return x==0?0:2; // local maximum
}

Refactorización: no necesaria

Prueba 3: cuando la entrada es 2, devuelve 4

Implementación:

function double(x) {
  return x==0?0:x==2?4:2; // needs refactoring
}

Refactorización:

function double(x) {
  return x*2; // new maximum
}
Sklivvz
fuente
1
Sin embargo, lo que he experimentado es que mi primer diseño solo funcionó para algunos casos simples y luego me di cuenta de que necesitaba una solución más general. El desarrollo de la solución más general requirió más pruebas, mientras que las pruebas originales para los casos especiales no funcionarán nuevamente. Me pareció aceptable eliminar (temporalmente) esas pruebas mientras desarrollo la solución más general, y las agregué una vez que el tiempo estuvo listo.
5gon12eder
3
No estoy convencido de que la refactorización sea una forma de generalizar el código (fuera del espacio artificial de "patrones de diseño", por supuesto) o escapar de los máximos locales. La refactorización ordena el código, pero no lo ayudará a descubrir una mejor solución.
Andres F.
2
@Sklivvz Entendido, pero no creo que funcione de esa manera fuera de los ejemplos de juguetes como los que publicaste. Además, le ayudó que su función se llamara "doble"; de alguna manera ya sabías la respuesta. TDD definitivamente ayuda cuando más o menos conoces la respuesta pero quieres escribirla "limpiamente". No ayudaría descubrir algoritmos o escribir código realmente complejo. Es por eso que Ron Jeffries no pudo resolver el Sudoku de esta manera; no puede implementar un algoritmo con el que no está familiarizado al TDD 'fuera de la oscuridad.
Andres F.
1
@VaughnCato Ok, ahora estoy en la posición de confiar en ti o de ser escéptico (lo cual sería grosero, así que no hagamos eso). Digamos, en mi experiencia, que no funciona como usted dice. Nunca he visto un algoritmo razonablemente complejo desarrollado a partir de TDD. Quizás mi experiencia es muy limitada :)
Andres F.
2
@Sklivvz "Siempre y cuando puedas escribir las pruebas apropiadas", ese es precisamente el punto: me parece que me estás pidiendo la pregunta. Lo que digo es que a menudo no puedes . Pensar en un algoritmo o un solucionador no se hace más fácil escribiendo primero las pruebas . Debes mirar la imagen completa primero . Por supuesto, es necesario probar escenarios, pero tenga en cuenta que TDD no se trata de escribir escenarios: ¡TDD se trata de probar el diseño ! No puede conducir el diseño de un solucionador de Sudoku (o un nuevo solucionador para un juego diferente) escribiendo primero las pruebas. Como evidencia anecdotidal (que no es suficiente): Jeffries no pudo.
Andres F.
13

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.

naranja confitada
fuente
13

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.

Jörg W Mittag
fuente
8

¿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 opone al desarrollo de software que permite agregar software que no cumple con los requisitos ... Kent Beck, a quien se le atribuye haber desarrollado o 'redescubierto' la técnica, declaró en 2003 que TDD fomenta la simple diseña e inspira confianza. ( Wikipedia )

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:

El software de trabajo es la medida principal del progreso ... Al final de cada iteración, las partes interesadas y el representante del cliente revisan el progreso y reevalúan las prioridades con el fin de optimizar el retorno de la inversión ( Wikipedia )

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!

svidgen
fuente
1
Es cierto, pero no escribí que el objetivo de TDD o cualquier otro método de desarrollo de software es una solución óptima en el sentido de un óptimo global. Mi única preocupación es que las metodologías basadas en pequeñas iteraciones a nivel de código fuente podrían no encontrar ninguna solución aceptable (lo suficientemente buena) en muchos casos
Frank Puffer
@Frank Mi respuesta está destinada a cubrir los óptimos locales y globales. Y la respuesta de cualquier manera es "No, para eso no están diseñadas estas estrategias, están diseñadas para mejorar el ROI y mitigar el riesgo". ... o algo así. Y eso se debe en parte a lo que dice la respuesta de Jörg: los "óptimos" son objetivos móviles. ... Incluso lo llevaría un paso más allá; no solo son objetivos móviles, sino que no son completamente objetivos o medibles.
svidgen
@FrankPuffer Quizás valga la pena un apéndice. Pero, el punto básico es que te estás preguntando si estas dos cosas logran algo que no están en absoluto diseñadas o destinadas a lograr. Además, se pregunta si pueden lograr algo que ni siquiera se puede medir o verificar.
svidgen
@FrankPuffer Bah. Traté de actualizar mi respuesta para decirlo mejor. ¡No estoy seguro de haberlo hecho mejor o peor! ... Pero, necesito salir de SE.SE y volver al trabajo.
svidgen
Esta respuesta está bien, pero el problema que tengo con ella (como con algunas de las otras respuestas) es que "mitigar el riesgo y mejorar el ROI" no siempre son los mejores objetivos. No son realmente objetivos en sí mismos, de hecho. Cuando necesitas algo para trabajar, mitigar el riesgo no va a ser suficiente. A veces, los pequeños pasos relativamente no dirigidos como en TDD no funcionarán: minimizará el riesgo de manera correcta, pero al final no llegará a ningún lugar útil.
Andres F.
4

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.

mcottle
fuente
1
Primero, cuando leí tu respuesta, pensé "sí, ese es el punto central". Pero después de repensar la pregunta nuevamente, pensé que no era necesariamente tan abstracta o poco realista. Si uno escoge ciegamente la arquitectura completamente incorrecta, TDD no resolverá eso, no después de 1000 iteraciones.
Doc Brown
@Doc Brown De acuerdo, no resolverá ese problema. Pero le proporcionará un conjunto de pruebas que ejercitan cada suposición y regla comercial para que pueda mejorar la arquitectura de forma iterativa. Una arquitectura tan mala que necesita una reescritura desde cero para arreglarla es muy rara (espero) e incluso en ese caso extremo las pruebas de la unidad de reglas de negocio serían un buen punto de partida.
mcottle
Cuando digo "arquitectura incorrecta", tengo en mente casos en los que uno debe tirar el conjunto de pruebas existente. ¿Leíste mi respuesta?
Doc Brown
@DocBrown - Sí, lo hice. Si quisiste decir "arquitectura incorrecta" que significa "cambiar todo el conjunto de pruebas", tal vez deberías haber dicho eso. Cambiar la arquitectura no significa que tenga que descartar todas las pruebas si están basadas en reglas de negocio. Probablemente tendrá que cambiarlos todos para admitir cualquier interfaz nueva que cree e incluso reescribir completamente algunos, pero las Reglas de Negocios no serán reemplazadas por un cambio técnico, por lo que las pruebas permanecerán. Ciertamente, la inversión en pruebas unitarias no debería ser invalidada por la improbable posibilidad de afectar por completo la arquitectura
mcottle
claro, incluso si uno necesita reescribir cada prueba en un nuevo lenguaje de programación, no necesita tirar todo, al menos uno puede portar la lógica existente. Y estoy de acuerdo con usted al 100% para los principales proyectos del mundo real, las suposiciones en la pregunta son bastante poco realistas.
Doc Brown
3

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.

Estoy pensando en un escenario en el que ha implementado una serie de casos de prueba y luego encuentro que el próximo caso de prueba requeriría un enfoque completamente diferente. Tendrá que tirar su trabajo anterior y comenzar de nuevo.

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.

JeffO
fuente
0

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.

JacquesB
fuente
¿Pero fue una reacción demasiado fuerte? Ciertamente ayuda en muchos casos donde la planificación inicial estricta resultó difícil de manejar y costosa. Sin embargo, algunos problemas de software deben abordarse como un problema matemático y con un diseño inicial. No puedes TDD ellos. Puede TDD la interfaz de usuario y el diseño general de Photoshop, pero no puede TDD sus algoritmos. No son ejemplos triviales como derivar "suma" o "doble" o "pow" en ejemplos típicos de TDD [1]). Probablemente no pueda burlarse de un nuevo filtro de imagen al escribir algunos escenarios de prueba; absolutamente debes sentarte y escribir y comprender fórmulas.
Andres F.
2
[1] De hecho, estoy bastante seguro 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!
Andres F.
Dos comentarios: (1) Tiene razón en que esto puede ser engañoso. Pero no escribí que TDD es lo mismo que la optimización matemática. Lo acabo de usar como analogía o modelo. Creo que las matemáticas pueden (y deberían) aplicarse a casi todo, siempre y cuando conozca las diferencias entre el modelo y la realidad. (2) La ciencia (trabajo científico) suele ser incluso menos predecible que el desarrollo de software. E incluso diría que la ingeniería de software es más como un trabajo científico que como un oficio. Las manualidades generalmente requieren mucho más trabajo de rutina.
Frank Puffer
@AndresF .: TDD no significa que no tenga que pensar o diseñar. Simplemente significa que escribe la prueba antes de escribir la implementación. Puedes hacerlo con algoritmos.
JacquesB
@FrankPuffer: OK, entonces, ¿qué valor medible es el que tiene un "óptimo local" en el desarrollo de software?
JacquesB