30 diciembre 2009

Libros que estuve leyendo: Historias de Terramar I y II

A veces los libros llegan a mi biblioteca de forma poco predecible.

Si alguien me recomienda un libro, es fija que me olvido. Hasta que un tiempo despues, de paso por una librería, veo un titulo o autor y pienso: "este me suena, a ver...".

La cuestión es que después de leer El Aleph tenía ganas de leer algo de literatura fantástica al estilo Señor de los anillos, es decir algo más pochoclero.
Entre a una librería de Av. Corrientes a buscar eso y por alguna razón me acorde del titulo "Terramar".

Asi fue como compre Historias de Terramar I: Un mago de Terramar. Aunque no es cierto que lo haya comprado en la primer librería que entré: por alguna razón no lo encontré en las típicas cadenas como Cuspide o Yenny.
 
El libro me encanto. No tiene alusiones intelectuales complicadas, ni metáforas que requieran mucho análisis. Esta claro que buscaba "pocholo".

En comparación al Señor de los Anillos tiene muchos menos nombres extraños que memorizar ;), y sorprenden algunas pequeñas similitudes con Harry Potter, más teniendo en cuenta que Historias de Terramar fue editado en el año 1968.

Me gustaron también algunas cuestiones atípicas para este tipo de historias: el personaje principal no es el típico héroe que pelea contra los malos (hay un enemigo bien definido, pero contarlo seria arruinarles la historia) y no hay tampoco una división del estilo "elfos bonitos y buenos" vs "orcos feos y malos". Incluso me atrevería a decir que hay cierto transfondo Taoista en la historia.

En fin recomendable para: distenderse con una historia fantastica entretenida, regalarle a ese primo adolescente que no lee ni por casualidad, o leerlo en una tarde de verano a la sombra de un árbol después de comerse un asado.

Y como me gusto, después fui en busca de la segunda parte: Las tumbas de Atuan (que también me costo encontrar en librerías).
No esta mal pero tiene un tono muy distinto: fue como escuchar Kid A esperando la continuación de The Bends.

NOTA: Imagen de Ged (protagonista de la historia) obtenida del sitio www.fantasymundo.com

27 diciembre 2009

Libros que estuve leyendo: El Aleph

Antes de que termine el año quería comentarles algunos libros leí desde mi último post literario.
Son varios, pero siguiendo el consejo del Torto, voy a hacer posts breves y comentarlos de a uno.

Seguía con ganas de leer libros de ficción y tras varios autores anglosajones, preferí aventurarme en un autor local. Así que empecé a leer El Aleph* de Borges.

Claro que no conocía nada sobre Borges, y quizás por eso pensé que iba a leer "un autor local" si darme cuenta que él se caracterizo por ser el escritor más universal de la literatura argentina ;)

El articulo sobre él de la Wikipedia en español parece estar muy completo y es una buena introducción.

Lo único que puedo decir es que el libro me pareció impecable, pero... no me engancho. No era su momento, y aunque leí la mayoría de los cuentos no llegue a terminarlo.

Me paso lo mismo la primera vez que leí Todos los fuegos el fuego de Cortazar y Farenheit 451 de Bradbury: me aburrieron terriblemente y los deje, pero años después volví a leerlos y me encantaron.

Quien sabe quizás me pase lo mismo con los cuentos de El Aleph.

* Cuidado las paginas de la Wikipedia tienen algunos spoilers... después no digan que no se los advertí.

10 diciembre 2009

Test driven development... algunas lecciones aprendidas

En este post quería compartirles algunas cuestiones sobre Test Driven Development (TDD) que fui aprendiendo de los proyectos y personas con las que trabaje.

TDD != Testing en busca de bugs

Es muy común confundir TDD con testing: al menos comparten la palabra "test" en el nombre.

La diferencia esta en que uno generalmente usa la palabra "testing" para referirse a la búsqueda de bugs. Mientras que en TDD la intención es otra: uno busca agilizar el diseño, facilitando la incorporación de cambios.

A grandes rasgos TDD consiste en expresar con un programa las "expectativas" sobre lo que se va a desarrollar.


Supongamos, por ejemplo, que estamos aprendiendo C con libro de Kernighan y Ritchie y su ya famoso "Hello World", así que escribimos:

#include <stdio.h>
int main() {
    printf("Hello World\n")
    return 0;
}

Compilamos...

$ cc hello.c -o hello
hello.c: In function ‘main’:
hello.c:5: error: expected ‘;’ before ‘}’ token

Arreglamos código, compilamos y ejecutamos, hasta que vemos en la pantalla el resultado esperado:

Hello World

La diferencia usando TDD es que primero escribimos un programa para verificar el resultado esperado (algo que comúnmente hacemos de forma manual):

ejecutarPrograma();
verificar(salidaDePrograma, "Hello World");

Nuestro "test", no intenta buscar bugs probando diferentes casos, o casos "borde" que puedan ser problemáticos.
Si no que simplemente es una forma de establecer que es lo que queremos hacer.

Esta diferencia es sutil pero tiene muchas implicancias en la forma de trabajar:
  • No es necesario pensar casos de test complejos, solo basta con pensar que es lo que se quiere hacer.
  • Con TDD el ciclo de escribir código - compilar, se transforma en: escribir tests/código - compilar/ejecutar tests.
    Es decir, crear/ejecutar tests debe ser parte común del desarrollo. Si los tests tardan en ejecutarse se transforman en una carga.

En mi caso los "tests" usados de esta forma se convierten en una especie de "TODO list". En efecto muchas veces siento que si no tengo planteado un "test" (aunque sea en mi cabeza) no sé por donde empezar.

¿Vale la pena dedicar tiempo a escribir tests?


El ejemplo de "Hello World" muestra una cuestión evidente: programar el test puede ser mucho más difícil que hacer el programa a testear.
Cabe preguntarse si es siempre así, y si el trabajo adicional vale la pena.

En general no siempre es tan difícil escribir tests, y hay factores que influyen en la dificultad:
  • Componentes y frameworks "acoplados" a sistemas externos.
    Por ejemplo, si quisieran escribir el test del "Hello World" en C deberían capturar el STDOUT del programa. Suele ser fácil reemplazar la salida standard, aunque si el stream fuese parametrizable sería mucho más fácil.
  • Frameworks que no proveen interfaces separadas de a implementación.
    Siguiendo con el "Hello World", supongamos que podemos parametrizar el stream de salida, sería ideal poder capturar la salida en un String.
    Si el "Stream" que nos brinda el lenguaje no permite hacerlo, podemos hacer otra implementación que cumpla la misma interfaz y capture el String. Pero si "Stream" es una clase cerrada, estamos en problemas.
  • Diseños que no cumplen con el principio de Single Responsibility.
    Si la "responsabilidad" de lo que queremos testear es acotada, entonces el test también va a ser más corto y fácil de escribir.
  • Dependencias globales mutables (como Singletons que no son "singletons").
    En un post anterior ya hable sobre los problemas del estado global... así que voy a evitar la tentación de volver a comentarlos :)
  • Tests con grandes conjuntos de datos.
    Complican la mantención del test y por lo general también hacen que la ejecución sea más lenta.
  • El lenguaje/tecnología a usar influyen en la forma de trabajo.
    Por ejemplo en Smalltalk es muy común desarrollar mientras se ejecuta el test (completando lo que falta implementar en el debugger), ya que entorno permite modificar clases sin "parar el mundo". En Java este tipo de prácticas no es posible.
    En otro extremo el lenguaje C++ no cuenta con reflection out-of-the-box, y por lo tanto escribir tests es mucho más trabajoso (hay que indicarle al framework explicitamente que tests ejecutar).
    Es conveniente tener en cuenta la tecnología que se usa, por ejemplo en C++ intentería tener tests más grandes para evitar el "overhead" que implica crear tests nuevos. En Smalltalk, Java u otros lenguajes el entorno ayuda a que este problema no exista.

Desde un punto de vista de management, quizás la cuestión esta en cual es el "retorno de inversión" de lidiar con todo esto. Para mi el valor esta en que:

  • Al tener tests automatizados uno tiene más confianza a la hora de hacer refactorings o cambios en el diseño.
    Esto puedo afirmarlo con mi experiencia: trabaje en un sistema financiero donde teníamos una gran cantidad de tests (si no recuerdo mal, más de 10.000).
    Tuve que hacer un rediseño importante de una parte "core" del sistema, hice los cambios y emepecé a ver los tests que fallaban.
    Los tests que entendí, los arregle. Los que no, los consulte con sus autores y los arreglamos en conjunto. Todo este trabajo hubiese llevado muchísimo más tiempo sin tests automatizados.
  • Ayuda a tener un diseño más abierto a los cambios
    La razón es simple: cuanto más acoplado esta un objeto al uso de sistemas o frameworks externos, más difícil es testearlo. Por lo tanto con TDD uno tiende a diseñar de forma tal de disminuir este acoplamiento.
    Lo mismo ocurre cuando los objetos tienen dependencias globales mutables: se vuelven dificiles de testear por que un test cambia el estado global, haciendo que otro test falle. Por lo tanto uno tiende a evitar este tipo de globales.
    A la larga las consecuencias de usar TDD son beneficiosas para el diseño.

