lunes, 28 de junio de 2010

Como lograr tests de unidad rápidos

Una de las principales características deseables en los tests de unidad de un proyecto es que sean rápidos. El típico ciclo de TDD de Red-Green-Refactor funciona mucho mejor cuando los tests se pueden ejecutar muy frecuentemente porque cuanto más tiempo dejamos pasar entre corridas de tests más dificil se hace saber que puede haber causado la falla de un test.

Ahora bien, ¿Qué tan rápidos necesitamos que sean nuestros tests? Digamos que tenemos tests que tardan en promedio 1 segundo. No parece demasiado lento, pero si hay 500 tests (un número fácil de alcanzar en un proyecto mediano) entonces la ejecución de estos tests llevaria más de 8 minutos, lo que nos impediría correr nuestros tests mas de dos o tres veces por hora.

Normalmente los tests que retrasan las corridas son aquellos que usan recursos externos, en especial las bases de datos. Michael Feathers considera que estos no son realmente tests de unidad, pero esto no quita que en el acceso a la base de datos puede haber errores y que esta funcionalidad se tiene que testear.

Una posibilidad para que los tests de unidad que tocan la base de datos no afecten al resto es armar dos test suites separadas. La test suite que no usa la BD se ejecuta dentro del ciclo normal de TDD y la otra solo en ocasiones especiales (por ejemplo antes de hacer commit en el repositorio). Este esquema es fácil de implementar, pero tiene el problema de que algunos tests de unidad se ejecutan mucho menos frecuentemente.

Otra alternativa, si uno tiene la suerte de trabajar en Ruby, es ZenTest. Esta herramienta tiene muchas utilidades para unit testing, pero la que nos interesa ahora es autotest que detecta los cambios que realizamos en el código y ejecuta solo los tests afectados por dichos cambios. También hay una herramienta parecida para Java, aunque no he tenido oportunidad de probarla. Con este tipo de herramientas ya no nos preocupa tener tests lentos, ya que solo vamos a ejecutar unos pocos por vez.

Una tercera posibilidad es extraer la funcionalidad que accede a la base de datos y esconderla detras de una Facade que funciona como un repositorio. Los tests de unidad de este repositorio acceden a la base de datos, pero los del resto de la aplicación no, ya que utilizan una implementación alternativa del repositorio que guarda los datos en memoria. De esta manera solo los tests que verifican realmente el acceso a la BD pagan la penalidad del mayor tiempo de ejecución mientras que el resto corre utilizando solo memoria. Para complementar este esquema se puede tener otra suite que ejecute todos los tests con acceso real a la base de datos. Esta suite no debería normalmente detectar errores que no detecte la suite normal, pero se la puede ejecutar como reaseguro, por ejemplo en un servidor de integración continua.

Aplicando este esquema mejoramos además el diseño ya que rompemos la dependencia entre nuestra aplicación y la DB. Los objetos de negocio dejan de tener la responsabilidad de saber persistirse y esa responsabilidad pasa al repositorio. Esta situación donde buscando que nuestra aplicación sea más testeable mejoramos su diseño es muy común, porque en general mejoras en la testabilidad se traducen en disminución del acoplamiento.

Existen otras alternativas, más a nivel de optimización de recursos, como utilizar implementaciones de bases de datos en memoria (esto es particularmente fácil con SQLite) o correr los tests en paralelo, explotando que los tests de unidad deberían ser independientes unos de otros. Con esta última idea hay que tener mucho cuidado porque dos tests que utilicen la misma BD y se ejecuten al mismo tiempo pueden fallar en forma impredecible.

Como ven hay muchas posibilidades. Lo importante es maximizar el feedback de los tests de unidad para lo cual es indispensable poder ejecutar los tests muchas veces por hora.

viernes, 25 de junio de 2010

Cargo cult programming

