Feeds:
Entradas
Comentarios

Archivos de la categoría ‘Filosofada’


(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 !

Read Full 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.

Read Full Post »


Hola a todos, pensaba hacer una reseña al uso sobre el SpringIO 2011 Madrid, pero las distintas sesiones a las que asistí me han dejado una idea bastante persistente en la cabeza: VMWare se está posicionando como clara competencia de Oracle en el mundo de las aplicaciones empresariales JAVA (plataforma JVM mejor dicho). Como la mayoría de vosotros sabeis VMWare compró SpringSource, así que ahora todas las decisiones estratégicas sobre el desarrollo de Spring se toman con la participación y el consentimiento de VMWare. Pensar otra cosa sería pecar de excesiva inocencia.

En la conferencia me percaté de que varios temas se repetían una y otra vez en las distintas charlas: Grails, Cloud Computing, noSQL y “Portable Service Abstractions”. Comentaré primero lo que se habló sobre cada uno de estos temas y después explicaré porqué todos ellos me hacen pensar que VMWare se postula como seria competencia de Oracle.

Sin saber mucho de Grails, me da la impresión que es un framework “orientado a la base de datos”. En los ejemplos que he visto siempre se habla del MVC, pero todos los modelos Grails que he visto no son más que JavaBeans (a.k.a. registros COBOL recauchutados), eso sí, codificados en Groovy, que es mas “cool”. Después tienes las vistas, que no son más que JSPs (perdonad, GSPs) y un controlador escrito en Groovy que en realidad es un controlador de Spring MVC (que es casi igual a un backing bean de JSF). Vamos, más modelo anémico. Ciertamente es mucho mejor que un modelo anémico que podamos hacer en JAVA, y sí, no hay que picar apenas código (se agradece), con lo cual considero a Grails la plataforma perfecta para este tipo de aplicaciones. Si estoy metiendo la pata perdonadme, pero ya sabéis que me hierve la sangre con este tema ¡ Si alguien sabe como hacer buena OO con Grails que me lo diga !

Dejando de lado esta diatriba, la charla a la que asistí de Grails fue muy interesante. Peter Ledbrook explicó de forma bastante clara como integrar Grails con el resto de la empresa. ¿Qué hacemos si queremos hacer un build con ANT? ¿Y con MAVEN? ¿Y si ya existe un modelo hecho con JPA? ¿Y si resulta que el administrador de base de datos nos obliga a una determinada nomenclatura? La verdad es que resolvió bastante bien todos estos escenarios, aunque la parte de integración con MAVEN me dio la impresión de estar un pelín verde. Quizás el mayor problema es cuando ya existe un esquema de base de datos que tenemos que respetar. Aquí es donde la solución que dio no me terminó de parecer clara, y me pareció que proponía que se hiciera un modelo JPA y se integrara éste modelo con Grails. Está claro que en SpringSource se han dado cuenta de que uno de los obstáculos principales para que Grails triunfe es poder integrarse con los elementos más usuales en un sistema empresarial.

Otro punto que se habló de Grails fue integrar GORM (motor de mapeo Objeto/Relacional de Grails) con bases de datos noSQL. Se mencionó este tema en la segunda key note e @ialcazar me comentó un poco sobre una sesión que se dio a este respecto. No termino de ver como se puede integrar un motor de mapeo objeto/relacional con una base de datos no relacional ¡ Precisamente una de las ventajas de una base de datos noSQL es que no necesitamos un mapeo Objeto/Relacional ! No termino de verle el sentido técnico, pero sí el de marketing. En cualquier caso creo que es una iniciativa muy interesante de la comunidad de Grails, esperemos que lo consigan sin cargarse la escalabilidad que nos da un noSQL. También noté preocupación por el rendimiento de Grails, y se ve que están trabajando muy duro para mejorarlo.

Otro tema que estuvo bastante presente fue el Cloud Computing. Durante la primera key note y alguna que otra charla se habló de que el objetivo a medio plazo es preparar a Spring para permitir desarrollar aplicaciones en la nube. Realmente no termino de ver que problema hay con las APIs actuales de Spring y la nube, así que todo esto me suena a simple marketing. En este sentido me hubiera gustado que hablaran de tecnologías como vFabric o GemFire, pero claro, aunque son parte de VMWare, técnicamente no forman parte de Spring, con lo que hubiera estado fuera de lugar nombrarlas. Tal vez cuando se hablaba de adaptar Spring a la nube se refirieran a conseguir mayor rendimiento, y sobre todo escalabilidad y elasticidad, cosa fundamental en este tipo de entornos.

Quizás en este sentido se entienda el esfuerzo por integrarse con sistemas de persistencia noSQL, que proporcionan mayor escalabilidad a las aplicaciones. Una de las charlas más interesantes fue la de Spring Data de Costin Landau. Realmente me gustó la propuesta de Spring de montar un API “estándar” para noSQL, sobre todo teniendo en cuenta que JEE no proporciona nada comparable. En concreto las APIs de bajo nivel y el concepto de “repositorio” me resultó interesante. También la idea de dar cobertura a bases de datos documentales y orientadas a grafos me resultó bastante atractiva. Sin embargo a otras propuestas, como incorporar un sistema de persistencia mixto que mantuviera parte del modelo en BBDD relacional y parte en noSQL no acabo de verles el sentido. Tal vez sea un compromiso de “marketing” para que a la gente no le de miedo adoptar noSQL, ya que seguirían teniendo el respaldo de bases de datos tradicionales. A mi simplemente me parece una postura originada por el miedo de la gente a noSQL. Ya sabéis que pienso que si el sistema tiene las consultas bien definidas, el enfoque noSQL es totalmente superior al tradicional, y que sólo usaría un sistema SQL si realmente el conjunto de consultas las va a decidir el usuario en runtime, como son el caso de bussiness inteligence y data mining por ejemplo.

En la línea de noSQL la charla sobre Redis de Alberto Gimeno fue curiosa. Personalmente pienso que Redis está un poco verde todavía, aunque a alguno le puede interesar el concepto de clave/valor avanzado. Al contrario que un clave/valor simple, el valor almacenado puede ser no sólo un array de bytes, sino estructuras de datos como contadores, listas, mapas o sets. Para cada tipo de datos REDIS proporciona un conjunto de operaciones específicas, que se pueden ejecutar de forma atómica, y extienden el paradigma clásico del clave/valor. No termino de ver por qué esta complejidad extra es necesaria, pero sí que es verdad que facilita a un recién llegado al noSQL la transición desde el mundo de las BBDD relacionales. En todo caso me alegró ver que la gente empieza a usar sistemas noSQL, ya sabéis de que pié cojeo.

Otro tema que fue hilo conductor de esta conferencia fue el concepto de “Portable Service Abstraction”. Este nombre es una forma “cool” de decir que si desarrollamos contra interfaces “estándar”, nuestro código será portable a otros entornos. Es una idea muy vieja, y es lo que intentó JEE. Como ejemplos de estas “Portable Service Abstraction” podemos tener Spring Data, Spring REST, Spring AMQP, Spring Social, Spring Mobile y Spring Android. En general me gusta el enfoque de Spring de hacer APIs sencillas, y de reaccionar de forma rápida y ágil a las necesidades de los desarrolladores. Sin embargo no es oro todo lo que reluce. No entiendo la necesidad de Spring REST, desde mi punto de vista JAX-RS era un estándar más que suficiente. De hecho ambos estándares son tan similares, que es difícil distinguir dos aplicaciones hechas sobre cada una de ellas. Por otro lado Spring Mobile y Spring Android me decepcionaron, ya que apenan aportan valor. El primero básicamente sólo te detecta el tipo de dispositivo móvil y te redirige a una página de presentación u otra en función de éste. Vamos, nada que no exista ya en las múltiples arquitecturas multicanal “clásicas”. Spring Android es sólo un cliente para hacer llamadas al servidor. Los que sí me gustaron bastante fueron Spring Social, Spring Data y Spring AMQP. En concreto este último, ya que me pareció que el RabbitMQ era genial.

Toda esta información que recibí en la SpringIO me hizo ver que VMWare está intentando atacar a Oracle por varios frentes:

  • Spring como substituto de JEE. Aunque Spring es compatible con JEE, en el fondo lo que pretende es que programemos contra sus APIs en vez de las de JEE, aduciendo motivos de portabilidad. Esto es gracioso, ya que JEE nos propone exactamente lo mismo: una aplicación programada contra JEE es portable al ser éste el estándar de iure para el desarrollo de aplicaciones empresariales. Así que, ¿cuál API es más estándar, JEE o Spring? Esta claro que Spring ha sabido evolucionar sus APIs de forma más ágil y práctica que JEE, con todos sus comités. Todo esto es una amenaza clara contra Oracle, que es la actual “dueña” de JEE.
  • Tomcat y Spring tc Server como substitutos de WebLogic (y de paso de WebSphere). Si vamos a programar contra Spring en vez de contra JEE no tiene sentido comprar un servidor JEE completo como WebLogic o WebSphere, nos basta un contenedor de Servlets como Tomcat. Para los que no lo sepáis WebLogic es de Oracle.
  • AMQP y RabbitMQ contra JMS. Aunque RabbitMQ se integra con JMS, en realidad es una alternativa viable a JMS, y a cosas como MQSeries.
  • Grails como substituto de JSF. Como ya sabréis el modelo de programación web JEE es JSF. De hecho Oracle ha invertido en su implementación de JSF, llamada Oracle ADF, y en un IDE especial para ADF basado en Eclipse. Oracle ADF y su IDE nos permite hacer aplicaciones orientadas a base de datos muy rápidamente (o eso me han contado). Está claro que SpringToolSuite junto con Grails es competencia directa de esta tecnología.
  • noSQL contra las bases de datos relacionales. Como sabéis Oracle es dueña no sólo de la base de datos relacional más famosa del mundo, sino que con la compra de Sun, puede hacerse con el control de MySQL. Obviamente apostar por noSQL es un golpe directo a la linea de flotación de Oracle. Curiosamente el único sistema noSQL del que se habló con detalle en la conferencia fue de REDIS, que está “esponsorizado” por VMWare.
  • Cloud Computing contra hosting tradicional. El Cloud Computing es un escenario que creo beneficia más a VMWare que a Oracle. El primero, ha hecho una apuesta fuerte en este sentido con vFabric y vmForce. Si tienes una instalación propia, puede venir un comercial de Oracle a venderte WebLogic y la base de datos de turno. En un cloud no. Si convences a una empresa para irse al cloud, es más que probable que sus bases de datos Oracle queden en desuso.

En definitiva, Spring pretende suplantar a JEE como plataforma estandarizada para el desarrollo de aplicaciones empresariales con JAVA. Bueno, tal vez yo sea un poco suspicaz, pero esto es lo que realmente leo entre lineas de la conferencia SpringIO. Por cierto fue un éxito de público, y en general el nivel de las sesiones y talleres fue bastante alto. Enhorabuena a la organización, ya que no creo que con 20 euros se pudiera haber hecho mejor. ¿A alguien le pareció caro? A mi no.

Read Full Post »


Hola de nuevo a todos, el tema que voy a tratar en este post en su momento me ocasionó un shock acompañado de su posterior revelación (es que soy un tío simple e impresionable).

En el anterior post de esta serie comenté que había que valorar el coste de complicar nuestro diseño contra el tiempo disponible y los beneficios a largo plazo que nos iba a reportar. En el caso de un proyecto gestionado mediante el paradigma ágil, el equipo de desarrollo debe ser capaz de aceptar muchos cambios a un ritmo alto. Estos cambios provienen al menos de dos fuentes diferentes: cambios o malentendidos a nivel funcional, y otros ocasionados por tener que entregar funcionalidad de forma continua, lo que nos deja en la posición de tener que cambiar y extender nuestro diseño de forma interminable. Como conclusión tenemos que nuestro diseño debe ser barato y rápido de cambiar y que toda técnica orientada a este fin es valiosa.

¿Cómo podemos abaratar y agilizar los cambios en el software? ¿DRY, SOLID, OO, Modelo de dominio? Bueno, no vayamos tan rápido, centrémonos primero en lo esencial. Pensad un poco en la siguiente pregunta, ¿qué es lo que hacemos siempre (siempre) cada vez que tenemos que introducir un cambio en nuestro sistema? …. Pues es obvio, siempre hay que leer el código ya existente y entenderlo para empezar a saber como cambiarlo. ¿Cuántas veces os ha pasado que no entendéis el código que escribisteis hace dos meses? ¿Y el de la semana pasada? A mi me ha ocurrido de no entender código del día anterior, ¡ menuda bazofia ! Si no podéis entender vuestro código, imaginaros la comprensión que tendrán de éste vuestros compañeros cuando les toque mantenerlo o añadir un cambio.

Para hacer que vuestro código se pueda modificar de forma rápida y barata, es indispensable que sea legible y comprensible (dios, que shock, ¡ no voy a poder dormir !). Este principio es muy básico y para mi es el principio de “calidad mundana” más importante que existe. Un nombre popular para este principio es el de “código expresivo”. Es un principio tan esencial que aunque no seáis ágiles lo vais a necesitar. Tal vez no sea tan crucial para proyectos no ágiles, pero sí es muy importante en la fase de mantenimiento, ¿alguno ha mantenido aplicaciones ilegibles alguna vez? Seguro que sí. Quizás el ejemplo clásico de “no ágil” y necesidad de “código expresivo” sea el caso del mantenimiento de aplicaciones legacy. Cuando hay que mantener una base de código que es difícil de leer, a veces es más barato reescribirla desde cero.

Un escenario más confuso se produce con aplicaciones de “campaña”, de usar y tirar. La aplicación debe estar lista para una fecha (time to market crítico), se usará durante dos o tres meses, y después se desechará. En estas aplicaciones no va a haber mantenimiento evolutivo. En un entorno ágil vamos a seguir necesitando que el código sea expresivo, ya que los cambios llegarán seguro durante la fase de construcción. En un entorno no ágil no habrá cambios durante la fase de construcción (¿seguro?), gracias a un fabuloso contrato cerrado, con lo que alguno podrá pensar que no es necesario que el código sea legible, y que la necesidad de facilidad de cambio (calidad mundana) tiende a cero. A esas mentes preclaras les deseo que entreguen el software con cero defectos, porque como empiecen a llegar bugs en la fase de explotación, que para colmo dura unas pocas semanas o meses, no sé como los vas a resolver a un ritmo suficientemente rápido como para que los de negocio queden satisfechos. Por lo tanto la legibilidad del código no sólo es importante para aceptar el cambio (ser ágil), sino también para arreglar bugs. Como todo el software tiene bugs (escondidos tal vez), todo el software, ágil o no ágil, de “campaña” o “estratégico”, debe ser legible.

¿Qué técnicas nos pueden ayudar a conseguir este fin? Pues hay muchas:

  • Pair programming (programación por parejas). Sí, señor, no hay nada como un compañero leyendo por encima de tu hombro lo que escribes, tratando de entenderlo y buscándole defectos, como para que tu código de repente se vuelva legible (para los demás). Afortunadamente cuando cambie el turno te podrás vengar, je, je }:-)
  • Peer review (revisión de código). El pair programming no es más que revisión de código continuo, ¿por qué necesitamos otra etapa de revisión de código? Pues muy sencillo, a veces las parejas desarrollan un sentido de la identidad compartida, y un sentido de la legibilidad un poco particular. Para evitar esto es conveniente rotar las parejas, y también que otros compañeros, y deseablemente alguien un poco externo al proyecto (normalmente alguien respetado), revise el código. Por supuesto en la medida de lo posible y las restricciones de tiempo y dinero.
  • Domain Model (modelos de dominio). Consiste en modelar de forma explícita conceptos usados en el dominio del problema. De esta forma cuando veamos el código reconoceremos conceptos del problema directamente en el código, lo que nos ayudará a entender éste. Este enfoque es muy popular y tuvo como repercusión la aparición del paradigma OO, en el que aparece el concepto de objeto que nos permite modelar los conceptos de nuestro problema de forma directa, algo muy costoso de hacer con un lenguaje procedural.
  • Fluent API (API fluida o API sexy) o Internal DSL y también External DSL. Como ya comenté en un post anterior, los DSL nos permiten una mayor legibilidad, al modelar directamente el lenguaje de dominio de nuestro problema. Este problema puede ser la funcionalidad de negocio, pero también puede ser un problema técnico (procesamiento de XML, de BBDD, seguridad, etc). Notad que en los DSLs el énfasis está en el lenguaje y la dinámica del problema, mientras que en el modelo de dominio nos centramos en la estructura estática. Esto hace ambas que ambas técnicas sean complementarias unas de otras.