No todo es color verde...

Como mencione antes: la intención de TDD es facilitar cambios y ayudar en el diseño. No encontrar bugs.

Por lo tanto aunque la aplicación pase los tests de unidad, puede contener errores funcionales (casos de test mal planteados), bugs por casos "especiales" y problemas de UI.

Volviendo al ejemplo de este sistema en el que trabaje con más de 10.000 tests de unidad:
Al principio no teniamos un equipo de QA, los tests automáticos pasaban y algunos de nosotros probamos la aplicación durante el desarrollo.

Llego el día "D": empaquetamos todo para producción y llevamos el software al cliente.

A las horas recibimos un llamado: un botón de la UI permanecía deshabilitado y el cliente no podía acceder a una funcionalidad del programa (funcionalidad que estaba desarrollada, pero inaccesible). Asi que tuvimos que corregir el problema y hacer un nuevo deploy.

Los tests de unidad no reemplazan a un equipo de QA. No es la intención de TDD reemplazar las prácticas comunes de testing y verificación de calidad.

La diferencia esta en los detalles

Supongamos los convencí de las "bondades" de TDD. Asi que empiezan a practicarlo en un proyecto.

Al tiempo hay una alta probabilidad de que empiece a pasar lo siguiente. Algunos tests fallan pero arreglarlos es un problema: los tests se volvieron enormes e inmantenibles. Solución rápida: los tests que fallan se ignoran. Un par de meses despúes los tests que fallan se siguen ignorando, y se pierde la ventaja de TDD: si necesitamos hacer un cambio de diseño no tenemos forma de saber que rompimos.

¿Por que se llega a este punto?
Principalmente es una cuestión de la costumbre del equipo en hacer y mantener los tests.
Pero además hay un montón de detalles que con el tiempo se acumulan cual bola de nieve e influyen en que los tests se vuelvan inmantenibles. Algunos "detalles" a tener en cuenta:

No depender de bases de datos externas

Hacer que un test de unidad requiera de una base de datos es problemático por que:
  • El entorno de desarrollo es dificil de configurar: cada desarrollador tiene que configurar los drivers y conexiones para poder empezar a trabajar con los tests.
  • Si se usa una base compartida: un cambio hace que los tests fallen para el resto de los desarrolladores. Para hacer TDD los tests se deben correr todo el tiempo, asi que tener tests que fallen por cambios de otros es inadminsible.
  • Es más trabajoso para usar un servidor de integración continua: hay que asegurarse de que el servidor de integración use correctamente la base de datos de prueba.
  • La ejecución de los tests suele ser más lenta, asi que correr todos los tests no es algo que se haga muy seguido.

En el caso de que un test necesite una base de datos es necesario distinguir:
  • Que es lo que se esta testeando: ¿Quiero realmente testear el acceso a la base de datos?
  • ¿Puedo reemplazar el acceso a la base de datos, por una implementación que "simule" la respuesta y no acceda a una base de datos real?

Muchas veces uno quiere testear la implementación a la base de datos, por ejemplo se usa Hibernate para responder algunas consultas y cambiar la implementación por una que simule la base no aporta nada.
En esos casos lo mejor es usar una base en memoria (como HSQLDB) y levantarla durante el test.

HSQLDB es liviano, soporta Hibernate y toda la sintaxis de SQL. El unico punto en contra es que si uno quiere "ver" como se guardan las cosas en la base hay que frenar el test para no bajar el servidor.

El siguiente es un pequeño ejemplo de como usar HSQLDB en un test:

public class TestDatabase {
    public static final String DEFAULT_DATABASE_NAME = "test";
    public static final int DEFAULT_SERVER_PORT = 9001;

    private Server hsqldbServer;
    private HsqlProperties hsqldbProperties;
    private String jdbcConnectionUrl;
    
    public TestDatabase(String databaseName, int serverPort) {
        hsqldbProperties = new HsqlProperties();
        jdbcConnectionUrl = "jdbc:hsqldb:hsql://localhost:" + serverPort + "/" + databaseName;
        hsqldbProperties.setProperty("server.port", serverPort);
        hsqldbProperties.setProperty("server.database.0", databaseName);
        hsqldbProperties.setProperty("server.dbname.0", databaseName);
    }

    public static TestDatabase startWithDefaultConfiguration() {
        return new TestDatabase(DEFAULT_DATABASE_NAME, DEFAULT_SERVER_PORT).start();
    }

    private TestDatabase start() {
        hsqldbServer = new Server();
        hsqldbServer.setProperties(hsqldbProperties);
        hsqldbServer.start();
        return this;
    }

    public void shutdown() {
        if (hsqldbServer == null) return;
        hsqldbServer.shutdown();
    }
}

public class AccesoALaBaseTest {
    private static TestDatabase database;

    @BeforeClass
    public static void startTestDatabase() {
        database = TestDatabase.startWithDefaultConfiguration();
    }
    
    @AfterClass
    public static void shutdownHsqldbServer() {
        database.shutdown();
    }
}

No usar herencia para compartir instancias de prueba

Usar herencia para compartir instancias entre tests es un error muy común.

Supongamos que estamos testeando la implementación de CajaDeAhorro. Para crear una instancia de la caja de ahorros, necesitamos una instancia de Cliente, que a su vez necesita una instancia de Contrato, que a su vez necesita una instancia de... bueno se hacen a la idea: crear todas estas instancias es trabajoso, nos tomamos el trabajo :(

Despúes queremos testear CuentaCorriente, y queremos reusar las instancias que creamos para el test de CajaDeAhorro.
Muchas veces se suele crear una superclase para el test, supongamos AbstractTest, donde se colocan estas instancias que necesitamos compartir.
Los desarrolladores empiezan a heredar todos los tests de AbstractTest, y van "subiendo" instancias que quieren compartir entre tests.

¿Se ve el problema?
AbstractTest termina siendo una gran bolsa de gatos. Cuando se quiere refactorizar AbstractTest hay otro problema: es engorroso buscar las referencias a cada variable usada en las subclases. Por lo general este refactoring implica mucho trabajo y nadie lo hace. AbstractTest sobrevive a varias versiones transformandose en una inmensa bola de lodo.

La solución:
No usar herencia para compartir instancias entre los tests (usar herencia para re usar código es mala idea, en este caso es malisima).
Es preferible crear una clase, por ejemplo ClienteTestResource (suelo usar el sufijo "TestResource" para esas clases) que brinda instancias de Cliente para los tests.

Nada evita que ClienteTestResource no se convierta tambien en una bolsa de gatos, pero la cuestión es más controlada: uno puede crear clases distintas para agrupar recursos necesarios en los tests. Y a diferencia de AbstractTest, los desarrolladores solo usan ClienteTestResource cuando lo necesitan.

Evitar código redundante

Otro problema común es la actitud de: "hago copy & paste, total es un test!".

Esto es perjudicial: los tests tambien hay que mantenerlos. Si hay que hacer un "copy & paste" para crear instancias que se necesitan en el test... usar un "TestResource".

Si hay que implementar un método en común para facilitar el testing: ¿Entonces por que no implementarlo en el modelo? Por ejemplo, si estoy testeando el balance de una cuenta y el codigo del test es algo asi:

cuenta.balanceAl(crearFecha("01/01/2009"));
...

private function Date crearFecha(String s) {
      SimpleDateFormat sdf .....
      return sdf.parse(s);
}

Y empiezo a copiar "crearFecha" en varios tests, la alternativa es dar una interfaz amigable en Cuenta (la otra alternativa es tener una interfaz más amigable para construir instancias de Date en general, un buen ejemplo de esto en Smalltalk es el framework Chaltén, que pemite expresar fechas como: "Jaunary first, 2009" donde Jaunary es un objeto first y , son mensajes... asi que la expresión es basicamente da una fecha) que permita escribir:

cuenta.balanceAl("01/01/2009");

Eso no quita que el metodo balanceAl(Date) no tenga que existir. El formateo de fechas depende del Locale y no debería usarse con un Locale implicito en la aplicación. Sin embargo es conveniente pensar en la facilidad de uso de las interfaces que uno provee: las interfaces de los objetos son como las interfaces graficas.

Tener interfaces que permitan expresar las cosas de forma natural, usando defaults apropiados y simplificando ciertas cuestiones, ayuda muchisimo a la hora de escribir tests.

La clave es pensar que cuando uno programa esta construyendo un lenguaje, y esto abarca tambien a los tests. No es lo mismo escribir:

assertThat(coleccion, hasItem("hola"));

Que escribir:

boolean found = false;
for (String s : coleccion) {
    if (s.equals("hola")) found = true;
}
assertTrue(found);

(Nota: sé que existe coleccion.contains("hola") pero quería explicitar la fealdad de esta alternativa ;) )

Un test por "observación"

