Feeds:
Entradas
Comentarios

(Aviso a navegantes: ¡Este post contiene mucho código escrito a altas horas de la noche!)

Hola de nuevo, en este post, el último sobre “extends”, voy a intentar mostrar alternativas a la herencia de implementación. Se trata de la técnica genérica llamada “composición y delegación”, técnica en la que se basan varios patrones de diseño, como el famoso “Command”, el “Strategy”, o el “Decorator” entre otros. Antes de nada voy a resumir lo contado hasta ahora. En el primer post vimos cómo usar la herencia de implementación de forma “ingenua” nos podía meter en un buen lío, ya que sin darnos cuenta podemos romper la encapsulación. En el segundo post intentamos arreglar los problemas usando la palabra reservada “final” y el patrón “Template Method”. Si bien al principio parecía que funcionaba, más tarde al ir añadiendo funcionalidades, nos dimos cuenta de que violábamos el principio DRY y se producía una explosión combinatoria de clases, además de dificultarnos el testing y tener que planear de antemano los “ejes” de extensibilidad de nuestra clase.

El problema más importante es la explosión combinatoria de clases y la violación del DRY. La base de este problema es que la lógica de filtrado, y de postprocesamiento de la inserción de elementos, no se encuentran debidamente encapsuladas y abstraídas, sino que están incrustadas dentro de las clases hijas de “ListaSencilla”.

Empezando por la lógica de filtrado, ¿no sería mejor si pudiéramos tener una clase donde realmente pudiéramos poner dicha lógica? De esa forma podríamos eliminar ésta de las clases “ListaAuditableConElementosSinEspacios”, “ListaAuditableConElementosLongitudPar”, “ListaConElementosSinEspacios” y “ListaConElementosLongitudPar”. Está claro que estas clases violan el principio de única responsabilidad, ya que implementan el contrato de “Lista” y además tienen lógica de filtrado. Si sacamos esa lógica de filtrado aparte, vamos a quitar responsabilidad de dichas clases y a simplificarlas.

Lo primero de todo sería definir una interfaz “EstrategiaDeFiltrado” que represente el contrato entre una implementación de “Lista” y la lógica de filtrado.

public interface EstrategiaDeFiltrado {
  boolean esInsertable(String elemento);
}

Sencillo, ¿verdad? Ahora sólo tengo que tener una implementación por estragia de filtrado en mi sistema. Primero para los elementos pares:

public final class FiltrarElementosLongitudImpar implements EstrategiaDeFiltrado {
  public FiltrarElementosLongitudImpar() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

Y ahora para los elementos con espacios:

public final class FiltrarElementosConEspacios implements EstrategiaDeFiltrado {
  public FiltrarElementosConEspacios() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Nótese ese “final” para cada clase. Ahora nadie podrá usar herencia de implementación de dichas clases y provocar el caos. Es también interesante notar que con este diseño puedo hacer unos tests muy sencillos de cada implementación de “EstrategiaDeFiltrado”. Esto pinta bien, pero aun no hemos arreglado nada. Vamos a ver como reutilizar estas implementaciones de “EstrategiaDeFiltrado” en nuestras listas. Primero un cambio pequeño en “ListaAuditableConElementosLongitudPar”:

public class ListaAuditableConElementosLongitudPar extends ListaAuditable {
  private EstrategiaDeFiltrado filtro = new FiltrarElementosLongitudImpar();
  public ListaAuditableConElementosLongitudPar(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

y lo mismo en “ListaAuditableConElementosSinEspacios”:

public class ListaAuditableConElementosSinEspacios extends ListaAuditable {
  private EstrategiaDeFiltrado filtro = new FiltrarElementosConEspacios();
  public ListaAuditableConElementosSinEspacios(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

Mmmm, si os fijais “ListaAuditableConElementosLongitudPar” y “ListaAuditableConElementosSinEspacios” tienen exactamente el mismo código, salvo que cada una usa una implementación concreta diferente de “EstragiaDeFiltrado”. Una violación del DRY muy clara. Además ese “new” es muy, pero que muy feo ¿No sería mejor pasar el colaborador, es decir, la implementación concreta de “EstrategiaDeFiltrado”, por el constructor? Esto nos elimina la duplicación y de paso podemos borrar una clase. ¡ Me encanta cuando puedo borrar código ! ¿Y a vosotros? Ahora ambas clases desaparecen y son sustituidas por “ListaAuditableConFiltro”:

public class ListaAuditableConFiltro extends ListaAuditable {
  private EstrategiaDeFiltrado filtro;
  public ListaAuditableConFiltro(Auditor auditor, EstrategiaDeFiltrado filtro) {
    super(auditor);
    this.filtro = filtro;
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

De forma análoga, “ListaConElementosLongitudPar” y “ListaConElementosSinEspacios” acaban siendo eliminadas y sustituidas por “ListaConFiltro”. Esto no me termina de convencer. Seguimos violando DRY. Por un lado “ListaConFiltro” y “ListaAuditableConFiltro” tienen lógica claramente duplicada. Por otro lado, el método protegido “esInsertable” es exactamente igual que la interfaz “EstrategiaDeFiltrado”. El código pide a gritos ser simplificado, así que, ¿qué tal si nos llevamos todo ese código común a la superclase “ListaSencilla”? El único problema es que de “ListaSencilla” heredan también listas que no tienen filtro, ¿qué hacemos? Podemos aplicar el patrón “null object” y crear una “EstrategiaDeFiltrado” que no haga nada. Veamos ese código:

public final class NuncaFiltrar implements EstrategiaDeFiltrado {
  public NuncaFiltrar() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return true;
  }
}

Por otro lado no queremos tener que configurar el colaborador filtro con la clase “NuncaFiltrar” cada vez que queramos una lista sin filtro. Por lo tanto necesitamos que el filtro sea una dependencia opcional dentro de “ListaSencilla”. El código quedaría:

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  private EstrategiaDeFiltrado filtro = new NuncaFiltrar();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  public final void configurarFiltro(EstrategiaDeFiltrado nuevoFiltro) {
    this.filtro = nuevoFiltro;
  }
  /**
   * 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();
  }
}

Obsérvese como se recibe el colaborador mediante un “setter” llamado “configurarFiltro”, y no por el constructor. También como se usa por defecto una instancia de “NuncaFiltrar” en caso de que no se configure nada. También hemos eliminado el método protegido “esInsertable”.

Finalmente hemos conseguido nuestro objetivo, hemos eliminado todas las subclases de “ListaSencilla” que creamos con el único fin de añadir lógica de filtrado. El patrón que estamos usando es el “Strategy” tal y como apareción en GoF. Podemos volver a usar el patrón “Strategy” para eliminar “ListaAuditable” y el método protegido “procesamientoAdicionalTrasInsertarElemento”. Para ello creamos una interfaz “PostProcesador”:

public interface PostProcesador {
  void postProcesar(String elemento);
}

y una implementación “Auditar”:

public class Auditar implements PostProcesador {
  private Auditor auditor;
  public Auditar(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  public void postProcesar(String elemento) {
    auditor.elementoInsertado(elemento);
  }
}

Obsérvese que “Auditar” no es más que un patrón “Adapter”, entre la interfaz “PostProcesador” y “Auditor”. Además sospechosamente “Auditar” se parece mucho a “ListaAuditable”. Finalmente “ListaSencilla” queda:

public final class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  private EstrategiaDeFiltrado filtro = new NuncaFiltrar();
  private PostProcesador postProcesador = new NoProcesar();
  public ListaSencilla() {
    super();
  }
  @Override
  public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    postProcesador.postProcesar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  public void configurarFiltro(EstrategiaDeFiltrado nuevoFiltro) {
    this.filtro = nuevoFiltro;
  }
  public void configurarPostProcesador(PostProcesador nuevoPostProcesador) {
    this.postProcesador = nuevoPostProcesador;
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Ahora “ListaSencilla” es “final”, sin ningún método protegido y hemos eliminado “ListaAuditable”. Hemos conseguido cubrir todos nuestros requisitos sin necesitar la “potencia” de la herencia de implementación. Además hemos simplificado los tests. Es muy sencillo hacer tests de las implementaciones de “EstrategiaDeFiltrado” y “PostProcesador”. También los tests de “ListaSencilla” pueden ser simplificados usando dobles de prueba.

Sólo nos queda una cosa, el famoso patrón “Factory”:

public final class NuevaLista {
  public NuevaLista() {
    super();
  }
  private ListaSencilla configurarAuditorEstricto(ListaSencilla lista) {
    lista.configurarPostProcesador(new Auditar(new AuditorEstricto()));
    return lista;
  }
  private ListaSencilla configurarFiltro(ListaSencilla lista,
      EstrategiaDeFiltrado filtro) {
    lista.configurarFiltro(filtro);
    return lista;
  }
  public Lista sencilla() {
    return new ListaSencilla();
  }
  public Lista auditable() {
    return configurarAuditorEstricto(new ListaSencilla());
  }
  public Lista sinElementosConEspacios() {
    return configurarFiltro(new ListaSencilla(),
        new FiltrarElementosConEspacios());
  }
  public Lista sinElementosLongitudImpar() {
    return configurarFiltro(new ListaSencilla(),
        new FiltrarElementosLongitudImpar());
  }
  public Lista auditableSinElementosConEspacios() {
    return configurarAuditorEstricto(configurarFiltro(new ListaSencilla(),
        new FiltrarElementosConEspacios()));
  }
  public Lista auditableSinElementosLongitudImpar() {
    return configurarAuditorEstricto(configurarFiltro(new ListaSencilla(),
        new FiltrarElementosLongitudImpar()));
  }
}

O si son ustedes partidarios de usar frameworks, pueden usar el framework “Spring” que da mucho juego para esto, y programarse exactamente la misma lógica que tenemos en “NuevaLista” pero en XML en vez de en JAVA (es que el XML queda más profesional). Con este patrón “Factory” podemos tener todas las combinaciones de “Lista”, “EstrategiaDeFiltrado” y “PostProcesador” que queramos, sin necesidad de volver a tocar una linea de código en ninguna de estas.

El patrón “Strategy” nos permite hacer una cosa que no podemos con “extends”, definir la combinación que queramos de “Lista”, “EstrategiaDeFiltrado” y “PostProcesador” en tiempo de ejecución.

Podríamos dejarlo aquí, pero he de comentar que “Strategy” comparte un problema con “Template Method”. Con ambos necesitamos planear de antemano que “ejes” de extensibilidad o composición vamos a tener. Esto hace que la clase “ListaSencilla” no sea tan sencilla. Estamos en cierto modo violando el principio de única responsabilidad, ya que está claro que la responsabilidad de “ListaSencilla” es simplemente gestionar la lógica de “Lista”. Además, estamos pagando una pequeña sobrecarga, al añadir lógica de llamada a filtrado y postprocesamiento en aquellos casos que realmente no la necesitamos. Cierto, es una sobrecarga muy pequeña, pero no deja de ser una mala señal. Yo lo que quiero es que “ListaSencilla” sea eso, “sencilla”. Quiero este código:

public final 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)
      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();
  }
}

¡ Ah, que gusto volver a los viejos tiempos donde las listas eran listas y nada más ! Pero necesitamos filtrado y postprocesamiento, ¿qué hacemos? Recurrir a otro patrón del “GoF”, el “Decorator”. Podemos hacer una clase que admita una “EstrategiaDeFiltrado”, pero que no implemente nada de la lógica de la lista, sino que la delegue a un colaborador. O sea, la responsabilidad de esta nueva clase es simplemente orquestar a una “Lista” y a una “EstrategiaDeFiltrado”, sin que ninguna de las dos sepa nada. Creemos, para tal efecto, la clase “ListaConFiltrado”:

public final class ListaConFiltrado implements Lista {
  private Lista lista;
  private EstrategiaDeFiltrado filtro;
  public ListaConFiltrado(Lista lista, EstrategiaDeFiltrado filtro) {
    super();
    this.lista = lista;
    this.filtro = filtro;
  }
  @Override
  public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    lista.insertar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    lista.insertarVarios(elementos);
  }
  @Override
  public String describirContenido() {
    return lista.describirContenido();
  }
}

También podemos hacer lo mismo y crear una “ListaConPostProcesamiento”:

public final class ListaConPostProcesamiento implements Lista {
  private Lista lista;
  private PostProcesador postProcesador;
  public ListaConPostProcesamiento(Lista lista, PostProcesador postProcesador) {
    super();
    this.lista = lista;
    this.postProcesador = postProcesador;
  }
  @Override
  public void insertar(String elemento) {
    lista.insertar(elemento);
    postProcesador.postProcesar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    lista.insertarVarios(elementos);
    for (String elemento : elementos)
      postProcesador.postProcesar(elemento);
  }
  @Override
  public String describirContenido() {
    return lista.describirContenido();
  }
}

¿Necesitaremos una clase “ListaConFiltradoYPostProcesamiento”? No, tal objeto se puede crear componiendo una “ListaConFiltrado” con una “ListaConPostProcesamiento”, pasando la última como argumento al constructor de la primera. De hecho ahora podemos eliminar “NuncaFiltrar” y “NoProcesar” ya que no las usamos. Además volvemos a simplificar los tests de “ListaSencilla”, ¡ ya no necesitamos dobles de prueba ! Por otro lado los tests de “ListaConFiltrado” y “ListaConPostProcesamiento” son muy sencillos, basta usar unos dobles de prueba para comprobar que las llamadas se delegan en el orden oportuno, y que no se llama ni a “insertar” ni a “insertarVarios” si el filtro nos devuelve “false”.

Si os fijais estoy usando varios patrones que no usan herencia de implementación, sino una filosofía de diseño general llamada “composición y delegación“. Esta filosofía consiste en tener objetos sencillos, y bien encapsulados, que puedan ser combinados mediante composición, para obtener nueva funcionalidad. Tanto “Strategy”, como “Decorator” usan esta técnica.

Siguiendo con el ejemplo, todos estos cambios afectan a nuestra factoría “NuevaLista”, que ahora queda:

public final class NuevaLista {
  public NuevaLista() {
    super();
  }
  public Lista sencilla() {
    return new ListaSencilla();
  }
  public Lista auditable() {
    return new ListaConPostProcesamiento(sencilla(), new Auditar(
        new AuditorEstricto()));
  }
  public Lista sinElementosConEspacios() {
    return new ListaConFiltrado(sencilla(), new FiltrarElementosConEspacios());
  }
  public Lista sinElementosLongitudImpar() {
    return new ListaConFiltrado(sencilla(), new FiltrarElementosLongitudImpar());
  }
  public Lista auditableSinElementosConEspacios() {
    return new ListaConFiltrado(auditable(), new FiltrarElementosConEspacios());
  }
  public Lista auditableSinElementosLongitudImpar() {
    return new ListaConFiltrado(auditable(),
        new FiltrarElementosLongitudImpar());
  }
}

Ahora ese “Factory” es más sencillo y elegante que antes al haber eliminado los “setter”. La clase “NuevaLista” simplemente compone o combina las distintas implementaciones de lista. Desgraciadamente cada vez que necesitemos una nueva combinación tenemos que tocar “NuevaLista” (o el XML de “Spring”). Para solventar ese problema podríamos aprovechar la potencia de “componer y delegar” y hacernos un mini DSL interno. Veamos cómo:

public final class FabricaDeListas {
  private List<EstrategiaDeFiltrado> filtros = new ArrayList();
  private List<PostProcesador> postProcesadores = new ArrayList();
  public FabricaDeListas() {
    super();
  }
  public FabricaDeListas(List filtros,
      List postProcesadores) {
    this();
    this.filtros.addAll(filtros);
    this.postProcesadores.addAll(postProcesadores);
  }
  public Lista fabricar() {
    Lista resultado = new ListaSencilla();
    for (PostProcesador procesador : postProcesadores)
      resultado = new ListaConPostProcesamiento(resultado, procesador);
    for (EstrategiaDeFiltrado filtro : filtros)
      resultado = new ListaConFiltrado(resultado, filtro);
    return resultado;
  }
  public FabricaDeListas conPostProcesador(PostProcesador procesador) {
    FabricaDeListas fabrica = new FabricaDeListas(this.filtros,
        this.postProcesadores);
    fabrica.postProcesadores.add(procesador);
    return fabrica;
  }
  public FabricaDeListas conAuditor(Auditor auditor) {
    return conPostProcesador(new Auditar(auditor));
  }
  public FabricaDeListas conAuditoriaEstricta() {
    return conAuditor(new AuditorEstricto());
  }
  public FabricaDeListas conFiltro(EstrategiaDeFiltrado filtro) {
    FabricaDeListas fabrica = new FabricaDeListas(this.filtros,
        this.postProcesadores);
    fabrica.filtros.add(filtro);
    return fabrica;
  }
  public FabricaDeListas sinElementosLongitudImpar() {
    return conFiltro(new FiltrarElementosLongitudImpar());
  }
  public FabricaDeListas sinElementosConEspacios() {
    return conFiltro(new FiltrarElementosConEspacios());
  }
}

Simplemente usamos un patrón “Value Object” mezclado con un “Builder”. La ventaja de este enfoque es que sea cual sea la combinación que quiera la puedo obtener fácilmente con “FabricaDeListas”, sin tener que tocar una sola linea de código dentro de “FabricaDeListas”. Puedo hacer cosas como:

Lista auditadaSinElementosConEspacios = fabrica
                                           .sinElementosConEspacios()
                                           .conAuditoriaEstricta()
                                           .fabricar();

Mucho más legible, ¿no?

Tras este largo camino, ¿alguien considera que el código resultante es ilegible, poco mantenible o poco potente? ¿Para que nos sirvió el “extends” si no fue sólo para darnos dolores de cabeza? Hemos aprendido una valiosa lección, cualquier diseño basado en “extends” y “Template Method” puede ser refactorizado en un “Strategy”, y éste a su vez en un “Decorator”. Por lo tanto, ¿para que perder el tiempo usando “extends” si puedo hacer un “Strategy” o un “Decorator”? Al fin y al cabo el diseño basado en “composición y delegación” es más seguro y más potente.

Sin embargo aquí entra en juego las herramientas que te da el lenguaje. Una pista nos la puede dar la clase “Auditar”, donde estamos creando una clase para simplemente adaptar la interfaz “PostProcesador” a “Auditor”, pero realmente no hay ninguna lógica ahí. A mi me parece que esa clase es un buen desperdicio de código, pero la verdad es que no se puede hacer mucho más en JAVA. Otro ejemplo es el método “describirContenido” en “ListaConFiltrado” y “ListaConPostProcesamiento”. Ese método no hace nada, sólo delega la llamada sin añadir lógica, ¡ qué desperdicio de líneas de código ! Tampoco podemos hacer mucho más en leguaje JAVA. Afortunadamente tenemos IDEs como “Eclipse”, “NetBeans” o “IntelliJ” que nos proporcionan el poderoso wizard “Generate Delegate Methods…”, y no las tenemos que escribir.

Cuando veo este tipo de detalles es cuando pienso que JAVA no está envejeciendo muy bien. Gracias señores estandarizadores de JAVA, perdieron ustedes una gran oportunidad de mejorar el lenguaje cuando lanzaron JAVA 5. En vez de parir ese aborto de “Map<? extends Enumerable, List<? extends X>>”, debían haber mejorado el lenguaje para añadir una forma sencilla de aplicar la técnica de “composición y delegación”, ¿tal vez una palabra clave “delegate”? ¿O soporte para funciones como primer ciudadano? No, ¿para qué? Los genéricos son mucho más “molones” y para reutilizar código ya tenemos el “extends”. Todo esto es mucho más simple de hacer en JavaScript, Groovy o Ruby. A ver si en JAVA 7 o JAVA 8 se ponen las pilas.

¡ Gracias por aguantar hasta el final de este post tan largo !


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

Seguir

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

Únete a otros 42 seguidores