Libro – Atomic Habits de James Clear

Hoy, querido lector, te traigo una (no tan breve) reseña de uno de los libros más interesantes que he leído últimamente sobre productividad, concretamente sobre los hábitos, desde un punto de vista fundamentalmente práctico.

El área de productividad lleva llamándome la atención desde hace muchos años, tanto que he leído e intentado poner en práctica muchos métodos, con mayor o menor éxito, y he documentado algunos de estos progresos en artículos como Tareas, Kanban, GTD y otras hierbas

Cuando hace unos meses me recomendaron Atomic Habits, no sabía que esperar. Me había leído (o más bien escuchado) The Power of Habit de Charles Duhigg un libro increíblemente detallado en los aspectos teóricos y físicos de la formación de hábitos aunque un tanto denso, así que otro libro sobre el tema me ponía el listón bastante bajo en cuanto a expectativas, aunque he de decir que las ha superado, con creces.

Nota importante: Este artículo cubre la edición en inglés de “Atomic Habits”, con lo cual la traducción de los conceptos explicados en el libro son propias y pueden no coincidir con la edición en castellano del mismo.

Las cuatro fases de un hábito

Tras una introducción en la que el autor habla de la relación entre identidad, resultados y hábitos, el autor descompone un hábito, en cuatro fases:

  • Cue (desencadenante): Lo que dispara la ejecución de una acción
  • Craving (antojo): Lo que genera la necesidad de llevar a cabo la acción
  • Response (respuesta): La ejecución de la acción.
  • Reward (recompensa): El resultado de la acción.

Las cuatro leyes de los hábitos

Una vez definidas las diferentes fases que componen un hábito y cómo se repiten constantemente a lo largo del día, el libro define el modelo que nos permitirá por una parte, incorporar hábitos positivos a nuestro día a día, así como interrumpir otros que resulten negativos, mediante el uso de cuatro “leyes” que define de la siguiente manera.

Si queremos agregar un hábito positivo a nuestra vida tenemos que conseguir:

  • Que sea obvio (desencadenante)
  • Que sea atractivo (antojo)
  • Que sea sencillo (respuesta)
  • Que sea satisfactorio (recompensa)

En el caso de querer eliminar un hábito que consideremos negativo, el proceso es el mismo, negando las premisas:

  • Que sea invisible (desencadenante)
  • Que sea poco o nada atractivo (antojo)
  • Que sea difícil (respuesta)
  • Que sea poco o nada satisfactorio (recompensa)

El resto del libro se dedica a expandir las definiciones de las cuatro leyes y qué pasos concretos podemos utilizar para implementarlas. Los capítulos son relativamente cortos, específicos y cuentan con un sumario al final de cada uno, así como un progreso en las diferentes leyes.

Ideas interesantes

El libro está lleno de ideas de implementación prácticas relacionadas con los hábitos, de las cuales me gustatría destacar algunas ideas que he sacado de pasajes que he subrayado:

  • Encadenar hábitos, crear un hábito justo a continuación de algo que ya hagamos de manera frecuente, o bien enlazar una acción que queremos hacer con una acción que necesitamos hacer.
  • Con el fin de crear un hábito, tener un entorno estable en el que todo tiene un lugar y un propósito hace que el entorno en sí mismo se convierta en el desencadenante del hábito y que posteriormente la acción a llevar a cabo sea más sencilla.
  • Podemos romper hábitos, pero es prácticamente imposible olvidarlos del todo, resistir la tentación es una estrategia poco eficiente, siendo más útil reducir la exposición al desencadenante (que sea invisible).
  • Es importante destacar la diferencia entre movimiento y acción, movimiento es pensar en escribir este artículo durante 3 meses, fijar un calendario, poner una lista de las ideas que discutir y tener la sensación de “progreso”, acción es sentarse durante 2 horas a escribir. Solamente la acción produce resultados.
  • La ley de los dos minutos nos permite fijar un objetivo ridículamente sencillo, con la idea de que empezar a trabajar en la acción sea fácil, y que no cueste trabajo seguir adelante.
  • Para llevar la cuenta de nuestros hábitos, podemos utilizar un registro, en el cual vamos anotando de manera inmediata y diaria los hábitos que cumplimos. Esto nos permite ser sinceros con nosotros mismos, ya que, habitualmente, pensamos que hacemos las cosas (bastante) mejor que como las hacemos realmente. Es importante que nuestra lista sea corta y concisa, ya que corremos el riesgo de querer llevar la cuenta de muchas cosas, y eso rompa el sistema.
  • Busca tu zona óptima de dificultad. Si algo es demasiado sencillo te resultará aburrido, si es demasiado complicado, te resultará inalcanzable, encuentra el punto medio para poder seguir progresando de tal manera que no te quedes atascado, ya que la mayor amenaza no es fallar, sino aburrirse.

Apéndices y extras

En la parte final del libro, podremos ver un resumen de todas las sugerencias, simplificadas para un acceso rápido, así como enlaces a los trackers, a listas paso a paso para mantener hábitos, y extras sobre hábitos en la paternidad o en el mundo de los negocios.

Conclusiones

Atomic Habits es un libro que no puedo dejar de recomendar si te gusta la ciencia, y sobre todo la práctica de la mejora continua a través de pequeños cambios que representan grandes resultados a lo largo del tiempo. El estilo práctico, unido a los ejemplos y detalles como resúmenes al final de cada capítulo y extras al principio y al final, hacen que la lectura sea rápida y entretenida.

Te recomiendo, además, que visites el blog del autor James Clear en el que define de manera monográfica muchos de los temas tratados en el libro, con lo cual si te gusta el libro te gustará el blog, y viceversa.

Atomic Habits está disponible en formato físico y ebook en Amazon y como audiolibro en Audible

Espero que te resulte tan interesante como a mí.

Creando un sistema basado en reglas en Java