Supongamos que para una CajaDeAhorro queremos testear que:
  • Los depositos incrementan el balance.
  • Las extracciones decrementan el balance.
  • No se puede hacer una extracción si no hay fondos.

Uno puede estar tentado a testear todo esto junto:

@Test public void depositoYExtraccion() {
assertThat(cuenta.balance(), is(monto));
cuenta.depositar(monto);
assertThat(cuenta.balance(), is(monto * 2));
cuenta.extraer(cuenta.balance());
assertThat(cuenta.balance(), is(0));
try {
    cuenta.extraer(monto);
    fail();
} catch (NoHayFondosException e) {}
}

Pero tiene algunos problemas:
  • Si el test falla es necesario debuggear para saber si el problema esta en depositar, extraer o balance.
  • El ejemplo es chico, pero en tests más grandes los errores tienden ser más dificiles de ver.

En comparación esta solución es mejor:

@Test public void losDepositosIncrementanElBalance() {
    assertThat(cuenta.balance(), is(0));
    cuenta.depositar(monto);
    assertThat(cuenta.balance(), is(monto));
}
@Test public void lasExtraccionesDecrementanElBalance() {
    assertThat(cuenta.balance(), is(not(0)));
    cuenta.extraer(cuenta.balance());
    assertThat(cuenta.balance(), is(0));
}
@Test(expected=NoHayFondosException.class)
public void noSePuedeHacerUnaExtraccionSiNoHayFondos() {
    assertThat(cuenta.balance(), is(not(0)));
    cuenta.extraer(cuenta.balance() * 2);
}

Parece más largo pero tiene varias ventajas:
  • Es más facil ver que esta fallando.
  • Si un test falla es más facil de corregir.
  • Tiene (para mi) una implicación psicologica: ir haciendo pequeños tests que van pasando, en general me ayuda a tener un mejor ritmo de trabajo. Incluso entre pequeños test a veces se me ocurren rediseños que no habia pensado originalmente.

Evitar tests no deterministicos

Cuando el resultado esperado depende de un valor del "entorno", por ejemplo: la hora actual o un número random. Es común terminar con tests no deterministicos: a veces pasan y otras veces no.

Por ejemplo supongamos que estamos testeando una aplicación que genera registros de auditoria con la hora actual:

registroEsperado = new RegistroAuditoria(nuevoTimestamp, etc, etc, etc);
sistema.ejecutarMetodoQueGeneraAuditoria();
assertThat(sistema.logDeAuditoria(), hasItem(registroEsperado));

El problema es que si la comparación de RegistroAuditoria involucra comparar el "time stamp", entonces el test pasa o falla según la resolución de la hora y si hubo pausas en el medio de la ejecución (por ejemplo se ejecutó el GC).

Una forma de evitar este tipo de cosas es "fijar" estos valores. Por ejemplo podemos tener una interfaz Clock que provee la hora:

Clock fixedClock = new FixedClock(nuevoTimestamp);
sistema = new Sistema(fixedClock);
registroEsperado = new RegistroAuditoria(nuevoTimestamp, etc, etc, etc);
sistema.ejecutarMetodoQueGeneraAuditoria();
assertThat(sistema.logDeAuditoria(), hasItem(registroEsperado));

Ya no hay más problemas de indeterminismo :)

Evitar delays

Muchas veces por razones de concurrencia, se termina agregando al test un "delay" (por ejemplo se ejecuta un thread y quiero esperar a que el thread actualize un valor que despues voy a verificar).

Este tipo de test tiene dos problemas:
  • El delay hace que los tests corran más lento.
  • Por lo general resultan en tests no-deterministicos por que dependen de como la plataforma maneja la ejecución de los threads.

La alternativa es investigar un poco más lo que se esta testeando. Si es necesario tener el thread separado en el test conviene usar un semanforo (o monitor en Java) para hacer un wait hasta que se modifique el valor.

Lamentablemente no tengo ningun ejemplo a mano para mostrar en el blog.
Lo he hecho tanto en Smalltalk y en Java, y quizas lo unico que puedo agregar es que las cosas concurrentes son extremandamente dificiles de testear.
Algunas veces una solución intermedia es usar "yield" para que la VM ejecute otros procesos, esto salva el delay pero puede generar tambien tests no-deterministicos... la mejor opción sigue siendo usar algun tipo de semaforo.

De todas formas vale la pena hacer la "investigación" para evitar el delay: uno termina aprendiendo muchas cosas de concurrencia :), y tener un delay de un 1seg en varios tests es muy molesto.

Usar mock objects con cuidado

Un mock object es simplemente una implementación "de mentira" que se usa para reemplazar en un test a una implementación real (esta es al menos mi definición de mock object).

En el ejemplo anterior FixedClock es una implementación de mentira de Clock, que nos facilita el testing.

Existen frameworks de mock objects que por lo general:
  • Facilitan la implementación de mock objects usando meta-programación.
  • Permiten hacer tests de caja blanca, pudiendo validar si se ejecuto o no un método del mock object.

En mi experiecia estos frameworks terminan derivando en tests dificiles de mantener:
  • Los tests de "caja blanca" suelen ser una mala idea, al primer refactoring fallan y se convierten en un "dolor" de mantener. En esos casos es mejor re-plantearse el caso de test (y recordar cual es la intención de los tests en TDD).
  • En general es mucho más facil tener una interfaz y hacer la implementación de mentira para el test que usar un framework de mock objects.
  • Cuando no se generan dependencias adicionales (explicación a continuación) y no hay problemas como en el ejemplo de FixedClock, es más facil usar objetos reales que mock objects (usar un "TestResource" puede ayudar a escribir menos codigo en estos casos).
  • En lenguajes dinamicos como Smalltalk es muchisimo mas facil crear mock objects, pero tambien tienen problemas: si se usan muchos mock objects y se hace un refactoring, es probable que los test compilen y pasen... aunque deberían haber fallado.

Un pequeño hint: usar mock objects no es necesariamente malo, pero necesitar de mucha logica en un mock object es signo de que algo esta mal. A veces no se necesita un mock object si no una implementación más sencilla de la "interfaz" que queremos simular.

Ser cuidadoso con las dependencias de los tests

Ya estoy llegando al final de este enorme post.

El tema de las dependencias de tests requiere un poco más de explicación, asi que voy a ser breve:

Muchas veces uno expresa los tests a más alto nivel por ejemplo:

"El usuario agrega items al carrito de compras. Procede al checkout, donde obtiene una factura de los items que compró"

Este test abarca toda una historia de usuario y a veces es bueno automatizarlo.

Sin embargo hay que tener en cuenta que este tipo de test no solo es más largo, si no que posiblemente dependa de otras implementaciones: por ejemplo el carrito de compras se implementa en un proyecto, la generación de factura en otro y quien usa ambas implementaciones (llamemosle Cajero) usa interfaces asi que el proyecto donde esta el Cajero no depende de una implementacion particular de carrito de compra o el generador de factura.

¿Donde colocamos el test?
Si lo hacemos en el proyecto donde esta Cajero, y no usamos mock objects, generamos dependencias adicionales que no son necesarias (o quizas dependencias circulares que son problematicas). Y si usamos mock objects para todo, el test se vuelve más dificil de mantener.

Lo mejor en estos casos es ver el test a otro nivel. Si quieren pueden llamarle "test de integración" (siguiendo una convención que usabamos en Mercap, prefiero llamarles "user story test" por que abarcan una historia de usuario, y reservar el nombre "test de integración" para tests que verifican la integración con sistemas externos).

Es conveniente que este tipo de test este en un proyecto separado. De esta forma se evitan dependencias circulares, y es más facil compartir información entre dintintos "user story tests" (usando de "TestResources").

Además este tipo de test suele ser más lento, y por lo general no se ejecuta con la misma frecuencia que los tests de unidad.

El problema de este tipo de test es que son más grandes, tienen más dependencias y por lo tanto más dificiles de mantener.

Pero cuando las fechas de entrega apremian, a veces no hay tiempo para estar haciendo muchos tests a nivel unitario. En esos casos con tests de este estilo se puede testear la implementación manteniendo un estilo TDD (uno escribe el "user story test" antes de empezar), sin necesidad de crear un test por cada una de las clases que se usan "internamente". En este caso las herramientas de covertura pueden ayudar a ver que se cubre con el "user story test" y que no.

Si bien esta es una alternativa que complementa a los tests de unidad, no los reemplaza: los tests de unidad son una buena forma de trabajar a "nivel micro", pueden ejecutarse todo el tiempo durante el desarrollo y son más faciles de mantener.

Espero no haberlos dormido con este post enorme, trate de resumir un poco las "lecciones aprendidas" con TDD.
Hasta la proxima :)

12 agosto 2009

Side effects

El post anterior estuvo dedicado a la representación de "estados" en el dominio, donde mencione al pasar que el código del estilo: "objeto.getEstado()/objeto.setEstado(X)" tiene además otras complejidades, producto de algo que los programadores de lenguajes funcionales odian bastante: side effects.

