Thursday, October 6, 2011

El problema que reside en definir un problema.

Resulta paradógico que cuánto más se intenta acotar y documentar el sistema a desarrollar, más tiempo es necesario para ello y por tanto más cambiarán las necesidades reales a las que se dedicará.

 La captura de especificaciones ‘congela’ el mundo al momento en que se establecen o bien genera lagunas que fuerzan a la toma de decisiones sobre suposiciones. Esto produce un desfase entre la necesidad real y la solución generada dado que durante su fabricación la realidad  ha cambiado y los detalles no documentados son fuente de imprecisiones.

En un enfrentamiento cara a cara contra la realidad, lo más probable es que ésta salga victoriosa a no ser que la sigas de cerca y te amoldes, negocies los cambios. La agilidad es una buena estrategia en el mundo del desarrollo actual.

La falta de calidad se debe directamente a la incapacidad del software para cumplir las necesidades reales, a esto se añade que el número de defectos sea muy alto y una baja mantenibilidad, responsable del alto coste de la corrección de errores y el añadido de nuevos requerimientos , otro pez que se muerde la cola.

Los defectos son fuente de costes no deseados, convirtiendo los sistemas en inestables, impredecibles o incluso en inútiles. Pueden dar lugar a pérdidas en lugar de beneficios. Las pruebas extensivas permiten reducir la probabilidad de fallo, he ahí su importancia y razón de ser.


Medir la mantenibilidad no es tarea fácil, es bien conocido que está directamente relacionada con la calidad del código (siempre y cuando el código se haya realizado con la mantenibilidad en mente), pero ¿qué distingue un mal código de uno bueno?  debemos reflexionar sobre ello.

Una técnica habitual en la agilidad para obtención de requisitos consiste en que el cliente, o bien alguien que lo represente, mantenga una lista de requerimientos. De esa lista se toman subconjuntos de entradas dividiendo cada una de ellas en tareas que pasan a formar parte de otra nueva lista de seguimiento de la iteración, donde las tareas se priorizan a partir de los criterios del cliente y del equipo de desarrollo. Comúnmente la duración de un ciclo ha de ser corta, desde unas horas a un par de semanas, permitiendo realizar seguimientos bastante precisos del esfuerzo realizado.

La toma de requerimientos no es tarea fácil, determinar la necesidad verdadera es una necesidad principal. Debemos cuestionarnos una y otra vez: ¿Estamos realmente cubriendo la necesidad real? Es perfectamente posible solucionar necesidades innecesarias de una forma completamente artística, cosa que debemos evitar pues dispara los costes y no mejora en nada la calidad del producto, de hecho es muy probable que ésta disminuya.

Dada una nueva característica debemos esclarecer:
  • En qué consiste exactamente.
  • Qué se interpone en nuesto camino de añadirla al producto.
  • Darle un nombre claro.
  • Determinar cómo solucionarlo.
No debemos olvidarnos de la ergonomía , una característica perfectamente funcional pero difícil de utilizar es inútil para el cliente.

Tuesday, October 4, 2011

El Ciclo de Trabajo en TDD

En el ciclo de vida clásico en cascada las pruebas son posteriores al diseño y la codificación, esto implica que un fallo en las pruebas conlleva una vuelta atrás al paso de codificación o bien, retroceder dos pasos y tener que modificar nada menos que el diseño, los costes por retroceso son conocidamente altos. La idea principal de TDD es anteponer las pruebas a todo el proceso, por tanto un fallo en las pruebas no supone una vuelta atrás y se reduce su coste dado que los pasos de codificación y diseño se subordinan a ellas. Por otro lado también se invierte el orden de la codificación y el diseño, dado que de la codificación que supera las pruebas en ciclos repetidos un buen diseño debiera emerger de forma natural, claro está que deben controlarse los índices de calidad.