El término Cargo Cult se refiere a ciertas religiones que surgieron en algunas tribus del Pacífico después de la segunda guerra mundial. Estas tribus vivían muy primitivamente hasta que, por la construcción de bases militares, se vieron expuestas repentinamente a la civilización occidental del siglo XX. Por este contacto, generalmente por via aérea, recibieron muchos productos inexplicables para ellos. Cuando la guerra terminó las bases fueron desmanteladas y los nativos se vieron privados de los bienes a los que se habían acostumbrado. Entonces para seguir recibiendo estos "regalos caidos del cielo" crearon unos rituales en los que imitaban los movimientos que habían visto hacer a los militares: Armaban "torres de control" con madera, limpiaban y preparaban pistas de aterrizaje, hasta usaban audifonos hechos de bambu, con la esperanza de que esto atrajera a los aviones... pero los aviones no llegaban.

¿Qué pasó? ¿Nos transformamos en un blog de antropología? ¿Que tiene esto que ver con desarrollo de software? Paciencia, todo tiene que ver con todo.

Miremos un poco estos ejemplos de código, tomados de código real posteado en The Daily WTF, pero que seguro se parecen a cosas que todos hemos visto en nuestra práctica profesional:



Ejemplo 1: alguien que no tiene idea como funciona un Garbage Collector

public class Destruction {
  public static void delete(Object object){
     object = null;
  }
}

Ejemplo 2: un método para reemplazar el operador !

/// 
/// Turns true into false and false into true
/// -- similar to the church of scientology.
/// True of false
/// False or true
private bool trueandorfalse(bool _booInpt)
{
  // I'm quite sure though there is a very
  // clever C# standard command doing this,
 // I just can't find it right now ...
 if (_booInpt == true)
  return false;
 return true;
}

Ejemplo 3: está más allá de mis posibilidades explicar el razonamiento detrás de este código

public override bool IsActive() {
 return true && true && true;
}

¿Como puede ser que haya programadores que escriben estas bestialidades? Básicamente, porque son gente que no entiende lo que están haciendo y trabajan realizando pruebas más o menos al azar, y mucho buscar codigo en google/copy/paste hasta que consiguen hacer que su aplicación parezca funcionar.

La relación entre este estilo de programación y Cargo Cult la propuso Steve McConnell en su artículo Cargo Cult Programming : se trata de programadores que repiten acciones que les han dado resultado en el pasado o que han visto hacer a otros programadores, sin saber que sentido tienen estas acciones ni porque funcionan.

La metáfora es maravillosa, uno se imagina a este tipo de programadores frente al monitor, con un hueso en la cabeza, haciendo ruidos guturales y asustándose cuando las luces cambian de color.

Ahora, ¿De que nos sirve esto a nosotros, los Programadores-menos-Bestias? Digo, además de para reirnos un rato. Nos sirve para recordarnos que siempre tenemos que entender como funciona todo lo que hacemos. Está bien tirarnos lances para ver si solucionamos algo, esta bien, si el tiempo apreta, buscar algun código que no terminamos de entender del todo y usarlo, pero una vez que lo logramos no debemos dar por terminada la tarea hasta que no aprendamos exactamente que fue lo que hicimos y porque funciono. Si no lo hacemos....el dia menos pensado nos vamos a encontrar usando audifonos hechos de bambu y esperando que baje un milagro del cielo.

lunes, 14 de junio de 2010

¿Por qué funciona Pair Programming?

Cuando se presentan las prácticas de Extreme Programming hay una que inmediatamente genera un rechazo especial: Pair Programming. Ese rechazo es completamente comprensible, ya que estamos poniendo a dos personas a hacer el trabajo que antes hacía uno. ¡El proyecto va a llevar el doble de esfuerzo! - grita el lider del proyecto, y al doble de Costo!! - corea desde su oficina el gerente de proyectos, estos programadores vagos que no quieren trabajar!!

Sin embargo, hay bastante evidencia de que trabajar de a pares nos hace más productivos. ¿Cómo puede ser?