Como veis la mayoría de las técnicas de diseño ágil tienen como una de sus ventajas, si es que no su principal ventaja, el hacer el código más legible. Algunas son más potentes que otras, y también más costosas que otras. Como siempre la decisión debéis tomarla teniendo en cuenta el coste. Por ejemplo, construir un DSL, ya sea interno o externo, puede tener costes altos, sobre todo en algunas plataformas tecnológicas. Otras como TDD, Pair Programming o modelado de dominio puede tener algún beneficio adicional.

Por otro lado, para que lo penséis, ¿se os ocurre alguna técnica no ágil que facilite la legibilidad del código? ¿Wizards y generadores de código? ¿Copy&Paste? ¿DFD? ¿Alguna idea? Al fin y al cabo el paradigma no ágil también necesita que su código sea legible.

No lo he puesto en la lista anterior, pero algunos refactors pueden ayudarnos con la legibilidad de nuestro código. El problema es que esos mismos refactors en diferentes circunstancias nos hacen perderla. En general yo uso la cantidad de legibilidad que gana mi código como guía para saber si hacer el refactor merece la pena o no.

Finalmente, si usáis TDD, tenéis que tener en cuenta que la legibilidad del código no sólo se aplica al código de producción, sino también al código de test. Para mi, es incluso más importante que el código de test sea legible a que lo sea el de producción, ¿por qué? Simplemente porque el código de test define la API de vuestro sistema (o componente), son los ejemplos de uso. Si estos ejemplos (los tests) son legibles, ¡ habréis conseguido que vuestro código de test sea a la vez la documentación ! Nada de irse al javadoc, un buen vistazo a los escenarios de vuestros tests y un programador ya sabrá como manejar vuestro sistema (o componente). Por otra parte, una buena suite de pruebas, bien legible, y refactorizada adecuadamente, os proporciona además, un “fluent API” o “internal DSL”. O sea, que simplemente por hacer que vuestro TDD sea legible, obtenéis un DSL y una guía del programador (con how to’s), gratis de serie con vuestro sistema con pruebas automáticas. A mi esta idea me seduce bastante.

Por supuesto si no practicáis TDD todo esto os dará igual :-P —> ¡ A qué esperais para empezar con vuestro TDD ! ¡ Es que acaso pensáis que el TDD sirve para “probar” !

Un punto importante a tener en cuenta es la plataforma tecnológica en la que nos movemos. Por ejemplo, algunos lenguajes nos permiten hacer nuestro código más expresivo que otros. Comparad en este sentido JAVA o COBOL con Ruby, Scala, Groovy, etc. La mayoría de los lenguajes modernos van en la dirección de facilitar cada vez más que nuestro código sea legible. De hecho la evolución de los lenguajes de programación, desde el código máquina hasta el Groovy, han ido en esta dirección. Los desarrollos más recientes (o no tanto) que hacen que JAVA quede obsoleto en este sentido son:

  • Intentar eliminar al máximo el su ruido sintáctico. Declaraciones de tipos, palabras reservadas, tokens, etc. oscurecen la sintaxis de nuestro código, haciéndolo menos legible. Algunas técnicas usadas por los lenguajes para mejorar esta:
    1. Para los lenguajes de tipado estático, la inferencia de tipos, como demuestra Scala, nos evita tener que escribir un montón de código. El compilador es inteligente y en los casos en los que el tipo de una expresión sea computable en tiempo de compilación, nos permite omitir la declaración de tipo en las variables, parámetros y valores de retornos de las funciones. Esto es una de las razonas por las que el compilador de Scala es más lento que el de JAVA.
    2. Los lenguajes de tipado dinámico no necesitan estas declaraciones, con lo que son de forma natural más sucintos y expresivos. Esto, junto con el auge del TDD, hace que este tipo de lenguajes se haga cada vez más popular.
    3. Sintaxis flexible. Los lenguajes modernos permiten una sintaxis flexible. Podemos ahorrarnos llaves, comas, puntos, puntos y comas y paréntesis en algunas circunstancias. El resultado es un código con menos ruido sintáctico y más parecido al lenguaje natural, lo que ayuda al desarrollo de DSLs internos. Groovy, Ruby o Scala son ejemplos de lenguajes que usan esta técnica.
  • Paradigma mixto. La mezcla de paradigmas OO y Funcional nos permite usar el paradigma más expresivo según para que tarea. El uso de closures o bloques de código nos permite simplificar mucho nuestro código haciéndolo más legible.
  • Metaprogramación. La metaprogramación nos permite construir DSLs internos de forma más sencilla. En este sentido Groovy y Ruby son bastante potentes.

Si veis que vuestro lenguaje de programación os impide que vuestro código sea lo suficientemente expresivo como para cambiarlo con suficiente rapidez, empezad a pensar en adoptar otro lenguaje.

Como os dije soy un tío simple e impresionable. ¡ A pasarlo bien !

Read Full Post »


Hola a todos, como veo que hay mucha inquietud por todas esas prácticas de desarrollo ágil, como el TDD, el refactor y el pair programming, y como me huelo que por ahí que se ha puesto de moda eso del SOLID, he decidido hacer unos cuantos de posts sobre el diseño de software ágil. Espero que me salga algo coherente…

En posts anteriores he hablado mucho de gestión de proyectos, sobre todo de Scrum. Me centré en que lo bueno de las metodologías ágiles es que proponen que se debe entregar valor de forma continua y cuanto antes mejor. Ésto es mucho más sencillo de decir que de hacer. Scrum, por ejemplo, no nos orienta sobre cómo conseguir implementar en cada sprint rodajas de funcionalidad potencialmente entregables al usuario. De hecho esto es bastante complicado de hacer, ya que implica que con cada nueva funcionalidad que queramos implementar, debemos modificar o extender los componentes de software que hayamos realizado hasta el momento. Además es probable que debamos introducir nuevos componentes, no previstos hasta el momento, en la arquitectura del sistema y que estos encajen con los ya existentes. Todo un desafío. Las prácticas de ingeniería ágiles no son más que unas herramientas para conseguir estos objetivos. Muchas de dichas prácticas existen desde hace mucho tiempo (pre agilismo) dentro del paradigma OO, aunque hayan sidos ignoradas por nosotros, programadores de JABOL.