Para los que se desayunan con el término voy a contarles de que se trata. Supongamos el siguiente programa:

a := 10
b := a + 10
if (b = a + 10) then retornar "OK" else retornar "FALLA"

Sabemos de ante mano que el programa retorna "OK", asi que podemos simplificar el código y ahorrarnos muchas vueltas (incluso el compilador podría realizar estas simplificaciones por nosotros). Ahora añadimos un procedimiento "inofensivo":

a := 10
b := a + 10
procedimientoRaro()
if (b = a + 10) then retornar "OK" else retornar "FALLA"

¿Seguimos sabiendo que el programa retorna siempre "OK"?
Antes de decir si, voy a poner una condición tramposa:
Dado que este es un programa en pseudo-código y no sabemos como se evalua nuestro supuesto lenguaje, supongamos que la variable "a" se puede acceder y modificar desde "procedimientoRaro()".

Ahora si volvamos a la pregunta: ¿El programa retorna siempre "OK"?
No lo sabemos :-(.
Tendríamos que examinar el "procedimientoRaro()" (si es que tenemos el código) un lindo procedimiento con más de 200 lineas de código y millones de ifs/fors anidados.

Sobre la "modularidad" del ejemplo podríamos decir que la variable "a" es una global y "procedimientoRaro" no debería usar globales, etc, etc. Asi que vamos a cambiarlo:

a := 10
b := a + 10
procedimientoRaro(a)
if (b = a + 10) then retornar "OK" else retornar "FALLA"

¿Seguimos sabiendo que el programa retorna siempre "OK"?
Quizás no, el problema es si "procedimientoRaro" puede modificar o no el valor de "a", y si nuestro lenguaje copia el argumento o lo pasa por referencia.

Podemos seguir con innumerables ejemplos, la cuestión es que en nuestro lenguaje imperativo "procedimientoRaro" puede tener side effects: afecta estados compartidos con el resto del programa.
La raíz de los problemas reside en la asignación. Al incorporar la capacidad de asignar valores a la variable "a", incorporamos implícitamente la noción de estado de ejecución.
Es decir la verificación "b = a + 10" ya no depende de la solo de la expresión, si no que depende además del estado. (hay una explicación muy buena sobre asignación y estado en el capitulo 3 del libro Structure and Interpretation of Programs).

“Todo esto parece muy teórico. En el trabajo diario ¿De qué me sirve?”

Supongamos que estamos trabajando en un sistema que se encarga de registrar la reserva de salas para una empresa de capacitación:
  • CalendarioDeSalas: Lleva un control de la reserva de salas. Una sala no puede reservarse en un intervalo de tiempo que ya este en uso.
  • IntervaloDeTiempo: Representa un rango de fecha-hora.



Con estas clases podríamos escribir un código así:

// reserva la sala del día 16 al 17
// el calendario verifica la disponibilidad
intervaloDeTiempo = new IntervaloDeTiempo();
intervaloDeTiempo.setDesde("16/07/2009");
intervaloDeTiempo.setHasta("17/07/2009");
calendario.reservar(unaSala, intervaloDeTiempo);

// reserva la sala del día 20 al 21
otroIntervaloDeTiempo = new IntervaloDeTiempo();
otroIntervaloDeTiempo.setDesde("20/07/2009");
otroIntervaloDeTiempo.setHasta("21/07/2009");
calendario.reservar(unaSala, otroIntervaloDeTiempo);

// cambia la fecha de la primer reserva..
// OOPS! el calendario ni se entera!
// este cambio rompe con la especificación del calendario
intervaloDeTiempo.setDesde("20/07/2009");
intervaloDeTiempo.setHasta("21/07/2009");

“Pero si esto es un problema: ¿Por qué no me encuentro con estos errores en mi mega sistema J2EE/Spring/Hibernate/etc/etc…?”

El problema del ejemplo puede aliviarse superficialmente si el calendario se encarga de copiar el intervalo antes de registrarlo.
Esto es básicamente lo que sucede con todos los mapeos Objeto-Relacional: lo valores se copian a la base de datos y luego las instancias son regeneradas.
Pero el problema aunque oculto sigue estando. Basta con agregar un “cache”, o usar la instancia de IntervaloDeTiempo de forma compartida sin darse cuenta (por ejemplo en un widget de UI), para que empiecen a ocurrir bugs inesperados.

También surgen un montón de complicaciones innecesarias en el código debido a las “validaciones”.

Supongamos que quiero que en un IntervaloDeTiempo la fecha inicial sea siempre menor a la final. Empiezo a agregar una guarda en “setDesde” otra en “setHasta”, y genero una excepción.

Pero ahora tengo que tener en cuenta en qué orden establezco los valores.
Bueno podemos hacer que cuando uno de los valores es null no se haga el chequeo… ¡HORRIBLE! Complicaciones y más complicaciones.

Simplifiquemos un poco, hacemos un método setIntervalo(desde, hasta), nos ahorramos problemas de validaciones. Pero seguimos teniendo problemas de otro tipo: para que el calendario funcione correctamente hay que asegurarse que el intervalo de la reserva no se cambie sin conocimiento el calendario.

Como mencione antes podemos aliviar el problema haciendo que calendario se encargue de copiar la instancia de intervalo que recibe. Pero esta es solo una solución superficial, supongamos que tras varias iteraciones decidimos hacer un refactoring, en lugar de:

calendario.reservar(unaSala, intervalo);

Ahora tenemos un objeto que representa la reserva:



unaReserva = new Reserva();
unaReserva.setSala(unaSala);
unaReserva.setIntervalo(intervalo);
calendario.registrar(unaReserva);

¿Cómo nos aseguramos que nadie nos cambie el intervalo de la reserva? Tenemos que:
  • Copiar la reserva al registrar en el calendario.
  • Copiar el intervalo a establecerlo en la reserva.
  • Asegurarnos de hacer una nueva copia del intervalo al retornar getIntervalo() en Reserva.
  • Asegurarnos de hacer una copia de la reserva al retornarla en el calendario, o decorarla de alguna manera para que nadie cambie la reserva sin que se entere el calendario.
  • Inicializar correctamente la sala y el intervalo, y chequear en “registrar” que tengan valores validos.
  • ¡HORRIBLE! Seguimos teniendo mucha complejidad.

“¿Cómo puedo aliviar/evitar estos problemas?”

Evitando side effects, es decir eliminar la asignación (setters) usando objetos inmutables.
En el ejemplo es preferible que el valor de las variables de instancia de IntervaloDeTiempo no se puedan cambiar. Es decir que no exista un “setDesde” y “setHasta”.

Para eso solo hay que definir un constructor adecuado:

new IntervaloDeTiempo(desde, hasta);

Lo mismo sucede con Reserva:

new Reserva(unaSala, intervalo);

Remando contra la corriente

"¿Hasta que punto se puede diseñar pensando en objetos inmutables?"

Es evidente que hay casos donde conviene que los objetos sean mutables, por ejemplo en entornos donde la configuración puede cambiar dinamicamte (sin ir más lejos un ejemplo de este tipo de cambios dinamicos es el entorno de Smalltalk).
En otros casos la necesidad de objetos mutables tiene que ver con cuestiones de performance e interacción con otros frameworks (donde por framework incluyo también a las librerías standard del lenguaje, por ejemplo las colecciones).

Este ultimo punto es para mi el más molesto para los programadores Java: la gran mayoría de los frameworks Java adoptaron la convención de JavaBeans (con la que ya me ensañe bastante). El problema es que muchas veces adoptan esa convención de manera totalmente innecesaria, fomentando malas prácticas.

Por ejemplo, Spring puede hacer inyección de dependencias usando constructores. Sin embargo el manual de referencia menciona que la forma preferida es usar "setters", eso significa que la mayoría de los tutoriales usan inyección por setters y por lo tanto la mayoría de los "usuarios" del framework también.

Pero si uno le presta más atención al por qué, el manual dice: "Setter methods also make objects of that class amenable to reconfiguration or re-injection later", lo que me genera la duda ¿Cuantas veces uno diseña una aplicación con este grado de re-configuración en runtime? Mi punto es: existen tecnologías como JMX que facilitan los cambios en runtime, sin embargo no es el requerimiento común, y si fuese un requerimiento de la aplicación uno tendría que diseñar pensando en la complejidad que pueden generar estos cambios dinámicos.

La otra justificación para usar setters en Spring es cuando existen dependencias circulares, sin embargo este tipo de dependencias a veces pueden evitarse incorporando en el diseño un tercer objeto que haga de mediador.

Otros frameworks son peores: al no permitir otro uso que no sea mediante la convención de JavaBeans fuerzan este tipo de prácticas.

Aún así hay algunas prácticas de diseño que ayudan evitar side effects:
  • Mientras sea posible usar objetos inmutables.
  • Hacer las validaciones en el constructor, de esta manera si uno tiene una instancia ya se sabe que la misma fue construida correctamente.
  • Si el constructor queda enorme (cosa que es muy molesta para manejar), pensar de "dividir" el objeto en conceptos más pequeños. Por ejemplo en el ejemplo anterior la clase Reserva podría recibir el valor desde y hasta del intervalo: Reserva(desde,hasta,sala). Sin embargo separando el concepto de intervalo es mucho más simple (incluso para las validaciones): Reserva(intervalo,sala).
  • Si la construcción es complicada utilizar un builder. Lo bueno es que si la interfaz del builder se diseña con cuidado se pueden hacer DSL internos, logrando un código muy comunicativo.
  • Si en el dominio hay distintos "estados", pensar como se representan esos estados y utilizar algunas de las técnicas que mencione en el post previo. Por ejemplo, si en la UI tenemos que modificar una Reserva, podríamos tener un objeto ReservaBorrador que sea mutable y que actúe como builder de una Reserva inmutable.
  • No implementar equals en objetos mutables: es una muy mala práctica. Si no se dan cuenta por que: creen un objeto mutable, calculen equals y hashCode en base a variables de instancia que pueden cambiar, agreguen el objeto a un Set, modifiquen la instancia y vuelvan a probar si la misma esta en el Set.
  • Tener cuidado al retornar colecciones en un setter. Para que la colección no sea modificada pueden hacer una copia, o en Java decorarla para evitar modificaciones.

Estas cuestiones de side effects suelen ser importantes, no solo para el diseño, si no también para la escalabilidad de un sistema. Por eso cada vez más se empiezan a incorporar a los lenguajes orientados a objetos conceptos que vienen de lenguajes funcionales, el lenguaje Scala es un buen ejemplo que les recomiendo ver.

13 julio 2009

Estados

Cuando encuentro código del siguiente estilo:

if (factura.getEstado() == EstadoFactura.PAGA) {
    // código
} else if (factura.getEstado() == EstadoFactura.ENTREGADA) {
    // código
} else {
    // otro
}

no puedo evitar pensar: "¡horrible!". Por que conozco los problemas que genera este tipo de código y sé que con otro diseño pueden evitarse.

La cuestión es explicar como mejorar el diseño. En estas situaciones, mis intentos de explicación son frases un tanto pedantes como: "este uso del 'if' es feo, por que las decisiones sobre que hacer según un 'estado', en objetos se pueden resolver utilizando polimorfismo".

Lamentablemente este esbozo de explicación no ayuda, y noto que en general produce la siguiente reacción: 
"Todo muy lindo esto de objetos, mensajes y polimorfismo. Pero tengo en mi tabla FACTURA un campo ESTADO y esto refleja directamente eso. Además ¿Cual es la solución? ¿Usar el patrón State? ¿Hacer un montón de clases para algo que resuelvo en un 'if'? Dejemos la 'estética de objetos' para la teoría." 

Una explicación técnica...

Voy a comenzar por el camino común de dejar en claro por que en objetos este tipo de "if" puede evitarse.

El siguiente ejemplo:

if (figura.getTipo() == Figura.RECTANGULO) {
    area = figura.getBase() * figura.getAltura();
} else if (figura.getTipo() == Figura.TRIANGULO) {
    area = figura.getBase() * figura.getAltura() / 2;
} else if (figura .... ya se hacen a la idea de como sigue

puede resolverse mediante clases que representen a los rectángulos, triángulos, etc. 
Si todas estas clases son polimorficas con el mensaje "getArea()" entonces toda la seguidilla de "if" puede resolverse en una sola línea: figura.getArea().
Donde el receptor del mensaje es quien encapsula la decisión de como hacer las cosas.


Complicando el problema

Muchas veces es deseable que el algoritmo a ejecutar según el tipo de objeto este separado de los objetos.
Por ejemplo si Figura posee un método "dibujar" es probable que el algoritmo de dibujo genere dependencias con un framework de dibujo, y quizás no quiero "atar" al modulo de figuras con un framework de dibujo en particular, o quizás el algoritmo de dibujo varíe según el contexto.

Para este tipo de casos las alternativas son usar Double Dispatch o bien algún tipo de interfaz entre distintos frameworks/implementaciones donde en este caso dibujar seria algo así como "dibujarSobre(unCanvas)" siendo "unCanvas" es un objeto que implementa esa interfaz común.

Pero volviendo al problema original (de la Factura y Estado), hay algunas cuestiones a tener en cuenta:
  1. En el caso de la factura el "if" se hace en base al estado y no a un "tipo de factura", eso significa que a diferencia del ejemplo de las figuras el estado puede variar una vez creada la factura.
  2. Crear un objeto que represente el estado y usar Double Dispatch no parece ser en este caso una buena solución. (El por que lo dejo como ejercicio)

¿Cuál es la solución a este problema?
Rta: Examinar mejor el dominio:

  • ¿Tiene sentido hablar de "estado" de una factura?
  • ¿Que representa la factura?
  • ¿Realmente el cambio de "estado" representa el cambio de estado de una misma cosa o en realidad representa cosas distintas?

Examinando el dominio

NOTA: El ejemplo de Factura/Estado es ficticio, pero creo que captura muchos de los casos de negocio donde encontré código del estilo "if estado then ...".

En "Stan's Shop" la gente agrega productos a su pedido, paga en la caja donde se le entrega una factura, finalmente en el mostrador de entregas un empleado prepara los productos y una vez que se los dio al cliente coloca un lindo sello de "ENTREGADO" en la factura (un esquema similar al que siguen todos los locales de comida rápida del micro-centro).

Los diseñadores del sistema pensaron que era bueno tener un objeto "factura" especificado por la siguiente clase:


Este diseño tiene varios problemas:
  • Si la factura se ve como un comprobante de pago (al momento de pensar este ejemplo ficticio desconozco si en el negocio tiene otros usos), entonces la factura debe ser inmutable. Este hecho se refleja en que agregarItem y quitarItem generan error: solución problemática por que en todos los lugares donde quiera enviar estos mensajes tengo que tener en cuenta la posibilidad de error. Lo mismo ocurre con getNumero donde se debe chequear por null.
  • La "mutabilidad" de factura genera otros problemas de implementación, pero prefiero dejar los detalles para otro post sobre "side effects".
  • ¿Cómo sé cuando una factura es "valida"? Puedo chequear por getEstado() == PAGA o ENTREGADA, puedo chequear por getNumero() != null o agregar un nuevo método esValida (de intention revealing ni hablar). En la práctica encontré que se mezclan las tres formas dependiendo del programador, lo que genera algunos dolores de cabeza al momento de hacer un refactoring.
  • ¿Tiene sentido tener un estado "ENTREGADA"? ¿El hecho de saber si los productos fueron entregados o no, no es responsabilidad de otra área de negocio? Este es un ejemplo ficticio y no aporta mucho escarbar en detalles de negocio, simplemente lo menciono por que en la mayoría de los casos los problemas de diseño muestran una falla en reconocer objetos del dominio.

La solución a este problema es sencilla:
  • La factura representa para mi un comprobante de pago y nada más. Por lo tanto una vez generada no puede modificarse ya que tiene implicaciones contables (y quizás legales).
  • Cuando el cliente llega al local y elige los productos que quiere no esta trabajando sobre una "factura", si no sobre una especie de "carrito de compras".
  • Saber si se entregaron o no los productos no es responsabilidad de la factura, si no del sistema que lleva en control de las entregas.



Con este diseño la separación de responsabilidades es clara, no hay forma que en el código modifique items en una factura, o de pedirle el número de factura a un CarritoDeCompras, por lo tanto no hay necesidad de verificar un "estado".
El caso de "ENTREGADA" lo voy a discutir a continuación por que me sirve para ejemplificar otro "patrón" común.

Cambios de estado sin cambios de comportamiento

En el ejemplo el cambio de CarritoDeCompras a Factura implica un cambio en la semantica de los objetos. ¿Pero que pasa con el sello de "ENTREGADA"? ¿No se puede pensar como un simple flag booleano en una Factura?

No quiero entrar en detalles de este dominio ficticio, pero quizás un "flag" no alcance. Es probable que quiera registrar quien realizo la entrega, a que hora, etc.
Por eso voy a hacer una simplificación: no me interesan esos detalles, solo quiero el equivalente al sello de "ENTREGADA".

Y dado que en este ejemplo yo pongo las reglas, voy a suponer que el operador en el mostrador de entregas tiene una pantalla que muestra las facturas pendientes de entrega, donde con un click puede cambiar el estado a "ENTREGADA".

Una forma de diseñar esto es pensar que uno tiene un "sistema" (modulo, o como deseen nombrar) que lleva el control de los pedidos, con dos "recipientes": uno para la facturas pedientes y otro para las entregadas. Entonces pasar de estado es mover la Factura de un recipiente a otro:





¿Pero el booleano no era mejor? No, este esquema tiene muchas ventajas más:
  • Factura sigue siendo inmutable, e independiente de como llevo el control de entregas.
  • SistemaDePedidos puede variar fácilmente, llevando por ejemplo el control de la fecha de entrega, etc.

¿Y la UI? "Quisiera hacer una página web que muestre el listado de facturas entregadas y no entregadas, con getEstado() o el flag es mucho más fácil".

En este caso también es fácil por que puede resolverse de la siguiente manera:
  1. La UI puede pedir al SistemaDePedidos directamente las Facturas entregadas o las no entregadas.
  2. Supongamos que la solución 1. no es satisfactoria: por como se utiliza la UI realmente se quiere tener un objeto al cual se le pueda pedir el estado. En ese caso conviene hacer un objeto especifico para la UI, este objeto puede verse como un DTO que se usa solo para transferir datos a la capa de UI.

¿Y la base de datos?
"Tengo una tabla Factura que tiene la columna ENTREGADA"

No hay problema: las herramientas de mapeo O/R permiten hacer mapeos complejos de asociaciones para resolver el caso entregada/pendiente en el SistemaDePedidos, o si es necesario discriminar la subclase a mapear.

Conclusiones

En el ejemplo ficticio que di en este post aplica a muchos dominios, algunos ejemplos que me vienen a la mente son: estados de un documento (publicado/no publicado) en un sistema de CMS, registración de compras, distinción entre un estado de "edición/en uso" para una configuración (ejercicio: examinen el código fuente de Struts y vean como la implementación de configuración puede refactorizarse y mejorarse usando lo que comente en este articulo), etc.

Creo que en la balanza de "bueno/malo" el código del estilo "if estado then codigo" tiene muchos problemas:
  • Probablemente no modela adecuadamente el negocio.
  • El "contrato" de los objetos es complicado: todo el tiempo dependo del estado para saber si puedo o no usar ciertos métodos.
  • En consecuencia el código se empieza a "contaminar" con este tipo de chequeos, con el agravante que quizás cada programador lo haga de una forma distinta según la situación, dificultando el mantenimiento y refactoring.
  • Se necesitan objetos mutables, en casos donde no conviene que lo sean (prometo hablar sobre "side effects" en otro post).

Mientras que las únicas ventajas que le veo son que es simple de implementar para ejemplos chicos y fácil de mapear a una tabla en la base de datos.

Por todas estas razones conviene evitar este tipo de "ifs" y en lo posible evitar el uso de Enums ya que fomentan este tipo de código.


09 julio 2009

Presentaciones

Empecé este post hace más de un mes: ¡que mes!
Estuve al limite del stress total, despierto hasta tarde para llegar a las entregas del trabajo y la facultad.
Por eso, este pequeño descanso (producto de los cierres por Gripe A) me viene muy bien.

Advierto: va a ser un post largo con recomendaciones sobre como crear presentaciones.

Estas recomendaciones provienen de:


(más las diferentes experiencias que tuve dando clases y presentaciones)

Ahi vamos...

Presentar != Dar una clase

Es común que se confundan ambas cosas.

Las presentaciones tienen una forma de comunicación donde se trata de dejar un mensaje en poco tiempo (en cierta forma tratan de "vender" una idea/tecnología/método/etc.).
En cambio una buena clase (para mi) debería fomentar la formación de "teorías", quizas mediante la experimentación o el debate (se nota que últimamente estuve leyendo a Freire).

En otras palabras: un curso no es solo mostrar slides y hablar frente a los alumnos, hay además una cuota de participación y experimentación que es fundamental para aprender. Este post trata sobre presentaciones, pero es bueno mencionar la diferencia, en mi experiencia la mayoría de la gente piensa que preparar unos cuantos slides es lo mismo que preparar un curso.

Olvidarse del PowerPoint y focalizarse en el mensaje

Buscando información sobre como hacer presentaciones me encontré con tips inútiles que se centran en cuestiones cosméticas de dudosa efectividad.


Lo importante es transmitir un mensaje. Si el mensaje es bueno y esta bien transmitido la gente va a recordar la presentación (Tudor Girba lo cuenta de una forma excelente).

Es fácil caer en la tentación de querer transmitir demasiado. Por ejemplo, hace poco di una presentación sobre usabilidad en el Microsoft Architecture Day (MAD): tenia ganas de contar muchas cosas y no aguante la tentación de querer compactarlas en 45min. El resultado: partes aburridas y por los comentarios que tuve me di cuenta que no todos captaron lo que quería transmitir.

Como suelo trabajar: Antes de armar una presentación, me dedico a investigar y leer la información que tengo. Con esa información voy armando un MindMap (en software o a veces en un cuaderno). De ahí voy sacando en limpio el mensaje que me gustaría transmitir (esa es la tarea más difícil).

Transmitir el mensaje contando una historia

La charla esta por empezar. El proyector y la maquina están encendidos, pero misteriosamente dejaron de funcionar, más de 20 personas están esperando y la única salida es usar el pizarrón. ¡Que no cunda el pánico!: tenemos el mensaje a transmitir, solo nos queda contarlo.

¿Se puede contar una historia en base a un tema técnico y especifico? ¡Si! un claro ejemplo son los documentales. Tomen cualquier documental de 2hs e imagenlo en PPTs de bullets y voz en off. ¡Un embole! se quedan dormidos a los 10min, y si están dormidos dificilmente puedan seguir la presentación.

Una vez que se tiene el mensaje lo mejor es ir pensando en como contarlo  sin PowerPoint. Mientras piensan como contar las cosas es probable que imaginen diagramas y graficos que ayuden a contarlas, esas ayudas son los graficos que van a ir en los slides (el "texto" es lo que uno cuenta con palabras, no es necesario repetirlo en bullets).

Como suelo trabajar:
Decidido el mensaje, y trato de redactar en un cuaderno lo que voy a ir contando. Aunque esta redacción quede en el cuaderno es muy útil, por que me sirve para definir las cosas que voy a visualizar con la computadora.

Contar la historia usando visualizaciones

La importancia de la computadora es proveer visualizaciones que sirvan como soporte a lo que estamos transmitiendo.

Los bullets no sirven como visualización: hay un problema con la transmisión de texto escrito y voz al mismo tiempo. Este problema se conoce como sobrecarga cognitiva y se debe a que nuestro cerebro procesa el lenguaje hablado y el texto de la misma forma. Para comprobarlo pueden hacer un experimento: inserten una película en DVD que tenga audio en español y subtítulos en español, activen ambos y traten de ver la película, al rato van a filtrar alguna de las dos cosas.
Leen el texto o escuchan el audio, hacer las dos cosas es muy molesto.

Cuando se muestra una imagen/diagrama como soporte ocurre otra cosa: la visualización ayuda a explicar el tema (por eso también debería ser conceptualmente buena).

Como suelo trabajar:
No uso bullets, la información "textual" trato de decirla con palabras y usar el proyector como complemento visual. Esta parte es muy trabajosa por que a veces hay que armar las imagenes, o buscar las fotos adecuadas (Cooliris es muy útil en estos casos).
Trato que los slides sean simples, y cuando armo figuras me sirve mucho imaginar como las voy a explicar.
También me resulta útil pensar en la presentación como si fuese un documental, donde soy la voz en off y junto con las imagenes voy contando la historia.

Algunos consejos para diseñar y crear visualizaciones

En las presentaciones suelo recurrir a dos tipos de ayudas visuales:
  1. Para reforzar ideas/mensajes.
  2. Para explicar mediante diagramas esquemáticos.

Para crearlas es necesario tomar el rol de "diseñador grafico" y dado que probablemente no somos diseñadores profesionales, es necesario recurrir a algunos tips.

Para el primer caso suelo usar fotos y texto (una frase o titulo corto). Lo importante es mantener bajo el "ruido" visual, especialmente en las fotos: una foto con muchos "focos" de atención (sujetos, colores, texturas) suele distraer.

El segundo caso es más complejo. Primero por que la resolución del proyector es limitada y a veces es necesario partir un diagrama esquemático en más de un slide (lo cual rompe un poco el flujo de la presentación). En este caso la palabra clave es: simplicidad. Simplicidad de diseño y de conceptos: para transmitir un concepto visualmente es necesario encontrar las analogías adecuadas (Envisioning Information de Tufte es una fuente de información muy buena sobre como crear visualizaciones), evitando cosas superfluas como clip-art o efectos visuales.

Resumiendo:
  • Simplicidad (evitar el ruido):
  • No es necesario poner en logo de la empresa en cada slide: nos saca lugar para los diagramas y nadie los ve, solo generan ruido.
  • Lo mismo aplica para los "bordes" usados en muchos templates de PowerPoint.
  • Si el slide queda muy complejo: pensar en partirlo en varios. (las presentaciones de Microsoft son un excelente ejemplo de como generar ruido visual inútil).
  • Ser consistente con las fuentes y colores. Creo que esta es la mejor forma de dar una identidad de empresa. Las presentaciones de Apple son un buen ejemplo, hagan este experimento: creen una presentación usando el font Myriad Pro, con letras blancas sobre un gradiente azul oscuro, despues preguntenle a algun "techie" a que les hace acordar la presentación (apuesto que más de uno va a decir Apple).

Otros tips

Control remoto: Es impresionante como ayuda tener uno, dan mucha libertad para hablar.
Suelo usar el que vino con mi notebook, pero tambien hay un mouse bluetooth de Microsoft con botones para controlar la presentación (algunos proyectores tienen un cable USB que permite manejar la presentación por control remoto, lamentablemente en la mayoría de las salas el proyector esta en el techo y sin acceso al cable).

Transiciones: No siempre son malas, lo que si es cierto es que la transiciones de PowerPoint o OpenOffice son tan feas que es mejor no usarlas.
Las de Keynote son otra historia, sobre todo Magic Move que me encanta, suelo utilizarla para no perder el flujo de la narración. Imagino como un documental, es decir no freno la presentación para hacer la transición de slide a slide, sino que lo presento en continuo (esta excelente presentación en TED usa este estilo), para este caso es muy importante contar con un "control remoto".

Para las transiciones aplica el mismo consejo que para las visualizaciones: muchos efectos locos generan ruido que no aporta nada.
 

Grabación: Nunca había grabado las presentaciones que di, hasta que para el Microsoft Architecture Day tuve que hacerlo (era un pedido de los organizadores), y encontré que es muy importante para aprender y mejorar.

Mientras veia la presentación vi muchas cosas a mejorar en el contenido y en mi forma de hablar.


Si dan la presentación con una notebook no necesitan nada para grabar (me refiero a grabar el audio junto con lo que muestran en pantalla): la mayoría trae microfono incorporado.

En Windows: Pueden instalar Camstasia para grabar la pantalla y el audio (incluso trae un plug-in que se integra con PowerPoint).
En Mac: Keynote ya trae una opción para grabar y exportar el video.

Bueno eso es todo :), les deseo un buen finde bien lejos de "la porcina".

11 mayo 2009

Ubiquity y UML

Hace tiempo que vengo usando y siguiendo el desarrollo de Ubiquity: un plug-in para Firefox basado en la idea de command line de Aza Raskin.
 
Si bien no siempre uso el plug-in (a veces simplemente olvido que esta y no uso los comandos), comparto las ideas de Aza: el pibe es un creativo e hizo millones de experimentos de user interface.
 
Hace poco en el laburo me entere de un sitio web que permite crear diagramas UML en base a texto: yuml.me
Y me dije... esto es ideal para hacerlo como comando de Ubiquity. Asi que en esta pagina pueden encontrar el comando de Ubiquity para insertar UML (Nota: solo tienen que entrar en la página y si tienen el plug-in instalado les tiene que aparecer un mensaje para subscribirse al comando).
La forma de usarlo es simple:
  1. Instalan Ubiquity y van a esta pagina para instalar el comando 
  2. Seleccionan un texto con el formato de yuml.me, por ejemplo: [Blog]->[BlogPost]
  3. Presionan CTRL-SPACE y escriben insert-uml-class (Ubiquity tiene autocompletar y pueden tiper solo una parte) o insert-uml-usecase
  4. Enter... y voila gráfico de UML insertado:

 
Esto funciona en cualquier pagina con un editor HTML: GMail, GDocs, etc.
 
Ubiquity es super interesante, y es probable que proximas versiones de Firefox incluyan algo similar (no creo que exactamente igual, ya que todavia estan tratando de resolver algunas cuestiones importantes: idioma, y posibilidad de ejecutar comandos sin tener que tipear).

Por el momento Ubiquity me parece el mejor ejemplo de mash-up: mediante los comandos es muy facil integrar distintos "servicios" pemitiendo hacer cosas que hasta en el desktop resultan incomodas. Por ahora la idea tiene que evolucionar para ser un poco más contextual, por ejemplo que uno pueda insertar una imagen como attachment si esta escribiendo un mail, o cosas por el estilo.

07 mayo 2009

Libros que estuve leyendo

Suelo leer demasiado contenido "técnico" (ya sea en Internet o en libros), por eso necesitaba "refrescarme" con libros de ficción: es impresionante lo relajante y lindo que es engancharse con una buena historia.

Les cuento un poco que estuve leyendo en los últimos meses:

La noche del oráculo - Paul Auster
Es el primer libro que leo de Paul Auster (en este momento estoy leyendo El palacio de la Luna ) y me gusto mucho.

La historia trata de un escritor que escribe una historia (hay como un juego "cíclico" en el libro). Lo más interesante es que va abarcando el proceso creativo del escritor: como a partir de una anécdota el autor va construyendo un mundo entero. Este juego esta presente en todo el relato, donde van apareciendo "puntas" a nuevas historias. Muy recomendable para todo aquel al que le guste escribir.

El hombre ilustrado - Ray Bradbury
Bradbury tiene una forma de escribir, que por momentos me parece "poética". Un maestro de la ciencia ficción.

Este es un libro de cuentos esta basado en una idea muy original: las historias surgen de los tatuajes de un tipo... El hombre ilustrado.

Los cuentos me gustaron mucho, aunque me queda una sensación de que la traducción le saca mucha riqueza. Algún día me gustaria probar de leerlo en inglés, no lo hago por que pienso que voy a enterdelo "por arriba" sin captar muchas de las metaforas.

Los hijos de Anansi - Neil Gaiman
Es un libro ideal para leer en vacaciones o en el colectivo: la historia es entretenida y tiene mucho humor. Me dieron ganas de leer otras historias de Gaiman (Nota: Neil Gaimain es un famoso autor de comics, entre otras cosas escribió la película Coraline , pero se hizo famoso por la novela grafica The Sandman).

En este libro el problema de la traucción fue peor que con el libro de Bradbury: muchos de los chistes son muy "ingleses" y encontre algunos errores en la edición (aunque menores y sin importancia).

Farenheit 451 - Ray Bradbury
Después del libro de cuentos de Bradbury me quede con ganas de más. Este libro estaba hechando polvo en mi biblioteca desde hace varios años: lo compre cuando estaba en el secundario y en ese momento no me entusiasmo, lei las primeras paginas y lo deje (simplemente no era el momento).

En cambio ahora me sucedio todo lo contrario: empece a leerlo y lo termine en una noche! (si me dormi como a las 5am).

El libro tiene un estilo similar al de 1984: historia futurista con un estado represor. Pero a diferencia de este, Farenheit se centra en los medios de comunicación: leer o tener libros esta considerado ilegal, solo hay películas y programas de televisión.

En particular me llamo la atención la esposa del personaje principal: ella se la pasa mirando una especie de "reality show" -Bradbury no usa ese nombre, pero yo me imagine el programa de "Gran Hermano"-. Para verlo tienen en la casa una habitación dedicada con pantallas en todas las paredes, con la particularidad que los aparatos están programados para que los personajes se refieran al televidente por su nombre.
La esposa del personaje esta tan enganchada que el programa de televisión es "su familia" (de hecho prefiere más a "su familia" que a su esposo).

Esa situación me hizo acordar a este verso de Galeano:
La Television/4
Me lo contó Rosa Maria Mateo, una de las figuras más populares de la televisión española. Una mujer le había escrito una carta, desde algún pueblito perdido, pidiéndole que por favor le dijera la verdad:
- Con yo la miro, ¿usted me mira?
Rosa María me lo contó, y me dijo que no sabía que contestar.


Antes de Adán - Jack London

Este es un librito viejo que compre por poca plata en una librería de Corrientes. La historia esta interesante: es sobre un tipo que sueña con la prehistoria... su antepasado fue un hombre de las cavernas y en cada sueño él se traslada al pasado (la idea del sueño sirve como introducción a la historia que después se centra exclusivamente en la pre-historia)

Lo más interesante es la descripción del pasado: uno tiene la sensación de estar colgado de un árbol, con miedo a los depredadores, a las fuerzas de la naturaleza y sin tener un idioma para expresarse.

Aunque la descripción esta buena y el libro me engancho, a la historia le falta un poquito más de "sal", me gusto pero no me parecio "wow!".

La mente alien - Philip K. Dick

Despúes de los libros de Bradbury segui con ganas de leer libros de ciencia ficción, asi que me compre este recopilado de cuentos de Philip K. Dick (autor que tuvo muchas adaptaciones al cine como Blade Runner, El vengador del futuro -Total Recall- y Minority Report ).

Los cuentos estan geniales y tienen una mezcla de ciencia ficción, humor y paranoia, y más de una vez te dejan pensando (eso es algo muy bueno de los textos de ciencia ficción: al no estar "limitados" pueden delirarse en situaciones que plantean muchas cuestiones complejas).

De la selección de cuentos en el librito solo uno no me gusto: Las pre-personas. Este es una especie de "manifiesto anti-aborto": la historia transcurre en un futuro hipotético donde los abortos pueden realizarse hasta los 12años de un chico. El punto del cuento es: ¿Cuando uno considera que un embrión es un ser humano o no?. Es un punto muy valido, pero lo que no me gusto del cuento es como se plantea, me dio la sensación de estar leyendo un texto personal dirigido en contestación a una cierta persona en particular.

Factótum - Charles Bukowski
Charles es tan raro como su apellido. El tipo escribe sobre su vida, sin filtros: cosa que me gusta mucho.

Por momentos el libro me resulto un poco aburrido, con algunos momentos poeticos. No se si lo recomendaria como un "buen" libro, lo que si se puede decir de Bukowski es que escribe por necesidad: para él escribir es como respirar, una necesidad de salir de la mierda que lo rodea y "vomitar" lo que siente en papel. Creo que eso es inspirador, especialmente quizas para un pibe que no lee mucho, o que piense que los libros son ñoños... este libro no tiene nada de ñoño.

También aca la traducción jugo en contra, en especial por que esta traducido a un español de España, asi que usan palabras como coño o follar... es terrible, los insultos son tan locales y dificiles de traducir, creo que Fontanarosa tenia razón: hay malas palabras que son irremplazables. (me doy cuenta que estuve leyendo muchos libros de autores de habla inglesa -nota mental leer algún autor Latino Americano-)

En el medio estuve leyendo tambien algunos libros sobre usabilidad y articulos sobre SOA... pero esos los dejo para otro post más tecnico ;)

