Jun 30

JUnit y Log4j

 

Cuando hacemos nuestras clases de test de JUnit, puede ser bastante normal testear clases que usan log4j. Si en nuestros test queemos que se muestre la traza de esas clases, debemos configurar log4j. Lo normal es hacerlo en el método setUp() del test (el que se ejecuta justo antes de cada uno de los test) de una forma similar a esta

public void setUp() {
   BasicConfigurator.configure();
}

Quizás, en vez de en el setUp() lo hagamos en el constructor de la clase de test.

Pero esto tiene una pequeña cosa que debemos saber. Cada vez que llamamos al BasicConfigurator.configure(), se añade un ConsoleAppender al log4j. Por ello, cada vez que se ejecuta setUp() (una vez por cada test), se añade un appender nuevo… y la traza empieza a salir repetida, ya que cada appender creado saca el mensaje por consola. Si lo hacemos en el constructor y tenemos varias clases de test, tenemos el mismo problema.

Para evitar esto, lo mas sencillo es llamar a BasicConfigurator.configure() en el método setUp() (y no en el constructor), pero poner tambien un método tearDown(), que JUnit llama automáticamente al final de cada test, y ahí borrar los appenders añadidos

public void tearDown() {
   LogManager.shutdown();
}

De esta forma, por cada test, primero se añadirá el appender y después se borrará. Cada test tendrá un único appender y la salida de log será única.

Jun 27

Tipos de debugger

 

Cuando queremos depurar nuestro código, hay tres tipos de debugger, que de menos evolucionado a más evolucionado son:

  1. Poner prints, compilar, ejecutar y mirar la traza. Ponemos más prints, volvemos a ejecutar y volvemos a mirar la traza. Así, por aproximaciones sucesivas, vamos aislando el problema hasta que damos con él.
  2. Usar un debugger propiamente dicho, el que venga con el compilador o el de nuestro IDE favorito. Ponemos breakpoints, watches, inspects y similares. Ejecutamos el códido y vamos dándole a "step over", "step into" y demás variantes. De esta forma, llegamos más lentos que en el paso anterior al error, ya que tenemos que aprender a usar el debugger y si apenas sabemos programar, es pedir demasiado.
  3. La opción buena. Cogemos nuestro proyecto y lo metemos un un zip. Hacemos un copy-paste del error/excepción que nos sale y lo subimos todo a varios foros junto con una explicación más o menos elaborada, pero sobre todo misteriosa (estilo "lo tengo todo bien pero no funciona"). Esta es la forma más cómoda, con un poco de suerte, el bug se soluciona solo y además usamos las metodologías más avanzadas (el internet) y compartimos conocimientos (otros pueden aprender de nuestros errores).

Lo siento. A veces me espanto con lo que veo en los foros …

Jun 26

CVS no vale para proyecto grandes

 

Llevamos ya varios días que hemos migrado algunos repositorios de CVS a Subversion y se nota una gran diferencia, sobre todo en los update. Tras usarlo varios días y si nos paramos a pensar un poco, es bastante lógico.

CVS mantiene un número de versión para cada uno de los ficheros en el repositorio y cada fichero es independiente totalmente de los demás. De hecho, puedes entrar directamente en el repositorio, borrar uno de los ficheros con su historia y el repositorio sigue funcionando perfectamente, se olvida por completo que ese fichero ha existido alguna vez.

Subversion no tiene número de versión para cada fichero, sino que tiene un único número de versión para todo el repositorio. Si se toca un solo fichero, el número de versión de todo el repositorio se incrementa en uno.

Esta diferencia, que en cuestión de gustos puede ser mejor una u otra, tiene una importancia importante en la eficacia de los update. Si estoy en CVS y hago un update de todo el proyecto, CVS no tiene más remedio que revisar todos los ficheros uno a uno para ver si en el repositorio hay una versión más moderna de ese fichero y actualizarla. Eso, en un proyecto grande, con varios miles de ficheros, puede tardar un rato.