Antes de meterme en materia técnica, y abrir el arsenal de armas de destrucción masiva, voy a explicar en este post lo que quiero decir cuando en una consultoría digo: “pues depende…”. Es un tema que me preocupa porque empiezo a oír hablar de SOLID, código expresivo y demás, y todo ello justificado en nombre de una entidad abstracta llamada “calidad”. Es una corriente que me está dando algo de miedo, ya que en nombre de la “calidad”, me puedo tirar refactorizando mi código y chapándolo en oro por los siglos de los siglos sin entregar nunca a tiempo nada de valor a mi cliente. Me da la impresión de que algunos hablan de la “calidad” de la misma manera en la que un sacerdote podría hablar del “bien supremo” o un artista de la “belleza” de su obra. Las técnicas de diseño ágil son armas poderosas, y con ese poder viene la responsabilidad de no hacernos pajas mentales con ellas :-P

Mi postura es que la calidad del software, como entidad platónica y absoluta no existe. Es algo que no es medible, ¿alguien me puede proponer una fabulosa métrica que la mida y que sea factible de implementar? Si algo no es medible no debemos tomarlo como guía para tomar decisiones de diseño de nuestro código, por lo tanto, yo no uso la calidad para estos fines. Veamos pues como bajar al mundo real para tomar decisiones de diseño. En el mundo real del desarrollo de software profesional el objetivo es entregar software al cliente y que éste esté satisfecho con él. Por satisfecho quiero decir que se deben cumplir las dos siguientes condiciones:

  • El funcionamiento del software cumple las expectativas del cliente de forma razonable.
  • El software se entrega en un plazo de tiempo y con un coste razonable para el cliente. Si es más importante el coste o el tiempo lo debe decidir el cliente al principio del proyecto, de forma que podamos balancear estos dos factores adecuadamente.

O sea, que no descubro la pólvora, el cliente estará satisfecho si el software funciona, está a tiempo y su coste no se ha ido de madre. La parte quizás más problemática es la definición de “razonable”. Para mi el significado de razonable es algo que se debe acordar con el cliente mediante negociación, aunque eso sí, es una negociación asimétrica donde el cliente tiene la última palabra.

Por lo tanto el objetivo del diseño de software ágil es hacer software que funcione, respetando unas restricciones de tiempo y de dinero, y se entregue dicha funcionalidad de forma continua e incremental, aceptando posibles cambios. Las técnicas y principios de desarrollo ágil como TDD, Pair Programming, SOLID, KISS, DRY, etc. no son más que meras herramientas para conseguir ésto, no las confundamos con el objetivo. Antes de aplicar un patrón de diseño, o una refactorización, calculad el coste que os supondrá hacerlo en tiempo y dinero y comparadlo con el ahorro de coste y dinero que os supondrá a largo plazo. Si no hay un beneficio obvio, no lo hagáis. Así que cuando alguien me pregunta: “¿es bueno seguir los principios SOLID?”; y yo respondo: “pues depende..”; simplemente estoy diciendo que en función del contexto de tu proyecto tendrás que analizar si te va a dar beneficios o no.

En este sentido, la primera técnica que podemos aplicar es usar un timebox. Un timebox es un intervalo de tiempo predefinido, y no alterable, que restringe la implementación de una tarea. Ejemplos de timeboxes son: pomodoro, jornada de trabajo, sprint, deadline… La idea es que nos concentremos en cumplir con nuestras tareas y sintamos la presión de que se no acaba el tiempo. Esta presión nos ayudará a evitar que nos “entretengamos” en cosas que no sean conseguir terminar nuestra tarea. Usar timebox es una técnica común en el mundo ágil, ya que nos permite controlar nuestro ímpetu por demostrar como de bien sabemos programar, y centrarnos en simplemente implementar la funcionalidad, y si da tiempo se mejora su “calidad”. Usar timebox es una forma efectiva de conseguir KISS y YAGNI, si tienes claro que tienes un límite de tiempo vas a lo práctico, y haces lo más simple que puede funcionar y te olvidas de cosas que no vas a necesitar.

Enganchando con la primera parte de este post, el desafío técnico del agilismo, consiste por un lado en entregar incrementos de funcionalidad, y por otro aceptar cambios en la funcionalidad y el contexto del proyecto. Por lo tanto en un proyecto gestionado de forma ágil, vamos a estar continuamente cambiando nuestra arquitectura, nuestro diseño y los componentes ya existentes. Esto es así, aunque no cambien los requisitos funcionales, al tener que modificar el software ya existente para poder acomodar al menos la siguiente funcionalidad a entregar. Si hay cambios funcionales, el ritmo de cambio a absorber por nuestra arquitectura es mayor aun. Por lo tanto nuestro diseño y nuestros componentes deben ser maleables y flexibles. Cualquier técnica o principio de diseño, que recorte los gastos o el tiempo de implementar un cambio, rendirá beneficio en un proyecto ágil.

Por lo tanto ya tenemos una regla para guiarnos a la hora de tomar rápidamente decisiones de diseño. Podremos aplicar una técnica y/o refactorización, siempre y cuando:

  • Podamos aplicarla si no nos vamos a saltar el timebox de la tarea a implementar. Esto asegura que no tiene un coste alto, y no nos desvía de nuestro objetivo de hacer software que funcione.
  • Tenemos claro que consigue hacer que nuestro diseño sea más sencillo de cambiar en un futuro.

La regla anterior tiene una debilidad, la posibilidad de que el timebox sea irreal y demasiado corto para realizar la tarea. En este caso nunca tienes tiempo para refactorizar ya que estas un ritmo de trabajo insostenible, y tu diseño puede hacerse frágil ante el cambio. Por lo tanto tengo otra regla que tiene más precedencia que la anterior: si te cuesta cada vez más encajar una nueva funcionalidad en el diseño, refactoriza. Esta regla actúa como una alarma que me indica que debo “complicar” el diseño, sí o sí, ya que éste no es lo suficientemente flexible para aceptar cambios.

Resumiendo mi postura,la calidad del software no existe en si misma. Lo que realmente nos importa es terminar en tiempo y dinero con software que funcione. En un contexto ágil esto va a implicar que tu software debe ser maleable y aceptar cambios de forma rápida y barata. Olvídate de métricas, documentación y estándares. Céntrate en que tu software funcione y acepte cambios fácilmente. Me gusta llamar a esto calidad mundana del software, en contraste con la idea de que la calidad es un ideal a alcanzar como objetivo. Para obtener suficiente “calidad mundana”, pero nada más, recopilando lo dicho anteriormente, yo suelo usar las siguientes reglas:

  • Usar timebox para centrarme en implementar funcionalidad y no salirte de costes y tiempos. Puedo usar distintos timebox (pomodoros, jornadas y sprints) para distintos ámbitos (tareas, escenarios y Product Backlog Items). Esto promueve KISS.
  • Si sobra algo del timebox, cambia tu diseño para que admita cambios de forma más sencilla. En función del nivel del timebox puedes afrontar cambios más locales o más globales en diseño. Por ejemplo, en un pomodoro puedes intentar un refactor local, en un sprint, proponer a tu equipo alguna tarea de review de código o de rediseño de un subsistema.
  • Si detectas, de forma empírica (nada de corazonadas o instinto), que cada vez es más difícil aceptar cambios, entonces tu arquitectura es frágil ante el cambio. Da la alarma y prepárate para refactorizar.
  • Si no tienes problemas céntrate en producir funcionalidad. Olvídate de posibles futuros problemas que pueden no ocurrir nunca (YAGNI). Al fin y al cabo la calidad cuesta tiempo y dinero, si no necesitas más, ¿para que comprarla?

Bueno, este es mi enfoque personal. Como veis es muy cauto e intento no complicar el diseño hasta que no es realmente necesario. Llegué a esta forma de trabajar tras darme cuenta de que tenía tendencia a “chapar en oro” mi software, y hacer programación barroca. Me di cuenta que esto es un defecto y hay que balancear muy bien el esfuerzo que se dedica a mejorar el código contra el que dedicas hacer funcionalidad. Hay que llegar a un sano equilibrio.

Para finalizar, si tu proyecto no es ágil, olvídate de todo esto. En los proyectos no ágiles, el cambio es algo malo, que hay que penalizar, no aceptar. Por lo tanto no tiene sentido diseñar para aceptar el cambio. Como mucho tendrá sentido diseñar para cumplir el plan. Ahora empiezo a entender, porque algunos principios como YAGNI, KISS, SOLID o DRY no se han aplicado como debían en la industria, porque simplemente en un proyecto no ágil no tienen mucho sentido.

¡ Salud a todos y felices fiestas !

Read Full Post »


Hola a todos, lamento no haber podido escribir antes, pero tenía bastante trabajo acumulado y el deber me llamaba. Aprovecho que me iba a ir de fiesta esta noche y que al final no, para hacer algo útil con mi tiempo y escribir este post. Espero daros una crónica de lo que fue mi participación en la AOS2010, espero que un poco distinta y un pelín más crítica ;-)

Asistí junto con @ialcazar, @antoniodfr y @mcberros. Llegamos tarde, con la sesión de votación a mitad y un poco desubicados la verdad. Yo pensaba proponer una sesión llamada “los frameworks crean zombies” pero un tal Xavi Gost(@Xav1uzz) se me adelantó. Menos mal, porque con eso de llegar tarde, y ser mi primer open, estaba un poco cortado de verdad (a pesar del pequeño intento de @jmbeas de que saltara a la palestra). Mi compañero @antoniodfr no tenía muy claro si el formato open space iba a ser bueno o si por el contrario iba a degenerar en el caos. Al final se rindió al concepto de autoorganización y salió enamorado del concepto open.

Después vino una noche de parranda por los bares de Barcelona. Conté con la colaboración inestimable de Ramón Clariso y el inefable Aleix Cerecedo. Los bares al ver a una horda de agilistas por la noche de Barna iban cerrando a cal y canto, para que no entráramos en ellos ¡ y es que somos peligrosos ! Al final acabamos en un asador cerca del barrio de Graçia (¿está escrito bien?) donde tuvimos una animada charla con Roberto Canales(@rcanalesmora). Después el pobre Aleix tuvo la difícil tarea de buscar un bar en dicho barrio donde cupieran 40 personas. El pobre no dejaba de mirar atrás acojonado y diciéndome: “porque eres tu Enrique, sino salgo corriendo :-S “. Al final su plan “A” fue acertado y el sitio donde queríamos ir estaba vacío, pero en cuanto entramos quedo lo petamos. Desgraciadamente no servían mojitos, (mea culpa @kinisoftware) y lo peor es que no quedaba Moritz. Nos conformamos con gin-tonics y tercios de cerveza. A la llegada al hotel puse el despertador, este me informo amablemente que sonaría en sólo 3 horas, y cuando me quise dar cuenta ya estaba desayunando en el campus La Salle. Enrique, al lío.