01 marzo 2009

Creatividad

Estuve pensando bastante sobre el ultimo post que escribí, así que me gustaría comenzar este con una auto critica:

Primero, como comento Hernan Parra, deje de lado que uno de los principales damnificados en la historia es la empresa que necesita de la funcionalidad del software.
Segundo mezcle en una misma historia dos cuestiones distintas:

Mas allá de esta auto critica lo que me dejó pensando fue la siguiente cuestión: ¿Qué puedo hacer desde mi posición de desarrollador/arquitecto de software?

Se me pasaron por la cabeza varias cuestiones:
  • Tener un pensamiento critico hacia las tecnologías que usamos.
  • Capacitación e investigación constantes, para fundamentar las decisiones que tomamos.
  • La importancia de pensar primero en el problema de dominio y luego en las tecnologías que se van a usar para resolverlo.
  • Una mayor comunicación, con los clientes y la gente de ventas, sobre todo para explicar por que la tecnología X no resuelve magicamente todo.
  • ... etc.

Pero después llegué a la conclusión de que todas estas cuestiones se pueden resumir en una: creatividad.

Es necesario que quienes hacemos software seamos más creativos.
Por "creativos" no me refiero a la creatividad sin limites como en el arte.
Me refiero a la creatividad de preguntarse como hacer las cosas mejores, sin limitarse a lo conocido. Eso no significa que el camino que recorrieron otros este mal, si más bien significa no aceptar axiomas del tipo: esto se hace así... por que siempre se hizo así (punto).