La principal explicación es que programar no es una tarea mecánica. La única parte más o menos mecánica es el tipeo del código y es una tarea que no consume mucho tiempo. En un proyecto en el que participé medí la cantidad de código que habíamos generado y la dividí por la cantidad de horas de desarrollo. El resultado fue que habíamos escrito algo así como unas 15 líneas de código por hora (y esto en un proyecto que fue completado exitosamente en el tiempo estimado).

Las tareas que verdaderamente se llevan el tiempo de desarrollo son otras, como descubrir lo que el usuario quiere que desarrollemos, entender el código existente para ver a donde nos conviene agregar las partes nuevas, diseñar la implementación, entender como usar nuestras herramientas y corregir bugs.

Pair programming funciona porque todas esas tareas se hacen en forma más eficiente en pares. Es más facil entender requerimientos o código o herramientas cuando discutimos con un par (y además es probable que uno de los integrantes del plan ya tenga experiencia al respecto) y es mucho más productivo hacer sesiones de brainstorming de a pares para pensar alternativas de diseño.

En cuanto a la corrección de bugs, trabajando de a pares disminuye, y mucho, la cantidad de errores introducidos debido a la constante revisión del código por dos personas. La búsqueda de los pocos errores que se producen también es más eficiente, simplemente por la necesidad de comunicación dentro del par. El efecto es parecido al que se da cuando nos rompemos la cabeza durante horas tratando de solucionar un problema y cuando finalmente pedimos ayuda y tratamos de explicar el problema a alguien, se nos ocurre la solución, antes de que la otra persona tenga tiempo de decir una palabra.

La productividad además aumenta porque disminuyen las distracciones y aumenta la focalización en los problemas. Esto a primera vista parece paradójico porque con dos personas uno pensaría que crece la posibilidad de divagar o distraerse. Sin embargo no es así, y la necesidad de concentrarse no solo en solucionar el problema sino, además, en comunicar con el par lo que uno cree que debe hacerse y discutir sobre la solución, todo esto evita que uno se distraiga con ruidos externos o con llamadas o la llegada contínua de mails molestos.

Por ultimo el compartir entre dos desarrolladores de diferentes niveles de skill y conocimiento constituye la mejor y más rapida forma para poner al dia a un nuevo programador que se incorpora al equipo. Y ademas, el más experimentado recibe una dosis frescas de dudas y cuestionamientos a ciertos temas que "siempre han sido asi" pero que no tienen porque serlos.

En definitiva, los programadores no trabajamos levantando paredes ni en una línea de montaje de una fábrica, por mencionar dos metáforas conocidas en nuestra profesión ("factorias de desarrollo") . Trabajamos pensando y ese tipo de trabajo se hace en forma más eficiente en equipo.

jueves, 10 de junio de 2010

Tres libros fundamentales sobre Test Driven Development

TDD es una de las prácticas ágiles que más interés generan. En este post presentamos tres libros fundamentales sobre el tema, cada uno apuntando a un aspecto distinto de la técnica. Test-Driven Development By Example - Kent Beck

tdd_by_example

Este es el primer (y el mejor) libro sobre la mecánica de TDD. Kent Beck nos presenta los principios básicos de la práctica y luego los ilustra mediante el desarrollo de dos proyectos reales: una biblioteca Java para manejar dinero con monedas heterogenas y un port de xUnit a Python.

Lo más interesante de este libro es ver a un gran programador como Beck en acción, lo que nos permite aprender sus técnicas y ver hasta que extremo lleva TDD. Es especialmente educativo ver como combate la duplicación de código.

Además el libro contiene una serie muy útil de patterns de TDD y Refactoring.

xUnit Test Patterns - Gerard Meszaros

xunit_test_patterns

Cuando los que escribimos este blog empezamos a hacer TDD (hace ya más de 5 años!), pensábamos que la calidad del código de los tests no era tan importante como la del código de producción. Muy pronto aprendimos que esto era un error y que descuidar el código de los tests nos llevaba a que costara más el mantenimiento de los tests que el del código de producción.