El objetivo inicial de una prueba es, curiosamente, que falle. En el mundo TDD una prueba que falla se representa con un icono rojo y una que se supera con uno verde. Dada una nueva característica implementamos la prueba, creamos el código justo y necesario para enlazar con la prueba y.. nos vamos a rojo. Tenemos algo muy valioso entre manos, nada menos que una prueba que falla conectada con un esqueleto de un código que no funciona, que es mucho más que no tener nada. Ahora nuestro objetivo es generar el código necesario para irnos a verde, prueba superada. Repitiendo el proceso una y otra vez veremos cómo las pruebas nos dirigen hacia la implementación final en pequeños pasos de integración.

Dos temas importantes se quedan en el aire y que veremos más adelante:
  • Maneras de enlazar con las pruebas.
  • Controlar la acumulación de código de mala calidad de un ciclo de pruebas al siguiente.

Por tanto, el ciclo de trabajo en TDD se puede representar por:

1) Escribir una prueba que falle.

2) Escribir código que supere la prueba.

3) Integrar el código eligiendo el mejor diseño que respete todas las demás pruebas anteriores.


Siguiendo este ciclo de trabajo conseguiremos que emerja un buen diseño, el código generado será demostrable ( pues tenemos pruebas de ello ), se tendrán garantías de respetabilidad del sistema en su globalidad siendo el diseño modular y cumplirá estar altamente cohesionado y bajamente acoplado. Claro está que todo ello depende directamente del tercer paso con el que vuelvo a repetir que es sumamente necesario:
  • Controlar la acumulación de código de mala calidad de un ciclo de pruebas al siguiente.
Las técnicas utilizadas para garantizar la calidad del diseño y el código asociado se denominan técnicas de Refactorización, contenido que veremos más adelante y que bien pueden ser tratados como un tema a parte.


Tuesday, September 27, 2011

Primero la prueba.

Me he comprado un móvil, es una preciosidad, tiene de todo. Da la sensación de que los fabricantes de hoy en día leen mi mente, todo lo que yo desearía tener en  mi móvil... ¡resulta que lo tengo!, y si no, me lo compro online, una pasada. Me pregunto cómo de complicado será diseñar y fabricar estos aparatos.

Soy un tipo raro, lo admito, en mi afán de disfrute decidí comprobar si mi nuevo dispositivo, bonito donde los hubiera, era sumergible, más aún, sumergible en salfumán, que uno nunca sabe qué líquidos pueden llegar a derramarse sobre tales aparatos. Para mi sorpresa, desde que hice la prueba, el móvil ya no se enciende, de hecho no tiene ni teclas, espero que lo ocurrido esté cubierto por la garantía...

De mi disgusto saco una conclusión: probar es normalmente mucho más fácil que fabricar. Debí haber comprado un modelo previamente probado y diseñado para resistir el salfumán. Y digo probado antes de diseñado pues imagino que esas cosas se deben hacer así, uno supone que lo que va a fabricar soporta las pruebas, así que diseña la prueba, la lanza sobre el primer intento y, casi con total seguridad: falla, modifica lo que tiene y vuelve a probar. De las pruebas exitosas sucesivas gradualmente emerge un diseño cuya calidad está avalada por las pruebas realizadas, mire usted qué cosas.

En general, podemos afirmar que en en la mayoría de casos:
Complejidad(Prueba) < Complejidad( Funcionalidad ) 

Gracias a que las pruebas son lo primero que se tiene en cuenta, el diseño se irá acomodando a superarlas, esto lo denominaremos diseño evolutivo. Pero un diseño que supera las pruebas puede no ser de buena calidad, eso complicará mucho los diseños y mantenimientos futuros. La solución a esta situación proviene de, una vez superada una prueba, mejorar la calidad lo máximo posible y volver a pasar la prueba, de forma que el diseño evolutivo se asiente en diseños previos de alta calidad. A esta etapa la denominaremos refactorización, tema en el que profundizaremos... tanto o más que mi móvil en salfumán.

Por ahora quedémonos con la síntesis de cómo se deben llevar a cabo las cosas al decidir trabajar con orientación a las pruebas:

Primero las pruebas, diseño evolutivo, automatización de tests, y refactorización sin piedad.
Lo de "sin piedad" suena a poderoso ¿verdad? Este tipo de frases es común en este tema que acontece: TDD.

Pasito a Pasito

El marco de trabajo establecido por TDD permite desarrollar código de una forma efectiva utilizando pequeños incrementos, es decir, pasito a pasito, como aprenden a andar los bebés. Estos incrementos deben ir dirigidos a realizar algo en concreto, así dispondremos de algo entregable al terminar cada uno de ellos, ya que superan las pruebas pues fue lo primero que establecimos y nos han ido dirigiendo a lo largo del pequeño viaje.

Puede que en la entrega no se hayan cumplido todos y cada uno de los requerimientos del cliente, pero dado que los ordenamos previamente antes de diseñar las pruebas, la probabilidad de que los más importantes hayan sido cubiertos es muy alta. Habiendo realizado las pruebas antes que el código y habiendo sido superadas una y otra vez en cada iteración corta habremos reducido al máximo el riesgo de incorporar código de mala calidad y errores de última hora que bien darían al traste con la entrega actual o complicarían la entrega siguiente.

Esto a mí me recuerda al cuento de Pulgarcito, que con iteraciones cortas, pasito a pasito, colocó garbanzos en el bosque que posteriormente le permitirían volver a casa.

 

Quién sabe, es probable que incluso no habiendo dado tiempo a implementar el requisito de baja prioridad pero pudiendo presentar algo que claramente funciona en los puntos más importantes, el cliente se muestre tan satisfecho que descubra que aquellos requisitos de baja prioridad simplemente eran innecesarios, o incluso que los ignore, olvide o de buena gana permita que se lleven a cabo en una etapa posterior. Imagino que hay un mundo donde el cliente es un ser bondadoso y amable. Todo es posible.

Monday, September 26, 2011

Las Perspectivas nos las dan las Pruebas

A primera vista puede resultarnos absurdo diseñar, describir e implementar las pruebas antes que código que lleva a cabo la tarea descrita por la petición del cliente. Después de todo, en un mundo perfecto, entenderíamos por completo al cliente y podríamos lanzarnos como tigres a realizar el código final, si cabe, directamente sobre el entorno de producción. De obrar así es de esperar una vida llena de altibajos y desasosiegos, una gran aventura digna de llevarse a una película, si llega a buen fin, porque las películas deben terminar bien.

Los habemos que preferimos una vida laboral más tranquila, sensata y racional. Esto en principio nos lo ofrece el disponer de las pruebas antes de realizar un producto entregable puesto que, una vez realizado y sometido a las pruebas, su calidad estará garantizada.  Las pruebas nos aportan confianza y una buena percepción del avance del proceso de desarrollo.

Un pensamiento pueril nos puede llevar a pensar que las pruebas nos roban tiempo, puestos a ser inmaduros eliminemos también la etapa de captura de requisitos, ¡programemos directamente frente al cliente! ¡Bienvenidos al LiveProgramming! La experiencia nos dicta que eso no funciona, al menos casi nunca.

Puede resultarnos osado, nuestro objetivo siempre es aumentar la productividad manteniendo las cotas de calidad deseables. En principio las pruebas pueden parecernos ir en contra de la productividad. Cada prueba es un punto de apoyo en la escalada hacia la cima donde se coloca un producto finalizado de calidad garantizada. No niego que existen maestros de la escalada capaces de llevar a cabo tal proeza sin anclajes ni puntos de apoyo (las pruebas), pero lo cierto es que aquellos que sobreviven terminan por aceptar que, con las pruebas, el proceso de desarrollo es más certero, previsible y productivo.

El Diseño Guiado por las Pruebas (a partir de ahora TDD: Test Driven Design) nos indica un marco de trabajo que permite desarrollar código en pequeños incrementos. En breve veremos su bases y en ellas radica su complejidad: hay que ceñirse a ellas y seguirlas con perseverancia y buen talante.