En el desarrollo de software somos extremadamente conservadores, por ejemplo:

Definición de requerimientos y casos de uso.
La definición de requerimientos es muchas veces así: alguien se sienta con el cliente/experto/futuro usuario y le hace una o varias entrevistas, de ahi se va sacando la funcionalidad. Luego dependiendo del grado de "burocracia" que tenga el proceso de desarrollo se hace un documento de requerimientos, uno de caso de uso o de historias de usuario, un product backlog, etc.

¿Es esta la única forma de hacer las cosas?
Si uno lee libros sobre diseño industrial o IxD, puede ver que hay muchas otras tecnicas para saber que necesitan los usuarios. De hecho, ninguna gran empresa de diseño industrial se quedaría solo con una entrevista al usuario: es un camino al fracaso.
Entonces... ¿Por que nosotros seguimos haciendo solo eso? ¿Hay formas mejores?

Ahi es donde entra en juego la creatividad: hay que salirse del molde que uno conoce e irse otros planos como por ejemplo libros de diseño industrial, de psicología, antropología, etc. (como diria Alan Kay moverse al plano azul)

Comunicación.
La comunicación en el desarrollo de software es muy importante, lo dice Fred Brooks, lo dice Alistair Cockburn, y otros más.
Tambien lo dice Booch et. al. y por eso crearon una notación y una forma de transmitir un diseño a varios niveles: UML.