Sin embargo, con subersion, mi copia local tiene un número de versión que es de la última vez que hice update. Si ahora hago un nuevo update, subversion sólo tiene que comparar la versión de mi copia local y la del repositorio (sólo compara un entero). Si coincide, es que el update no tiene que hacer nada. Si no coincide, el repositorio tiene guardado qué ficheros se tocaron para esa nueva versión (y las anteriores si hay varias versiones de diferencia), así que sólo hay que hacer update de esos ficheros. No es necesario recorrerlos todos uno a uno.

En resumen, un update de un proyecto grande que antes tardaba tres o cuatro minutos, ahora se hace en unos segundos.

Por ello, si tu proyecto va a ser grande, con varios miles de ficheros, CVS no es una buena opción, ya que perderás mucho tiempo haciendo los updates diarios de los fuentes (y si el proyecto es grande, serán muchos días). Es mucho mejor Subversion (o cualquier otro sistema de control de versiones que lleve todo el repositorio con un único número de versión), ya que el update diario es cuestión de segundos.

De la misma forma, crear tags o ramas es muy lento en CVS, porque debe recorrer todos los ficheros y marcar el tag o rama en cada uno de ellos. Mientras que subversion le basta con marcar que la revisión 1234 es un tag o rama. (En subversion no hay tags o ramas, como en CVS, sino que son copias, pero son copias "perezosas", es decir, no se hace realmente la copia hasta que el fichero cambia en la copia).

Por cierto, teníamos varios repositorios de CVSNT y aunque la herramienta de migración cvs2svn comenta que puede haber problemas en la migración a Subversion de CVSNT, no hemos encontrado ninguno (salvo temas de acentos y eñes dentro de los ficheros, en el nombre de los mismos o en el comentario de los commit).

Jun 23

Test Driven Development by example, de Kent Beck.

 

Aprovechando mi puente de cuatro días (fiestas locales de la ciudad en la que trabajo), me he leído "Test Driven Development by example", de Kent Beck. El libro se lee rápido, ya que hay poco texto en cada hoja, mucho código de ejemplo, mucha hoja en blanco para que pongas tus anotaciones y, sobre todo, porque al estar en inglés, me he saltado alegremente todo lo que no he entendido ;-)

De todos son conocidos los tres famosos pasos que hay que seguir con TDD

  1. Escribir un test automático de prueba y ejecutarlo para ver que falla.
  2. Hacer el código mínimo necesario para que el test pase.
  3. refactorizar el código.

Sin embargo, es necesaria la lectura de un libro como este (o aprender de alguien con experiencia) para poder entender qué significan exactamente estos pasos y cómo seguirlos de forma eficiente. Paso a continuación a detallar algunas de las conclusiones prácticas a las que te lleva la lectura del libro.

¿Cuánto debemos avanzar cada vez?

Siguiendo los tres pasos anteriores, podemos irnos a dos extremos. Por un lado, podemos hacer un test tonto, que nos lleve un minuto hacerlo. Luego podemos hacer el código para que pase ese test, que nos lleva otros dos minutos, y luego el refactor, en otros dos minutos. Al final del día podemos haber escrito doscientos tests y docientos trozitos de código y apenas haber avanzado en nuestro proyecto. El otro extremo es pasarnos dos días haciendo un test, una semana para el código de ese test y otra semana para hacer el refactor.

Según el libro, ni lo uno ni lo otro, hay que llegar al punto justo. ¿Y cual es ese punto?. Somos nosotros los que debemos decidirlo en función de nuestras "sensaciones" al ir programando. Los test y el código que hagamos no nos deben resultar demasiado triviales de forma que nos de la sensación de estar perdiendo el tiempo. Sin embargo, si deben ser del tamaño justo para que no nos cueste demasiado pensar el código que tenemos que hacer. Debemos ser capaces de hacer con cierta facilidad el código necesario para el test, sin que nos lleve más de, digamos, media hora conseguir que el test pase.

Si el código que hacemos para cada test nos resulta demasiado trivial, podemos hacer test un poco más grandes. Si el código para el test que estamos haciendo nos empieza a dar que pensar, no estamos muy seguros de cómo hacerlo o si va a funcionar a la primera, debemos hacer test más pequeños. Por ejemplo, si hacemos un test para un método suma, podemos implementar fácilmente el método. Si hacemos un test para un método recursivo de factorial, los más experimentados o con mejor cabeza para la programación pueden hacerlo directamente, pero los más novatos quizás necesiten hacer primero un test para el caso trivial e implementarlo, luego un test para otro número e implementarlo, etc.