Hace tiempo cuando intentaba aprender Scala, compré el libro Exercises for Programmers y últimamente he dedicado algo de tiempo a hacer uno de los ejercicios, que consiste en un sistema basado en reglas muy simple.

Los sistemas basados en reglas son un subconjunto de los sistemas expertos, que a su vez se engloban en el área de la inteligencia artificial. En el contexto de este artículo, simplificaremos los conceptos, quedándonos con un simple árbol de decisión, que nos permitirá llegar a una conclusión basándonos en las respuestas del usuario.

Podemos encontrar ejemplos de estos sistemas en asistentes paso-a-paso, en las centralitas telefónicas (“Para consultar el saldo, pulse 1”) y en los asistentes digitales como Alexa, Siri, Cortana o Google. Cuando escribimos un sistema de este tipo, nuestra lógica tendrá el siguiente aspecto:

sistema pregunta a.
si usuario responde si:
    sistema pregunta b.
    si usuario responde = "si"
        sistema responde c <- Respuesta final
si no
    sistema pregunta d
...

Esta información puede vivir en nuestro código fuente en formato de if, else anidados o puede formar parte de los metadatos de nuestro sistema. En el artículo de hoy diseñaremos uno de estos sistemas utilizando Java y la siempre útil consola de comandos.

Además de escribir nuestro sistema de reglas, en este artículo mencionaremos por encima los siguientes temas:

  • Tests unitarios con JUnit y AssertJ
  • Generación de Setters y builders con Lombok
  • Manejo de Ficheros yaml con snakeyaml
  • Inyección de dependencias e inversión de control
  • Creación de paquetes JAR con Gradle incluyendo dependencias

Los tests

Una práctica que intento seguir a la hora de hacer este tipo de ejercicios, es empezar por las pruebas al más puro estilo TDD, definiendo de manera inicial el estado al que queremos llegar.

Podemos emplear JUnit y AssertJ para ejecutar los tests, y agregar las referencias a Gradle es tan sencillo como utilizar las siguientes líneas:

testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
testCompile(“org.assertj:assertj-core:3.11.1”)

Con ello podemos escribir nuestro primer test:

@Test
public void question() {
    assertThat(expertSystem.getMessage()).isEqualTo("Are you able to see any wifi network?");
}

Una vez pedido el estado inicial, hemos de comprobar que el motor pasa del primer estado al siguiente, como hemos en el siguiente test:

@Test
public void firstAnswerYes() {
    assertThat(expertSystem.getMessage()).isEqualTo("Are you able to see any wifi network?");
    expertSystem.answer(true);
    assertThat(expertSystem.getMessage()).isEqualTo("Is the network ID visible?");
    assertThat(expertSystem.isDone()).isFalse();

Finalmente, podemos comprobar que hemos llegado a un estado final con el siguiente test:

@Test
public void secondAnswerYesYesDone() {
    assertThat(expertSystem.getMessage()).isEqualTo(Are you able to see any wifi network?");
    expertSystem.answer(true);
    assertThat(expertSystem.getMessage()).isEqualTo("Is the network ID visible?");
    expertSystem.answer(true);
    assertThat(expertSystem.getMessage()).isEqualTo("Contact your network provider");
    assertThat(expertSystem.isDone()).isTrue();
}

Con estos tests, definimos un sistema de reglas que:

  • Recibe una respuesta binaria, que puede ser sí o no.
  • Proporciona el estado actual así como la información de si el estado actual es final o no.

El motor de reglas

Para este ejemplo, el motor de reglas no es más que un árbol binario con una clase muy sencilla que contiene dos hijos, “sí” y “no”, para cada rama:

@Getter
@Builder
class Stage {

    private String status;
    private Stage yes;
    private Stage no;

    boolean isEnd(){
        return yes == null && no == null;
    }
}

Para crear tanto la construcción de los objetos como los getters como el patrón builder podemos recurrir a Lombok, del que ya hemos hablado en otros artículos de este blog, y que podemos agregar a nuestro modelo de Gradle con las siguientes líneas:

compileOnly ‘org.projectlombok:lombok:1.18.8’
annotationProcessor ‘org.projectlombok:lombok:1.18.8’

A la hora de pasar de una etapa a otra, el motor de reglas simplemente decide qué hijo tiene que buscar:

private Stage current; //Aquí inicializaremos la etapa inicial

public String getMessage() {
    return current.getStatus();
}

public void answer(boolean answer) {
    if (answer){
        current = current.getYes();
    } else {
        current = current.getNo();
    }
}

public boolean isDone() {
    return current.isEnd();
}

Una comprobación que no forma parte del código es qué pasa si la etapa ya es final. Por otra parte se podría transformar bloque IF en un operador ternario, dando como resultado algo como current = answer ?? current.getYes() : current.getNo();

El almacenamiento

En el apartado anterior no hemos mencionado cómo inicializar las reglas. Podemos empezar por crear los objetos como parte del constructor, dando como resultado algo así:

Stage root = Stage.builder()
        .status(“Are you able to see any wifi network?”)
        .yes(Stage.builder()
                .status(“Is the network ID visible?”)
                .yes(Stage.builder()
                        .status(“Contact your network provider”)
                        .build())
                .build())
        .no(Stage.builder()
                .status(“Wireless network might be off. Reboot computer”)
                .build())
        .build();

Sin embargo, esta aproximación genera un fuerte acoplamiento entre el código y los datos, así que es una buena práctica sacar las reglas del código a un formato diferente, optando en este caso por YAML.

Al transformar nuestro árbol de Java a YAML tenemos como resultado la siguiente estructura, y este fichero lo podemos almacenar en la carpeta resources de nuestro proyecto:

text: "Are you able to see any wifi network?"
yes:
  text: "Is the network ID visible?"
  yes:
    text: "Contact your network provider"
  no:
    text: "Reboot the wireless router"
no:
  text: "Wireless network might be off. Reboot computer"

Para leer el fichero YAML podemos utilizar SnakeYaml, que podemos importar en Gradle con la siguiente línea:

compile group: ‘org.yaml’, name: ‘snakeyaml’, version: ‘1.8’

Finalmente, podemos cargar el fichero YAML en memoria de la siguiente manera.

class YamlLoader implements FileLoader<Stage> {

    public Stage loadFromFile() {
        Yaml yaml = new Yaml();
        InputStream inputStream = this.getClass()
                .getClassLoader()
                .getResourceAsStream("options.yaml");
        Map<Object, Object> obj = (Map<Object, Object>) yaml.load(inputStream);

        return getStage(obj);
    }

    private Stage getStage(Map<Object, Object> obj) {
        if (obj == null) {
            return null;
        }

        return Stage.builder()
                .status(obj.get("text").toString())
                .yes(getStage((Map<Object, Object>) obj.get(true)))
                .no(getStage((Map<Object, Object>) obj.get(false)))
                .build();
    }
}

Para no tener un acoplamiento entre nuestra clase ExpertSystemy YamlLoader, la segunda implementa una interfaz genérica llamada FileLoader que simplemente define un método, lo que nos da la posibilidad de agregar otros gestores de ficheros en el futuro como xml o JSON.

public interface FileLoader <T> {
    T loadFromFile();
}

Finalmente la conexión entre nuestra clase ExpertSystemy el gestor de ficheros se realiza en el constructor:

public ExpertSystem(FileLoader<Stage> fileLoader) {
    current = fileLoader.loadFromFile();
}

Este último paso no deja de ser inyección de dependencias e inversión de control. De esta manera, nuestra clase ExpertSystem es completamente independiente del formato en el que almacenemos nuestros datos, y podemos probarla de manera aislada, mientras mantenemos nuestra lógica de carga de YAML independiente de la lógica del motor de reglas.

La interacción

Para que nuestro código sea utilizable necesitamos, además de un algoritmo que funcione, una manera de interactuar con nuestro sistema. Para eso, volvemos a los mecanismos basados en System.outy System.in para escribir y leer por la consola.

public class UI {

    public static void main(String[] args) {
        ExpertSystem system = new ExpertSystem(new YamlLoader());

        Scanner scanner = new Scanner(in);

        while (!system.isDone()) {
            out.printf("%s %s ", system.getMessage(), Option.getOptions());
            system.answer(Option.parse(scanner.nextLine()));
        }

        scanner.close();
        out.println(system.getMessage());
    }
}

Finalmente, podemos extraer la gestión de la entrada a una clase específica llamada Option:

class Option {

    private static final String truthy = "yes";
    private static final String falsy = "no";

    static String getOptions() {
        return String.format("[%s/%s]:", truthy, falsy);
    }

    static boolean parse(String input) {
        if (input.toLowerCase().equals(truthy)) {
            return true;
        } else if (input.toLowerCase().equals(falsy)) {
            return false;
        }

        throw new IllegalArgumentException();
    }
}

Esta clase analizará la entrada que recibimos de la consola y generará un valor verdadero o falso, que luego podemos pasar posteriormente a nuestro motor.

Creando nuestro paquete jar

El último paso de nuestro sistema es empaquetarlo para pode distribuirlo. En nuestro caso lo que queremos es un fichero jar que se pueda ejecutar de manera independiente.

Para ello, hemos de modificar nuestro fichero Gradle para agregar ciertas directivas que, por una parte, establecen la clase principal de nuestro proyecto, y por otra parte, fuerzan a que se combinen las diferentes dependencias que tiene nuestra aplicación. Este paso se muestra a continuación:

jar {
    manifest {
        attributes "Main-Class": "net.rlbisbe.expert.UI"
    }

    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

Finalmente, solamente tenemos que ejecutar java -jar NuestroPaquete.jar, y podremos ver nuestro sistema en funcionamiento:

Recapitulando

A lo largo de este artículo, hemos visto cómo crear un simple sistema basado en reglas, cómo cargar dichas reglas de un fichero YAML, mientras repasábamos conceptos como inversión de control e inyección de dependencias, pruebas unitarias, y aprendíamos a manejar dependencias en Gradle.

No pierdas la oportunidad de echarle un ojo a Exercises for Programmers que tiene este y otros problemas para aprender nuevos lenguajes, probar maneras diferentes de hacer las cosas o practicar conocimientos.

Puedes encontrar el código fuente de este ejemplo en Github – Expert-System

Sobre cultura de trabajo en equipos remotos

El pasado jueves estuve en la primera sesión del grupo MADnagers in tech y el tema de conversación era “Gestionando equipos en remoto”, presentado por Pablo Carranza, Head of Engineering en Cabify.

Aunque tuve la mala suerte de llegar un poco tarde y perderme los primeros minutos de presentación, lo que pude ver me resultó realmente interesante, así que no quería perder la ocasión de compartir mis notas y aprendizajes de esta sesión.

Dos ritmos de trabajo en paralelo

Una de las ideas más interesantes que se comentó fue la de los dos ritmos de trabajo a los que nos enfrentamos en una organización, y que funcionan a la par:

El primero se refiere al aquí y ahora, las características, bugs, y problemas a los que se está enfrentando el equipo a fecha de hoy. Este ritmo es el que se suele ver diariamente en las standups, en los correos “urgentes” y en la cola de tickets que nos vienen de atención al cliente. El responsable de llevar este ritmo es el propio equipo, que tiene todo el contexto sobre el aquí y ahora, siendo la labor del líder del equipo la de desbloquear situaciones, pero no la de controlar cada tarea.

El segundo se refiere al futuro, aquí es donde están las nuevas peticiones, el estado de los proyectos que se ejecutarán más adelante, las nuevas iniciativas, las migraciones, y todo lo que no tenga el carácter de “Urgente”. En este caso el responsable de llevar este ritmo es el líder del equipo, de fijar el contexto y mantener la conversación sobre el futuro, especialmente a la hora de encontrar bloqueos que afecten al equipo

Uno de los puntos en los que más incidió Pablo fue en la necesidad de que tanto el presente como el futuro convivieran, idealmente, en el sistema de control de tareas, ya sea Jira, Github, Gitlab u otros.

Esto es debido a que el sistema de control de tareas permite mantener una conversación de manera asíncrona y pública y en diferentes hilos. Esto es especialmente importante en el caso de equipos remotos, ya que por una parte, no todos los miembros del equipo tienen que estar disponibles a la misma hora, y por otra, garantiza que en el futuro cualquier miembro del equipo pueda acceder a este historial, especialmente nuevos miembros.

Roadmaps permanentes y controlados

Otro punto destacado fue la gestión del Roadmap, en la que pasamos de un documento de Word compartido por email a un sistema controlado y versionado.

Un roadmap es importante en cuanto a que define la dirección a la que vamos, y por tanto es un documento que debería cambiar lentamente y de manera controlada. Si un roadmap está cambiando constantemente se convierte en un documento que no es fiable, y por tanto se le deja de presentar atención.

Es por ello que una idea que plantea Pablo es la de mantener el roadmap de manera controlada y controlar qué cambios se hacen al mismo. La manera más fácil de hacer esto es mantener el roadmap en un fichero markdown dentro de nuestro control de versiones, que las actualizaciones del mismo se realicen vía pull request, y que se pueda tener una conversación sobre la misma de manera pública y asíncrona.

Esto además nos permitiría mantener el histórico de donde estábamos hace 1 año, 6 meses, y ver si lo que queríamos hacer entonces lo queremos hacer ahora.

Si no está escrito, no pasó

En este punto, mencionaba dos ceremonias que suelen ocurrir cuando tenemos un equipo parcialmente remoto, las reuniones formales, como por ejemplo la standup, y las reuniones informales, como las conversaciones de pasillo.

Por lo general, salvo que exista una agenda definida y unos minutos tras la reunión, no se suele dejar constancia de que lo que se ha contado en una reunión, especialmente si la información solamente se comparte en ese momento y no queda en una tarea.

Esto es especialmente delicado en lo que respecta a las conversaciones de pasillo, ya que suelen ir acompañadas por una toma de decisiones que no suele quedar documentada, ni la decisión, ni las razones de la misma.

Pensando en este tema me ha venido a la memoria el año 2012, cuando formaba parte del equipo de Códice Software haciendo PlasticSCM, que durante las standups un miembro del equipo tomaba nota del estado que compartía cada miembro del equipo, y luego ese estado se actualizaba en una wiki de manera pública.

De esta manera, al final del sprint se podría comprobar cómo había sido el progreso del mismo, y habría un registro. Por otra parte, si alguien se incorporaba después de unos días de vacaciones, era tan sencillo como ir al registro del sprint, leer el estado y ponerse al día de manera asíncrona,

Cuidado con los Silos

Otro de los puntos comentados se refería a cómo el no compartir la información de manera pública puede acabar provocando silos de información, cuando solamente ciertas personas conocen el estado de un sistema porque lo diseñaron ellos, o peculiaridades de una integración que no están documentadas porque es conocimiento tribal.

Para ello es muy importante tomarle el pulso al equipo, ver cómo están hablando, cómo se están comunicando y encontrar los mejores canales para ello, wikis, sistemas de tareas, repositorios y otros mecanismos más allá de reuniones, chats o email.

Equipos remotos y la ley de Conway

La importancia de mantener unos niveles de comunicación adecuados en un equipo va más allá de que sea más sencillo seguir el rastro a tareas, de que el equipo no tenga que estar preocupado de lo que va a decir en la standup, o que las reuniones de sincronización se extiendan durante varias horas hasta que el estado quede claro.

De acuerdo con la ley de Conway, esto afecta directamente a cómo producimos el software en nuestras organizaciones:

Las organizaciones dedicadas al diseño de sistemas […] están abocadas a producir diseños que son copias de las estructuras de comunicación de dichas organizaciones

Melvin Conway

Por tanto, si formamos parte de equipos remotos y nuestra estructura de comunicación es pública y asíncrona acabaremos haciendo mejor software.

Conclusiones

  • Tenemos muchos sistemas a nuestra disposición para compartir el estado de nuestras tareas más allá de una reunión de 15 minutos en la que cada miembro del equipo tiene 30 segundos para hablar.
  • Compartir información de tal manera que no necesitemos a todos los miembros del equipo presentes a la misma vez nos permite ganar tiempo, ya que la información se mantiene en un punto común y de manera estable, especialmente a la hora de tomar decisiones, documentando las decisiones tomadas y las razones para las mismas.
  • Mantener la información públicamente accesible y simplificar canales de comunicación sencillos permite mitigar la aparición de silos de información, en las que solamente un subconjunto del equipo conozcan cierta información que todos deberían conocer.
  • Conseguir un sistema que permita que el estado esté fácilmente disponible, y que los canales de comunicación estén claros, no solamente afecta a la actitud del equipo, sino que tiene un impacto directo sobre el software que producimos.

Lecturas adicionales

Pablo escribió un artículo en su blog en el que habla de estos temas y los desarrolla con un gran nivel de detalle, además durante el evento mencionó el Remote Manifesto de Gitlab, y la teoría de las limitaciones (theory of constraints) que se hizo conocida en el ibro The Goal y que se ha llevado al campo de IT en el libro The Phoenix Project, que comenté el pasado año.

[Cloud Running – I] Creando un dashboard en QuickSight con los datos de Strava

A lo largo de los últimos meses he estado trabajando casi a diario utilizando QuickSight, una herramienta de AWS que nos permite visualizar datos de manera sencilla, completamente SAAS y con la capacidad de acceder a un montón de orígenes de datos, ah, es gratis como parte de la capa gratuita de AWS y tiene su interfaz localizada tanto en castellano como en otros idiomas.

QuickSight no es tan conocido como otras herramientas como Tableau o PowerBI, ya que está menos extendida y presenta una manera muy simple de trabajar con datos, así que quería aprovechar lo aprendido en estos últimos meses y compartirlo en el blog, ya que me parece una herramienta muy interesante sobre todo por la simplicidad de uso.

En este capítulo y a lo largo de esta serie que he llamado Cloud Running, veremos cómo podemos crear un análisis de QuickSight utilizando un conjunto de datos simple, revisando las limitaciones que podamos tener, creando campos calculados y generando nuestras dashboards, y mi plan es continuar la serie complicando el conjunto de datos, jugando con actualizaciones, y finalmente trabajando con orígenes de datos adicionales.

El resultado final de este artículo será el dashboard, o panel como lo denomina QuickSight en su traducción al castellano, que te muestro a continuación:

Subiendo nuestro conjunto de datos de ejemplo

Todo en QuickSight empieza por el conjunto de datos. Un conjunto de datos no deja de ser una información tabulada que analizaremos posteriormente, que puede ir desde ficheros separados por comas, bases de datos relacionales, o fuentes de datos utilizando APIs como Twitter, como podemos ver a continuación en el menú de Administrar datos -> Nuevo conjunto de datos:

Para este ejemplo usaremos un fichero separado por comas que tiene este aspecto:

Este fichero lo obtuve de Strava, que permite una opción de descargar toda nuestra información en un fichero ZIP como parte del proceso de eliminación de nuestra cuenta. Podemos obtener el fichero sin eliminar la cuenta, y es suficientemente completo como para usarlo en este ejemplo.

Una vez subimos nuestro fichero separado por comas a QuickSight, veremos la pantalla de edición de nuestro conjunto de datos:

En esta pantalla podemos cambiar el nombre de los campos, el tipo de dato, seleccionar el separador de nuestro conjunto de datos, así como generar campos calculados, que veremos más adelante.

Un apunte importante: Una de las claves de la rapidez de QuickSight es su motor en memoria SPICE, que permite cargar un conjunto de datos en memoria y acceder a él con gran rapidez, de modo que, al manipular nuestro conjunto de datos no necesitamos acceder a los datos originales, sino que estamos accediendo a una proyección de dichos datos. Esto significa que no siempre tendremos los datos más actualizados, aunque discutiremos actualización en el próximo artículo.

Una vez estemos satisfechos con nuestro conjunto de datos, el siguiente paso es crear un análisis, lo cual nos presenta una pantalla como la siguiente:

Para empezar a generar gráficos, tan solo tenemos que pulsar en los campos que queremos combinar, y el tipo de gráfico que queremos mostrar. Para crear gráficos adicionales, podemos hacer click en el botón + situado en la barra de herramientas:

Si tenemos seleccionada la opción de AutoGraph marcada por el rayo, QuickSight generará un tipo de gráfico adecuado a cada tipo de datos.

Por ejemplo, seleccionar un único campo como distance nos generará un campo de totales:

Una selección de type nos puede generar un gráfico de barras:

Y finalmente la combinación de los campos distance y date nos generaría una serie de tiempo:

Personalizando nuestros gráficos

Además de utilizar la función AutoGraph, podemos seleccionar el tipo de gráfico que queremos, así como los valores que queremos para las diferentes dimensiones, que varía en función del tipo de gráfico:

Tenemos además diferentes opciones de personalización, que podemos ver haciendo click en los diferentes desplegables con forma de V.

A la hora de personalizar las dimensiones, por ejemplo, en los campos de fecha podemos cambiar la granularidad de la agregación o el formato en el que se muestra la información. En el campo valor, podemos utilizar suma, media, contar, contar excluyendo duplicados, max y min, entre otros, así como personalizar el número de decimales y la escala en la que mostramos nuestros datos, teniendo miles, millones, miles de millones y billones, lo que permite mostrar el K que vemos en el gráfico original.

Además de las dimensiones podemos personalizar los gráficos, seleccionando Formato del elemento visual en el desplegable con forma de V. En el caso que nos ocupa, podemos personalizar valores como el intervalo a mostrar, qué hacer cuando faltan datos, el numero de líneas que tienen los ejes, o las etiquetas de los propios datos.

Enriqueciendo nuestros datos con campos calculados

Otra de las posibilidades que nos aporta QuickSight es el uso de campos calculados tanto a nivel de conjunto de datos como de análisis.

Importante: Las funciones que están a nivel de conjunto de datos son diferentes de las que encontramos a nivel de análisis!

Además de las operaciones aritméticas como suma, resta, multiplicación y división, tenemos un conjunto de funciones lógicas como ifelse, matemáticas como round, floor o ceil y de manejo de cadenas de caracteres como trim, replace, concat u otras.

Un ejemplo de estas funciones, ha sido el tiempo total en minutos, que he hecho con la siguiente fórmula simple: {elapsed_time} / 60 y el ritmo (en min/km), que he utilizado el campo calculado anteriormente: {elapsed time in minutes}/{distance}*1000

A nivel de análisis, el conjunto de funciones que podemos utilizar es diferente, ya que incluye, funciones que podemos aplicar al conjunto total, y no fila a fila, entre los que podemos encontrar count, avg, max, min, percentile así como funciones de manejo de fechas, entre otras.

Limitaciones

Una de las limitaciones con las que me he encontrado a la hora de hacer este ejemplo es el soporte limitado para campos de tipo tiempo. Si bien es cierto que podemos manipular fechas, en el ejemplo que estaba buscando, uno de los valores que quería conseguir era ritmo en minutos por kilómetro, utilizando formato de minutos y segundos, algo que en estos momentos no podemos conseguir en QuickSight.

Creando un dashboard o panel

Los análisis nos permiten editar los campos, moverlos y desplazarlos a nuestro antojo, y compartirlos con otros miembros de nuestro equipo para modificarlo. Sin embargo, habrá situaciones en las que queramos un modo de “solo lectura” para compartir con otros perfiles de nuestra empresa.
Para ello podemos publicar un dashboard o panel, donde podemos publicar uno nuevo o reemplazar existentes. Una vez publicado, lo podemos hacer disponible a todos los miembros de la cuenta o bien invitar solamente a personas específicas.

Otras características

QuickSight tiene más características de las que hemos visto en este artículo pero que se me quedan fuera de este, entre las que destacan:

  • Filtros, que nos permiten ver un subconjunto de datos en un gráfico, en un conjunto de gráficos o en todo el análisis.
  • Parámetros, que permiten modificar nuestro análisis utilizando una lista de valores predeterminados o dinámicos, y que podemos asociar a filtros y controles para modificar nuestro análisis de manera sencilla.
  • Acciones de URL, que permiten enlazar una página web externa utilizando un formato que especifiquemos, por ejemplo, podríamos enlazar el ID de actividad de Strava para que nos llevara directamente a la URL de la misma.
  • Historia en la cual podemos definir varias etapas del mismo análisis, útil cuando queremos tener varios tipos de filtros aplicados en cada una de las historias, y queremos hacer una narrativa de cómo han cambiado los números por trimestre fiscal, por ejemplo.

Conclusiones

QuickSight como hemos visto es una herramienta bastante versátil para análisis de datos, completamente online, y que nos permite crear conjuntos de datos, utilizar campos calculados a nivel de conjuntos de datos y de análisis, crear gráficos y personalizarlos a nuestro gusto, así como cómo guardar y compartir las vistas que creamos.

En el próximo artículo de la serie, siguiendo con la temática de Running, veremos qué pasa cuando tenemos nuevos datos a lo largo del tiempo, y cómo podemos hacer que estos cambios se vean reflejados en QuickSight.

Lecturas adicionales

Una manera diferente de hacer tests en Java con Specnaz

La popularización de lenguajes como Ruby y JavaScript de lado de servidor, ha generado en los últimos años nuevos frameworks de pruebas centrados en lo que se denomina pruebas de especificación, en la cual probamos nuestro código utilizando siempre los casos de uso de dominio, de una manera visual, herramientas como como RSpec, Mocha, o Jest, que siguen una estructura muy similar a la mostrada a continuación.

Supongamos que queremos probar una estructura de tipo Stack:

describe "A Stack"
    describe "pop"
        it "should get the item from the top of the stack"
            //CODE

        it "should get null if stack is empty"
            //CODE			

Esta manera de hacer pruebas contrasta con el estilo que podemos ver en lenguajes como Java en JUnit:

class StackTests

    void test_when_pop_get_item_from_top
        //CODE
    
    void test_when_pop_get_null_if_empty
        //CODE

Aunque son ejemplos muy sencillos, al aumentar la complejidad de nuestra base de código, aumentan también los casos de uso que manejamos, y por tanto el número de tests. Para ello, el estilo de las pruebas de especificación nos permiten agregar casos adicionales reduciendo duplicidad y mejorando la legibilidad.

En el artículo de hoy veremos un framework que nos permitirá hacer este tipo de pruebas en Java, llamado Specnaz y creado por Adam Ruka.

Para este ejemplo utilizaré la kata bowling definida aquí: http://kata-log.rocks/bowling-game-kata.

Nuestro primer test

Para nuestro primer test definimos la primera condición de nuestro juego, que es que un juego vacío devuelva 0 como primer resultado.

...
public class BowlingSpec extends SpecnazJUnit {
    {
        describes("A Game", it -> {
            Game game = new Game();
            it.should("show 0 as score when first created", 
                                () -> assertThat(game.getScore()).isEqualTo(0));
        });
    }
}

Nuestro objeto "Game" tendrá este aspecto inicialmente:

...
public class Game {
    public int getScore() {
        return 0;
    }
}

Inicializadores

Una de las cosas que podemos hacer en nuestros tests, es crear una inicialización antes de cada test, así como una limpieza, esto lo podemos conseguir utilizando el comando de beginsEach como se muestra en el ejemplo:

...
public class BowlingSpec extends SpecnazJUnit {
    {
        describes("A Game", it -> {
            Game game = new Game();
            it.beginsEach(game::reset);
            it.should("show 0 as score when first created", 
                                () -> assertThat(game.getScore()).isEqualTo(0));
        });
    }
}

En vez de crear una instancia nueva de Game, en este ejemplo creamos un método de inicialización dentro del objeto Game que reinicia el estado de la partida. Debido a que estamos en el contexto de una lambda, no podemos redefinir el valor de una variable externa, con lo cual ejecutar el constructor repetidamente no funcionaría

Creando ramas en nuestros tests

Una de las características más interesantes que nos aportan este tipo de tests es la posibilidad de crear ramas con las diferentes condiciones de prueba sobre las que ejecutamos nuestros tests. Veamos un ejemplo:

describes("A Game", it -> {
    ...
    it.describes("when throwing a spare", () -> {
        it.should("account for the number of pins knocked down by next roll", () -> {
            game.roll(4);
            game.roll(6);
            game.roll(4);
            game.roll(0);
            assertThat(game.getScore()).isEqualTo(18);
        });
    });
});

Dentro de cada grupo de "describes" podemos además definir nuestras propias secuencias de inicio y fin de cada test, creando un árbol de casos de uso y evitando repetición innecesaria.

Usando parámetros

En el caso de la kata bowling, una manera muy útil de agrupar los tests es utilizando parámetros, y para ello podemos utilizar una construcción de Specnaz llamada SpecnazParamsJUnit en el caso de que estemos utilizando JUnit como motor de ejecución de tests.

En este ejemplo podemos ver además cómo especificamos los parámetros, y cómo en la cabecera should podemos asignar valores a parámetros:

public class BowlingParamsSpec extends SpecnazParamsJUnit {
    {
        describes("A Parametrized Game", it -> {
            Game game = new Game();
            it.beginsEach(game::reset);

            it.should("properly calculate the gameplays of %1 equal %2", (List<Integer> rolls, Integer expected) -> {
                rolls.forEach(game::roll);
                assertThat(game.getScore()).isEqualTo(expected);
            }).provided(
                    p2(Arrays.asList(2, 3), 5),
                    p2(Arrays.asList(4, 6, 4, 0), 18),
                    p2(Arrays.asList(2, 4, 6, 3), 15),
                    p2(Arrays.asList(10, 3, 6), 28),
                    p2(Arrays.asList(10, 10, 10, 0, 0), 60),
                    p2(Arrays.asList(0, 0, 10, 10, 10), 60),
                    p2(Arrays.asList(10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10), 300)
            );
        });
    }
}

El resultado en nuestra ventana de tests será el siguiente:

should properly calculate the gameplays of [2, 3] equal 5
should properly calculate the gameplays of [4, 6, 4, 0] equal 18
should properly calculate the gameplays of [2, 4, 6, 3] equal 15
should properly calculate the gameplays of [10, 3, 6] equal 28
should properly calculate the gameplays of [10, 10, 10, 0, 0] equal 60
should properly calculate the gameplays of [0, 0, 10, 10, 10] equal 60
should properly calculate the gameplays of [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] equal 300

Manejando excepciones

No es raro que tengamos que encontrarnos con situaciones más o menos excepcionales cuando creamos nuestros tests, y eso incluye ciertos casos de prueba. Para probar excepciones podemos recurrir a shouldThrow que nos permite controlar el tipo de excepción que lanzamos:

it.shouldThrow(IllegalArgumentException.class, "when trying to roll more than 10 bowls in a single roll", () -> {
   game.roll(11);
});

El resultado incluirá además el detalle del tipo de excepción que estamos intentando controlar en el mensaje que se muestra por la consola:

should throw IllegalArgumentException when trying to roll more than 10 bowls in a single roll

Tanto el ejemplo con parámetros como el ejemplo que acabamos de ver nos libran de cometer errores en la cabecera de los tests, ya que se define la excepción que estamos probando así como los parámetros, de tal manera que si cambiaran, cambiaría el nombre del test.

Conclusiones

Como hemos visto durante el artículo, Specnaz es un framework de pruebas de especificación que nos permite definir nuestras pruebas de una manera robusta, evitando duplicidades y permitiendo escribir con detalle qué estamos probando y bajo qué especificaciones.

Además de los ejemplos de este artículo, en su repositorio de github se pueden ver ejemplos de uso con herramientas como mockito para generar dobles de test y poder simular el comportamiento de otras clases, ya que en raras ocasiones probaremos código que no tenga dependencias.

Puedes aprender más detalles en los siguientes enlaces:

Creando diagramas de manera efectiva con el modelo C4

En nuestro día a día empleamos múltiples niveles de abstracción para referirnos a los sistemas de información que construimos, manipulamos o mantenemos, nos referimos a sistemas, aplicaciones, servicios, APIs, llamadas, funciones, almacenamiento, memoria, ficheros, eventos, etc.

Muchas de estas abstracciones las comentamos de manera informal, las describimos a través del código que escribimos y que ejecutamos, las describimos en prosa, o empleamos la herramienta favorita de los arquitectos y la más temida por los desarrolladores, los diagramas.

Un diagrama, ya sea de componentes, de secuencia, de interacción o de clases, no es más que otra abstracción en la que describimos como es o cómo pretendemos que sea nuestro sistema. El diagrama nos puede permitir pasar por ciertas capas de descubrimiento, descubrir o fijar los límites de nuestro servicio en comparación con otros, y es una herramienta que nos permite mantener una conversación.

Esta herramienta es crítica cuando tenemos que interactuar con otros equipos, o compañeros que pueden no tener todo el contexto. Es por ello que es importante tener en cuenta el público, el objetivo, y el nivel de abstracción que queremos mantener. al crear dichos diagramas y representar contextos

Una metodología para la representación de diagramas que he estado poniendo en práctica recientemente es el modelo C4 por Simon Brown, en el cual, siguiendo ciertas prácticas e iterando sobre nuestros diagramas, podemos diseñar nuestro sistema utilizando varios niveles de abstracción:

  • Contexto
  • Contenedor
  • Componente
  • Código

Pese a que el modelo define 4 capas, en este artículo hablaremos de las tres capas superiores, ya que la cuarta se asemeja bastante a diagramas a los que podamos estar más familiarizados, y en ocasiones no es necesario ni siquiera llegar a ellas.

El ejemplo

A la hora de escribir el artículo pensé en algo del estilo “lista de tareas” dada mi obsesión por las aplicaciones, metodologías y otros, pero al final me decanté por un sistema que me permitiera escribir artículos en un blog.

Los requisitos son los siguientes:

  • Un lector puede ver artículos publicados y escribir comentarios.
  • Un autor puede escribir artículos, editarlos y borrarlos
  • Un autor, además, recibirá una notificación cuando se cree un nuevo comentario.

Vayamos manos a la obra:

Nivel 0: Contexto

tbe-blog-page-3

A este nivel queremos definir de manera clara quienes son los actores que interactúan con nuestro sistema, qué sistemas tenemos, y una primera idea de cómo se relacionan entre ellos.

A este nivel podemos tener una conversación con un potencial usuario del sistema, o con nuestros compañeros de negocio, ya que los casos de uso generales se definen aquí.

Es también una oportunidad para destacar qué queda fuera del sistema, y hasta qué punto se pueden modificar esos sistemas. Podemos utilizar esquemas de colores para destacar qué sistemas podemos y cuales no podemos modificar.

Nivel 1: Contenedores

Tbe Blog-Page-4

En este caso hacemos zoom en cada uno de los sistemas, y lo separamos en contenedores que tienen una función lógica. En el caso de nuestro blog tenemos dos subsistemas diferentes, uno encargado de la edición de artículos y otro de la visualización, así como una API entre la que interactúan ambos sistemas.

A este nivel podemos tener conversaciones a nivel técnico con diferentes equipos involucrados en el proyecto, el nivel de abstracción es menor, y podemos tomar decisiones como cuantos subsistemas construimos y cómo se reparte el trabajo de los mismos.

Nivel 2: Componentes

Tbe Blog-Page-5

Este nivel es el más profundo al que llegaremos en este artículo, aquí podemos establecer una separación más clara, en el caso de la UI podemos separar las diferentes páginas que existirán, y en el caso de la API backend los diferentes componentes que podemos emplear, en el que cada componente puede ser una librería, un paquete, un proyecto o una unidad semántica de código.

En este nivel podemos tomar decisiones de arquitectura directamente a nivel de equipo, entender las diferentes relaciones entre los diferentes componentes y modularizar más, si fuese necesario.

Evolución

Una de las características más interesantes de este estilo de modelado es que es iterativo, una vez que empiezas y vas pasando por las diferentes capas descubres que estabas empleando el modelo erróneo de abstracción, y mueves “cajitas” entre las diferentes capas.

El primer diagrama que hagamos nunca será perfecto, pero el hecho de tener tres diagramas diferentes e iterar entre ellos nos permitirá descubrir nuestra arquitectura, encontrar límites y tener una mejor idea del alcance de los productos que vamos a construir.

Desde el punto de vista práctico, recomiendo utilizar una herramienta que nos permita tener los tres tipos de diagramas visibles a la vez, ya que nos veremos en la situación de estar cambiando componentes entre los diferentes niveles de abstracción.

Retrospección

Podemos utilizar estos diagramas no solamente como una manera de crear software, sino también de familiarizarnos con software existente. A la hora de enfrentarnos a un nuevo proyecto, entender el sistema en el que nos encontramos nos permite mitigar el factor ”Pulpo en garaje” y ser más productivos desde el principio.

Conclusiones

En general los diagramas son otra herramienta que podemos utilizar tanto para descubrir, explorar o enterarnos de donde nos hemos metido, no son el fin, ni mucho menos, sino un medio para construir mejores sistemas. Metodologías como C4 nos permiten crear mejores diagramas y con ello, mejores sistemas.

Más información sobre el modelo C4 en la web de Simon Brown: The C4 model for software architecture

Imagen por Kaleidico vía Unsplash

Libro: ¿Cuando? La ciencia de encontrar el momento preciso

Soy de esas personas que, además de tener libros en casa que no han empezado, tengo muchos que empiezo y dejo por la mitad. En este mes de marzo he intentado ponerme al día en los libros que tengo empezados, algunos de ellos he tenido que volver a empezar desde el principio pero otros los he retomado por donde estaba.

Uno de los libros que he retomado es ¿Cuando?, de Daniel H. Pink (When: The Scientific Secrets of Perfect Timing en su versión en inglés).

Es un libro que habla de muchas cosas, pero sobre todo es un libro que habla del tiempo, desde la manera en la que somos productivos a lo largo del día, la diferencia entre búhos y alondras, pasando por los principios, los intermedios y los finales, hasta temas como el concepto del tiempo y del futuro, que no está presente de manera tan fuerte en todos los lenguajes y modifica no solamente la manera de interactuar, sino la manera de ver el mundo.

El libro se divide en tres bloques. Cada bloque, además, consta de una parte que podríamos denominar teórica o divulgativa, y una parte más práctica que Daniel denomina “Manual del Hacker del Tiempo” con consejos prácticos para poner en uso lo enseñado en el apartado anterior.

  • El día: En ella habla de cómo definimos nuestro día, cómo y cuando dormimos, cómo algunos somos de levantarnos pronto y otros de quedarnos hasta tarde, cómo afecta eso a nuestras decisiones, y cuando tomar decisiones importantes.
  • Comienzos, finales y mitades: En este bloque habla de diferentes tipos de comienzo, la importante de volver a empezar en algunos momentos de nuestra vida, la importancia de los finales, de por qué hacemos locuras o cosas intensas en el último año de cada década o cómo varían las relaciones entre la edad adulta y la vejez.
  • Sincronizar y pensar: Finalmente, usando como ejemplos a los dabbawalas, los miembros de un coro, y de un equipo de remo, habla de la importancia y la necesidad de mantener sincronía con aquello que marca el tiempo (que puede ser una persona, un reloj, una situación), sincronía con el grupo y finalmente, lo que denomina sincronía del corazón, es decir, la razón última de por qué hacemos las cosas, en el coro puede ser por motivos religiosos, en el remo por el esfuerzo conjunto, y en el caso de los dabbawalas, por la importancia de lo que están transportando, comida hecha en casa.

Un libro interesante, que te puede hacer pensar y reflexionar, siendo mi parte favorita la de los comienzos, finales y mitades. Todo lo que hacemos, inexorablemente tiene un principio, una mitad, y un final, como también lo tiene este artículo que comparto contigo, querido lector.

Si sientes curiosidad, tienes el libro disponible aquí: ¿Cuándo?: La ciencia de encontrar el momento preciso o si lo prefieres en versión original: When: The Scientific Secrets of Perfect Timing (English Edition)