lunes, 18 de julio de 2011

La memoria y TDD: Refactor/test backlog & Sleeping in Red

Hace tanto tiempo que no escribo en este blog, y ahora vuelvo a escribir (sin razon aparente) para contarles unas impresiones acerca de como se me ocurre optimizar TDD en relacion con la memoria (humana), en esto cualquier comentario que puedan aportar no solo sera bienvenido sino que tambien muy apreciado

Lo basico

Bueno, para no dar mas vueltas voy directo al tema empezando por la basico (salteen este parrafo si saben basicamente en que consiste TDD)
TDD es una practica de programacion que basicamente consiste en escribir los tests primero y refactorear el codigo convenientemente, este proceso se ejecuta de manera iterativa pasando por tres fases en cada iteracion: rojo, verde y refactor. A grandes rasgos:
  • La fase "roja" consiste en escribir tests que fallen expresando especificaciones de como las cosas deberian funcionar, antes de que se implementen
  • La fase "verde" consiste en buscar que esos tests pasen a toda costa, es decir que "vale todo" de manera que el codigo se llena de chanchadas en esta fase (lease codigo duplicado, harcodeos, variables globales y cualquiera de esas cosas feas que se ven en las pesadillas)
  • La fase de refactoring la cual hace mas lindo el codigo, limpiando todo lo que se ensucio en la fase verde anterior mientras se mantenga el 100% de tests en verde
Para mas informacion de TDD pueden ver el articulo de la wikipedia o simplemente buscar en la web que esta lleno de blogs que explican cosas muy interesantes

¿Que tiene que ver la memoria con TDD?

La memoria tiene que ver con todo, y en el caso especifico de TDD, la memoria del programador actua diferente de fase en fase (suponiendo que no use un soporte externo como voy a explicar mas adelante).
  • En la fase roja, que inicia el ciclo de TDD, la informacion que el desarrollador utiliza para escribir los tests son las especificaciones (para reducirlas a especificaciones de componentes en el codigo, etc...). No se necesita ninguna informacion memorizada ya que las especificaciones vienen escritas (y si no, ya no es un asunto de TDD). Solo se requiere informacion extra para escribir casos "borde", caminos "no felices" o cualquier cosa que no este explicitamente en la especificacion.
  • En la fase verde, se tiende a usar informacion que se escribe en los tests para hacerlos pasar, como por ejemplo nombre de clases, metodos u otra informacion que se escribio en los tests. Depende del caso, se suele utilizar mucho conocimiento del proyecto, pero por ahora eso no importa porque esa memoria esta mas alla del scope de un ciclo de TDD
  • En la fase de refactor, se utiliza informacion que esta en el codigo (por ej, al observar metodos duplicados) e informacion producto de ideas, razonamientos, observaciones al codigo efectuadas en cualquier momento, ya que el codigo se esta observando siempre y hay que considerar que refactorear codigo para mejorar su calidad interna no es una tarea trivial (como si lo es hacer que los tests pasen) y requiere mas trabajo mental
Las fase que mas informacion producida en el mismo ciclo y mas trabajo mental requiere es la de refactor, la fase verde requiere mas trabajo mental que la roja

Refactor backlog

El que haya usado TDD, seguro se encontro en la situacion de duplicar cierto codigo para hacer que un test pase teniendo en mente refactorizarlo despues (no en el momento), no deberia memorizarse esa informacion ya que eso es proclive a olvidos y disminuye la concentracion en la tarea del momento, en lugar de eso es mejor usar un "backlog" (que seguramente no sera el mismo backlog agile, es algo distinto) o un "ToDo list" el cual se debe alimentar cada vez que se observe algo que deba ser refactoreado y que no corresponde hacerlo en ese momento

Test Backlog

Para aplicar el mismo concepto que en el parrafo anterior, cada vez que miramos al codigo, se nos ocurre "¿y si llamara a tal metodo con tal argumento, fallaria?", lo mejor en ese caso sera escribir un test que pruebe eso. No suena muy practico interrumpir el trabajo para hacer pasar los tests o el trabajo de refactoring para escribir el nuevo test fallido en el momento, por eso es mejor anotar los test nuevos en algun lugar a medida que se van descubriendo e implementarlos mas tarde. En mi opinion, lo ideal es hacerlo al finalizar la fase verde o de refactor con todos los tests pasando (se considera que se vuelve a la fase roja)