De alguna forma, el libro da a entender que tenemos que tender más al ensayo y error con los test que pasarnos ratos largos pensando cómo implementar algo. Una buena medida de que debemos empezar a hacer test más pequeños y menos código en cada paso es cuando los test empiezan a sorprendernos con fallos inesperados, es decir, cuando ya no estamos realmente controlando la situación.

Centrarse en un test cada vez y hacer sólo lo necesario para este test

Este es quizás uno de los puntos que más fácilmente nos podemos saltar. Si hacemos un test y nos ponemos a hacer un código, es muy fácil que nos salgan situaciones o código auxiliar que necesitemos y nos pongamos a hacerlo en condiciones.

Por ejemplo, imagina que para pasar un test que requiere buscar un Alumno en una lista, vemos que necesitamos implementar el método equals() en la clase Alumno, que ese método equals() requiere un código algo rebuscado y además deberíamos implementar el método hashCode() que nos aconseja java siempre que implementemos el equals(). Es muy fácil que nos desviemos de nuestro test para hacer ese equals() y ese hashCode() lo más completos posibles y nos olvidemos temporalmente del test que nos ocupa. O quizás, haciendo el código usemos un método ya hecho y descubramos que ese método necesita un arreglo porque hay un caso que no contempla o debería hacer algo más. Es fácil que ahora nos pongamos a arreglar ese método.

Según el libro, no debemos hacer eso. Debemos implementar únicamente un equals() y/o hashCode() mínimo que nos permita pasar el test lo antes posible, incluso aunque devuelva directamente true o false y un hashCode cero, si con eso basta para que pase el test. Una vez que pasa nuestro test, podemos dar la siguiente iteración y hacer un test para el método equals() y entonces hacer una implementación correcta de equals().

Y la mejor forma de hacer esto y que no se nos olvide después y que nos sintamos cómodos haciendo esas "chapuzas" temporales, es tener un papel al lado del teclado en el que apuntemos los test que creemos que debemos  hacer más adelante. Si tenemos que hacer un equals() en condiciones, apuntamos en el papel que tenemos que hacer un test de equals() de la clase Alumno, escribimos ahora un equals() con la implementación mínima necesaria para que funcione el test en el que estamos trabajando, y nos olvidamos del equals() hasta que le toque el turno. Si tenemos que arreglar aquel método que hemos descubierto, pues apuntamos hacer un test para ese método y no lo tocamos ahora, nos centramos en el test que estamos actualmente trabajando.

Dejar que TDD nos vaya llevando al diseño más simple

Según TDD, debemos hacer en cada momento el código más simple posible que haga pasar el test. En el refactor, según el libro, debemos sobre todo trata de eliminar repeticiones (DRY, Dont repeat yourself o "no te repitas", para los amigos) y es precisamente en este paso de refactor, donde debemos "complicar" nuestro diseño sólo lo justo para evitar esas repeticiones. Veamos esto con un ejemplo concreto.

Imagínate que uno de nuestros test dice que al jefe le podemos fijar el sueldo. Hacemos un test que a la clase Jefe le pone un setSueldo() y comprueba que getSueldo() devuelve el sueldo que hemos pasado. (sí, ya sé que es muy tonto y que este tipo de cosas ni siquiera merecen la pena ser testeadas). Bueno, hacemos nuestra clase Jefe y ese par de métodos tontos y el test pasa.

Ahora, el siguiente test nos dice que podemos hacer lo mismo con un currito. Hacemos el test, hacemos la clase Currito y le ponemos los dos métodos de marras. Los test pasan, pero ahora toca refactorizar. ¿Código repetido?. Sí, las dos clases enteras, lo único que cambia es el nombre. Tal cual tenemos ahora, deberíamos hacer una clase Empleado con todo el código de setSueldo() y getSueldo() y BORRAR las clases Jefe y Currito. Sí, borrar, no hacer herencia, ni interface común ni nada parecido. Ahora mismo no hay nada que distinga a Jefe de Currito y el diseño más simple es tener una única clase con el código común (todo el código) para ambos tipos de personajes. No debemos dejarnos llevar en ningún momento por nuestro entusiasmo ni nuestros bastos conocimientos de OO para mantener la clase Jefe y Currito y hacerles una clase padre Empleado. De momento, simplemente no es necesario y por tanto, no lo hacemos.