Ahora venimos nosotros: alguien nos dice que tenemos que documentar.
Entonces leemos libros largos y complicados sobre como documentar arquitecturas y sus diferentes vistas usando UML y otras notaciones. Hacemos un curso de UML y nos aprendemos toda la sintaxis. Pero... ¿Nos detenemos un momento a preguntarnos si es la única forma de comunicar? ¿Cuales son las formas de comunicar?
Hay gente que dedico muchos años a investigar la comunicación entre personas: educadores, sociologos, lingüistas, etc. ¿Que puedo aprender de ellos?

Desarrollo.
A veces veo desarrolladores que pasan horas tratando de hacer funcionar un framework, sin plantearse nunca ¿Que valor agregado me da? ¿Lo necesito? ¿Puedo hacerlo mejor? ¿Qué problemas me trae el framework para expresar el modelo de dominio?
Aca el ejercicio creativo es muy importante. No para re inventar la rueda una y otra vez, si no para darse cuenta como funcionan las cosas, y llegar quizás a mejores alternativas.

Alguien que lee todo esto podría objetarme lo siguiente:
No hay tiempo. Si cada desarrollador hace esto, entonces no terminamos nunca. Si cada uno se pone a "ser creativo" el desarrollo es un caos.
Si queres escribir un documento de requerimientos, segui el template. Si queres desarrollar, aca usamos el framework X, trabajamos siempre asi y no lo vamos a cambiar, asi que ni pierdas el tiempo en ver que problemas puede tener X, o en evaluar alternativas distintas.