Vayamos a las sesiones a las que asistí:

  • Coaching. Una sesión excelentemente moderada. Hubo un intercambio sano de ideas y experiencias, bastante refrescante. Allí me llamó la atención la intervención de Emma (¡dios, no se su twitter, que alguien me ayude, que quiero seguir a esta chica), según entendí es ScrumMaster quemada de muchas guerrillas en su empresa.
  • Software Craftsmanship. Propuesto por Xavi Gost y @ecomba. Bueno, nos contaron los principios del manifiesto y un poco como funcionaban, todo lo de los maestros y aprendices y demás. Me he leído el manifiesto y puedo decir que suscribo todo lo expuesto en él, ¿quién no podría estar de acuerdo? Es como los diez mandamiento, que aunque no seas judeocristiano, es probable que los pudieras firmar sin cargos de conciencia. Por ejemplo, el tema de educar mediante un sistema de maestro/aprendiz es obviamente la mejor manera de transmitir conocimientos. Por otro lado me sigue sin gustar el nombre o la analogía, ya que en el desarrollo de software no se construye nada. Los artesanos construyen pero en el software no se construye, se diseña. El que construye es el compilador. Por eso me sigue gustando más hablar de nosotros como ingenieros, y no como artesanos, porque nosotros diseñamos y el compilador construye. No veo por que el término ingeniero debe ser “evil”. En fin, es un tema de nomenclatura, porque a nivel de valores, que es lo que importa, me encuentro muy cercano a ésta forma de pensar. Un punto negativo en esta sesión fue que la moderación no fue muy efectiva, hablaron sobre todo Xavi y @ecomba, y la charla fue muy unidireccional (como me señaló alguien que no quiero nombrar).
  • La batalla de los frameworks. De nuevo con Xavi Gost, @ecomba y con la colaboración inestimable de @rcanalesmora en el papel del malo maloso de la película. Yo me lo pasé pipa y participé bastante, sobre todo para meterle caña a @rcanalesmora. Yo en esta estoy del lado del frente artesano y creo que los frameworks son armas de destrucción masiva a manejar con cuidado y mucha responsabilidad. El bando opuesto defendía que los frameworks te permitían contratar “personal poco cualificado” (a.k.a. “monos”) en masa para hacer los proyectos y ganar mucho dinerito calentito. El señor @rcanalesmora hizo una interpretación torticera de unas palabras que le dije la noche anterior, entre vinitos, y yo le respondí dándole cañita brava (pero con cariño).
  • Manipula. La señorita @rlaina nos dio una charla sobre el tema seguido de un debate. Tanto Emma como @jmbeas contaron sus problemas, que se parecen sospechosamente a los que tenemos todos. El problema es que cuando el debate se ponía interesante se nos acabó el tiempo y ¡ rompimos el timebox ! Cometimos el feo pecado de extender la charla a pesar de los heroicos esfuerzos de @amaliahern de salvar nuestras almas. La verdad es que el debate estuvo interesante, pero nos saltamos media comida.
  • Como tratar con locos paranoicos. Propuesta y dirigida por Xavi Albadalejo fue una de las charlas más interesantes. Usando la técnica de identificación de causas raíces analizamos porqué muchas personas recurren al Command & Control (a.k.a. “aquí se hace lo que yo digo”). Llegamos a la conclusión de que existían dos tipos de jefes autoritarios: los paranoicos y los desconfiados. Si te toca el primer tipo es mejor plantearte si dejar la empresa, ya que es una señal de que la cultura empresarial fomenta este tipo de personas como jefes. Si te toca el segundo tipo entonces hay esperanza. Lo curioso es que las técnicas que fuimos sacando para desactivar el ordeno y mando de un jefe desconfiado es simplemente… usar Scrum Efectivamente las técnicas propuestas por Scrum tienen como resultado aumentar la transparencia y esto acaba por aumentar la confianza. Muy enriquecedora esta sesión.
  • Diseño ágil. En esta sesión estuve bastante activo. Hablamos de como conseguir un buen diseño OO sin tener que hacer un diseño detallado al principio del desarrollo, práctica que va en contra de los principios ágiles. Yo conté mi experiencia, de que usando sólo TDD junto con un refactor continuo y agresivo, llegas a un diseño bueno. Sólo necesitas hacer al principio un pequeño diagrama de “cajitas” con la arquitectura de alto nivel. El detalle de cada “cajita” te la da el TDD+Refactor. También hice notar que esto por otro lado no te resolvía aspectos como el rendimiento y la seguridad, que tal vez sí hay que planearlos más de antemano. Algunos compañeros comentaron que ellos ponían pruebas de rendimiento en el build nocturno y que pedían a un experto en seguridad, que atacara el entorno de demo cuando el quisiera. Una aportación muy interesante. También se habló de las revisiones de código, y hubo unanimidad en que eran necesarias. Yo sostuve que la programación por parejas era equivalente a una “revisión de código continua”. Un asistente comentó que además ellos tenían un “moderador de proyecto”, que era externo al proyecto y neutral al equipo, que hacía revisiones de código. A mi me pareció una excelente idea. Ellos usaban un plugin de JIRA llamado Crucible, para hacer dichas revisiones. Lo tengo que probar. Todos estuvimos de acuerdo en que el UML era bueno para hacer bocetos de ideas y arquitectura de alto nivel. También consensuamos que había que hacer toda la documentación técnica necesaria para hacer el mantenimiento del sistema, pero por lo general, cuando el código está bien diseñado, éste es bastante legible, y necesitas menos documentación de la necesitarías normalmente.

Por supuesto hubo otras charlas, que supongo cada uno habrá comentado en su blog. Finalmente vino la retrospectiva, dinamizada, de forma genial, por el señor Ariel Ber (no se si tiene twitter). Se habló de las “AgileGirls”, de hacer un “Scrumbar” y de hacer el siguiente Open más grande. En concreto yo creo que una mejora para el AOS2011 debería ser que la gente participara más, me explico. En muchas sesiones la charla estuvo dominada por los “pesos pesados” y los “gurus” y vi a la gente un poco cohibida, lo que creo que impidió que se generara más debate. Yo mismo soy culpable de ese pecado y en muchas sesiones me hubiera gustado participar más, pero no me atreví. Tal vez en el siguiente open solucionemos eso.

Por supuesto hubo una segunda noche de fiesta, comimos en el “divinus”  y terminamos trasegando mucha cerveza y sangría en el “Ovella negra” (¿?) mientras @ydarias y compañía hacían lobby para que la siguiente AOS se celebre en Canarias (¡yo voto por vosotros!)

Quiero hacer mención especial de @amaliahern, que estuvo de fotógrafa e iba de sesión en sesión procurando que se cumpliera el horario. También creo que Ariel Ber estuvo excelente durante la retrospectiva, como comenté anteriormente.

Por último quiero poner mi opinión sobre un debate “silencioso” que se produjo de forma soterrada durante muchas sesiones: certificaciones sí, certificaciones no. De hecho hubo una sesión explícita sobre las certificaciones. Yo personalmente me he certificado CSM con Alan Cyment y Ariel Ber. Os recomiendo su curso, aprendí más sobre el espíritu de Scrum que en cualquier libro. Entiendo perfectamente a los que les da miedo que las certificaciones se perviertan y terminen siendo un modo fácil para que las grandes empresas se pongan el sellito de ágil y vendan por ahí lo que no son. También se corre el riesgo de que el agilismo quede preso de grandes consorcios certificadores. Sin embargo queda claro en todos sitios lo que significa ser CSM: que simplemente has dado un curso y que has comprendido lo que es Scrum, nada más. De comprender lo que es Scrum a practicarlo de verdad hay un mundo. De hecho la certificación que dice que usas Scrum con éxito no es la CSM, sino la CSP, en la que debes acreditar experiencia y pasar un “peer review”. De todas formas, sería de incautos juzgar la experiencia de alguien sólo por su certificación. Creo que los certificados son necesarios, pero no indispensables, y que tienen sus peligros, pero no por ello se debe generar un sentimiento de animosidad entre ambos bandos. Pensad, ¿se necesita ser universitario para ser un buen desarrollador de software? Por supuesto que no ¿Debemos abolir los títulos universitarios de ingeniería de informática por ello? Rotundamente no, aunque algunos políticos quieran. Pues lo mismo con las certificaciones.

En fin señor@s, yo vengo muy contento del AOS2010, y espero un AOS2011 aun mejor, tal vez en Canarias ;-)

P.S. Las fotos que tengo mejor no las cuelgo, curiosamente parecemos todos unos borrachos por la noche…

P.S.S. Señor @jmbeas péguele un tiro en la cabeza a su zombi, es para que no sufra el pobre :-D

Read Full Post »


… y lo sabéis en el fondo de vuestro corazón, ¿o no? Bueno, a lo mejor sí que existen, pero a mi me parece que no. En este post os voy a contar mi punto de vista sobre esto de los proyectos cerrados ¡ Bien, hoy no hay TDD, menos mal ! Creo que es importante tratar este tema de los proyectos cerrados, sobre todo en relación a las metodologías ágiles, ya que está muy extendida la idea de que dichas metodologías no pueden aplicarse en proyectos cerrados. Mi idea es que eso de los proyectos cerrados no existe, y que cuando se dice que un proyecto es cerrado en realidad estamos soltando una buena mentira.

¿Os acordais del teorema CAP? Bueno, pues lo que voy a contar a continuación se parece mucho. A alto nivel y desde el punto de vista de planificación y preventa de un proyecto hay tres variables: el alcance, el tiempo de entrega y el coste unitario. Veamos que son estas tres variables:

  • Alcance. El alcance es la cantidad de funcionalidad o de historias de usuario que va a tener el proyecto. También se puede interpretar como el tamaño del proyecto. En los proyectos de alcance pequeño sólo hay que implementar unas pocas funcionalidades, en los de alcance grande tenemos una lista de requisitos grande (o unos pocos requisitos enormes). Algunos factores no funcionales, como la complejidad de las tecnologías implicadas en el proyecto, o la complejidad a nivel de interlocución, también pueden afectar al alcance.
  • Tiempo de entrega. Cuanto tiempo tenemos para terminar el proyecto.
  • Coste unitario. El coste de mantener un equipo de trabajo y toda su infraestructura por unidad de tiempo. Cuanta más gente en el equipo, mayor coste unitario. Cuantas más máquinas y más caras, más coste unitario. Cuantas más licencias de pago tengamos, más coste unitario. No confundir con el coste total, que lo da la suma del coste unitario a lo largo de todo el proyecto.

Bien, todo sencillo. En este contexto podemos definir un proyecto cerrado como aquel que tiene un coste unitario, tiempo de entrega y alcance fijo. Sin embargo esto en la práctica no existe. Un proyecto cerrado sólo podría existir en un mundo estático, donde el entorno no cambia y las cosas son totalmente predecibles y repetibles, y el mundo real no es así. En la realidad las necesidades de negocio cambian durante el desarrollo de los proyectos, los clientes y usuarios se equivocan y cambian de opinión, la productividad de un equipo de desarrollo cambia con el tiempo. Sólo un novato puede pensar que un cliente no va a cambiar de opinión o que un acta de reunión se va a respetar (en Alemania tal vez, en España no). He visto demasiadas actas de reunión y contratos y análisis funcionales haciendo compañía al papel higiénico (hay que reciclar). Todo esto lo sabe la gran mayoría de los profesionales y por ello en la práctica tenemos mecanismos de defensa como:

  • Las notas de cambio de alcance y la gestión del cambio. Es el mecanismo de defensa típico del proveedor. ¿Que el cliente cambia el alcance? Pues pagas dinero. La opción de quitar alguna funcionalidad a cambio no es tan popular.
  • Las cláusulas de penalización si un proyecto se retrasa. Con esto el cliente se defiende tanto de un proveedor en el que no confia como de posibles retrasos debido a imponderables.
  • Precio total del proyecto cerrado. Como el anterior permite al cliente defenderse de proveedores malvados o ineptos o simplemente ante cualquier tipo de desgracia acaecida durante el proyecto.