Supón ahora que un test nos pide que escribamos en pantalla el tipo de personaje que es. Si es jefe o currito. Pues bien, la solución más simple no es volver a hacer las clases Jefe y Currito. La OO nos lo pide a gritos, pero no es la solución más simple para los tres test que tenemos. La solución más simple es hacer un enumerado JEFE, CURRITO y ponerle un atributo a la clase Empleado, que puede rellenarse en el mismo constructor, con el método getTipoEmpleado() correspondiente.

Ahora, otro test hace que alguna cosa, por ejemplo, comprobar si el Empleado tiene derecho a coche de empresa, y cómo no, sólo si es jefe tiene derecho a ello. Pues bien, mientras sólo este este test, la solución más simple es poner un if comprobando en el atributo enumerado si es jefe. El método tieneCocheEmpresa() de empleado simplemente devuelve el restultado del if.

Y finalmente, piensa en otro test que también da un privilegio al jefe, como comprobar si Empleado tiene derecho a un sillón cómodo y nuevamente sólo si es jefe tiene ese derecho. La solución más simple que podemos dar en este momento es añadir otro método tieneSillonComodo() otro if del atributo … pero cuando llega el momento de refactor vemos que hay código repetido. Ese if está en dos sitios distintos. Ahora, y sólo ahora, es el momento donde TDD nos aconseja que empezemos a pensar en la existencia de dos clases separadas, la de Jefe y la de Currito, cuando empezamos a ver que el "si es jefe" empieza a repetirse en varios sitios. Y sólo si las nuevas clases Jefe y Currito tienen código repetido, debemos mantener la clase Empleado y  heredar de ella. Si dejara de existir ese código común, simplemente eliminamos Empleado y no hacemos herencia. O quizás, en este caso concreto, sea más fácil mantener una tabla de booleanos/privilegio dentro de la clase Empleado.

De hecho, uno de los ejemplos del libro es precisamente algo parecido a esto. Comienza haciendo dos clases (Dolar y Franco) y acaba descartándolas para hacer una única clase (Dinero) que tiene un atributo que indica el tipo de moneda.

Descansos frecuentes

Y el consejo que más me ha gustado del libro: Tener siempre una botella de agua a mano, de esta forma, la fisiología te obligará a tomarte descansos frecuentes … para ir al baño.

Jun 19

¿Será que no me explico?

Hoy no es mi día. Envíe dos correos distintos, de temas distintos, a dos personas distintas. Me levanté del sitio y fuí a hacer algo (¿tomar un café?). Cuando volví a mi sitio, tenia dos correos nuevos. Las respuestas a los que había enviado. Una de las respuestas era …

"¿mande?"

La otra era …

"¿Cualo qué?"

En fin, creo que hay días en que uno no se levanta de la cama con la facilidad de hacerse entender.

Jun 17

El difícil compromiso entre KISS y OO

 

KISS es la abreviatura de "Keep it simple, stupid" o traducido, "Manténlo simple, estúpido". La idea es que el código debe hacerse siempre lo más sencillo posible. Esto ayuda a que haya menos errores y a que sea más fácilmente mantenible en un futuro, ya que será más fácil entenderlo por terceras personas y corregir posibles errores que queden.

OO es la orientación a objetos. Un código diseñado con orientación a objetos debe usar cosas como clases, herencias, polimorfismos, encapsulación, etc. Tras muchos años de diseño orientado a objetos, se ha llegado a que hay determinadas formas de combinar clases e interfaces para obtener unos resultados óptimos, los llamados patrones de diseño. Como consecuencia, un buen diseño orientado a objetos normalmente implica el uso de determinados patrones de diseño. También, en un buen diseño orientado a objetos, se usan varias clases cada una de ellas con una responsabilidad concreta muy definida, es decir, suelen ser clases pequeñas y que sólo hacen cosas concretas. El fin último de la orientación a objetos es conseguir que el código sea reutilizable. Podemos llevarnos a otros proyectos sólo aquellas clases que realmente necesitamos y además, están hechas de tal manera, que no es necesario modificarlas, sino que nos sirven tal cual.