Este libro es una colección de patterns que permiten escribir tests más rápidos,más mantenibles, más claros y más sólidos. También contiene una serie de smells, es decir de síntomas que nos permiten detectar problemas en nuestros tests aun antes de que estos problemas se hagan evidentes. Si están por introducir tests de unidad en un proyecto, sería una gran idea leer este libro antes.

Working Effectively with Legacy Code - Michael C. Feathers

legacy_code

Cuando hacemos presentaciones sobre TDD, una de las dudas que más frecuentemente surgen es ¿Cómo puedo usar esto cuando tengo mucho código desarrollado? Este libro es la respuesta. Michael Feathers presenta patterns, técnicas y herramientas para introducir gradualmente tests de unidad en aplicaciones que no los tengan.

Es interesante su definición de aplicacion legacy: una aplicación sin tests de unidad. Es decir que si uno está desarrollando una aplicación nueva sin tests, está generando una aplicación legacy aun antes de pasar a producción!

lunes, 7 de junio de 2010

Flexibilidad ante el cambio

Inmaginense la siguiente situación: Comienzo de un proyecto, el Gran-Gerente de proyecto, Gran-Jefe-Saco-Agua-De-Las-Piedras reune a todo el equipo y anuncia "El equipo de funcionales va a comenzar con la (Gran)Etapa de relevamiento, durará unos 10 meses. Queremos lograr entender completamente lo que quiere el usuario asi no vamos a tener ningún cambio y vamos a saber todo para cuando entremos en la etapa de desarrollo, que durará unos 3 meses.". Esta es una situacion real, números de meses incluidos. Me sucedió a mi.

Una y otra vez todos hemos estado en proyectos adonde pasa mas o menos lo mismo: Una Gran Etapa De Relevamiento al principio del mismo para "evitar" tener que realizar Cambios y para aprender TODO lo que hay que aprender. Una y otra vez hemos llegado a la mitad o al final del proyecto, con varios meses de retraso respecto de la fecha original, cuando, al mostrarsele el sistema a los usuarios comienzan a aparecer estos "cambios-que-se-suponian-que-no-tenian-que-existir".

Porqué pasa esto? Son los usuarios una fraternidad de sádicos a los que les gusta torturarnos? Son los funcionales, los desarrolladores personas altamente perturbadas y disfuncionales (eh... mejor no contestemos esto) que no pueden entender lo que el usuario les pide? Existe el destino y se empeña en ponernos piedras en el camino?

O será que el cambio es una parte normal de todo proyecto?

Hasta el surgimiento de las metodologías ágiles el cambio se trataba (se sigue tratando) como una anomalía, como un error del usuario o como un síntoma de que alguien se equivocó y no relevó lo suficiente. Cuando hablamos de cambio lo hacemos en todo sentido pero particularmente de los requerimientos y necesidades del usuario.

Por este motivo es que Waterfall indica que debe realizarse una Gran Etapa de Relevamiento al Principio (en inglés, BRUF, Big Requirements Up Front), seguido de escritura de casos de uso (indicado por el DR. RUP) y terminando con requerimientos firmados por usuarios temerosos (de meterse en camisas de once varas y de estar firmando los "10 mandamientos del sistema" tallados en Mármol!).

PMI lo trata con un proceso de "gestión de cambio" o "Control de Cambio" adonde en base a un pedido de cambio del relevamiento inicial se realiza un "cambio de alcance", lo cual implica reestimar costos, chequear asignación de recursos, reestimar tiempos, replanificar y obtener la autorización del sponsor / Gerentes del proyecto para proceder con el cambio. Cualquier parecido con la tortuga de Mafalda es pura coincidencia (La tortuga se llamaba Burocracia).

Justamente uno de los principios comúnes a todas las metodologías ágiles es: El cambio es parte de la realidad!