Fijaros en la contradicción, todo el mundo habla de que los proyectos de verdad son cerrados, pero todo el mundo asume que habrá retrasos, malentendidos, incompetencia, imponderables y cambios de necesidades de negocio. Todo el mundo hace proyectos cerrados, pero a la vez usan tácticas de defensa. El problema real es que la gente tiene en cuenta la realidad, pero las metodologías tradicionales no. Las metodologías tradicionales asumen que una vez pasada la fase de análisis de requisitos (o preventa como se llama en España), se puede hacer un proyecto cerrado, ya que presuponen que el mundo es estático e inamovible. Como la realidad es caótica y cambiante, pero todos se empeña en usar metodologías pesadas, se producen estas contradicciones. Usar metodologías basadas en concepciones irreales es peligroso para tu negocio, no me extraña que la mayoría de los proyectos no sean exitosos. En vez de usar metodologías que no se ajustan a la realidad y que tienen una visión inocente respecto al nivel de caos de un proyecto real, es más eficiente usar metodologías diseñadas con la cruda realidad en mente, y que se basan en aceptar el cambio y gestionarlo. ¿Cúales pueden ser estas metodologías? Mmmm, ah, sí, eso raro del agile.

Visto que el caos de la realidad nos impide tener proyectos cerrados, ¿qué hacemos? Tal vez no podamos cerrar las tres variables, pero si algunas de ellas y jugar con las demás. Veamos que posibilidades tenemos:

  • Alcance abierto, coste unitario y tiempo de entrega cerrado. Este tipo de proyectos es el clásico ejemplo en las metodologías ágiles. Se tiene un tiempo de entrega cerrado, ya que en estos casos el time to market suele ser vital en el negocio. Dada una estimación del alcance, se propone un equipo cerrado (coste unitario cerrado). Se cierra el equipo ya que normalmente añadir o quitar miembros a un equipo suele ser malo para la productividad (costes de formación, ponerse al día con el proyecto, etc). Durante el proyecto se acepta cualquier cambio de alcance, pero al acabar el tiempo pactado, se entrega. Esto permite ofertar a coste total cerrado. El proyecto se considera exitoso si el alcance conseguido es similar al estimado. Más vale estimar bien, sino el cliente quedará descontento.
  • Alcance cerrado, coste unitario cerrado y tiempo de entrega abierto. Como ya he comentado con anterioridad este caso no se produce en el mundo real, excepto tal vez, en proyectos pequeños, debido a que el alcance siempre cambia, ya sea por cambios en necesidades de negocio o por malentendidos. Aun suponiendo que el alcance es cerrado, el que el tiempo de entrega sea abierto, implica que la eficiencia o productividad del equipo varía o se ha estimado mal la capacidad del equipo. Otra posibilidad es que el time to market no afecte al valor del proyecto, pero esto es poco realista. En este caso el coste total del proyecto es abierto, ya que no sabemos por cuanto tiempo vamos a tener al equipo trabajando.
  • Alcance cerrado, coste unitario abierto y tiempo de entrega cerrado. De nuevo como el anterior este caso no es muy real, tal vez se puede dar en aplicaciones pequeñas pero con un time to market muy estricto. Para conseguir llegar a la entrega se invertirán los recursos que hagan falta. De nuevo el coste total del proyecto es abierto.
  • Alcance y coste unitario abierto, pero tiempo de entrega cerrado. Se corresponde con un proyecto crítico, no sabemos exactamente lo que se va a implementar, pero tiene que estar listo en una fecha concreta. Para conseguirlo se pondrán todos los recursos que sean necesarios.
  • Alcance y tiempo de entrega abierto, pero coste unitario cerrado. Esto es un proyecto sin prioridad ni ningún tipo de urgencia. Puede ser un proyecto personal o entre amigos.

El tipo de proyecto que os podéis encontrar más frecuentemente en la realidad es el primero, el que deja abierto el alcance y cerrado lo demás. Qué casualidad que es el tipo de proyecto en el que se centran las metodologías ágiles. Este tipo de proyectos tiene unas cualidades muy interesantes: el coste total es cerrado y por otro lado se ajusta mejor a la realidad. Esto es bueno para el proveedor, ya que evita entrar en perdidas y puede asegurarse un beneficio económico. También es bueno para el cliente, pero sólo si el alcance conseguido es suficientemente bueno.

Esta circunstancia pone al cliente en una posición de debilidad con respecto al proveedor. Muchas consultoras sin escrúpulos se aprovecharon de esto en el pasado. Por eso los clientes ahora demandan “proyectos cerrados”. Lo que en realidad hacen es protegerse contractualmente. Es muy raro que un cliente admita “alcance abierto” debido a esa debilidad. Sin embargo si la relación cliente/proveedor es de una gran confianza y la experiencia pasada sea buena, si puede admitir este modelo de proyecto con alcance abierto, al fin y al cabo lo que le suele interesar al cliente es protegerse de proyectos que no se acaban nunca y que consumen dinero sin fin. Si el fracaso del proyecto no supone un gran perjuicio para el cliente, aumenta la posibilidad de que éste se preste a un contrato abierto. Otro caso es el de los proyectos internos, donde es absurdo penalizarse a uno mismo, y es mejor aceptar la realidad. En este caso el poder hacer “lo máximo posible” en un tiempo y coste acotado es bastante atractivo.

En el resto de los casos el cliente querrá un modelo de penalización o bien de precio cerrado. Esto no significa que no tengamos un proyecto con alcance abierto, sino que tenemos un contrato cerrado. Sólo debemos aceptar contratos cerrados si estamos muy seguros de poder cumplirlos o si estamos muy desesperados. Para tener esa seguridad nuestra organización debe ser lo suficientemente madura como para poder hacer estimaciones certeras. En este caso se trata de estimar el alcance, y en función de esta estimación saber que coste unitario hay que invertir para cumplir los plazos. Esto no es fácil, sobre todo si tenemos en cuenta que el coste unitario no afecta de forma muy predecible a la productividad del equipo. Podemos gastar mucho pero mal, por ejemplo comprando caras licencias de productos que no son adecuados o poniendo un pelotón de becarios en vez de unos pocos desarrolladores expertos.

En cualquier caso debemos estar preparados para lo peor. Sabemos que aunque el proyecto sea abierto el cliente normalmente se protegerá con un contrato cerrado, asi que ¿qué puede ocurrir si fracasa el proyecto? Es decir, ¿que pasa si el alcance logrado no da el valor suficiente al cliente? En estos casos el cliente tiene diferentes mecanismos para protegerse:

  • Si lo más importante para el negocio es alcanzar un nivel de funcionalidad aceptable, se da un tiempo extra al proyecto, a cambio de una penalización económica debido a los “daños” de no tener el sistema a tiempo.
  • Si lo que realmente importa es el time to market, se pone en producción pero se penaliza al proveedor en función de la cantidad de funcionalidad que falte, para compensar los inconvenientes de que el sistema no tenga suficiente funcionalidad.
  • Precio cerrado. El proyecto es precio cerrado, y el cliente no te pagará ni un céntimo más del pactado. Si el time to market es lo importante tendrás que aumentar el coste unitario para reforzar el equipo. Si lo importante es la funcionalidad, tu equipo estará más tiempo del planificado. Combinaciones de ambos escenarios son bastante típicas. Todo esto puede lleva a proyectos con poca rentabilidad o pérdidas si no consigues estimar bien.
  • Te demandan y/o devuelves el dinero más el perjuicio recibido por el cliente debido a no conseguir tener la aplicación.
  • Si aplicas una metodología que lo permita, se puede abortar el proyecto en cuanto se vea que se va a fallar. Las metodologías ágiles son especialmente adecuadas en este sentido. Si se falla al principio, el cliente aun está a tiempo de contratar a otro, con lo que tiene menos perjuicio. Desgraciadamente en España es raro ver proyectos abortados aunque claramente se encaminen al fracaso.

¿Cómo afrontamos los contratos cerrados? ¿Nos rendimos y hacemos lo “mismo de siempre”? Muy al contrario, lo ideal en estos casos es usar metodologías ágiles, aunque la idea parezca de poco sentido común. Desde el punto de vista de los contratos cerrados, las metodologías ágiles son muy eficaces, ya que nos permiten gestionar el riesgo mejor y por otro lado nos permiten tener estimaciones precisas. Uno de los pilares de dichas metodologías es el uso de ciclos de feedback frecuentes, lo que nos permite:

  • Enterarnos rápidamente de los cambios de alcance. Mediante entregas frecuentes y desarrollo incremental el cliente tiene acceso rápidamente a la funcionalidad con lo que nos puede advertir de los cambios de requisitos. Este es un mecanismo muy útil para mitigar uno de los riesgos más típicos: construir funcionalidad que el cliente no quiere.
  • Aceptar cambios e implementar la funcionalidad más importante primero. Cuando se llegue a la fecha de entrega, al menos se habrá cubierto la funcionalidad más importante, con lo que la posibilidad de fracasar disminuye.
  • Mediante las reuniones diarias nos enteramos de los posibles impedimentos al proyecto, lo que nos permite tomar acciones correctivas, mitigando riesgos tecnológicos y organizativos.
  • Análisis de viabilidad frecuente. Por ejemplo, en Scrum, podemos decidir al final de cada iteración o sprint, cancelar el proyecto, de acuerdo al feedback del usuario y de los equipos de desarrollo.
  • Las estimaciones mejoran más rápidamente, ya que al tener un feedback más rápido, tenemos más datos y por lo tanto podemos ajustar mejor las estimaciones. Con una metodología pesada el feedback es al final del proyecto, y las estimaciones sólo se pueden ajustar al final del proyecto. En Scrum ajustas las estimaciones al menos una vez por iteración (sprint). En kanban, una vez cada vez que se termina una historia de usuario.

También hay que tener en cuenta que cada sprint o iteración se puede considerar como un proyecto en si mismo, pero de alcance muy pequeño y duración corta. Si el alcance es pequeño y la duración corta,  pensar que el alcance está cerrado, sí es una buena aproximación a la realidad. Al contrario que en un proyecto real, durante un sprint, hay muy poco tiempo, y por lo tanto poca probabilidad de que el alcance del sprint cambie. Podemos considerar pues, que cada sprint es un verdadero proyecto cerrado en el sentido estricto del término. De esta forma gestionar un sprint sí es sencillo, comparado con gestionar un proyecto. De todas formas, en proyectos muy cambiantes (o altamente caóticos), el alcance cambia incluso durante el sprint. En estos caso Scrum puede que no sea la mejor opción, y habría que utilizar enfoques más ágiles todavía, como kanban o el agilismo minimalista.

Como vemos, los proyectos cerrados no existen, pero los contratos cerrados sí, lo que hace los proyectos sean arriesgados para el proveedor. Sin embargo el mito de que una metodología ágil no es útil con contratos cerrados se cae bajo su propio peso. Al contrario de lo que muchos piensan, las metodologías ágiles permiten una mejor estimación y gestión del riesgo de un proyecto con contrato cerrado, además de incorporar de forma natural la gestión del cambio. Tal vez, cuando tu cliente vea que no lo engañas y que cumples tus contratos, te deje hacer un “contrato abierto”.

Read Full Post »


Hola, seguimos con la polémica del TDD, pero no temais, en este post no voy a hablar de TDD (al menos no mucho). En el anterior post defendí que si se hacía bien el TDD, con su refactor y que si además se complementaba con un pair programming, la necesidad de métricas estáticas de calidad de código desaparecen. Comentarios a dicho post por parte de @jmbeas, Chuidiang e @ydarias indicaban que mi postura era correcta en un equipo experto, pero que si tenías equipos menos maduros, entonces necesitabas estas métricas porque los equipos no iban a hacer TDD, refactor y pair programming, ya sea por inexperiencia o por simplemente no querer. Bien, esto es cierto, de hecho es interesante que al enseñar TDD y refactor a equipos novatos,  usemos análisis de código estático para ver como de forma incremental, y emergente, el código alcanza una buena calidad.