Pues bien, KISS y OO, hasta cierto punto son opuestos y veamos un pequeño ejemplo tonto. Imagina que vamos a hacer un programa que pida al usuario por pantalla los coeficientes de una ecuación de segundo grado y nos muestre el resultado por pantalla.

Si llevamos KISS a sus últimas consecuencias y tratamos de hacer el código lo más simple posible, sin preocuparnos en absoluto de posible reusabilidad de lo que hacemos, lo más sencillo es hacer una única clase con un método main(). En ese método pedimos por pantalla los coeficientes (podemos hacer un método estático encargado de pedir un número por pantalla), echamos las cuentas (podemos meterlas en otro método estático) y sacamos los resultados (si acaso en otro método estático). Resultado final: una clase sencilla, que hace lo que tiene que hacer, con quizás tres métodos además del main(), pero que no es en absoluto reusable. Hace lo que hace y nada más. Sólo pide coeficientes por pantalla, echa unas cuentas y saca los resultados por pantalla.

Si pensamos más bien en hacer algo reusable y aplicamos orientación a objetos, posiblemente hagamos una clase EcuacionSegundoGrado a la que le pasemos los coeficientes y nos devuelva los resultados. Haremos otra clase (o dos clases) para pedir y sacar cosas por pantalla. Estas clases, idealmente, deberían implementar interfaces de pideDato() y muestraDato(), de forma que si más adelante en vez de pantalla es una base de datos, nos baste con implementar esas interfaces. Finalmente, posiblemente hagamos otra clase más con un main() encargada de instanciar las anteriores y hacerlas funcionar conjuntamente. El resultado final es que tenemos una serie de clases con responsabilidades separadas y reusables. Podemos coger cualquiera de esas clases y llevarla tal cual a otro proyecto y usarla a nuestro gusto.

Pero…. hemos perdido la sencillez. Ojo, no quiero decir que estas clases no estén bien hechas o que sean dfiíciles de entender si están bien hechas, pero donde antes teníamos una única clase y siguiendo el main() lo veíamos todo, ahora tenemos tres o cuatro clases, dos interfaces y seguir el código nos obliga a navegar de una a otra … y navegar a través de una interface no es sencillo, puesto que no sabemos qué implementación hay detrás (en este ejemplo sí sabemos cual hay detrás, porque sólo hay una). Obviamente, todo esto es peor si nuestro programa no es tan tonto como una ecuación de segundo grado.

¿Cual es entonces la situación ideal?. Las metodologías ágiles, en general, nos dan la solución bien clara. Haz el código lo más simple posible sólo para lo que tienes que hacer ahora (la historia de usuario concreta que estás haciendo ahora, el bug que estás corrigiendo, etc).

Si tenemos una historia de usuario que dice que pedimos los coeficientes por pantalla, resolvemos la ecuación y mostramos por pantalla los resultados, debemos, sin dudarlo, hacer la primera implementación, la del KISS puro y duro, tal cual nos aconsejan las metodologías ágiles. Cualquier metodología ágil nos aconseja también a hacer test automáticos de prueba. de forma que si más adelante tenemos que rehacer, tengamos seguridad de no estropear.

Supongamos ahora una segunda historia de usuario que nos dice que los coeficientes se deben leer de base de datos. Bien, este y no otro es el momento de hacer la interface con el método pideCoeficiente(), hacer dos implementaciones, la de pantalla y la de bd, y cambiar nuestro código para que no vaya directamente contra pantalla, sino contra esta interface cuya implementación alguien le pasará de fuera. Al tener test de la primera parte, tendremos seguridad de no haber estropeado lo que ya funcionaba después de tocar y ya habremos empezado nuestro diseño OO, por necesidad, y no por gusto. Ojo, todavía no hemos extraido la clase EcuacionSegundoGrado ni hemos hecho metodo en la interface de muestraResultado(), porque nadie nos ha pedido mostrarlo en otro sitio que no sea pantalla.

