Feeds:
Entradas
Comentarios

Hola de nuevo, como vimos en el anterior post la herencia de implementación puede ser un verdadero problema. Sí, es verdad, existen técnicas para diseñar nuestras clases para que puedan ser reutilizadas mediante herencia. Notad la cosa tan fea que acabo de decir. Si quiero que mi clase sea reutilizada, tengo que diseñarla de antemano, es decir, no importa para nada la interfaz de la clase, ni los tests que tenga sobre esta, ni la especificación del contrato. Si quiero reutilizar utilizando “extends” tengo que saber como está implementada la clase internamente, incluyendo todas las cosas “private” que tenga por dentro. A algunos les parecerá de lo más normal, a mi me da que pensar, ¿y entonces, para que leches sirve la encapsulación?

En cualquier caso voy a profundizar un poco en como hacer las cosas “bien” con la herencia de implementación, para ver las limitaciones de la técnica, y para ver si me quito el San Benito de programador petardo.

Tras reflexionar sobre el problema de mantenibilidad que tienen con la clase “Lista”, el equipo decide contratar a un consultor externo, y tras convencer a la mesa de compras de que paguen la estratosférica tarifa de 45 euros/hora, consiguen contratar a uno de los más reputados expertos locales. Haciendo honor a sus honorarios, el experto diagnostica y resuelve el problema en un santiamén (lástima que cobre jornada o fracción). “Pero señor@s”, dice el experto, “¿es que nunca habéis leído el GoF?”, continua, “aquí os pongo un patrón Template Method que os vais a chupar los dedos”. Ni corto ni perezoso modifica el código, y ListaSencilla le queda así.

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos)
      insertar(elemento);
  }
  /**
   * Este método es invocado cada vez que un nuevo elemento
   * es insertado en la Lista
   * @param elemento El elemento que acaba de ser insertado
   */
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    // Intencionadamente en blanco, es un "hook" para sobreescribir
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Nótese el uso de la palabra clave “final”. Esta palabra clave se puede poner a nivel de método, o mejor, a nivel de clase, indicando que no se puede sobrescribir el método o hacer extends de la clase. La palabra clave “final” es un gran invento de JAVA, pero claro,  hay que leer entre líneas: un lenguaje que tiene herencia de implementación, pero que además tiene una palabra clave que prohíbe la herencia, muy sospechoso. En realidad los diseñadores de JAVA sabían muy bien que la reutilización de código mediante herencia de implementación es peligrosa. Conscientes de ellos, añadieron “final”, para dar la capacidad al desarrollador de marcar una clase o método con “¡Peligro, no heredar o sobrescribir!”, en el caso de que no hubieran diseñado de antemano la clase para herencia. Desgraciadamente no he visto que mucha gente use “final”.

Siguiendo con la historia, al usar “final” y “Template Method” en conjunción con el método protegido “procesamientoAdicionalTrasInsertarElemento”, se desactiva cualquier problema. Por un lado no podemos sobrescribir los métodos peligrosos, ya que son “final”. Por otro lado el implementador de la clase diseña su código de tal forma que si quiero reutilizar la clase, no lo pueda hacer de cualquier forma, sino sólo sobrescribiendo el método protegido a tal fin. Con este cambio, la clase “ListaAuditable” queda como sigue:

public class ListaAuditable extends ListaSencilla {
  private Auditor auditor;
  public ListaAuditable(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    auditor.elementoInsertado(elemento);
  }
}

Da la impresión de ser un código bastante elegante. Desde luego ha merecido la pena pagar la exorbitante tarifa del consultor externo. Sólo hay un problema pequeñito, el test de “ListaSencilla” se ha complicado. Claro, ahora no sólo hay que testear el contrato “Lista” en “ListaSencilla”, que es el que define como se usa la clase por parte de los consumidores de esta. Ahora hay otro contrato, representado por el método protegido, entre “ListaSencilla” y todas las clases que la extiendan. Este contrato es “en negro”, ya que no es público, sino “protegido”. Necesitamos un test que pruebe que el método protegido es llamado en los momentos adecuados y con los parámetros adecuados. Pero este contrato no está en una interfaz de un colaborador, sino en un método “protected”, ¿cómo hacemos el test? Afortunadamente con un poco de programación “clever” se puede terminar haciendo testing de esto. Lo dejo como ejercicio al lector.

Otro pequeñito problema es que alguien siga pensando que no tiene por que entender la implementación de la superclase, que le vale sólo entendiendo el contrato público (pobrecito), y al heredar haga cosas como esta:

public class ListaAuditablePetarda extends ListaSencilla {
  private Auditor auditor;
  public ListaAuditablePetarda(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    super.insertar(elemento); // Ouch !!!
    auditor.elementoInsertado(elemento);
  }
}

¡ Ay, que cosa más fea ! ¿Es que nadie se lee el Javadoc? Obviamente nadie debería usar super para llamar a un método peligroso (final), sólo sobrescribir métodos protegidos o métodos públicos no peligrosos (no final). Bueno, a parte de estos problemitas subsanables con un poco de “hacking”, y despedir al inútil que cometió la anterior tropelía, parece que la técnica funciona.

Pasa el tiempo y aparece otro problema con este enfoque. ¿Qué ocurre si queremos hacer la extensión de una clase de una forma no prevista de antemano? No puedes. Tienes que volver a modificar la clase padre para añadir extensibilidad en el nuevo “eje” que te haga falta. Por supuesto en cualquier desarrollo serio, ha habido una extensa e intensiva fase de análisis funcional y diseño técnico, y esas cosas no pueden pasar, ya que se han cubierto todos los posibles cambios. Desgraciadamente nuestros amigos no son tan profesionales y usan una cosa llamada “agile” que les impide hacer un buen análisis y diseño “up-front” como dios manda, y no tienen muy claro las necesidades de diseño futuro de su sistema.