En el fondo, mi problema es que pienso que los equipos que no hacen TDD o pair programming, sencillamente no están haciendo agilismo… Enrique, no te metas en más líos, mejor digamos que sencillamente no están usando una buena metodología. En el resto de este post voy a explicar tan inverosímil afirmación.

El agilismo propone un metaproceso para definir un proceso de desarrollo e ir evolucionándolo en el tiempo, con el objetivo de adaptarnos a los cambios en nuestro entorno y mejorar dicho proceso de forma continua, de manera que maximicemos el valor entregado al cliente. No vale con que el proceso sea bueno hoy, sino que debe ser mejor mañana y no debe quedarse obsoleto. Y por mejor entendemos que entregamos más valor al cliente en menos tiempo y con menos coste. Obviamente, para poder implementar el proceso de desarrollo en la realidad, y poder hacer mejora continua, se nos propone un conjunto de buenas prácticas. Mi pregunta es, ¿cuál es el conjunto mínimo de buenas prácticas para considerarnos ágiles? Dicho de otra forma, ¿cuál es la metodología ágil más ligera posible? Dada la definición de agilismo anteriormente expuesta, la metodología ágil más ligera, es aquella que sólo obliga prácticas que proporcionen lo más rápidamente posible información de los problemas del proyecto, y nos ayuden a resolverlos cuanto antes. Armados con unos ciclos de feedback rápidos, que nos informen sobre problemas en el proyecto, podemos practicar mejora continua, y terminaremos con una metodología que es perfecta para nuestro entorno de trabajo específico, y que va a evolucionar para adaptarse a cualquier imprevisto.

Dicho esto, hay que tener en cuenta que los problemas nos lo podemos encontrar a múltiples niveles: estimación y planificación, desarrollo, build y pases de entornos, explotación, etc. Necesitamos pues, ciclos de feedback y mejora continua en cada uno de esos niveles. Obviamente algunos de dichos niveles escapan a mi control, pero al menos planificación y desarrollo sí están bajo mi control. Al conjunto mínimo de prácticas, que te permiten conseguir mejora continua de forma efectiva, lo llamaré agilismo minimalista.

A nivel de control de proyecto y mejora continua lo mínimo que puedes hacer es:

  • Retrospectiva al final de proyecto. Hay que reunirse para saber las causas del éxito o fracaso del proyecto, y que cosas se pueden mejorar y cómo. Es el nivel básico de mejora, aprender del resultado de un proyecto para el siguiente. Debería participar la dirección de la empresa también y el cliente.
  • Múltiples reuniones de seguimiento del proyecto, a ser posible a intervalos regulares, predecibles y ni demasiado largos ni cortos. Un ejemplo de reunión de seguimiento es la retrospectiva de sprint dentro de SCRUM. Otro ejemplo es la demo de final de sprint, este último más orientado a obtener feedback del cliente. Nos permite enterarnos rápidamente de los problemas que se producen durante el proyecto sin esperar a que acabe. Si hay algún problema podemos tener la oportunidad de solucionarlo. También podemos ajustar nuestras estimaciones con la realidad del proyecto, y aprender a estimar mejor en el futuro. Se puede involucrar a todos los participantes en el proyecto, al fin y al cabo no se hace todos los días. Esto es importante para que la información del estado del proyecto llegue a todo el mundo por igual y no se quede información escondida en nichos. También debe participar un representante del cliente, para ver como va el proyecto, proporcionar información adicional y aclarar dudas.
  • Reunión diaria o daily scrum. Es un nivel de feedback más rápido, diario, que involucra a los miembros de un mismo equipo y al menos un responsable. En proyectos grandes pueden haber varios equipos cada uno con su propia reunión diaria.
  • Reunión de emergencia. Para solventar cualquier problema grave y no previsto, detectado mediante cualquier mecanismo.

A nivel de planificación y estimación:

  • Dividir el trabajo en unidades manejables. Dichas unidades deben ser estimables, representar un incremento en el valor entregado al cliente, concisas y claras, de un tamaño similar entre si, independientes entre si, tener un criterio de aceptación y no demasiado grandes. Esto se suele conocer como historias de usuario. La importancia de las historias de usuario es que te proporcionan una unidad de planificación y estimación que está bien correlacionada con el valor del proyecto desde el punto de vista del cliente. El criterio de aceptación es importante para saber cuando se ha terminado de implementar la historia.
  • Revisa las estimaciones frecuentemente, teniendo en cuenta los resultados obtenidos en las retrospectivas y reuniones anteriormente mencionadas. Si las historias no fueran pequeñas, independientes y de tamaños similares, el ajuste de las estimaciones basándonos en lo que pasó en otras historias sería prácticamente imposible.
  • Refina y revisa la definición de las historias frecuentemente. Tal vez a intervalos regulares (SCRUM sprint planning) o tan pronto como te sea posible (kanban). Para ello hay que hablar con el propio usuario, o en su defecto con un experto de negocio.
  • Implementa primero las historias con más valor. Esto maximiza el valor entregado al cliente. También nos evita tener que implementar historias que pueden cambiar o quedar obsoletas con el transcurso del tiempo.

De momento a nivel de gestión y planificación, el agilismo minimalista no es ninguna sorpresa. El punto es que si no implementas todas estas buenas prácticas, no eres ágil. Simplemente no vas a poder reaccionar a los cambios en tu proyecto ni mejorar la forma de trabajo. Casi todas estas prácticas las realizan todas las empresas serias que conozco, excepto la reunión diaria, las historias de usuario y la implementación por orden de valor. Si no practicas la reunión diaria tendrás problemas ocultos y enquistados al menos hasta la próxima reunión de seguimiento, que será dentro de ¿entre 2 y 4 semanas? ¿No es muy ágil cierto? Hasta aquí casi todos estareis de acuerdo, pero todo esto no es suficiente para reaccionar ante problemas y mejorar en un proyecto software. Necesitamos tener en cuenta otro aspecto, la ingeniería.

Desde el punto de vista de mejora continua de la ingeniería y desarrollo:

  • Programación por parejas. Es otro ciclo de feedback, esta vez sobre el diseño y la calidad de código. Lo podemos considerar una revisión de código continua o una QA continua. El feedback proporcionado por un compañero a la persona que está tecleando es rapidísimo, del orden de segundos.
  • TDD. Nos permite saber si nuestro código funciona, o si la modificación que hemos hecho rompe algo. Feedback del orden de minutos.
  • Refactor frecuentemente. Dentro de un ciclo de TDD para garantizar que se hace a menudo, y guiado por la programación por parejas, nos permite mejorar de forma continua la calidad de nuestro código.
  • Integración Continua. Feedback a nivel de horas, que nos permite detectar problemas de integración.

Bien, con esto quiero decir que el agilismo minimalista necesita de mejora continua no sólo a nivel de gestión, sino a nivel de ingeniería, con lo cual necesitas hacer TDD, refactor, pair programming e integración continua, como mínimo, para ser ágil. Si no haces integración continua no detectas los problemas de integración hasta que no haces un pase, ¿es eso ser ágil? Si no haces TDD no detectas si has roto funcionalidad al introducir un cambio hasta que no lo prueba el equipo de QA, ¿es eso ser ágil? Además si no haces TDD no puedes hacer integración continua, sino como mucho, compilación continua. Si no haces pair programming no detectas errores de programación o fallos de diseño hasta dios sabe cuando, ¿es eso ser ágil? El refactor frecuente te permite arreglar tu código, sin necesidad de esperar al super arquitecto/guru. Lo importante es que todas estas prácticas se realimentan y se producen sinergias positivas entre ellas. Cada una cubre cosas que la otra no. Si quitas una sola el edificio se empieza a derrumbar. Por ejemplo si quitas el pair programming puedes introducir fake TDD o simplemente saltarte el refactor. Si quitas el TDD te cargas la integración continua. Las necesitas todas, no son opcionales, son obligatorias.

Ya contamos en la CAS2010 nuestra experiencia de implantación del agilismo. Hubo un momento que teníamos sprints, retrospectivas, etc. Pero no teníamos TDD ni pair programming ni se hacía refactor, y mucho menos integración continua. Eso no funcionaba. Asi que lo siento, a aquellos que piensen que puedes hacer agilismo con un equipo que no controle estas técnicas y no esté dispuesto a hacerlas, os aviso: no va a funcionar, no basta con el sprint planning y el daily scrum, no vas a ser ágil, necesitas prácticas ágiles de ingeniería y gente capaz de llevarlas a cabo.

Afortunadamente parece que hay gente por ahí de acuerdo conmigo, sino leed lo mismo que digo pero explicado de otra manera en este blog de Luis Artola. SCRUM y KANBAN sólo se ocupan del nivel de gestión y planificación de proyecto, pero no de la ingeniería. Para que tu proyecto sea exitoso, necesitas también buenas prácticas de ingeniería y trabajadores capaces de llevarlas a cabo. Muchos piensan que son ágiles porque aplican prácticas ágiles a nivel de planificación y gestión, pero se olvidan de la ingeniería. Esta es una maldición eterna en el mundo de los proyectos software, parece que por poner una capa de gestión se arreglan todos los problemas, pero nadie presta atención a lo más básico, la ingeniería y la capacidad profesional de los “pikachus”.

En resumen, lo mínimo que puedes hacer para considerarte ágil (agilismo minimalista) es: pair programming, TDD+Refactor, integración continua, reuniones diarias, reuniones periódicas de seguimiento (internas y externas para el cliente), retrospectivas de proyecto y planificación y estimación basada en historias de usuario. Si te falta, aunque sea una sola de estas prácticas, no eres ágil.

¿Y las demás buenas prácticas? Bueno, para mi no forman parte del agilismo minimalista, las considero muy dependientes del entorno en el que se mueva cada uno. Aquí es, donde creo yo, que se debe aplicar eso de probar a usar una buena práctica, experimentar y ver si va con tu proyecto o no. Lo importante es que con el agilismo minimalista irás descubriendo cuales son útiles y cuales no. Por ejemplo, a nosotros los sprints no nos han funcionado bien, por eso estamos pensando en pasar a kanban, pero conservando los principios del agilismo minimalista. Quizás esto de agilismo minimalista+kanban sea algo parecido al scrumban que he escuchado por ahí. En cualquier caso el resto de las prácticas debéis experimentar con ellas antes de adoptarlas en serio o no.

Corolario: si ves que necesitas métricas estáticas de calidad de código, es una señal de que no eres ágil, algo falla (pesaito soy). Saludos y ahora me voy a dormir, que ya me vale.

Read Full Post »


Hola a todos, de vuelta de la AgileSpain2010, y con un poco de “resaca” de la conferencia, me toca defender un par de frases que solté en nuestra sesión. Lo que ocurrió realmente fue lo siguiente:

Creando polémica en la CAS2010

Fanático del TDD en la CAS2010