Resumiendo, es muy importante para tener un código lo más claro posible, no lanzarse sin ton ni son a hacer clases, interfaces, aplicar patrones y usar a tope la OO. Hay que ser realista y usar esas características de OO sólo cuando es estrictamente necesario o cuando estamos seguros al 99% de que vamos a necesitarlas. En el ejemplo anterior, si tenemos la lista de historias de usuario en algún sitio y vemos que la segunda está justo detrás de la primera, quizás, y en contra de las metodologías ágiles estrictas, podamos ir haciendo la interface ya en la primera implementación.

Jun 11

¿La programación es una carrera que llena tu vida?

 

Leo en DosIdeas el siguiente post : ¿La programación es una carrera que llena tu vida ?. Precisamente eso es algo en lo que estaba reflexionando en días anteriores y ahí van los porqués.

En el curro me dedico o intento dedicarme a programar. Siempre he huído despavorido de ser jefe de proyecto, de un grupo o de cualquier otra cosa. ¿Por qué?. Por que pienso que todas las relaciones con clientes y en general con gente en ambiente de trabajo siempre acaba llevando a tener roces. Al principio con el cliente todo son buenas palabras, pero cuando los plazos empiezan a echarse encima y log bugs del sistema empiezan a aparecer y ser complejos de resolver, la relación con el cliente puede volverse tensa. Con los compañeros de trabajo, habiendo buen rollo, no suele haber problemas. Pero al llegar a las etapas finales de proyecto, la presión, los bugs y las muchas horas de más pueden llevar también a roces y discusiones. "Mi parte funciona, el bug es tuyo". "Y unas narices". Si ya entre compañeros puede llegar a ser tensa, no te digo entre jefe y curritos. Además, en momentos de presión, un jefe debe ser malo, debe conseguir que la gente se quede a echar horas y terminen a tiempo. Los jefes inútiles lo hacen amenazando, regañando, chillando e imponiéndose. Los jefes astutos (y esos son los peores), lo consiguen sin necesidad siquiera de pedirlo. Todavía recuerdo un Domingo que me quedé hasta las dos de la madrugada para un proyecto que pasaba pruebas el Lunes y lo peor es que la jefa de proyecto… ¡ ni siquiera me pidió que fuera el fin de semana !, consiguió que yo lo hiciera voluntariamente.

Sin embargo, cuando me dedico a programar es cuando realmente estoy en mi salsa. Puedo pasar horas entretenido, sin darme cuenta del tiempo que pasa. Y cuando consigo acabar algo o resuelvo algún bug especialmente complejo, es cuando más satisfecho me voy a casa. De hecho, cuando llego a casa, suelo encender el ordenador y ponerme a hacer/aprender cosas de programación o relacionas. Además de mi trabajo, es también mi hobby.

Sí, entiendo que si quisiese ser jefe de la forma tradicional, cogiera proyectos como jefe de proyecto, me dedicara con mi traje y corbata a aguantar y engañar al cliente y me importara tres pepinos "explotar" a mis compañeros, posiblemente cobraría más y estaría mejor considerado en la empresa, pero… a mi, por lo menos, no me merece la pena. Prefiero tener la conciencia tranquila y dedicarme a algo que realmente me gusta. Irme a mi hora a casa (salvo agún Domingo excepcional), llevarme bien con la gente y aprender cosas nuevas todos los días.

Jun 05

Scrum: tan simple y tan complejo

 

Llevamos aproximadamente tres sprints de una semana con el grupo de tres desarrolladores que participan en seis proyectos simultáneamente. La verdad es que scrum se aprende en diez minutos, pero me da la impresión de que se puede tardar toda una vida en aplicarlo correctamente.

Nuestro primer sprint fue más o menos bien, seguimos casi todo lo de Scrum a rajatabla y hasta donde pudimos, pero sprint tras sprint, la cosa se va convirtiendo en rutina y las prácticas de Scrum se van relajando. Aquí van los problemillas que vamos teniendo y los porqués.