Es cierto que a veces hay que balancear: tiempo y otros requisitos. Tambien es cierto que para trabajar en equipo hay que respetar las normas del equipo.
Sin embargo no vi ninguna empresa/producto exitoso que no hay roto el molde en algun sentido, por ejemplo:
  • Linux surgio por que Linus Torvalds quizo experimentar creando su propio SO. Imaginense que hubiese sido si Torvalds decia: "ey para que perder el tiempo en esto si ya existe Minix".
  • El framework Spring, surgio para resolver problemas de J2EE... hoy se usa en todos lados y la gente que lo hizo pudo haber dicho: hagamos todo como lo dice el Blueprint de Sun.
  • Si Jeff Sutherland se hubiese quedado solo con el modelo un proceso tipo "cascada", hoy no existiría SCRUM.
  • 37signals, comenzo a hacer sitios web usando Ruby (cuando lo no conocia nadie), para esto diseñaron su propio framework: Ruby on Rails. Si querian usar Unix se podian haber quedado con Perl.
  • En el 99/2000 la tendencia de todos los buscadores*: Yahoo , AltaVista, Lycos, Excite , etc., era la de hacer portales. De hecho la palabra portal se puso de moda con esos sitios: un portal era el punto de partida a Internet. Asi que la "portada" del portal tenia que tener de todo: noticias, directorio de busqueda, acceso a servicios (y sobre todo publicidad). ¿Que hubiese pasado si Google hacia otro portal más?
  • Ebay usa bases de datos sin constraints para mejorar la performance, planten esto a un DBA y les va a tirar el manual de Oracle por la cabeza.
  • GMail uso JavaScript para mejorar la interactividad de la aplicación y comunicarse con el servidor... cuando todos usaban JavaScript solo para hacer un "roll-over". (bueno en ese momento ya habian varios ejemplos y tutoriales de AJAX por la web, pero creo que GMail fue en primer web mail en hacerlo bien).
  • Tanto con el iPod como con el iPhone, Apple decidio ser más creativo que el resto: en lugar de hacer otro MP3 Player u otro celular, se plantearon ¿Como puedo mejorar la experiencia del usuario?... y para responderse esa pregunta experimentaron mucho.

Hay que tener en cuenta que las empresas que crearon esos productos "exitosos" no tenian el camino tan claro cuando empezaron (claro que estoy tomando para el ejemplo "exito"="mainstream", lo cual no necesariamente es asi).

Por ejemplo el libro de Bill Buxton (Sketching the user experience) cuenta un poco la historia del iPod: se necesitaron varios experimentos y modelos distintos para lograr un exito. Lo mismo sucede con las historias que cuenta Designing Interactions (nota: los capitulos de este libro se pueden bajar del sitio... tiene muchas historias de como se diseñaron la mayoria de las cosas que usamos en la compu, realmente es una buena fuente de inspiración).

La conclusión importante de todo esto es:
No se puede hacer algo bueno sin plantearse primero como mejorar lo que existe.
La respuesta no va a ser obvia, y se requiere de prueba y error (que puede ser costosa). Pero si uno mata esa creatividad y se queda con lo "estandard" o lo dado, sin si quiera hacerse un re-planteo, entonces siempre vamos a seguir con los desarrollos mediocres... atrapados en el modelo de negocios orientado a buzzwords.

* los links apuntan a las versiones del 2000, es interesante comparar algunos con la version 2009... Por ejemplo el exito de Google hizo que AltaVista (en el 2000 tan conocido como Yahoo hoy) avandonara el estilo "portal".