Nuestros sufridos desarrolladores se encuentran con un nuevo requisito, ahora resulta que algunas especializaciones de “ListaSencilla” pueden rechazar un elemento, y negarse a insertarlo si cumple alguna característica concreta. Desgraciadamente en las clases hijas de “ListaSencilla” no pueden añadir esta funcionalidad, deben abrir su implementación, y siguiendo “Template Method” añadir un punto de extensión más.

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    if (!esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  /**
   * Este método es invocado para pedir permiso sobre si un elemento
   * puede ser insertado o no
   *
   * @param elemento
   *        El elemento que queremos insertar
   * @return si se puede insertar o no
   */
  protected boolean esInsertable(String elemento) {
    return true;
  }
  /**
   * Este método es invocado cada vez que un nuevo elemento
   * es insertado en la Lista
   *
   * @param elemento
   *        El elemento que acaba de ser insertado
   */
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    // Intencionadamente en blanco
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Bueno, no duele tanto, al fin y al cabo el código parece razonablemente limpio y “ListaAuditable” ni se ha enterado. Ahora implementamos el requisito, realmente ocurre que pueden existir tres tipos de listas auditables, aquellas que no admiten elementos que contengan espacios y aquellas que sólo admiten elementos de longitud par. Bueno, a implementar. La cosa queda como sigue:

public class ListaAuditableConElementosSinEspacios extends ListaAuditable {
  public ListaAuditableConElementosSinEspacios(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Y también:

public class ListaAuditableConElementosLongitudPar extends ListaAuditable {
  public ListaAuditableConElementosLongitudPar(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

Ok, no esta nada mal, casi podemos cantar victoria, ¡además hemos reutilizado “ListaAuditable”!

Pasa el tiempo, y con él llegan cambios, e inevitablemente nos llega otro requisito. Resulta que las listas que no son auditables también pueden existir en la forma de listas que no admiten elementos con espacios, y las que sólo admiten elementos de longitud par ¡Qué dura es la vida del desarrollador! De nuevo al código, queda lo que sigue:

public class ListaConElementosSinEspacios extends ListaSencilla {
  public ListaConElementosSinEspacios() {
    super();
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Y también:

public class ListaConElementosLongitudPar extends ListaSencilla {
  public ListaConElementosLongitudPar() {
    super();
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

¡Han aparecido otras dos clases! ¡Ya tenemos 6 implementaciones de “Lista”! Bueno, al menos son cortitas y sencillas, y estamos reutilizando un montón. El problema es que estamos a empezar a detectar un tufillo a violación del DRY. De hecho es la misma implementación de antes pero heredando de “ListaSencilla” en vez de “ListaAuditable” ¿Quién dijo que el copy&paste era malo? Quizás si en vez de usar “extends” usáramos otra cosa… nada, todo el mundo sabe que la herencia es “esencial” a la OO, su “killer feature”, mejor seguimos con el “Template Method”, y no liamos más la cosa.

Seis meses más tarde tenemos 60 clases “Lista”. Resultó que salieron cinco formas más de filtrar los elementos de las listas, y encima además de listas “auditables” y “sencillas”, tenemos listas “lazy”, listas “persistentes”, etc. En fin, que necesitamos una clase por cada combinación posible. La verdad es que esto ya no parece tan limpio.

Una última reflexión. Para el que sea propenso a pensar en cosas inútiles, aquí lanzo una pregunta, ¿cómo es mejor implementar ListaAuditableConElementosLongitudPar? ¿Heredando de ListaConElementosLongitudPar o heredando de ListaAuditable? ¿Da igual? Y en caso de que sea igual, ¿no parece un accidente histórico el decidir implementarla de una forma y no de otra, y no una decisión de diseño guiada por criterios de ingeniería del software? Inquietante, al menos para mi.

Reconozco que este es un ejemplo un poco “cogido por pinzas”, pero no me negaréis que ilustra un problema real, que tal vez muchos estáis reconociendo de haberlo sufrido. Esta explosión combinatoria de clases es algo que se conoce también desde antiguo, y está ligado al hecho de que tenemos herencia de implementación simple. Si alguno está pensando en solucionarlo con herencia de implementación múltiple, nada vosotros mismos. Si no habéis tenido bastante con la herencia simple, ahora si que os podéis meter en un buen lío. Está claro que el verdadero problema reside en usar “extends” como mecanismo de  reutilización de código.

Resumiendo:

  • Puedo reutilizar código con “extends”, pero tengo que diseñar de antemano para ello.
  • Al diseñar de antemano, sólo puedo reutilizar aquello que está pensado para serlo, y no cualquier característica pública de la interfaz de la clase. Si necesito reutilizar mediante “extends” alguna característica de la clase que no esté diseñada para ello, pues o me aguanto, o no uso “extends” o simplemente abro la clase padre y la modifico (muy SOLID no es, no).
  • El que reutilice con “extends” una clase debe conocer si ésta está diseñada para ello, y por lo tanto conocer la implementación interna de la clase padre.
  • La forma más común (no la única), es usar un patrón “Template Method” de GoF, pero esto dificulta el testing, ¿cómo pruebo los métodos protected?
  • Algún programador petardo, que no se haya leído nuestro excelente Javadoc de los métodos “protected”, puede acabar invocando al “super” y terminar con errores en las nuevas clases, errores que a veces son sutiles de detectar.
  • Esta forma de reutilizar código está sujeta además a la explosión combinatoria de clases.

Algunos argumentaron en el post anterior que “extends” no sirve como mecanismo de reutilización de código, sino como mecanismo de especialización. O sea, para especializar clases pero sin reutilizar código. Pues vaya tela, ¡yo pensaba que lo que mantenían relaciones de especialización eran las interfaces!. Y que alguien me diga, ¡ para que sirve la herencia de implementación si no es para reutilizar código ! ¡Para que quiero especializar una clase sin reutilizar su código! ¡ Quiero un ejemplo concreto !

¿Existe alguna forma mejor de reutilizar código? La respuesta es sí, pero viene con un precio a pagar. A algunos, como a mi, este precio le parece pequeño, pero a otros les parece enorme. Lo veremos en el siguiente post.


Hola a todos, este post me lo venía pidiendo el cuerpo desde hace tiempo. Es común oírme decir que la herencia de implementación (“extends” para los javeros) es una mierda, y cada vez que lo digo veo caras de estupefacción e incluso recibo alguna que otra crítica. La verdad es que no lo entiendo bien ya que esto se sabe desde hace bastante tiempo, es más hasta tiene un nombre: “problema de la clase base frágil”, aunque yo prefiero decir que “extends es una mierda”. Incluso, algunos dicen que James Goslin, el padre de JAVA, reconoció que incluir “extends” en el lenguaje fue un gran error. Yo considero que “extends” no tiene nada que ver con la OO (al menos no con el concepto original), sino que se incluyó porque para algo tenían que servir las clases, y se pensó que se podrían usar para reutilizar código mediante la herencia de implementación.

Vamos al lío, os voy a contar una historia. Suponed la siguiente interfaz:

public interface Lista {
	void insertar(String elemento);
	void insertarVarios(Collection elementos);
	String describirContenido();
}

Se trata de una interfaz sencilla que representa una lista, con dos métodos para añadir contenido. Veamos ahora una implementación de tal interfaz:

public class ListaSencilla implements Lista {
	private List datos = new ArrayList();

	public ListaSencilla() {
		super();
	}

	@Override
	public void insertar(String elemento) {
		datos.add(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		for (String elemento : elementos)
			this.insertar(elemento);
	}

	@Override
	public String describirContenido() {
		StringBuilder descripcion = new StringBuilder("Contenidos: ");
		for (String elemento : datos) {
			descripcion.append("'");
			descripcion.append(elemento);
			descripcion.append("' ");
		}
		return descripcion.toString();
	}
}

¿Sencillo verdad? Es una implementación muy directa y no tiene mayor misterio. Ahora supongamos que a otro miembro del equipo, que no escribió la clase “ListaSencilla”, le encargan desarrollar una implementación de “Lista” que cada vez que se añada un elemento a ésta, informe a un objeto auditor de tal suceso. Como nuestro desarrollador está enfocado en el objetivo de esta nueva clase, que es informar al auditor, y no le interesa volver a reimplementar y duplicar la lógica de inserción de datos, decide reutilizar código. ¿Qué mejor manera de utilizar el venerable “extends”? Al fin y al cabo, la herencia de implementación es la verdadera ventaja de la OO y su mecanismo más potente para reutilizar código. Mirando la especificación de “Lista” y conociendo que ya hay una implementación “ListaSencilla” disponible, le sale esto:

public class ListaAuditable extends ListaSencilla {
	private Auditor auditor;

	public ListaAuditable(Auditor auditor) {
		super();
		this.auditor = auditor;
	}

	@Override
	public void insertar(String elemento) {
		super.insertar(elemento);
		this.auditor.elementoInsertado(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		super.insertarVarios(elementos);
		for (String elemento : elementos)
			this.auditor.elementoInsertado(elemento);
	}
}

El desarrollador ni se molesta en probarlo, obviamente va a funcionar, él ha respetado el contrato de la interfaz y está usando a los amiguetes “extends” y “super”. Más tarde le llega un bug, ¡resulta que el auditor es notificado por cada elemento por duplicado! Desconcertado nuestro programador recurre a esa poderosa arma de la ingeniería del software, el depurador, y consigue aclarar el misterio. La implementación del método “insertarVarios” en la superclase llama internamente a “insertar”, que como está sobreescrito en la clase hija también notifica al auditor, pero claro, “insertarVarios” de la clase hija también notifica al auditor por su cuenta, y de ahí la doble notificación al auditor. Mas claro agua, y si no lo habéis entendido, ¡depurad!

El desarrollador aprende la valiosa lección de que no sólo debe saber el contrato del código que quiere reutilizar, sino que debe analizar con detalle como está diseñada internamente la clase a reutilizar. Ya sabía él que todo esto del diseño por contrato y las interfaces no eran más que paparruchas para vender libros y cursos. Tras pensar detenidamente cambia el código y le queda:

public class ListaAuditable extends ListaSencilla {
	private Auditor auditor;

	public ListaAuditable(Auditor auditor) {
		super();
		this.auditor = auditor;
	}

	@Override
	public void insertar(String elemento) {
		super.insertar(elemento);
		this.auditor.elementoInsertado(elemento);
	}
}

Genial, bug resuelto de la mejor manera posible: borrando código. Más contento que unas castañuelas, el desarrollador se va a casa con la satisfacción del buen trabajo cumplido.

Pasa el tiempo, y un desarrollador senior, revisando la clase “ListaSencilla”, e ignorante de que esta clase tiene más descendientes que Gengis Khan, decide optimizar el método “insertarVarios”. Decide ahorrarse el bucle, y que leches, reutilizar código de la clase “ArrayList” que para eso viene en la JDK, estos desarrolladores junior sufren del síndrome NIH… La nueva versión:

public class ListaSencilla implements Lista {
	private List datos = new ArrayList();

	public ListaSencilla() {
		super();
	}

	@Override
	public void insertar(String elemento) {
		datos.add(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		datos.addAll(elementos);
	}

	@Override
	public String describirContenido() {
		StringBuilder descripcion = new StringBuilder("Contenidos: ");
		for (String elemento : datos) {
			descripcion.append("'");
			descripcion.append(elemento);
			descripcion.append("' ");
		}
		return descripcion.toString();
	}
}

¡ Qué sencillez ! ¡ Qué elegancia ! No por nada es un “lead developer” que se conoce la JDK al dedillo. Otro que se va para casa inconsciente de la que acaba de armar. ¿Por qué debería preocuparse? Al fin y al cabo el contrato de la interfaz “Lista” se cumple, y para demostrarlo tiene un test automatizado de hermoso color verde. Mientras tanto, la “ListaAuditable” y posiblemente muchas más subclases de “ListaSencilla” comienzan a fallar sin previo aviso.

Al día siguiente, el “lead developer” decide arreglar el problema, mira a ver el código de “ListaAuditable” y lo solventa en menos que canta un gallo. El código queda así:

public class ListaAuditable extends ListaSencilla {
    private Auditor auditor;

    public ListaAuditable(Auditor auditor) {
        super();
        this.auditor = auditor;
    }

    @Override
    public void insertar(String elemento) {
        super.insertar(elemento);
        this.auditor.elementoInsertado(elemento);
    }

    @Override
    public void insertarVarios(Collection elementos) {
        super.insertarVarios(elementos);
        for (String elemento : elementos)
            this.auditor.elementoInsertado(elemento);
    }
}

¿Os resulta familiar ;-) ?

“Tonto desarrollador junior”, piensa el lead developer, “se nota que en vez de fijarse en la interfaz y los tests, se ha mirado la implementación de la superclase, y por ahorrarse un método la que liado…”. En fin, muy triste :-(

Algunos de vosotros pensará que he hecho trampas, que no he usado “bien” la herencia de implementación y que soy un petardo de programador. En el siguiente post explicaré como hacerlo correctamente. Pero ésto no es lo importante, lo que quiero mostrar es que tenemos un mecanismo de reutilización de código, la herencia de implementación, que es más peligrosa que una caja de bombas. Yo prefiero no usar un mecanismo con el que tengo que andarme con pies de plomo, aunque me haya leído y entendido el contrato del código que quiera reutilizar. La herencia de implementación no es segura porque rompe la encapsulación, y necesito saber cómo está implementada la clase padre para poder heredar de ella de forma segura. Todo esto se produce porque mezcla el estado e implementación privada de la superclase con la de la clase hija en una única instancia, en vez de mantenerlas separadas. ¿No sería mejor tener un mecanismo de reutilización de código que siempre fuera seguro? Sí, claro que existe, y lo veremos más adelante.


Hola a todos, aquí estoy de nuevo tras un tiempo de inactividad. La verdad es que andaba yo sin ningún tipo de inspiración para un nuevo post cuando el mundo real me ha obligado a escribir este. La verdad es que en estos meses, tanto en conversaciones, como en twitter, como en varios cursos de calidad, agilismo y testing que he impartido, han surgido de manera recurrente las siguientes preguntas: ¿y cómo engancho el selenium en todo esto? ¿Y cuándo grabo las navegaciones para automatizar las pruebas funcionales? ¿Y porque tanto rollo de BDD si ya tengo el producto “Super UI Test Automator Robot” que me graba las navegaciones? Y así una detrás de otra. Reconozco que si no te interesa el enfoque BDD ni el TDD, puedes seguir grabando navegaciones como modo de “automatizar test funcionales”. Para el resto de vosotros os ofrezco varias razones por las que considero que usar éste enfoque es una aberración:

La primera razón es bastante trivial, si haces BDD entonces tienes un enfoque “test first”, es decir, escribes el test antes que la implementación, y no se escribe ni una sola línea de código hasta que no tienes una especificación (test) en rojo. En este sentido es completamente imposible usar el paradigma de grabar navegaciones para automatizar el testing, ya que tienes que tener el test automatizado antes que la implementación y por lo tanto no puedes grabar nada.

La segunda razón tiene que ver con el mantenimiento. Si decidimos que el test de cada escenario de una historia de usuario se va a automatizar grabando una navegación, entonces lo que estamos testeando realmente es la UI y no la funcionalidad propiamente dicha. Cada vez que se produzca un cambio trivial en la UI de una página o panel, vamos a tener que grabar de nuevo todas las navegaciones que usen ese panel o página, aunque la funcionalidad no haya cambiado realmente. Claramente al usar el paradigma de “grabar navegación” estamos acoplando nuestros tests al diseño fino de la interfaz de usuario, que como todos sabemos, cambia con más frecuencia que la funcionalidad propiamente dicha de la aplicación. Un simple cambio en el atributo “name” o “id” de un elemento, o eliminar un botón de “buscar” para hacer una búsqueda en tiempo real, nos va a estropear las navegaciones.

Ojo, también nos podemos meter en este lío haciendo BDD, con por ejemplo Cucumber, si no tenemos cuidado. Pero mi argumento es que si usamos una herramienta que graba navegaciones como base de nuestra estrategia de testing funcional, este problema es inevitable. Por el contrario si decidimos usar un enfoque basado en programar nuestros tests podemos evitarlo fácilmente. ¿Cómo? Simplemente usando el patrón “page object”, e implementar cada “page object” con algún framework de automatización de UI, como WebDriver de Selenium o Watir. De esta forma si se produce un cambio en una UI, que no altere la funcionalidad, entonces sólo necesitamos retocar el “page object” correspondiente, sin necesidad de modificar nada en nuestros tests, escenarios y steps.

El que nuestros tests sean código hecho por nosotros es bueno. Podemos aplicar todas las técnicas de ingeniería del software que conocemos y aumentar la mantenibilidad de nuestros tests. Eso es, recordad que el código de test tiene que ser mantenible, y todo el tema de legibilidad, DRY y SOLID se le aplica, y por lo tanto podemos usar toda nuestra habilidad para que esto se cumpla.

Bien, y esto es todo, ¡ no diréis después que mis posts son muy largos ;-) !

P.S. Sois programadores, ¡ que no os asuste programar ! ¡ No os escondais en herramientas !


Hola a todos, tras digerir un poco la experiencia CAS2011, saco dos conclusiones:

  • Esta CAS2011 me ha gustado más que la del año pasado. Desde mi punto de vista la organización ha mejorado y ha habido más caras nuevas.
  • Nos estamos mirando el ombligo.
Algunos sabréis que hace poco estuve en la ScrumGathering 2011, organizada por la ScrumAlliance en Londres. Cuando la gente se me presentaba, el diálogo resultante llevaba más o menos las siguientes líneas:
Agilista Internacional: ¿Ah, Español?
Agilista de pueblo (o sea, yo): Sí
AI: ¿Y allí hay agilistas también?
AP: Sí, claro, tenemos dos eventos importantes al año, la CAS y el AOS
AI: ¿Y va mucha gente?
AP: Sí, entre 150 y 250 personas a cada una
AI: ¡ Wow, es mucha gente ! Pero, …. ¡ no había oído hablar de estas conferencias en mi vida !”
AP: Claro, por que es todo en español
AI: WTF !
Iteremos sobre toda la gente con la que hablé y os hacéis una idea de lo quemado que me volví al pueblo. Pero eso no fue lo peor, lo peor fue que la mesa redonda de la CAS2011, ¡ fue en español ! El pobre J.B. Rainsberger tuvo que hablar en castellano, ¡ inaudito ! Seguro que @jbrains no vuelve a ninguna conferencia española…
Creo que ha llegado el momento de dar un paso adelante, sacudirnos los complejos y desencasquetarnos la boina de la cabeza. ¿Por qué no empezamos a montar la siguiente CAS pensando en que sea un evento internacional? Creo que eso tendrá al menos los siguientes beneficios:
  • Variedad en los asistentes y en las propuestas.
  • Proyección internacional de la comunidad española.
  • Atraerá a más patrocinadores. Una CAS de 300 o 400 personas con ámbito internacional seguro que es más atractiva que una de 200 con proyección nacional.
  • Networking. No es networking si siempre hablamos con la gente que conocemos (gracias por recordarme esto @david_bonilla). Si vienen personas de todo el mundo las oportunidades de networking aumentan.
  • Cuando yo vaya a conferencias internacionales no tendré que escuchar más WTF y sufrir más miradas condescendientes :-)
Por lo tanto yo propongo que orientemos el siguiente evento a un ámbito más amplio. La web y los folletos en inglés. Las sesiones impartidas en inglés. Los voluntarios ayudando en inglés. Nuestros pesos pesados promocionando a nivel internacional (@ecomba, @david_bonilla, @xquesada, @acyment …). Y cualquier acción que se os ocurra.
¿Y el precio? Pues no podemos seguir pensando en pagar 4 perras. Si queremos una conferencia como dios manda hay que pagar más dinero, ¿200 o 300 euros la early bird? Yo se de gente que se puede gastar 200 euros en un fin de semana de fiesta y después dicen que 200 euros es mucho dinero por un fin de semana de agilismo.
Os dejo con una reflexión, ¿qué es la CAS? ¿Un evento de agilismo organizado por españoles para españoles? ¿O un evento de agilismo organizado en España para el mundo entero? Vosotros decidís.

Hola a todos, algunos sabréis que @etnassoft, @pasku1 y yo estuvimos en la HTML5Party de Madrid participando en una hackaton. La verdad es que me gustó mucho el evento y me divertí bastante, también tomé notas del “cómo se hizo” ya que me gustó mucho como la organizaron. Pero este post no es para hablar de lo bonita que fue la hackaton, sino para diseccionar el “fracaso” de nuestra aplicación. La verdad es que me gusta mucho diseccionar los fracasos, ya que es en este tipo de ejercicios donde más se aprende.

Algunos pensareis que las hackatones son situaciones especiales donde no se aplican las reglas normales del desarrollo de software. Como tenemos poco tiempo sólo puedes ponerte a codificar como pollo sin cabeza, echar jornadas maratonianas y beber mucho red bull… ¡ nada más lejos de la verdad ! Este tipo de concursos representan muy bien la mayoría de los proyectos que te vas a encontrar en el día a día normal. Veamos… tiempo y recursos insuficientes, lista de requisitos a todas luces excesiva, problemas de última hora, contratiempos, pánico y la sensación de no llegar a producción, o sea, ¡ el verdadero “mundo real” ! Por lo tanto si al participar en una hackaton dejas de lado la disciplina de trabajo, entonces es probable que en un proyecto de verdad también abandones la disciplina. Por eso, cuando me presenté me dije: “esto hay que hacerlo con TDD y entrega continua de valor a muerte” Je, je, que iluso. Veamos que pasó.

El primer día empezamos bien, nos reunimos y nos pusimos de acuerdo en la funcionalidad, hicimos una maqueta papel, y nos pusimos manos a la obra. El compañero @etnassoft decidió hacer una maqueta HTML y @pasku1 y yo nos pusimos a hacer BDD. Todo bien, ¿no? ¡ Mal ! Esa fue la raíz de todos los problemas. Si vas a hacer entrega continua de valor, no puedes dividirte en equipos de especialistas, todos tienen que trabajar en equipo para sacar lo más rápidamente posible cada historia de usuario. Lo que terminó ocurriendo, de forma inconsciente, es que la UI se desarrolló por completo, para todas las historias de usuario. Igualmente, el servidor se desarrolló por separado para todas las historias de usuario. Lo curioso de esto, es que fue un fenómeno totalmente subconsciente, fruto de separarnos “temporalmente”. En el fragor de la batalla nos olvidamos de que teníamos que centrarnos en historias y no en capas. Cierto, cada capa se desarrolló por historias, pero no integramos hasta que estuvieron todas las historias hechas en cada componente, ¡demasiado tarde!

En un enfoque orientado a la entrega continua de valor, hubiéramos integrado la UI y el servidor por cada historia. Los problemas los hubiéramos detectado al principio, cuando es más fácil, al ser la aplicación pequeña, y encontrarnos más descansados. Además de ser más eficientes hubiéramos eliminado el factor pánico, al tener al menos algunas historias (las más importantes) listas para producción.

¿Hicimos TDD? ¡ Sí ! ¿De que sirvió? De poco. Nuestra capa de “negocio” quedó cubierta por bastantes tests, ¡ pero ninguna capa de negocio sirve sino está conectada a una UI ! Esto debe servir de lección a los que piensen que sólo con prácticas de ingeniería y usar tecnologías y frameworks puedes llegar al éxito de tu proyecto. Tus prácticas de ingeniería deben usarse en el contexto de unas buenas prácticas de gestión de proyectos, y viceversa. Todas las prácticas se apoyan e interactúan unas con otras y están pensadas para cubrir las debilidades de las demás.

Durante el transcurso de la hackaton ocurrieron los consabidos e inevitables imprevistos de todos los proyectos (lo dicho, igualito que el mundo real). A saber:

  • Se cayó el github. ¡Nooooo!
  • Twitter tuvo un bajón de rendimiento. Todas las peticiones tardaban al menos 30 seg. Eso hizo que nuestro ciclo de pruebas integradas fuera impracticable. Ayer probé y la respuesta era instantánea.
  • Un espacio en una cadena de texto de una configuración nos estuvo fastidiando durante 4 horas hasta que nos dimos cuenta.
  • Los drivers de node.js para MongoDB fueron un dolor.
  • Heroku no quería arrancar…. (¿qué demonios le pasó a la nube ese fin de semana?)
  • Un troll se coló por twitter y nos desconcertó bastante. Sí, es cierto, cosas que pasan.
¿Son estos imprevistos una excusa para no haber entregado la funcionalidad prevista? En absoluto, si hubiéramos hecho entrega continua y usado una “bala trazadora”, nos hubiéramos dado cuenta al principio de los problemas con MongoDB y Twitter y hubiéramos hecho un workaround cuando aun estábamos a tiempo.
¿Cómo terminamos el proyecto? Al final nos encontramos con una UI muy bonita (menos mal que estaba @etnassoft) y un servidor sin fallos, pero sin integrar, sin ninguna funcionalidad en producción, cansados y a falta de 5 horas para entregar. En un esfuerzo final conseguimos integrar un 30% de las historias de usuario, pero no precisamente las más interesantes. Si lo hubiéramos hecho con el enfoque ágil, al final de la hackaton nos hubiéramos encontrado con la funcionalidad más interesantes en producción, y no hubiéramos sufrido tanto.
¡ También hicimos cosas bien ! TDD, ritmo sostenible, usar estándares, buen diseño y arquitectura, refactorizar (pero no tanto como hubiera querido), puestas en común, trabajo en equipo, buen ambiente… y por eso no puedo terminar el post sin hacer mención de honor a @pasku1 y @etnassoft. Ha sido un placer trabajar con los dos y una experiencia muy divertida e instructiva. ¡ La próxima ganamos fijo !
P.S. Al final ganamos el segundo premio, que no está nada mal (Pero es que soy un tío exigente)

P.S.S. Enhorabuena a los organizadores, ¡en especial a @VictorSanchez!


Hola a todos, la mayoría de vosotros sabréis que recientemente he tomado la decisión de abandonar atSistemas, la empresa que me ha visto crecer como profesional durante estos últimos 11 años. Han sido muchos años, llenos de buenos recuerdos, trabajo duro y éxitos profesionales, pero nada dura eternamente.

Desde hace dos años empecé a sentir que existe otra forma de trabajar posible, basado en los conceptos de desarrollo Agile y Lean. Desgraciadamente para mi, la reciente crisis ha hecho que tal forma de trabajo sea imposible de implantar en atSistemas de forma simple y sin poner en peligro a la empresa (que ofrece actualmente bastante más de 350 puestos de empleo). Me marcho manteniendo relaciones cordiales con mi antigua empresa y espero seguir colaborando con ellos en cualquier proyecto agile que les surja.

Reconociendo este hecho, decidí abandonar atSistemas e intentar demostrar que se puede ganar dinero trabajando de forma ágil, y anteponiendo la calidad a la tarifa. De esta forma podré llevar a cabo mis ideas con mi propio dinero y bajo mi propia cuenta y riesgo. El intentar encontrar y explotar mercado para esta filosofía de trabajo es un objetivo personal que pienso perseguir con mucha energía. Si os digo la verdad, ahora me encuentro muy ilusionado y lleno de fuerza. Sólo el tiempo dirá si he apostado por caballo ganador, pero yo tengo fe en mi. Es la misma confianza que hizo que me marchara de Sevilla a Madrid a trabajar en una empresa que en aquel entonces no conocía de nada, a una ciudad desconocida, sin experiencia laboral, con el dinero justo para vivir un mes y habiéndome leído por primera vez lo que era una JSP en el AVE. Yo diría que aquella fue una decisión acertada, y no veo por que ésta no puede serlo.

Ahora quiero despedirme de toda la gente con la que he trabajado en atSistemas; toda la gente que me despidió el viernes y me hizo saltar una lagrimita cuando no miraban; despedirme de los chic@s de Cádiz, mis “conejillos de indias”, con los que experimenté con el agilismo y mis malditos “frameworks de la muerte” (aun me queda algo de aquella botella de serie limitada); despedirme de los compañeros de Barcelona (Aleix, tenemos que vernos); despedirme de “mis” arquitectos, espero que sigáis defendiendo el fuerte, y también de las chicas de administración y de RRHH. También me despido de los comerciales (sí, también de vosotros) a los que les di la brasa con mis ideas. Aunque espero que no sea una despedida absoluta, sino que nos vayamos viendo de vez en cuando (y Lourdes, espero mandarte alguna factura de vez de en cuando :-) ).

Tengo que dar también las gracias a gente que no es parte de atSistemas por ayudarme y apoyarme, me refiero como no, a la comunidad ágil en España, que además me ayudaron a comprender que no era el único que estaba haciendo “locuras” por ahí. El encontrar esta comunidad ha sido algo muy importante para mi, y me ha animado a salir de la “cueva”. Me sorprendí de sus “retuits” y mensajes de ánimo cuando anuncié mi nueva etapa personal y profesional. Me resultó revelador que me dierais enhorabuenas más que ánimos :-O.

Y por supuesto un beso a mi mujer, @mcberros, que me ha dado soporte incondicional en esta arriesgada (y emocionante) decisión.

Y aquí os dejo un par de fotos. En la primera están muchos, pero faltan muchos más, que están distribuidos por todas partes de España:

La gente de la oficina de las Rozas

Y aquí la otra foto, donde se atestigua por qué no me sentía parte de atSistemas…

Las chicas de las Rozas

…porque obviamente soy el más feo de la oficina :-)

Saludos a todos y espero que podamos hacer una quedada.

P.S. Y por supuesto gracias por el iPad2, ¡ que era lo que me faltaba para convertirme en un apple fan boy !

P.S.S. No puede faltar la foto de la botella edición limitada:

Edición limitada @eamodeorubio


Acabo con este post la serie dedicada a la estimación. Aunque no lo parezca, entre el primer artículo y el segundo de esta serie ya he explicado lo más importante de la estimación en proyectos ágiles. Todo se resume en cinco reglas sencillas:

  1. No estimes si no lo necesitas. Normalmente se estima para adelantar una previsión a los interesados en el proyecto y para evaluar si algo se debe implementar o no.
  2. Sólo debe estimar personal experto, que haya construido software similar en el pasado.
  3. Estima por comparación, y no usando unidades absolutas.
  4. No compares cosas dispares, tanto en tamaño como en nivel de abstracción. Por ejemplo, tareas con historias de usuario, o historias de usuario con releases.
  5. No necesitas estimar tareas.

La regla número 2 nos la dice explícitamente Scrum: el PO sólo estima el valor y el equipo de desarrollo estima el coste. Cada uno estima dentro de su área de conocimiento. Ninguna de las partes debe admitir presiones por parte de la otra para que cambie su estimación. Se asume un equipo experto, si algún miembro es un junior, yo recomiendo no darle mucho peso a su estimación, pero que participe en ella para que vaya cogiendo experiencia.

La forma más sencilla de aplicar la regla número 3 es no permitir usar unidades absolutas, como horas, dinero, etc. Simplemente se coge una funcionalidad a estimar y se pregunta, ¿en qué grado esta funcionalidad es más grande o pequeña que esta otra? ¿En qué grado el cliente valora más esta funcionalidad que aquella otra? A dichas preguntas se debe responder en unidades relativas, por ejemplo, “es el doble”, “es similar”, “es un 123% mayor”, etc.

Si a la regla número 3 le añadimos la 4, obtenemos una escala de medida relativa pero que a la vez está simplificada. Si sólo debemos comparar cosas que sean similares, ¿tiene sentido decir que esta funcionalidad es 100 veces más grande que esta otra? No. Claramente las dos cosas que estamos comparando no se encuentran al mismo nivel. Otro problema es intentar obtener una precisión en la estimación absurda, similar o incluso mayor a nuestro margen de error. No es razonable estimar que tal funcionalidad es 1.07 veces más grande que tal otra, cuando normalmente nuestro margen de error es mucho mayor de un 7%. Para reforzar estas ideas se han propuesto varias escalas de medida, y al realizar una estimación no se puede asignar un valor que no aparezca en la escala.

La más famosa de dichas escalas es la escala de Cohn. En la escala de Cohn se admiten los siguientes valores: 0, 1, 2, 3, 5, 8, 13, 20, 40, 100. Si os fijáis en escalas pequeñas tenemos 0, 1, 2 y 3, admitiendo que podemos tener cierta precisión. A escalas más grandes, 5, 8, 13, 20, 40 y 100, hay huecos. Estos huecos indican que no debemos invertir esfuerzo en ajustar nuestra precisión ya que estaríamos excediendo el margen de error que normalmente tenemos al estimar.

Siguiendo con el razonamiento que defiendo a lo largo de toda esta serie, ¿tiene sentido decir que una funcionalidad es 20 veces mayor que otra? Seguramente no, y estemos comparando funcionalidades en escalas muy distintas de tamaño. Normalmente una funcionalidad mayor que 5 u 8 se etiqueta como épica. De hecho las funcionalidades épicas normalmente no caben en un sprint, y por lo tanto no son aptas para ser implementadas. Si en vez de Scrum usas kanban, tampoco te salvas, ya que si introduces en el sistema funcionalidades de tamaño muy variable, tanto tu tiempo de entrega como tu productividad se van al garete (teoría de colas). Por lo tanto las funcionalidades épicas son carne de analista funcional, no se aceptan en la fase de desarrollo y deben ser analizadas y descompuestas con más rigor.

Lo inverso ocurre con funcionalidades de tamaño 0. En este caso se consideran tan pequeñas que no merece la pena de gastar esfuerzo en estimarlas. Normalmente representan un nivel de abstracción por debajo del que estamos tratando. Podría se una tarea en vez de una funcionalidad, o un simple detalle de ésta. En general cuando tenemos varias cosas de tamaño cero, o bien las descartamos por estar en un nivel de abstracción inadecuado, o bien las fusionamos si están relacionadas entre si.

Si tenemos en cuenta estas dos prácticas, podemos llegar a la conclusión de que la escala de Cohn, aunque útil para principiantes, no es realmente lo que buscamos. Así llegamos a la otra escala que se usa mucho: tallas de camiseta. Los valores admisible son S, M, L y XL. Las funcionalidades XL se consideran épicas. Sólo se aceptarán funcionalidades S, M o L. Este sistema simplifica el proceso de estimación y evita que caigamos en las trampas de estimar en horas o con una precisión irreal. La dificultad de este sistema estriba en que la capacidad del equipo debe medirse también en tallas de camiseta. Por ejemplo: en un sprint el equipo será capaz de 1 funcionalidad L, 2 M y 4 S. Esto puede causar problemas de estilo de “si tenemos 1 funcionalidad L, pero no hemos escogido ninguna que sea M o S, ¿podemos escoger otra funcionalidad L?”.  Una posible solución es definir equivalencias entre tallas: 1 L son 2 M y 6 S, pero 1 M son 2 S.

Otra posible solución para este problema es simplemente no usar una escala. En esta modalidad las funcionalidades o bien son épicas o no lo son. En este caso se deben descomponer todas las funcionalidades de forma que sus tamaños no sólo sean comparables, sino muy similares y constantes.  De esta forma el proceso de estimación se simplifica enormemente, y si se hace bien se reduce la variabilidad de las funcionalidades a implementar. Esto último es especialmente importante, ya una mayor variabilidad implica peores estimaciones y también peor productividad (de nuevo teoría de colas). De esta forma acercamos bastante Kanban a Scrum, donde realmente estaríamos haciendo Kanban si la capacidad de nuestro equipo fuera siempre igual a su límite de WIP. También nos permite estar alineados con la regla 1. La dificultad de esta técnica radica en que tanto el equipo como PO deben ganar mucha experiencia estimando, analizando las funcionalidades y conociendo la capacidad de la otra parte. Por lo tanto sólo lo veo factible en equipos muy maduros.

Finalmente, la regla número 5 simplemente nos indica que tanto al PO como a los stakeholders del proyecto no les interesa saber la descomposición en tareas del proyecto. Si las funcionalidades se descomponen o no en tareas, y si éstas se estiman o no, es una cosa del equipo de desarrollo. Al PO no el interesa el Sprint Burndown, ni el Task Board, sino el Project Burndown, el reporte de bugs y el flujo acumulado de entrega de funcionalidad. Como excepción a esta regla tenemos el caso en el que parte del equipo de desarrollo pertenece al cliente, o que tal vez no hay un equipo sino varios equipos de especialistas que forman un pipeline. Para asegurar una correcta coordinación, puede ser razonable hacer un plan de tareas y llevar un seguimiento de éstas, pero obviamente esta forma de estructurar equipos no es la recomendada en un proyecto gestionado de forma ágil.

Como apunte meramente personal, mi posición con respecto a la estimación es bastante pesimista, ya he visto muchas veces como hasta la estimación más cuidadosa fracasaba, y como planes muy bonitos plantados sobre un “project” se iban al garete en cuanto tenían el más mínimo contacto con la realidad. Mi opinión personal es que hay que estimar en formato “rápido y sucio”, y el proceso de desarrollo que uséis debe estar preparado para trabajar con este tipo de estimaciones aproximadas. Bueno, pues con esto acabo la serie sobre estimación, espero que os haya sido útil.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 34 seguidores