Dormir en rojo

Todos tenemos que descansar en algun momento, y el verdadero descanso consiste en distraccion total y si es posible completo olvido del trabajo (y si es posible irse a dormir :P). Eso implica que, al volver al trabajo, tratar de recordar informacion acerca de la tarea que se estaba realizando antes de interrumpirla para descansar, es significativamente mas costoso que recordar lo que se estaba haciendo hace pocos minutos o segundos (como en el caso del trabajo continuo).
La fase que menos memoria reciente requiere (despues de la inicial) es la verde (la de hacer pasar los test), por eso si se va a interrumpir la tarea lo mas conveniente es hacerlo con todos los nuevos tests fallando (al terminar la fase roja) ya que si se hace antes del refactor al volver se tendra que recordar los items de refactor pendientes (a pesar de usar el backlog, este solo actua como un ayuda-memoria que hay que leer, no como una memoria auxiliar). La fase roja inicial requiere menos memoria reciente pero es mas trabajosa, usa informacion de las especificaciones y es mucho menos trivial por lo que aunque tambien sea util interrumpir antes de escribir los tests no es tan conveniente como pausar despues de escribirlos.

Conclusiones
  • Voy a asegurarme de escribir todos los test que pueda antes de interrumpir la tarea para descansar, y despues hacerlos pasar cuando vuelva a trabajar
  • Voy a implementar un backlog/ToDo con items de refactor que voy a consultar cuando haga pasar todos los test
  • Voy a implementar un backlog/ToDo con items de test (pero voy a ver si eso realmente es util o no)
Links

TDD en la wikipedia: http://es.wikipedia.org/wiki/TDD

2 comentarios:

Carlos Ble dijo...

Me gusta mucho lo de la memoria, uso siempre el backlog de desarrollo como buffer y de hecho, creo que TDD no está bien hecho sin este buffer que suelo llamar libreta. Lo unico que no me ha cuadrado del post es que con TDD se escribe un test y se hace pasar antes de escribir el siguiente. En las conclusiones de tu post, pone que se escriben varios tests, y luego se hacen pasar. Eso no seria exactamente TDD.
Saludos :-)

Dario dijo...

Si, por lo general yo tambien suelo seguir el scope mas reducido de TDD (escribir SOLO UN test fallando, hacerlo pasar). Pero siempre tengo en mente la posibilidad de extender ese scope, por ej, si puedo conocer muchos casos de uso de una determinada nueva funcionalidad y tengo la posibilidad de escribir todos los test correspondientes fallando, entonces eso para mi es una oportunidad de hacer mas si estoy en "red" y no me da la cabeza para hacerlos pasar en ese momento, Mi metodo es escribir todos los tests que se puedan, sin bloquearse tratando de hacer pasar un test u otro y solo empezar a hacerlos pasar cuando ya no haya mas tests que escribir o cuando este inspirado para trabajar en la implementacion.
Para mi los nuevos tests fallando son mas valiosos que cualquier registro en un ToDO o item de backlog (al margen de que creo q estos ultimos se tienen que usar casi siempre), ya que esta mas claro (desde el punto de vista de la programacion) que es lo que hay que hacer.

Para redondear, el escribir solo un test y hacerlo pasar lo veo mas como un scope mas chico (en tiempo) que para mi solo seria necesario cuando coincide con el scope de un ciclo de releases. Por ej para mi no tiene sentido que el scope de TDD dure media hora si el sprint dura 3 semanas, pero por otro lado scopes demasiado grandes de TDD tb pueden ser un problema (digo por ej, escribir tests durante una semana y hacerlos pasar a la semana siguiente).
La unica regla practica que saco en limpio de todo esto es escribir todos los tests que se esta seguro que van a estar antes de comenzar a implementar algo