Uno de los problemas gordos es la tendencia de los programadores a coger las tareas/historias de usuario de los proyectos en los que tradicionalmente están asignados, o de las funcionalidades que tradicionalmente conocen mejor. Esto no es demasiado grave, pero tiene dos pegas importantes:

  • Los daily scrum son más un cotilleo que un intercambio de información útil. Cada uno trabaja en lo suyo y cuenta a los demás qué está haciendo, pero a los demás realmente no les afecta. Escuchan por cortesía o por curiosidad, pero no va más allá de eso.
  • El que termina su historia de usuario de su proyecto/funcionalidad, tiende a coger otra tarea de su proyecto/funcionalidad que no está planificada. Podría coger alguna de las de los demás, pero el cambio de contexto es muy fuerte: debe actualizar de CVS el otro proyecto, ponerse al día e implementar algo que no conoce. Al ser los sprint tan cortos, de una semana, esto suele suceder el Miércoles o el Jueves, por lo que para un día no le merece la pena ese cambio de contexto.

Sí, es cierto que no deberían tener un proyecto/funcionalidad asignada (y de hecho no lo tienen, pueden coger libremente lo que quieran), pero la inercia de años de trabajo es muy grande para cambiarla en unas pocas semanas. Supongo que no se conseguirá plenamente hasta que se vayan cerrando los proyectos actuales y se comiencen los nuevos.

Otro problema gordo es la corta duración de los sprint. Elegimos esa duración para poder cambiar de semana en semana de un proyecto a otro (recordad, tres desarrolladores, seis proyectos), para poder atender sus necesidades y que ninguno se sienta desatendido mucho tiempo. Haciendo sprints de una semana, podemos decirle a un jefe de proyecto…. "¿puedes esperar a que hagamos eso que nos pides la semana que viene?" y no suele haber pegas. Si le dices, "¿puedes esperar un mes?", pondrá el grito en el cielo.

Pero los sprint tan cortos tienen la pega de organizar demos para ver muy poca funcionalidad implementada. Si para cada semana cogemos un par de proyectos y un par de historias por proyecto y no acabamos alguna de las historias… queda un demo muy pobre. Así que la demo del segundo sprint no la hicimos (había poca funcionalidad nueva que enseñar, ya que casi todo fue un paquete de corrección de bugs).

Con este grupo concreto estoy pensando en algo como Kanban. Por lo que he leido, viene a ser como Scrum, pero sin los sprint fijos cada cierto tiempo. Se hacen las historias de usuario, se sigue llevando el juego de post-it y se van acabando por prioridades, midiendo tiempos. La diferencia es que no hay sprint fijo, por lo que las historias tampoco son fijas. Se pueden ir cogiendo historias sobre la marcha y desarrollándolas, dejando la demo para cuando se considere adecuado. La única limitación es que no puede haber simultáneamente más de X historias haciéndose, más de Y tareas haciéndose, etc.

En cualquier caso, independientemente de lo bien o mal que hagamos Scrum, sí se han notado mejoras muy importantes en el trabajo y paso a enumerar algunas.

  • Por un lado, yo, como responsable oficial del grupo y gracias al daily scrum, estoy muchísimo más al tanto de lo que están haciendo en cada momento. Eso hace que no me desespere tanto cuando las cosas parece que tardan, ya que vivo los problemas día a día y sé los motivos de la tardanza. Y también hace que yo curre más, ya que al ser consciente de esos problemas, intento solucionarlos lo antes posible…. ¡¡ Incluso me he puesto a codificar más en serio !!. Antes también codificaba algo, pero picoteando de aquí y de allí, en lo que más me apetecía en cada momento. Ahora lo hago centrado en ayudar a alguien con lo suyo y conseguir que una historia de usuario se termine.
  • Por otro lado, aunque cada desarrollador tiende a lo suyo, es consciente de las tareas y problemas de los demás, por lo que siempre tienden más a ayudarse por iniciativa propia. Antes la ayuda se daba sólo si alguien te la pedía y según el momento de la petición, podía ser molesto. Ahora se ofrecen voluntariamente para ayudar, más que nada, porque son conscientes de que pueden ayudar si a ellos les cuesta menos realizar esa tarea.
  • Los proyectos van avanzando y hay más visibilidad de ello. Todos somos conscientes de las historias que se van terminando y sabemos qué cosas están o no hechas en cada proyecto y hasta qué punto están hechas. Antes alguien cogía una funcionalidad, se ponía a implementarla y un par de meses después decía "ya tá". Y estaba o no estaba, según lo hábil que fuera ese desarrollador concreto desarrollando y probando. Ahora sabemos día a día qué cosas están y si están bien o todavía con fallos.

