Hola a todos, de vuelta de la AgileSpain2010, y con un poco de «resaca» de la conferencia, me toca defender un par de frases que solté en nuestra sesión. Lo que ocurrió realmente fue lo siguiente:
Bien, pues lo solté y me quedé tan fresco, de hecho no me pareció que fuera una frase polémica, pero empecé a ver caras raras y a la salida de la sesión vi por twitter que mi frase había causado cierta extrañeza. Empezaré aclarando mi frase: «Si haces TDD bien no necesitas análisis de calidad de código estático». Ojo, hablo de hacer TDD bien, no de hacer TDD a medias. Existe un malentendido respecto al objetivo del TDD, si bien uno de ellos es lograr un conjunto de pruebas automatizadas con alta cobertura de código, éste no es el único, de hecho es sólo la mitad de la historia. El otro objetivo, igualmente importante, es conseguir un código de calidad de forma incremental y/o evolutiva. De hecho algunos autores hablan de calidad o diseño emergente, pero yo prefiero no fliparme tanto de momento. Si tenéis el libro de Carlos Blé sobre TDD, veréis que se llama «Diseño Ágil con TDD», no programación con TDD o pruebas con TDD o QA con TDD, sino diseño. Éste es el entendimiento general que todos los autores tienen sobre este tema: TDD lleva a pruebas automáticas de alta cobertura y a alta calidad de código, si lo haces bien, claro.
Llegado a este punto conviene explicaros mi percepción de los niveles de adopción del TDD:
- Fake TDD. En este nivel de adopción el TDD no se practica, sino que se simula practicar. En el fake TDD los tests no representan realmente la funcionalidad de las historias de usuario o de la interfaz del componente que queremos probar. No se hace un esfuerzo serio por entender la funcionalidad del componente bajo pruebas y se escriben tests con poco contenido, contenido incorrecto o simplemente tests de relleno sin contenido. Ésto puede ser por desconocimiento de la técnica, con lo que habremos de dar más formación y hacer talleres. También puede ser por presión, que tiende a romper la disciplina del programador, y por miedo a no estar en fechas, se ignora el TDD. La tercera razón para hacer fake TDD la veremos en el siguiente post.
- Naive TDD. Simplemente consiste en no realizar la fase de refactorización durante el ciclo de TDD. Las causas suelen ser de nuevo inexperiencia o prisas. Normalmente si no se entiende TDD como una metodología de diseño nos encontraremos en este caso. Otro tipo de Naive TDD se produce cuando se escribe primero código de implementación y después el test.
- TDD. Adopción completa, haces test con funcionalidad correcta pero no consideras el ciclo terminado hasta que no has refactorizado. Para que un ciclo de TDD se considere completo y puedas hacer commit, debe existir un test, con los contenidos adecuados, que al ejecutarse pase, y el código que implementa la funcionalidad bajo prueba sea limpio. Si el código no es limpio debemos refactorizar.
¿Qué es pues código limpio? Pues es código que tiene unos niveles de calidad razonables, pero, ¿qué es la calidad del código? Difícil pregunta. Para ello las distintas organizaciones y empresas definen un modelo de calidad, que consiste en un conjunto de métricas de código que se van a tomar y que resultados mínimos son exigibles para esas métricas. Las métricas se clasifican en dos tipos: estáticas y dinámicas.
La métricas dinámicas miden propiedades de tiempo de ejecución del sistema. Ejemplos típicos son corrección, rendimiento y escalabilidad, seguridad y usabilidad:
- La corrección la conseguimos con el propio TDD (hasta donde es posible dado que los requisitos son cambiantes y difusos).
- El rendimiento y escalabilidad se consiguen con pruebas de stress, algo que está aparte del TDD, hasta donde yo sé.
- La seguridad del mismo modo está en un mundo aparte, y que yo sepa no se pueden hacer pruebas automatizadas satisfactorias para esto, salvo quizás los ataques más típicos.
- La usabilidad tiene que ver con la facilidad de manejo de la aplicación y su atractivo para el usuario, definitivamente esto no se puede probar de forma automática, para ello podemos usar pruebas de aceptación tradicionales.
Por otro lado las métricas estáticas miden propiedades del código que se pueden detectar en tiempo de compilación. Veamos:
- Nomenclatura. Si tu código se ajusta o no a determinada nomenclatura. Bueno, que deciros, no considero esto importante para un desarrollo ágil, es mucho más importante la legibilidad y la documentación. Puedo entender que en un lenguaje con tipado dinámico esto pueda ayudar, al fin y al cabo no hay compilador que te diga si una variable es un string o un integer. Sin embargo si vas a usar un lenguaje de tipado dinámico (ruby, javascript, smalltalk, etc) es mejor que no pienses en java, sino que saques partido a las características de dicho lenguaje, que normalmente no está pensado para que una variable sea siempre un string, es simplemente otra filosofía de diseño.
- Nivel de documentación. Bueno, esto sí es interesante, me habéis pillado 🙂 Sin embargo no considero que esto se pueda medir de forma totalmente automática. La dificultad radica en que es muy difícil automatizar la decisión de si un método o clase debe estar documentada. Desde el punto de vista del agilismo debemos documentar sólo aquello que merezca la pena, no todo, y por supuesto tampoco vale no documentar nada. Detectar si documentar un artefacto de código «vale la pena» o «o aporta valor» de forma automática es difícil, ¿no creéis?
- Legibilidad. Lo más importante, tu código debe ser legible por tus compañeros, si no, no podrán mantenerlo. Esto tampoco se puede detectar automáticamente.
- Estilo y formato de código. Esto realmente es un aspecto de la legibilidad.
- Tamaño del sistema. No se muy bien para que se quiere medir esto, y además, ¿en qué lo medimos? ¿Lineas de código?¿Puntos función? Sin comentarios. Tal vez lo que queremos medir realmente es la cohesión.
- Alta cohesión y bajo acoplamiento (principio de una única responsabilidad). Realmente estas son muy interesantes de medir y a mi juicio sí que son necesarias. Los artefactos de código que tengan muchas interdependencias entre ellos deben agruparse en artefactos de nivel superior. Por ejemplo, un montón de métodos que son muy interdependientes podrían agruparse en la misma clase. Y viceversa, si un artefacto esta compuesto de muchos subartefactos que apenas interaccionan entre si, podemos dividir ese artefacto en varios más pequeño. El ejemplo típico es la clase «monstruo» con todos los métodos del sistema dentro de ella, ay, cuantas de éstas habré visto a lo largo de mi carrera. Estas propiedad del sistema sí que se pueden medir automáticamente, al menos en los lenguajes de tipado fuerte. Si se pueden medir automáticamente en los lenguajes de tipado dinámico tengo mis dudas, pero me callo por no ser experto.
- Duplicación de código. Esto se puede medir fácilmente con una herramienta. La existencia de código duplicado es un signo de mal diseño y baja calidad, excepto en el caso de extrema necesidad de legibilidad.
- Malas prácticas. ¿Acaso el analizador de código estático es inteligente? No, es tonto, es un robot que se limita a pasar patrones sobre el código. Si no tiene inteligencia, ¿cómo va a saber si una práctica es mala? ¿Acaso entiende el código? Sólo va a detectar las malas prácticas más sencillas. No me lo creo a pesar de lo que me digan los vendedores de herramientas sofisticadas (y caras).
Existen muchísimas más métricas estáticas, algunas de alta complejidad. Yo personalmente no las entiendo, ni veo cual es su sentido. Todavía no conozco a nadie que me las haya podido explicar, así que de momento me quedo con las que os he contado anteriormente. En cualquier caso, ¿qué utilidad tiene medir una cosa sin entender lo que estás midiendo? Si alguien me sabe explicar alguna métrica arcana, que sea esencial para medir la calidad del software, y no haya mencionado, que me lo comente.
En el modelo de calidad que yo uso, y que me gusta pensar que es un modelo de calidad ágil, considero que el código es limpio si cumple en la medida de lo posible los siguientes criterios de calidad: corrección, legibilidad, sin código duplicado, bajo acoplamiento y alta cohesión. Si os fijáis, excepto la legibilidad, el resto de los criterios te los incorpora de forma natural el ciclo de TDD que incluye la refactorización. La legibilidad es algo que no se puede comprobar automáticamente. O sea que si hacemos TDD bien, y encima lo reforzamos con otras buenas prácticas, como la programación por parejas o la revisión de código, no necesitamos para nada complicar vuestro build con análisis de código estático. Una aclaración, yo considero que el código es limpio si todo el código pasa el modelo de calidad anteriormente mencionado. Esto implica no sólo el código de implementación, sino el código de test. La tarea de refactorización incluye también al código de test no sólo el código bajo pruebas. No es válido tener código duplicado en el código de test, y este debe ser legible, y poseer una buena cohesión. De hecho las pruebas unitarias deben ser independientes unas de otras, lo que implica un acoplamiento entre ellas muy bajo. Un error típico es no refactorizar el código de test, sólo el de implementación.
Muchos estareis pensando que todo esto es muy bonito, pero lo que queréis realmente es controlar que las cosas se hacen bien. Así que muchos me diréis, ¿cómo controlo que el código está bien hecho?¿Cómo se que se está haciendo TDD bien y no me están engañando? Muchos de los expertos en agilismo os dirán que el simple hecho de que hagáis esta pregunta significa que no sois ágiles. Si necesitáis que una herramienta os diga si el equipo está programando según las buenas prácticas es que no estáis al lado del equipo, que no practicáis go&see y que estais encerrados en vuestros despachos sin interactuar realmente con la realidad, es decir, con vuestro equipo.
Ciertamente lo comentado en el anterior párrafo puede ser cierto en las circunstancias típicas de un proyecto ágil. En otras circunstancias, como en el caso de trabajar con equipos distribuidos, es lícito que tengáis dudas sobre si vuestro equipo hace el TDD como debiera, ya que al fin y al cabo, no podéis estar en la misma sala con todos los equipos a la vez. Así pues, si tienes equipos distribuidos, ¿cómo sabes que se hace bien el TDD? La respuesta está en el nivel de cobertura y en aplicar un poco de psicología:
- Si la cobertura es baja, entonces realmente no se está haciendo TDD bueno. Puede ser porque se está haciendo Fake TDD, y por lo tanto los tests al no ser suficientemente completos no ejerciten bien el código. Puede ser también que tengamos Naive TDD, y se escriba código de implementación antes que el test, con lo que puede quedar zonas del código de implementación sin probar. La falta de refactorización del naive TDD también puede llevar a baja cobertura, después lo comento. También puede ocurrir que simplemente no se ha hecho TDD en todos los casos, y se ha escrito código de implementación sin ningún tipo de test.
- La cobertura es alta (mayor del 70%). En este caso existe una alta probabilidad de que el código tenga una buena calidad y se haya hecho TDD del bueno con refactorización. La razón de esto es sutil. Lógicamente si hacemos TDD bien vamos a tener una cobertura alta. ¿Podemos llegar a tener cobertura alta haciendo Fake TDD? Claramente no, a menos que el programador dedique sus esfuerzos a sabotear el proyecto y haga casos de tests con alta cobertura, que pasen pero que no prueben nada. Ciertamente esto es bastante bizarro. Sobre la gente que se dedica a hacer esto hablaré en el siguiente post ¿Pero y el Naive TDD? Al fin y al cabo sí hacen tests, podríamos tener alta cobertura a pesar de no refactorizar, ¿no? La verdad es que ciertamente es posible pero es difícil. Al no refactorizar duplicas código, y el diseño de tus clases hace que se vayan volviendo cada vez más difícil de testear, ya que no guardan el principio de alta cohesión, bajo acoplamiento, etc. Por un lado duplicar código hace que tengas que duplicar también los fragmentos de test que ejercitas ese código. Esto se va haciendo pesado y se tiende naturalmente a no hacerse con lo cual la cobertura baja. También la duplicación invita a que los tests fallen, ya que un cambio en una historia de usuario o un bug, implica cambiar código en muchas zonas de la aplicación, con lo que harán que el test que prueba ese bug falle. Finalmente, al no refactorizar, las clases van perdiendo calidad, y va siendo cada vez más difícil hacer pruebas unitarias, ya que estas se van acoplando cada vez más con su entorno, y no presentan una buena encapsulación y cohesión. Como vemos el no refactorizar al principio no tiene mucha importancia, pero conforme pasa el tiempo se hace más difícil añadir tests, con lo cual los tests se hacen más laxos y la cobertura baja.
Yo recomiendo encarecidamente mezclar TDD con dos prácticas más: programación por parejas y revisiones de código, especialmente de los tests. Ambas contribuyen a que los desarrolladores cuiden su prestigio y no pierdan la disciplina, con lo que baja la probabilidad de que rompan el ciclo de TDD. La programación por parejas tiene la ventaja adicional de enseñar la técnica a los más novatos. Os puedo contar que en el último proyecto estaba presionado de tiempo y rompí la disciplina del TDD para una funcionalidad que era «trivial». Como no estaba haciendo programación por parejas pude hacer esta trampa. ¿El resultado? ¡ Zas, en to la boca ! Efectivamente sufrí una concentración de bugs en ese código «trivial», necesité una jornada de 20 horas de programación ininterrumpida para solventar el desaguisado. Cuando llegué a casa mi mujer casi me mata de un sartenazo pensando que era un vulgar ladrón. Después estuve una semana KO por el sobreesfuerzo. Nunca más.
Si aun veis que queréis un análisis estático de código podéis usarlo como método didáctico. Conforme vais haciendo TDD, ireis viendo como vuestra cobertura aumenta y también los resultados de las métricas de calidad. Esto es recomendable para equipos que empiezan con el TDD y todo esto de la refactorización. Cuando se trata de un equipo que sabe hacer TDD y refactor, realmente las métricas os sobran, así que podéis aligerar vuestro sistema de integración continua y ahorraros un dinero en licencias desactivando tales métricas.
Por supuesto existe una razón no ágil para esto de las métricas: el informe de colores para impresionar a la dirección y a los clientes. Si necesitáis un informe lujurioso para justificar el avance de vuestro proyecto estais en una situación mala. Yo justifico el avance de mis proyectos con demos, enseñando software que funciona y que cada vez tiene más funcionalidad.
En el siguiente post os cuento la otra pequeña polémica que tuvimos.
Je, je, en mi opinión, aún un pelín polémico.
Supongamos un escenario estándar dentro de un equipo que aspira a ser ágil. Hay gente que hace TDD, hay gente que lo intenta, hay gente que ni sabe lo que es y hay gente que lo contempla como una sobrecarga inútil.
Como responsable de cinco equipos más (por ejemplo), no tengo tiempo de estar con ellos el tiempo suficiente para saber cómo van. ¿Delegar? ¿Confiar? Buff, muy largo me lo fiáis mi señor… Pero si tuviera algo, no sé, una herramienta que me ayudara a aplicar ciertas heurísticas que me apuntaran hacia el equipo que presumiblemente necesita más ayuda… 😉
Pero claro está, estoy 100% de acuerdo contigo. Sólo es un matiz. ¿Cuándo necesito una herramienta? Cuando yo solo no puedo. 😛
[…] This post was mentioned on Twitter by Enrique Amodeo Rubio, Jose Manuel Beas. Jose Manuel Beas said: Aún polémico 😉 RT @eamodeorubio: http://wp.me/pQAdW-3M Te lo dije…Con TDD no necesitas análisis de calidad de código estática […]
Buenas, aunque estoy totalmente de acuerdo en la parte que indica que TDD es una herramienta de diseño y que el código que se genera practicando TDD es de una calidad sobradamente superior (generalmente) al que se genera sin usar TDD. Apunto al tema indicado por José Manuel respecto a que un equipo esta compuesto por varias personas y sobretodo con varios niveles de experiencia. Esto se soluciona como indicas con Programación por parejas y revisiones de código, aunque a veces esto no es posible.
Por lo que (para montar más polémica aún :-P) solo estoy de acuerdo con esa afirmación si estamos hablando de un programador experimentado y que conoce muy muy bien el lenguaje utilizado.
Hola:
Haciendo bien TDD el código se supone que sale mejor, pero las métricas están en su mayoría muy pensadas también para asegurar un código claro, seguro, ampliable y reutilzable. El no entenderlas no implica que no sea bueno seguirlas.
Pongo un ejemplo. Hay una métrica que falla si en un constructor se utiliza un método de la clase que no sea final. Esto está pensado para que nadie inadvertidamente en una clase hija sobreescriba ese método y estropee sin querer la inicialización de la clase padre. Obviamente, si hay un TDD bien hecho, en cuanto estropeemos el constructor sobreescribiendo el método, algún test fallará. Pero, ¿por qué dejar abierta la puerta a que alguien estropeé el constructor y lo descubra al ejecutar un test antiguo (no el que acaba de hacer) y mire el por qué si simplemente poniendo final al método lo evitas y le ahorras tiempo?
Otro ejemplo que mencionas, nomenclatura. En java las variables comienzan con minúscula (independientemente de su tipo, no sirven para distinguir el tipo) y las clases con mayúscula. No sé tú, pero yo he intentado seguir código de compañeros míos que no siguen esa simple regla y es algo que despista mucho. Una métrica te lo pilla enseguida, con TDD no se contempla.
Y otro más, la complejidad ciclomática. Si un método no tiene código repetido ni dentro del método ni con otros métodos, por TDD podríamos ponerle toda la complejidad que quisieramos. Sí, el programador, si es bueno, se encargará de partirlo en métodos más pequeños o incluso separar clases … o no.
TDD bien hecho sirve para hacer el código mejor, pero no cubre todos los aspectos que puede corregirte un análisis de código. Y sí, un análisis de código no asegura nada si el que hace el código no se preocupa del sentido verdadero de la métrica, pero pasa lo mismo con TDD si sólo nos preocupamos de hacer test para conseguir la cobertura.
Mi conclusión final es la de siempre, una metodología/herramienta es buena o mala dependiendo de quién la use y cómo.
Se bueno.
De lo que estamos hablando es: confianza y control. Agilismo se basa en la confianza en los desarrolladores, no en el control. Todo lo que dice Enrique es que no *necesitas* control con metricas. Para poder confiar hay que seleccionar bien.
[…] polémica del TDD, pero no temais, en este post no voy a hablar de TDD (al menos no mucho). En el anterior post defendí que si se hacía bien el TDD, con su refactor y que si además se complementaba con un […]
[…] dejarlo impecable. Además, se habilitan una serie de ventajas más. Algunos incluso afirman que si tienes TDD ya no necesitas nada más ( una idea provocadora y algo punky que me encanta, pero me parece algo […]
(me ha vuelto a salir el error de la otra vez, por lo que no sé si se ha enviado correctamente el comentario, así que lo vuelvo a enviar a ver, espero que no salga duplicado, si es así, mis disculpas)
Sobre la parte relativa a la documentación de código, lo de «documentar sólo lo necesario» lo malo que tiene es que es muy subjetivo, ¿qué es necesario documentar y qué no? Igual para alguien que controla mucho, una parte es evidente y no necesita documentarse, pero lo mismo luego lo coge otra persona que no tiene el mismo nivel, y le habría venido bien que eso estuviera documentado. Sin contar que la documentación de código me parece una buena forma de comprobación de posibles puntos de error.
Hablando con algunas personas me dicen que el código ya te dice lo que hace, pero una cosa es lo que hace el código y otra cosa es lo que debería hacer. Si yo documento el código indicando lo que estoy haciendo y luego resulta que el código como tal no hace lo mismo, ya salta una alarma, y ahí algo no encaja. Claro, siempre asumiendo la premisa de que la gente actualiza tanto el código como los comentarios cuando cambia algo.
A mí personalmente comentar el código ha sido una de las cosas más útiles y que mejor me han venido, tanto cuando he tenido que coger código ajeno y estaba comentado, como cuando he tenido que coger mi propio código tiempo después. Y por experiencias ajenas, cuando he pasado código mío a personas que lo han heredado, una de las cosas que más me suelen agradecer ha sido que todo estuviera comentado. El porqué de tomar tal o cual decisión en ciertos fragmentos, lo que quiero hacer en cada punto, etc. Incluso aspectos que yo daba por evidentes luego no lo eran tanto para otras personas que lo han heredado.
Por lo que mi experiencia personal en este sentido es que documentar todo el código siempre ha terminado resultando útil.
Scherzo