No es un hecho excepcional, no es causado por usuarios viles y sádicos que quieren hacer un infierno de nuestras vida. No es causado por analistas funcionales perezosos (bah, vagos) o que no saben como escribir un caso de uso (que los hay, los hay) ni por malos programadores que malinterpretan lo que esta escrito en la lista de requerimientos (de estos también hay). Sencillamente es que no importa cuanto tiempo le dediquemos al relevamiento, no importa cuantas Maquetas, prototipos o casos de uso hagamos para mostrarle al usuario, no importa cuanto esfuerzo le dediquemos a reuniones de validación, siempre siempre siempre va a haber cambios a partir del relevamiento inicial (por favor, vuelvan atras y lean de nuevo esta frase).

Porqué? bueno, los principales motivos son :

  1. El usuario ve el sistema funcionando y ahí se da cuenta lo que realmente quería .
  2. El usuario descubrió a lo largo de todo el tiempo de trabajo en conjunto con analistas y desarrolladores lo que necesita.
  3. Funcionales y Programadores aprendieron del dominio del problema y son capaces finalmente de entender lo que el negocio necesita ahora que el usuario finalmente aprendió.
  4. El negocio simple y sencillamente cambió.
  5. La legislación cambió.
  6. El/Los usuarios cambiaron (y usuarios distintos quieren soluciones distintas).

Es decir, simple y sencillamente porque es NORMAL que la realidad cambie (un cambio en alguna regulación, etc, el mercado cambia, aparece un nuevo producto, etc). Y es NORMAL que el conocimiento cambie a medida que uno va teniendo más información y aprendiendo. Esta información solo se puede obtener a través de la experiencia. Esto es así porque al ser un sistema un modelo abstracto de la realidad el usuario sólo se da cuenta de las consecuencias de lo que pidió cuando lo ve funcionando, cuando lo "toca".

Que mente perversa nos metió en la cabeza la idea de que podemos preveer de entrada la forma en que se va a comportar la realidad!? Que cambios se van a producir en el mercado? que leyes saldrán? que va a querer el nuevo usuario o de que se va a dar cuenta el usuario que tenemos cuando le mostremos el comportamiento de las reglas de negocio que relevamos en casos concretos? seria como predecir el clima, pero de aca a un año!.

Una simple muestra para ver el lugar que ocupa el cambio en las metodologías ágiles, en el primer libro de XP, llamado "XP Explained", de Kent Beck, tenia como subtitulo "Embrace Change", abracemos el cambio!

Escuchemos de nuevo finalmente a Kent Beck, en un fragmento de su libro "Implementations Patterns" :

"Nuestra industria parece adicta a la idea de que solo si diseñamos el software bien de entrada no tendremos que cambiar luego el sistema.

Recientemente lei una lista de razones por las que el software cambia. En la lista estaban los programadores que no hacen bien su trabajo al entender los requerimientos, sponsors y usuarios que cambian de parecer y otros motivos mas. El unico factor que faltaba en la lista era justamente el cambio legítimo. La lista asumía que el cambio es siempre un error.

¿Porque no sirve un pronostico del tiempo para todos los dias? Porque el tiempo cambia de manera impredecible. ¿Porque no podemos listar de una sola vez todas las formas en las que necesitaremos que el sistema sea flexible? Porque los requerimientos y la tecnología cambian de manera impredecible.

Esto no nos releva de nuestra responsabilidad de hacer lo mejor que podamos para desarrollar el sistema que el usuario necesita ahora pero sugiere que hay límites al valor de hacer sistemas a prueba-de-cambios-futuros, a través de la especulación.

Poniendo todos estos factores juntos, la necesidad para que el sistema sea flexible ante el cambio, el costo de esa flexibilidad, la impredecibilidad de adonde será necesaria, me lleva a creer que el momento para introducir esa flexibilidad es sólo cuando la misma es absolutamente necesaria".

jueves, 3 de junio de 2010

Como transformarnos en mejores programadores leyendo código