Resumiendo, Scrum se aprende en cinco minutos, es muy difícil de hacer correctamente, pero se obtienen importantes beneficios incluso no haciéndolo bien al 100%. Hay más sensación de equipo y la gente trabaja más centrada en unos objetivos concretos.

Jun 04

Migrando a Subversion

 

¡¡ Por fin tenemos nuestro primer proyecto migrado a subversion !!.

La historia ha sido una verdadera odisea. Normalmente usamos CVS, pero en el momento de empezar a trabajar con ramas un poco en serio, el tema se hacía demasiado pesado. CVS crea las ramas recorriendo todos los ficheros del repositorio y marcando la rama para cada uno de ellos. Crear una rama en CVS tardaba cerca de media hora en cada proyecto implicado. Subversion, al tener todo el repositorio con un número de versión único, no necesita recorrer todos los ficheros uno a uno para hacer la rama, le basta con marcar en algún sitio que en ese número de versión comienza una rama. Tarda como la mitad de medio segundo (es un decir) en hacer la rama de un repositorio, independientemente de la cantidad de ficheros que haya almacenados.

Por otro lado, nuestro servidor de CVS se estaba quedando "viejito". Una estación solaris del año del "catapúm" con 128 megas de ram (al que hace poco se le estropeo un módulo de 64 y creo que de esos ya no hay ni el rastro), con el disco duro (de pocos gigas) permanentemente al 99%. Total, que se imponía también un cambio de máquina.

Así que aprovechando lo uno con lo otro, decidí que nos pasabamos a Subversion en un servidor nuevo. Dicho….. y a esperar.

Primero convencer a la gente de las excelencias de Subversion sobre CVS. Como siempre, a unos pocos les parece estupendo, a muchos les importa tres pepinos y a algunos lo de "¿Para qué vamos a cambiar si ahora funciona?". A base de dar la paliza, conseguí convencer (o al menos dejaron de protestar) a todos.

Después lo de conseguir el servidor nuevo. Ni el departamento ni los proyectos para los que trabajamos estaban dispuestos a comprar un servidor (pensé seriamente en ir a tomar los cafés encima del servidor actual, para que ocurriera "una desgracia"). Así que a negociar con el departamento de informática de la empresa, a ver cuánto nos cobran por darnos un subversion en uno de sus servidores. Tras mucho tira y afloja (y sobre todo gracias a la persona que nos hizo de intermediaria en la software lab con la que trabajamos), nos dieron el subversion gratis. Entre pedir el servidor a los proyectos, intentar convencerles y hablar con el departamento de informática pasaron casi dos meses.

Y hoy, por fin, la primera migración. Me bajé el cvs2svn e intenté migrar uno de nuestros proyectos. Desgraciadamente, intenté hacerlo sobre windows y no hubo manera. Tenía el cygwin y teóricamente todo lo necesario, pero me fallaba en un punto en el que se hace una llamada al comando "sort". El de windows no vale, porque funciona distinto del de unix. El de cygwin aparentemente me daba problemas porque se liaba con las \ de los directorios (al ser unix, espera / de directorio). Me bajé los UnxUtils, pero al ejecutar el comando sort daba un error muy raro (insuficiente espacio en disco, o algo así, habiendo más de 20 Gigas libres). Así que reinicié el ordenador para arrancar en linux y ahí fue todo rodado a la primera.

Total, ya tenemos nuestro primer proyecto en subversion. El primero de una larga lista de ellos que debemos ir migrando poco a poco.