Bien, pues lo solté y me quedé tan fresco, de hecho no me pareció que fuera una frase polémica, pero empecé a ver caras raras y a la salida de la sesión vi por twitter que mi frase había causado cierta extrañeza. Empezaré aclarando mi frase: “Si haces TDD bien no necesitas análisis de calidad de código estático”. Ojo, hablo de hacer TDD bien, no de hacer TDD a medias. Existe un malentendido respecto al objetivo del TDD, si bien uno de ellos es lograr un conjunto de pruebas automatizadas con alta cobertura de código, éste no es el único, de hecho es sólo la mitad de la historia. El otro objetivo, igualmente importante, es conseguir un código de calidad de forma incremental y/o evolutiva. De hecho algunos autores hablan de calidad o diseño emergente, pero yo prefiero no fliparme tanto de momento. Si tenéis el libro de Carlos Blé sobre TDD, veréis que se llama “Diseño Ágil con TDD”, no programación con TDD o pruebas con TDD o QA con TDD, sino diseño. Éste es el entendimiento general que todos los autores tienen sobre este tema: TDD lleva a pruebas automáticas de alta cobertura y a alta calidad de código, si lo haces bien, claro.

Llegado a este punto conviene explicaros mi percepción de los niveles de adopción del TDD:

  • Fake TDD. En este nivel de adopción el TDD no se practica, sino que se simula practicar. En el fake TDD los tests no representan realmente la funcionalidad de las historias de usuario o de la interfaz del componente que queremos probar. No se hace un esfuerzo serio por entender la funcionalidad del componente bajo pruebas y se escriben tests con poco contenido, contenido incorrecto o simplemente tests de relleno sin contenido. Ésto puede ser por desconocimiento de la técnica, con lo que habremos de dar más formación y hacer talleres. También puede ser por presión, que tiende a romper la disciplina del programador, y por miedo a no estar en fechas, se ignora el TDD. La tercera razón para hacer fake TDD la veremos en el siguiente post.
  • Naive TDD. Simplemente consiste en no realizar la fase de refactorización durante el ciclo de TDD. Las causas suelen ser de nuevo inexperiencia o prisas. Normalmente si no se entiende TDD como una metodología de diseño nos encontraremos en este caso. Otro tipo de Naive TDD se produce cuando se escribe primero código de implementación y después el test.
  • TDD. Adopción completa, haces test con funcionalidad correcta pero no consideras el ciclo terminado hasta que no has refactorizado. Para que un ciclo de TDD se considere completo y puedas hacer commit, debe existir un test, con los contenidos adecuados, que al ejecutarse pase, y el código que implementa la funcionalidad bajo prueba sea limpio. Si el código no es limpio debemos refactorizar.

¿Qué es pues código limpio? Pues es código que tiene unos niveles de calidad razonables, pero, ¿qué es la calidad del código? Difícil pregunta. Para ello las distintas organizaciones y empresas definen un modelo de calidad, que consiste en un conjunto de métricas de código que se van a tomar y que resultados mínimos son exigibles para esas métricas. Las métricas se clasifican en dos tipos: estáticas y dinámicas.

La métricas dinámicas miden propiedades de tiempo de ejecución del sistema. Ejemplos típicos son corrección, rendimiento y escalabilidad, seguridad y usabilidad:

  • La corrección la conseguimos con el propio TDD (hasta donde es posible dado que los requisitos son cambiantes y difusos).
  • El rendimiento y escalabilidad se consiguen con pruebas de stress, algo que está aparte del TDD, hasta donde yo sé.
  • La seguridad del mismo modo está en un mundo aparte, y que yo sepa no se pueden hacer pruebas automatizadas satisfactorias para esto, salvo quizás los ataques más típicos.
  • La usabilidad tiene que ver con la facilidad de manejo de la aplicación y su atractivo para el usuario, definitivamente esto no se puede probar de forma automática, para ello podemos usar pruebas de aceptación tradicionales.

Por otro lado las métricas estáticas miden propiedades del código que se pueden detectar en tiempo de compilación. Veamos:

  • Nomenclatura. Si tu código se ajusta o no a determinada nomenclatura. Bueno, que deciros, no considero esto importante para un desarrollo ágil, es mucho más importante la legibilidad y la documentación. Puedo entender que en un lenguaje con tipado dinámico esto pueda ayudar, al fin y al cabo no hay compilador que te diga si una variable es un string o un integer. Sin embargo si vas a usar un lenguaje de tipado dinámico (ruby, javascript, smalltalk, etc) es mejor que no pienses en java, sino que saques partido a las características de dicho lenguaje, que normalmente no está pensado para que una variable sea siempre un string, es simplemente otra filosofía de diseño.
  • Nivel de documentación. Bueno, esto sí es interesante, me habéis pillado :-) Sin embargo no considero que esto se pueda medir de forma totalmente automática. La dificultad radica en que es muy difícil automatizar la decisión de si un método o clase debe estar documentada. Desde el punto de vista del agilismo debemos documentar sólo aquello que merezca la pena, no todo, y por supuesto tampoco vale no documentar nada. Detectar si documentar un artefacto de código “vale la pena” o “o aporta valor” de forma automática es difícil, ¿no creéis?
  • Legibilidad. Lo más importante, tu código debe ser legible por tus compañeros, si no, no podrán mantenerlo. Esto tampoco se puede detectar automáticamente.
  • Estilo y formato de código. Esto realmente es un aspecto de la legibilidad.
  • Tamaño del sistema. No se muy bien para que se quiere medir esto, y además, ¿en qué lo medimos? ¿Lineas de código?¿Puntos función? Sin comentarios. Tal vez lo que queremos medir realmente es la cohesión.
  • Alta cohesión y bajo acoplamiento (principio de una única responsabilidad). Realmente estas son muy interesantes de medir y a mi juicio sí que son necesarias. Los artefactos de código que tengan muchas interdependencias entre ellos deben agruparse en artefactos de nivel superior. Por ejemplo, un montón de métodos que son muy interdependientes podrían agruparse en la misma clase. Y viceversa, si un artefacto esta compuesto de muchos subartefactos que apenas interaccionan entre si, podemos dividir ese artefacto en varios más pequeño. El ejemplo típico es la clase “monstruo” con todos los métodos del sistema dentro de ella, ay, cuantas de éstas habré visto a lo largo de mi carrera. Estas propiedad del sistema sí que se pueden medir automáticamente, al menos en los lenguajes de tipado fuerte. Si se pueden medir automáticamente en los lenguajes de tipado dinámico tengo mis dudas, pero me callo por no ser experto.
  • Duplicación de código. Esto se puede medir fácilmente con una herramienta. La existencia de código duplicado es un signo de mal diseño y baja calidad, excepto en el caso de extrema necesidad de legibilidad.
  • Malas prácticas. ¿Acaso el analizador de código estático es inteligente? No, es tonto, es un robot que se limita a pasar patrones sobre el código. Si no tiene inteligencia, ¿cómo va a saber si una práctica es mala? ¿Acaso entiende el código? Sólo va a detectar las malas prácticas más sencillas. No me lo creo a pesar de lo que me digan los vendedores de herramientas sofisticadas (y caras).

Existen muchísimas más métricas estáticas, algunas de alta complejidad. Yo personalmente no las entiendo, ni veo cual es su sentido. Todavía no conozco a nadie que me las haya podido explicar, así que de momento me quedo con las que os he contado anteriormente. En cualquier caso, ¿qué utilidad tiene medir una cosa sin entender lo que estás midiendo? Si alguien me sabe explicar alguna métrica arcana, que sea esencial para medir la calidad del software, y no haya mencionado, que me lo comente.

En el modelo de calidad que yo uso, y que me gusta pensar que es un modelo de calidad ágil, considero que el código es limpio si cumple en la medida de lo posible los siguientes criterios de calidad: corrección, legibilidad, sin código duplicado, bajo acoplamiento y alta cohesión. Si os fijáis, excepto la legibilidad, el resto de los criterios te los incorpora de forma natural el ciclo de TDD que incluye la refactorización. La legibilidad es algo que no se puede comprobar automáticamente. O sea que si hacemos TDD bien, y encima lo reforzamos con otras buenas prácticas, como la programación por parejas o la revisión de código, no necesitamos para nada complicar vuestro build con análisis de código estático. Una aclaración, yo considero que el código es limpio si todo el código pasa el modelo de calidad anteriormente mencionado. Esto implica no sólo el código de implementación, sino el código de test. La tarea de refactorización incluye también al código de test no sólo el código bajo pruebas. No es válido tener código duplicado en el código de test, y este debe ser legible, y poseer una buena cohesión. De hecho las pruebas unitarias deben ser independientes unas de otras, lo que implica un acoplamiento entre ellas muy bajo. Un error típico es no refactorizar el código de test, sólo el de implementación.

Muchos estareis pensando que todo esto es muy bonito, pero lo que queréis realmente es controlar que las cosas se hacen bien. Así que muchos me diréis, ¿cómo controlo que el código está bien hecho?¿Cómo se que se está haciendo TDD bien y no me están engañando? Muchos de los expertos en agilismo os dirán que el simple hecho de que hagáis esta pregunta significa que no sois ágiles. Si necesitáis que una herramienta os diga si el equipo está programando según las buenas prácticas es que no estáis al lado del equipo, que no practicáis go&see y que estais encerrados en vuestros despachos sin interactuar realmente con la realidad, es decir, con vuestro equipo.

Ciertamente lo comentado en el anterior párrafo puede ser cierto en las circunstancias típicas de un proyecto ágil. En otras circunstancias, como en el caso de trabajar con equipos distribuidos, es lícito que tengáis dudas sobre si vuestro equipo hace el TDD como debiera, ya que al fin y al cabo, no podéis estar en la misma sala con todos los equipos a la vez. Así pues, si tienes equipos distribuidos, ¿cómo sabes que se hace bien el TDD? La respuesta está en el nivel de cobertura y en aplicar un poco de psicología:

  • Si la cobertura es baja, entonces realmente no se está haciendo TDD bueno. Puede ser porque se está haciendo Fake TDD, y por lo tanto los tests al no ser suficientemente completos no ejerciten bien el código. Puede ser también que tengamos Naive TDD, y se escriba código de implementación antes que el test, con lo que puede quedar zonas del código de implementación sin probar. La falta de refactorización del naive TDD también puede llevar a baja cobertura, después lo comento. También puede ocurrir que simplemente no se ha hecho TDD en todos los casos, y se ha escrito código de implementación sin ningún tipo de test.
  • La cobertura es alta (mayor del 70%). En este caso existe una alta probabilidad de que el código tenga una buena calidad y se haya hecho TDD del bueno con refactorización. La razón de esto es sutil. Lógicamente si hacemos TDD bien vamos a tener una cobertura alta. ¿Podemos llegar a tener cobertura alta haciendo Fake TDD? Claramente no, a menos que el programador dedique sus esfuerzos a sabotear el proyecto y haga casos de tests con alta cobertura, que pasen pero que no prueben nada. Ciertamente esto es bastante bizarro. Sobre la gente que se dedica a hacer esto hablaré en el siguiente post ¿Pero y el Naive TDD? Al fin y al cabo sí hacen tests, podríamos tener alta cobertura a pesar de no refactorizar, ¿no? La verdad es que ciertamente es posible pero es difícil. Al no refactorizar duplicas código, y el diseño de tus clases hace que se vayan volviendo cada vez más difícil de testear, ya que no guardan el principio de alta cohesión, bajo acoplamiento, etc. Por un lado duplicar código hace que tengas que duplicar también los fragmentos de test que ejercitas ese código. Esto se va haciendo pesado y se tiende naturalmente a no hacerse con lo cual la cobertura baja. También la duplicación invita a que los tests fallen, ya que un cambio en una historia de usuario o un bug, implica cambiar código en muchas zonas de la aplicación, con lo que harán que el test que prueba ese bug falle. Finalmente, al no refactorizar, las clases van perdiendo calidad, y va siendo cada vez más difícil hacer pruebas unitarias, ya que estas se van acoplando cada vez más con su entorno, y no presentan una buena encapsulación y cohesión. Como vemos el no refactorizar al principio no tiene mucha importancia, pero conforme pasa el tiempo se hace más difícil añadir tests, con lo cual los tests se hacen más laxos y la cobertura baja.