En un post anterior hablamos sobre las diferencias de capacidad entre programadores y sobre como podemos transformarnos en mejores programadores mediante la repetición de ejercicios a los que llamamos Katas.

En realidad esto es solo posible si tenemos claro hacia donde queremos ir, ya que el efecto de la mera repetición de ejercicios es solo la fijación de los conceptos e ideas que estamos practicando. Si nuestros hábitos son malos, al repetirlos y fijarlos estaríamos empeorando en vez de mejorando.
Una excelente manera de descubrir ideas para aprender es la lectura de código fuente. Así como la gente que lee mucho generalmente tiene buena ortografía y vocabulario, el leer mucho código mejora nuestra forma de programar.

Por supuesto, esta idea no es nueva. Cuando estaba aprendiendo a programar en Basic en la escuela secundaria compraba unas revistas de programación donde venían juegos para correr en mi MSX (si, ya se, soy un dinosaurio). Pero estos programas no venían en diskette, venían en forma de listados de código fuente BASIC que uno debía transcribir en la máquina para poder usar el juego. La verdad es que era un bajón, pero también era manera buenísima de aprender el lenguaje e incluso de hacer tus primeras experiencias de debugging, buscando los errores que podías haber introducido al tipear el código.

Yendo un poco más atrás todavía, el mismo Bill Gates recomienda esta idea como la mejor manera de aprender a programar y dice que el aprendió mucho robando listados de tachos de basura.


Lo bueno es que ahora ya no es necesario copiar programas de revistas viejas o buscar listados en tachos de basura para encontrar código del que aprender ya que tenemos disponibles miles de proyectos open-source en prácticamente todos los lenguajes y tecnologías existentes.

¿Que tipo de cosas podemos aprender así? En primer lugar muchos proyectos open-source hacen uso de algoritmos avanzados cuyo conocimiento puede cambiar nuestra forma de atacar ciertos problemas. Como ejemplo se puede pensar en los algoritmos de comparación de versiones de archivos de los programas de manejo de versiones como Subversion o Git, o los algoritmos de base de datos en MySQL o SQLite o los algoritmos de encriptación en los proyectos de Apache, por citar unos pocos.


Por otro lado, leer código en un lenguaje que no dominamos nos permite entender los "giros idiomáticos" de ese lenguaje. Por ejemplo, el siguiente es código Ruby válido para recorrer un vector:

v = [1,2,3,4]
0.upto(v.length) do |x|
  print v[x]
end

Pero la manera idiomatica de hacerlo es la siguiente:

v = [1,2,3,4]
v.each do |elem|
  print elem
end

El primer ejemplo es la típica manera en que un programador proveniente de otro lenguaje recorrería un vector mientras que la segunda es una manera más propia e Ruby (y más linda, debo decir). Leyendo código de programadores expertos en el lenguaje que queremos aprender podemos evitar caer en el clásico error de escribir Fortran en cualquier lenguaje. También es posible que nos encontremos con cosas que ni siquiera nos imaginábamos que se podían hacer.

Otra fuente de mejoras importante es enfocarse en los aspectos malos del código que estamos leyendo. Si nos cuesta entender una parte del código podemos intentar refactorizarlo para que sea más claro. Si quedamos muy contentos con nuestro cambio podemos incluso enviarle un patch a los dueños del proyecto open-source.

Una variante de esta técnica es no limitarse a leer el código si no buscar algún reporte de error del proyecto e intentar corregirlo. Es importante que al hacer la corrección imitemos lo más posible el estilo del código existente, ya que la idea no es solo corregir el bug si no mejorar nuestro estilo de programación.

Además de mejorar nuestra forma de programar es muy importante acostumbrarse a leer código desconocido ya que los programadores pasamos muchísimo más tiempo leyendo código que escribiendolo. Cuanto más efectivos seamos entendiendo código ajeno, más productivos vamos a ser.

En un futuro post vamos a mostrar el análisis de un pequeño plugin de jQuery y todo lo que se puede aprender de el.