Yo recomiendo encarecidamente mezclar TDD con dos prácticas más: programación por parejas y revisiones de código, especialmente de los tests. Ambas contribuyen a que los desarrolladores cuiden su prestigio y no pierdan la disciplina, con lo que baja la probabilidad de que rompan el ciclo de TDD. La programación por parejas tiene la ventaja adicional de enseñar la técnica a los más novatos. Os puedo contar que en el último proyecto estaba presionado de tiempo y rompí la disciplina del TDD para una funcionalidad que era “trivial”. Como no estaba haciendo programación por parejas pude hacer esta trampa. ¿El resultado? ¡ Zas, en to la boca ! Efectivamente sufrí una concentración de bugs en ese código “trivial”, necesité una jornada de 20 horas de programación ininterrumpida para solventar el desaguisado. Cuando llegué a casa mi mujer casi me mata de un sartenazo pensando que era un vulgar ladrón. Después estuve una semana KO por el sobreesfuerzo. Nunca más.

Si aun veis que queréis un análisis estático de código podéis usarlo como método didáctico. Conforme vais haciendo TDD, ireis viendo como vuestra cobertura aumenta y también los resultados de las métricas de calidad. Esto es recomendable para equipos que empiezan con el TDD y todo esto de la refactorización. Cuando se trata de un equipo que sabe hacer TDD y refactor, realmente las métricas os sobran, así que podéis aligerar vuestro sistema de integración continua y ahorraros un dinero en licencias desactivando tales métricas.

Por supuesto existe una razón no ágil para esto de las métricas: el informe de colores para impresionar a la dirección y a los clientes. Si necesitáis un informe lujurioso para justificar el avance de vuestro proyecto estais en una situación mala. Yo justifico el avance de mis proyectos con demos, enseñando software que funciona y que cada vez tiene más funcionalidad.

En el siguiente post os cuento la otra pequeña polémica que tuvimos.

Read Full Post »


Hago un alto con la serie de posts sobre la estructuración de los equipos para hacer una introducción a un tema candente, las bases de datos no relacionales y el movimiento noSQL.
Algunos pensarán que esto del noSQL es una moda más, pero lo curioso es que se enfrenta a otra moda mucho más poderosa e influyente, el SQL y las base de datos relacionales. En muchos sitios me enerva una práctica muy común que consiste en que al diseñar la aplicación y analizar los requisitos lo primero que se hace es… ¡el modelo de tablas relacional! Muchos consideran que la aplicación está casi hecha en cuanto tenemos un esquema de base de datos y unas sentencias SQL, y acoplan todo su código a esto. Esta forma de diseñar aplicaciones está muy bien cuando trabajas con COBOL pero desperdicia por completo las características de cualquier lenguaje de programación OO moderno. La cosa llega a tal extremo que muchas personas sólo piensan en aplicaciones CRUD y de hecho hay frameworks como Grails o Rails que te proporcionan una aplicación CRUD muy rápidamente si no te importa acoplarte con la BBDD.
Mi filosofía es un poco diferente, usar Acceptance TDD y DDD y DSL (a.k.a. API Sexy) para definir un core de negocio orientado a objetos que sea fácil de probar, fácil de usar y modele el dominio de la aplicación. Es decir, como uso OO, lo que hago es realmente modelar el negocio mediante un modelo de objetos, que refleja la realidad del problema que queremos resolver y contiene la lógica de negocio. Sobre este modelo monto un DSL para poder trabajar con el modelo escribiendo código que parezca lenguaje natural o al menos sea legible por el analista funcional. En esta filosofía de trabajo la interfaz de usuario y la capa de persistencia son periféricas a este core de negocio. De hecho se puede diseñar el sistema para que tenga distintas interfaces de usuario y puedas cambiar el mecanismo de persistencia de forma sencilla.

Desde este punto de vista, si yo diseño mi sistema con un core OO de negocio robusto, y necesito almacenar los objetos de forma persistente, entonces debo usar una capa de persistencia, y no me importa especialmente cual sea mientras cumpla mis requisitos de la forma más sencilla posible. Desde este punto de vista analicemos lo que nos ofrece una base de datos relacional:

  • Persistencia. Ok, es lo que busco.
  • Lógica de negocio, en forma de procedimientos almacenados y triggers. Pues no, esto me sobra, lo tengo en el core de negocio.
  • Transacciones ACID. Esto depende, si estoy haciendo operaciones críticas que deben realizarse de forma instantánea desde el punto de vista de negocio entonces sí. Si estoy escribiendo una aplicación de redes sociales y quiero escribir una actualización de estado, no me importa mucho si falla al grabarse, o tengo lecturas fantasma. Otro ejemplo de operación no ACID es el de una transferencia monetaria bancaria. Ciertamente das de alta la transferencia de dinero de forma atómica, pero la consolidación de los balances entre la cuenta destino y origen puede tardar días, teniendo mientras tanto un estado “inconsistente”.
  • SQL. Un lenguaje de query complejo, que me permite hacer casi cualquier consulta y que para optimizarlo tengo que ser un experto en BBDD. No gracias, las consultas complejas las hago en mi core de negocio, en mi lenguaje de programación favorito, y por que no, con mi DSL. Yo sólo quiero grabar, leer, y hacer unas cuantas consultas muy concretas, no cualquiera. Además, ¿cómo puede una BBDD ser más óptima en la ejecución de la consulta que mi código, hecho específicamente para mi problema? Es el típico caso de que una solución genérica a un problema no puede ser más eficiente que una solución específica para un problema específico. En este caso SQL es un lenguaje de query genérico, y no conoce los detalles específicos y posibles optimizaciones de mi aplicación, sólo puede usar técnicas generales.
  • Esquema. Estructura rígida y tipada de almacenamiento en forma de tablas con columnas ¿Para que quiero eso, si la estructura y el modelo y los tipos y validaciones del sistema está en el core OO? Realmente esto es un dolor, todos habréis sufrido con problemas de conversión de tipos entre columnas de tablas y campos de objetos, o entre objetos y tablas. Tal es el problema que nuestra aplicación termina haciendo uso de algún framework de mapeo objeto relacional, como Hibernate o iBatis o JPA, que añade otro grado de complejidad y configuración innecesario. Es innecesario porque la estructura de la información ya está en el modelo de objetos, no es necesaria replicarla en la capa de persistencia.

Si somos prácticos y aplicamos el principio KISS, vemos que una BBDD relacional puede ser, en muchos casos, algo que nos añade una complejidad innecesaria. Nos añade funcionalidades duplicadas con el core OO o simplemente innecesarias en algunos casos. Lo que ocurre es que el típico escenario de aplicación, el típico caso de uso, está cambiando respecto a lo que era antes. Cada vez tenemos más aplicaciones que tienen alguna o todas de las siguientes características:

  • Deben soportar grandes volúmenes de datos. Pensad en cualquier red social o en aplicaciones de Google, como el google maps.
  • Deben soportar altas cargas de transacciones online, debido a que el número de usuarios en la web puede ser enorme. De hecho cuantos más usuarios tengamos, los señores de negocio estarán más contentos.
  • El rendimiento no debe degradarse debido a los dos puntos anteriores.
  • ¡Que no se caiga! Hoy en día la caída e indisponibilidad de una aplicación tiene consecuencias monetarias muy fuertes.
  • En aplicaciones como las redes sociales, la consistencia no es crítica, con lo que las transacciones ACID no son necesarias.
  • ¡Fácil de usar! No quiero sufrir para simplemente grabar datos y hacer cuatro consultas básicas. No quiero saber nada de configuraciones de Hibernate o explains de Oracle.
  • ¡Fácil de integrar con un core OO de negocio!

Como vemos en muchos tipos de aplicaciones, las bases de datos relacionales te ofrecen capacidades que no te interesan a cambio de complicarte la vida. Por otro lado puedes tener muchos problemas con los requisitos de rendimiento, escalabilidad y disponibilidad. Para solucionar esos problemas vas a necesitar expertos, máquinas y productos normalmente caros. Lo peor que es que dichos expertos pueden coger una posición de poder y empezar a imponer sus reglas. Lo ideal es que las reglas las ponga el cliente, no un grupo de sacerdotes de una oscura tecnología.

Hasta ahora por tradición y por moda (o por imposición) cada vez que se nos planteaba la necesidad de persistir información elegíamos una BBDD relacional de forma automática y sin pensar. Como la integración entre un core OO y la BBDD relacionales es complicada, la gente llegó a varias soluciones (no necesariamente excluyentes):

  • No usar un core OO de negocio. Me hago una aplicación igual que la que hacía en COBOL pero traducida literalmente a JAVA.
  • Uso un Grails o un Rails, me acoplo al esquema de la BBDD y ya está.
  • Uso mi framework de mapeo objeto relacional (Hibernate, iBatis, etc),  e invierto esfuerzo en configurarlo y optimizarlo.

Esto es un razonamiento torticero, como tengo que usar BBDD relacionales entonces me complico la vida o paso de la OO. Es decir, me creo problemas “artificiales”, que no existían originalmente, porque elijo no solucionar el problema original (la persistencia) de forma óptima, sino hacer según manda la tradición y la moda. Lo lógico es analizar que tiene que hacer tu sistema de persistencia y después decidir qué usas. Puedes terminar usando una BBDD relacional si realmente la necesitas, pero en la mayoría de los casos os daréis cuenta que no os hace falta. Este es precisamente el punto de partida del movimiento noSQL.

Resumiendo, el movimiento noSQL, entre otras cosas, te propone:

  • No tienes porque usar una BBDD relacional, tienes alternativas para la persistencia de tu modelo de objetos.
  • No te hace falta un lenguaje de triggers ni de consultas complejas ya que la lógica de negocio está en la aplicación, no en el sistema de persistencia.
  • No necesitas un esquema rígido en tu sistema de persistencia, ya tienes tu modelo de objetos. Puede que incluso trabajes con un lenguaje de programación de tipado dinámico (js, ruby, etc). Simplemente quieres persistir objetos enteros o documentos. La estructura exacta de lo que guardes no debe ser de la incumbencia del sistema de persistencia, sino de tu modelo de negocio.
  • Te vale una API sencilla y un modelo de consulta sencillo. Uno muy popular es el clave valor, o tabla hash persistente. Guardas objetos o documentos y los asocias a una clave para poder recuperarlos.
  • Puedes prescindir de ACID para conseguir alta disponibilidad, escalabilidad y bajos tiempos de respuesta.
  • No odies SQL, simplemente debes saber cuando usarlo y cuando no. Hay casos en los que es la mejor solución, pero no en todos, y creo que cada vez en menos.

En siguientes posts hablaré sobre que sistemas de persistencia noSQL son más usados, su paradigma de desarrollo y como podemos eliminar, en muchos casos, la necesidad de transacciones ACID y el 2PC, con el objetivo de aumentar la disponibilidad y escalabilidad. Iré mezclando posts de noSQL con los de estructuración de equipo para que haya variedad.

Read Full Post »

Older Posts »

Seguir

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

Únete a otros 41 seguidores