Reflexiones sobre cómo desarrollar software (y no morir en el intento)

Clasificación automática de textos

Imaginemos que queremos construir un servicio que recomiende artículos para leer basándose en las preferencias de los usuarios. Una forma de empezar es preguntando a los usuarios por qué tipo de noticias les interesa más (deportes, política, economía, etc.) y sugerirles noticias de aquellos temas que han elegido. Pero, si queremos enviar noticias de distintas fuentes, más o menos conocidas, primero vamos a tener que resolver un problema: ¿cómo sabemos a qué categoría pertenece una noticia? En esta entrada veremos una forma rápida y sencilla de hacer esto, con una implementación real que funciona gracias a la librería scikit-learn the Python. El resultado final será un programa capaz de clasificar un texto en función de otros textos que ha analizado previamente y que le han servido para aprender a clasificar textos. Hablamos de machine learning en estado puro.

Representación de documentos

Consideremos un texto (o documento) como un conjunto letras, que forman palabras, que, a su vez, forman párrafos, que, finalmente, componen un texto. Queremos que nuestro programa aprenda de qué trata cada documento**, para lo que vamos a necesitar representar los documentos de una forma que podamos procesar después.

Una forma muy común de representar documentos en problemas de clasificación es mediante una bolsa de palabras, que no es más que una estructura que contendrá todas las palabras que aparecen en el documento junto con el número de veces que aparece en el mismo. Veamos un ejemplo con un documento muy sencillo:

La política puede llegar a ser muy divertida.
Las mejores noticias sobre política directamente en su smartphone.

Este documento podría representarse así:

a
directamente
en
la
las
llegar
mejores
muy
noticias
política
puede
ser
smartphone
sobre
su
1
1
1
1
1
1
1
1
1
2
1
1
1
1
1

Como podemos ver, todas las palabras aparece una sola vez excepto la palabra política, que aparece dos.

Distancia entre dos documentos: cuánto se parecen dos artículos

Ahora que tenemos una forma de representar documentos, veamos cómo podemos medir cuánto se parecen entre sí, es decir, vamos a buscar una medida de similitud para dos documentos. Tomemos las dos bolsas de palabras que representan cada documento:

1
0
0
0
5
3
0
0
1
0
0
0
0
3
0
0
0
2
0
0
1
0
1
0
0
0

Hemos ordenado la lista con las apariciones de cada palabra de la misma forma en ambos casos, por lo que sabemos que una cierta palabra aparece una vez en el primer documento y tres veces en el segundo, otra aparce cinco veces en el primer document y dos en el segundo; otras sólo aparecen en uno de ellos o en ninguno (Llegados a este punto, realmente no nos importa qué palabras son).

Podemos medir la similitud de dos documentos multiplicando las apariciones de cada palabra en cada uno de los documentos entre sí y sumando los resultados para todas las palabras:

1*3+0*0+0*0+0*0+5*2+3*0+0*0+0*1+1*0+0*1+0*0+0*0+0*0 = 13

Ahora os podréis preguntar, ¿realmente esto funciona? Pongámosle cara a los ejemplos para entender un poco mejor lo que está pasando. Supongamos que los dos primeros documentos corresponden a dos artículos sobre deportes (concretamente fútbol) y que las palabras cuya frecuencia estábamos represetando eran las que siguen:

Messi
rifle
conflicto
paz
gol
portero
guerra
seguridad
regate
falta
política
guerrillero
banda
1
0
0
0
5
3
0
0
1
0
0
0
0
3
0
0
0
2
0
0
1
0
1
0
0
0

Supongamos que ahora queremos ver cómo de similar es el primer artículo con otro sacado de la sección de internacional del periódico, que trata sobre sobre un conflicto armado:

Messi
rifle
conflicto
paz
gol
portero
guerra
seguridad
regate
falta
política
guerrillero
banda
0
0
1
0
0
0
9
0
0
0
6
4
0

Usando la misma fórmula que en el caso anterior:

1*0+0*0+0*1+0*0+5*0+3*0+0*9+0*0+1*0+0*0+0*6+0*4+0*0 = 0

Tenemos que nuestra medida de similitud nos da en este caso un valor de cero. Es decir, los dos artículos de fútbol tienen una medida de similitud de 13 mientras que el primer artículo de fútbol y el que trata sobre el conflicto armado tienen una medida de similitud de cero. Este resultado concuerda con la idea intuitiva que tenemos acerca de cómo de parecidos son estos artículos.

El problema de la longitud de documentos

Es fácil darse cuenta de que esta forma de calcular el parecido entre dos documentos ponderará de forma distinta documentos con distinto tamaño. Imaginemos que los dos artículos de deportes son el doble de largos y que la aparición de las palabras se mantiene en la misma proporción (irreal pero ilustrativo). Las bolsas de palabras se parecerán a las siguientes:

2
0
0
0
10
0
6
0
0
2
0
0
0
0
6
0
0
0
4
0
0
2
0
2
0
0
0
0

Si calculamos la similitud de ambos ahora tendremos:

2*6+0*0+0*0+0*0+10*4+6*0+0*0+0*2+2*0+0*2+0*0+0*0+0*0 = 52

Como vemos, el número es sensiblemente superior al original, 13. Este problema podemos resolverlo normalizando el vector con las apariciones de cada palabra. Para ello, sólo tenemo que dividirlo por la raíz cuadrada de la suma de los cuadrados de cada elemento (en el caso del primero documento, es 6):

1/6
0
0
0
5/6
3/6
0
0
1/6
2
0
0
0

Priorizando las palabras distintivas

Mediante la normalización podemos librarnos del problema con documentos con distintos tamaños pero aún podemos mejorar nuestra forma de comparar documentos basándonos en las palabras que aparecen en ellos.

Hay palabras que se repetirán mucho en todos los documentos (verbos y sustantivos muy comunes, preposiciones, etc.) mientras que otras se repetirán mucho únicamente para aquellos documentos relacionados. Las primeras serán palabras comunes en el conjunto de todos los documentos que harán que la medida de similitud entre dos documentos cualesquiera sea más alta aún no estando relacionados. Las segundas son las que realmente nos interesa ponderar, pues son la que realmente diferencian documentos parecidos de documentos sin relación. Es decir, queremos ponderar aquellas palabras que:
  • Aparecen mucho en un documento
  • Aparecen poco en el conjunto de todos los documentos
Decimos que las palabras importantes son aquellas localmente comunes y globalmente raras. Así llegamos al vector de frecuencias de términos y frecuencia inversa en documentos (term frequency - inverse document frequency, tf-idf). El cálculo de dicho vector para un documento es sencillo. Partiendo del vector original donde ya teníamos la frecuencia de cada palabra en un documento. consideramos también un vector para las mismas palabras donde cada valor se calcula como:




Si miramos con detalle esta fórmula para cada palabra, veremos que para aquellas palabras que aparecen en casi todos los documentos el resultado será el logaritmo de un número cercano a 1, el cual se aproxima a cero, mientras que, para palabras que aparecen en pocos documentos, será el logaritmo de un número cada vez mayor conforme la palabra aparece en menos documentos (más rara es). El cálculo del vector tf-idf para un documento no es más que el resultado de multiplicar el primer verctor (el de frecuencias de palabras en un documento) por éste último.

Gracias a la ponderación que se hace de las palabras realmente importantes, nuestra medida de similitud entre dos documentos será mucho más precisa si utilizamos el vector tf-idf que si sencillamente usamos el vector de frecuencias de palabras.

Encontrando documentos parecidos

Supongamos que tenemos un conjunto de documentos y queremos saber cuál es el documento que más se parece a un documento cualquiera. Calculando el vector tf-idf para el primer documento así como para el conjunto de documentos que tenemos, y calculando el parecido entre documentos usando dicho vector tf-idf, el documento con un valor más alto según nuestra medida de similitud será el candidato que más se parece dentro de nuestro cuerpo de documentos, es decir, será el vecino más cercano.

Agrupando documentos parecidos

Hemos visto que podemos definir el documento más parecido a otro como el documento más cercano. Es decir, podemos usar nuestra medida de similitud como una medida de distancia entre documentos. Esto nos permite utilizar uno de los algoritmos más famosos en machine learning para agrupar documentos que se parecen: K-means clustering. Sin entrar en muchos más detalles, nos basta saber que mediante este algoritmo podemos agrupar un conjunto de elementos por cercanía entre los mismos. Como ya tenemos una medida de la distancia entre documentos, podemos utilizarlo para obtener grupos de elementos que minizan la distancia respecto del centro de la agrupación. Así, formaremos categorías de documentos que se parecen entre sí.

Una vez que tengamos los grupos formados para un conjunto de documentos (ejemplos que utilizaremos para el aprendizaje) podemos predecir a qué categoría pertenece un nuevo documento calculando su distancia al centro de cada uno de las categorías que tenemos. La categoría predicha será aquella cuyo centro está más cercano al documento.

Clasificando documentos con scikit-learn

Partamos de un conjunto de artículos de ejemplo extraídos de algunos periódicos online. Dichos artículos pertenecen a las secciones de deportes, ciencia y economía. Cada artículo está almacenado en un fichero de texto dentro de un directorio llamado igual que la categoría a la que pertenece el artículo.

Vamos a empezar leyendo todos los artículos y creando un diccionario de artículos y etiquetas (cada etiqueta será el nombre de la categoría a la que pertenece).


Y ejecutamos al función anterior proporcionando el directorio raíz donde se encuentra cada uno de los directorios con los artículos:


Creamos nuestra matriz tf-idf con el conjunto de artículos que tenemos. Para esto, vamos a utilizar la librería de Python scikit-learn:


Ahora que tenemos una representación adecuada del cuerpo de documentos de entrenamiento (tf-idf), ejectuamos nuestro algoritmo de aprendizaje (K-means clustering), para lo que también utilizaremos scikit-learn:


Ahora tenemos un clasificador (clf) que puede predecir la categoría de un artículo. Probamos con un conjunto nuevo de datos y medimos cómo de buena es la predicción:


Con los conjuntos de entrenamiento y prueba proporcionados, tenemos predicciones razonablemente buenas, teniendo en cuenta lo reducido de nuestro conjunto de artículos de ejemplos (no teníamos más de 50 artículos para ninguna de las categorías).

Podemos mejorar nuestro clasificador de una forma muy sencilla. Como vimos antes, tf-idf intenta obviar aquellas palabras que se repiten en muchos documentos y que, por lo tanto, no sirven para distinguirlos. Sin embargo, podemos hacerle la vida más fácil y obtener mejores resultados si sencillamente nosotros proporcionamos una lista de palabras que no queremos que se tengan en cuenta en absoluto. Por ejemplo, para artículos en español podríamos hacer lo siguiente:



Con este sencillo ajuste, nuestro clasificador obtiene ahora una fiablidad de más del 92%. ¿A que no ha sido difícil?


Hemos visto una técnica para clasificar documentos y una implementación que, pese a no llegar a 50 líneas de código, obtiene buenos resultados con menos de 50 artículos de ejemplo por categoría. Como vemos, cada vez utilizar técnicas de machine learning requiere menos esfuerzo. Con unas cuantas líneas de código en Python, podemos crear un prototipo bastante rápido y luego, si creemos que los resultados son prometedores, meternos más de lleno. ¿Queréis saber alguna aplicación que pueda tener un clasificador de artículos como el que hemos visto? Por ejemplo, para determinar automáticamente en qué idioma está escrito un artículo.

Código fuente y artículos de ejemplo

Puedes encontrar todo el código fuente utilizado en este artículo así como un conjunto de artículos para entrenar vuestro clasificador y comprobar su precisión en el siguiente repositorio en Gthub: https://github.com/joragupra/text-classifier.


** Realmente nos basta con que aprenda a reconocer documentos que tratan sobre el mismo tema.
Continue Reading

Tomando notas de forma efectiva (I)

Tengo la manía de querer recordarlo todo. Pero mi cerebro, como el de todos, está hecho más bien para analizar y componer ideas nuevas que para guardar un montón de información. Así que hago lo que casi todo el mundo ha hecho desde siempre: tomar notas. Es algo que, si no hago, creo que no estoy trabajando (o, peor todavía, que estoy trabajando sin organización y de forma poco efectiva). Puedes llamarme maniático, pero así de importante es para mí llevar un registro de lo que hago, lo que se me pasa por la cabeza, en qué empleo mi tiempo, etc.

¿Por qué quieres mantener un registro?

Se trata, en mi opinión, del elemento que más puede aumentar la eficacia de una persona con la mínima inversión de tiempo (y dinero) requerida: tomar notas y procesarlas correctamente. Desde que empecé a trabajar me di cuenta de que las personas que mejor hacían su trabajo, y a las que me gustaría parecerme, tomaban notas sin parar. Así que decidí ponerme a tomar notas (llegando a veces a excesos que llevaron a ganarme algún que otro mote gracioso). Esto es lo que hago.

Empezando por el principio: ponte a escribir

En mi opinión, el proceso consta de dos partes fundamentales: tomar notas  y procesarlas. No tendrás mucho que procesar si no has escrito nada. Así que el primer paso, y el más importante, es empezar a tomar notas durante tu vida ordinaria.
No te obsesiones con la forma en que deberías hacerlo, cómo organizarlas, qué estructura darles, etc. Eso no hará más que detenerte y que no empieces nunca. Además, no es importante cuando estás empezando (lo importante realmente es empezar). Al poco tiempo verás que ya has desarrollado una forma con la que te sientes cómodo y te resulta últil cuando estés revisándolas después. Así que empieza a escribir ya.

Cosas que van a ayudarte

Una vez has cogido algo de inercia y lo de coger notas se ha convertido en un hábito ordinario, empieza a mejorar la forma en que lo haces. En mi caso, considero que ayuda bastante hacer lo siguiente:
  • Dale un título descriptivo a tus notas. Basta con algo sencillo y no muy largo que te proporcione algo de contexto acerca de qué trata la anotación que viene a continuación.
  • Enumera las páginas. Así podrás localizar cualquier cosa rápidamente mediante un índice (explicado más adelante) o hacer referencias a otras notas.
  • Pon siempre la fecha de cuándo hiciste la anotación. Siempre que hagas la primera anotación del día, empieza poniendo la fecha.
  • Crea un índice. Reserva las primeras tres o cuatro páginas para el índice y ve rellenándolo conforme haces más y más anotaciones (esto lo suelo hacer una vez al día). Este índice debería contener, como mínimo, la página donde se encuentra la anotación, la descripción (título) y la fecha.
Siguiendo estas cuatro pautas ya tienes un cuaderno más organizado que el 90% de los que me he visto en mi vida. Creo, además, que resulta bastante sencillo: nada de usar distintos colores, poner pegatinas para marcar páginas especiales o dividir un cuaderno por temas. Sencillamente escribir siguiendo un orden cronológico manteniendo estos cuatro elementos.

Algunos detalles más

Usa símbolos para identificar notas especiales. A este respecto, intento que sean los mínimos necesarios: una exclamación para algo importante (!), algo que parezca una bombilla para una idea sobre la que quiero volver más adelante y un círculo para una tarea nueva que he pensado que tengo que hacer. Apunte: intento no utilizar la libreta para gestionar tareas, pero a veces es mejor escribirlas a mano que perderlas (sobre cómo gestiono mis tareas escribiré más adelante).
Otra cosa que ayuda es dejar un espacio (al menos una línea) al final de cada anotación. De esta forma podrás añadir alguna aclaración que se te ocurra posteriormente. En mi caso, lo más normal es que sea una referencia a una página posterior donde continúo tratando el mismo tema.

Olvídate de la tecnología y presta atención a los detalles

Quieres que el proceso de tomar notas sea rápido, que lo puedas hacer en cualquier momento y cualquier lugar y quieres que no te falle nunca: quieres papel y lápiz. No intentes tomar notas en utilizando una tablet o escribiendo directamente en tu ordenador portátil. No será ni la mitad de efectivo y la sensación de que tienes algo "definitivo" (ya no lo tienes "que pasar a limpio", ¿eh?- venga ya, chaval, que no estás en el instituto) hará que sea más difícil que vuelvas sobre tus notas más adelante. Y créeme, ésa es la parte donde esto se pone interesante, no te la puedes saltar.

Conclusión

Así es como termina la primera parte de esta serie. La próxima tratará de qué hacer con esa información que has estado recopilando. Ahí es donde esto cobra sentido pero, como dije antes, hay que empezar por el principio (y es mejor hacerlo bien).

Si tienes alguna técnica ninja especial y quieres compartirla, usa los comentarios. O, si lo prefieres, puedes criticar lo que he dicho si te parece que no sirve para nada. Escríbelo también: aquí no censuramos ni le decimos a la gente lo que tiene que pensar.
Continue Reading

Mitos, falacias, errores y paradojas temporales

Cuando escribes código (o cuando lo estás probando) te encuentras realidades que tienen distintos efectos en tu trabajo: desde hacerte perder (un poco de) tiempo a convertir tu sistema en un auténtico infierno donde no quieres pasar ni un minuto más del estrictamente necesario.
Una variedad especial que se me ha repetido continuamente son las relacionadas con el tiempo. Por diversos motivos, tratar con el tiempo en un sistema puede llegar a ser extremadamente complicado. La cosa se complica todavía más cuando olvidamos tener en cuenta los detalles más básicos, lo que nos lleva a la motivación de este artículo.


Este post es un resumen, sin seguir ningún orden particular, de algunos de los descuidos, malentendidos y paradojas que me he encontrado al trabajar con sistemas que trataban con fechas, horas y el paso del tiempo.
Lo que sigue es bien conocido por todo el mundo y cualquiera podrá decir, con razón, que olvidar estas cosas es de tontos. Sin embargo, me he visto más de una vez empantadado en mi propio software por culpa de ellas.

No todos los meses tienen el mismo número de días

Es algo que todos saben y, sin embargo, no es infrecuente encontrarte código que, por ejemplo, cuando queremos saber qué día es exactamente dentro de un mes hacen cosas como sumar siempre 30 días a la fecha actual (o sumar siempre 31 días que, para el caso, es igualmente inválido con carácter general).
Y es que mayo tiene 31 días, junio tiene 30, julio tiene 31 y agosto tiene también 31. Y, por supuesto, tenemos febrero, que tiene 28 días, la mayoría de las veces aunque...

No todos los febreros tienen 28 días

Algo que normalmente se aprende en la escuela de pequeño y que se suele olvidar todavía con más frecuencia que el apartado anterior. Pero sí, resulta que, por una mezcla de cienca e historia, algunos meses de febrero tienen 28 días y otros tienen 29. Y, como, colorario...

No todos los años tienen 365 días

Por el mismo motivo por el que hay meses de febrero más largos que otros (y como consecuencia de ello), hay años que tienen 366 días. Se llaman bisiestos, ocurren cada cuatro años y hasta podemos hacer un sencillo cálculo para saber si un año es bisiesto.
Pero no siempre lo tenemos esto en cuenta y, más veces de las que deberíamos, damos por sentado que, por ejemplo, si tenemos que programar algo para que se ejecute automáticamente dentro de un año exactamente, podemos sumar 365 días a la fecha actual y tendremos la fecha en que queremos que nuestra tarea sea ejecutada (error).

Y no todos los minutos tienen 60 segundos

Y por eso hay que meter un segundo aquí y allá de vez en cuando -aunque nunca hayas tenido que tratar con este problema directamente, hay quien se lo toma muy en serio.
De acuedo, éste ha sido un poco más friki de la cuenta. Volvamos con algunos errores más ordinarios, por ejemplo, cuando tratamos con intervalos de tiempo. Empecemos por uno muy simple.

Un intervalo de tiempo de una hora no siempre empieza y acaba en el mismo día

Como ocurre, por ejemplo, con el intervalo de una hora que empieza a las 23:48 de hoy, que terminará mañana. Sencillo,¿ verdad? No sé por qué me he encontrado código que daba por hecho lo contrario (por ejemplo, asumiendo que al sumar una hora a la hora actual tendrías otra hora en el mismo día que tenías al principio).

Tampoco todos los intervalos de una hora empiezan y terminan el mismo mes

Si, por ejemplo, el intervalo que empieza a las 23:48 resulta que lo hace el 30 de abril. Así que, ya sabéis, sumar una hora a un instante de tiempo puede dar lugar a un instante de tiempo en otro mes distinto. Más aún...

No todos los intervalos de una hora ni siquieran empiezan y terminan el mismo año

Cuando el intervalo del que hablamos antes, en vez de ser el 30 de abril, es el 31 de diciembre, ya la hemos liado.
Bueno, creo que ya he explotado bastante el ejemplo y la idea se capta. Pero, ¡ah, recuerda!
  • No todas las semanas empiezan y terminan el mismo mes
  • Ni el mismo año -eso de que el uno de enero caiga en lunes es lo menos frecuente. De hecho, no volverá a ocurrir hasta el 2018.
Es momento ahora de ponerse un poco más técnico. La siguiente es obvia pero provoca bastantes problemas.

El reloj del sistema no siempre tiene la hora correcta

Y es que hay muchas formas de que te dé una hora equivocada. Por ejemplo cuando

El reloj del sistema está configurado como si estuviera en una zona horaria diferente

Y es que al que montó la máquina no le pareció importante asignar la zona horaria correcta cuando instaló el sistema operativo en el servidor de pruebas. Así que la hora parecerá la misma, y servirá para casi todo igual, pero llegará un momento en que tengas que tener en cuenta la zona horaria y, de repente, todo empezará a parecer que ocurre dos horas antes (o dos horas después, o lo que sea) que cuando debería ocurrir.
O también puede ser que sencillamente...

El reloj del sistema puede ser aleatoriamente inconsistente

No tiene sentido, pero ves que el sistema está configurado con un reloj exactamente adelantado 1 hora y 37 minutos y eso ha hecho que pierdas dos horas intentado encontrar la causa de que las horas de los cambios hechos en una tabla de tu base de datos sean incorrectas. Y entonces es cuando uno que pasa por ahí te dice que ayer cambió la hora del sistema porque quería ver si un proceso batch que normalmente se ejecuta a las seis de la tarde terminaba correctamente y se tenía que ir a casa a las cinco y cuarto.
Hablando de relojes cambiados, recuerda que:

La hora en tu servidor no tiene que coincidir con la hora que tienen los clientes

Y es que quien se conecta puede ser un mentiroso y haber cambiado su reloj para que parezca que solicitaron una oferta antes de que terminara el plazo en que expiraba.
O puede que sencillamente ellos viven en Sindey, tú en Madrid y tu servidor está en alguna parte de Irlanda. Lo que nos lleva a un subtipo especial.

Los relojes de tu servidor y de los clientes no siempre están en la misma zona horaria

Así que usa la differencia entre ambas zonas horarias para hacer tus cálculos. Y cuando los hagas, acuérdate también de tener en cuenta en qué día y qué mes estamos porque...

La diferencia entre regiones situadas en zonas horarias distintas no es siempre la misma

Puede variar a lo largo del año, por ejemplo, si en una zona aplican el horario especial de verano mientras que en otra, no.
Y es que trabajar con el tiempo nunca fue fácil y son muchas las cosas que hay que tener en cuenta, por lo que no es tan infrecuente que pasemos por alto algo que luego nos dará problemas. Espero que ahora que las he puesto por escrito no se me olviden más.
Continue Reading

El mito del trabajador siempre ocupado

Tengo que confesar que tengo un problema: no respeto a la gente que trabaja mucho. Bueno, mejor dicho, a la gente que está siempre haciendo cosas. Bueno, tampoco se trata de respeto propiamente dicho, más bien de falta de entendimiento. Sí, creo que eso es más bien lo que me pasa: no entiendo a la gente que está todo el día ocupada haciendo cosas. Ahora está mejor (perdón a los que se hayan podido sentir ofendidos).


Profundizando un poco más en el tema, la gente que se empeña en rellenar cada espacio libre que les queda en la agenda para hacer todavía un poco más de no sé muy bien qué realmente me desconcierta. En mi mente, eso se corresponde con un esquema de pensamiento industrial que considera que lo más costoso es tener una máquina parada así que asegurémonos de que las máquinas estén siempre funcionando, haciendo algo, si es preciso organizando turnos, para que no paren nunca de producir.

Pero mi visión es diametralmente opuesta a ésa. Yo no veo ninguna maquinaria súper cara que haya que mantener todo el día en funcionamiento para que sea rentable y, por lo tanto, el tiempo ocioso no implica para mí una pérdida de dinero (ni de productividad) sino más bien al contrario. Para mí, el tiempo desocupado significa otra cosa bien distinta: significa oportunidad. ¿Oportunidad para qué? Para hacer algo que marque la diferencia, algo único, original, que sobresalga, y hacerlo realmente bien. Sinceramente, no me imagino haciendo algo de verdadero impacto si me paso veinticinco horas al día atareado con mil cosas. Y no quiero decir que sólo quiera dedicarme a hacer las partes más bonitas, chulas e interesantes de un proyecto o empresa en la que nos hayamos embarcado ni que le tenga tirria al trabajo duro y la dedicación a los detalles más oscuros y desagradables. Ciertamente creo en el valor del trabajo duro por encima de todo. Una vez que tienes definida una meta y sabes adónde quieres llegar, lo que debes hacer es trabajar duro hasta conseguirlo, tanto como sea necesario.

Pero también creo que hay que tener tiempo y cierto espacio mental para poder estar atento a la próxima oportunidad que se presente. Porque se van a presentar oportunidades, y muchas veces no somos capaces de verlas porque estamos ocupados haciendo otras cosas.

Creo que, en definitiva, el problema es que existe cierta confusión entre:
  • Ser eficaz
  • Ser eficiente
  • Estar ocupado
Y el orden en que he escrito estas cosas no es casual: mi principal objetivo es siempre ser eficaz (porque quiero conseguir cosas). Si puedo conseguirlas empleando menos recursos, es decir, si puedo ser eficiente, estaré más contento. Pero eso es opcional, un segundo paso. Porque si he exprimido al máximo mis recursos para no conseguir lo que quería (o, peor aún, para conseguir algo que no es lo que quería), entonces no voy a estar nada contento. Y, por supuesto, lo último que quiero es estar ocupado.
Muchos confunden eficacia y eficiencia, y creen que ésta significa estar siempre ocupados
Estar ocupado es una consecuencia de los objetivos que quiero conseguir y los medios que tengo a mi disposicion. Quizá estoy intentando conseguir algo muy difícil o muchas cosas al mismo tiempo (cuidado, la pérdida de foco es otro problema importante, pero de ese tema podemos hablar otro día) o quizá no dispongo de todos los recursos que quisiera para poder alcanzar mis objetivos. Entonces estaré muy ocupado trabajando para conseguir lo que quiero. Pero, por lo general, ése no es un estado en el que me quiera encontrar, y mucho menos todos los días de mi vida.

Desgraciadamente muchos confunden la eficacia con la eficiencia, priorizando esta última, que, además, creen que significa estar siempre ocupados. Es una forma de conseguir cierta paz mental al poder decirnos por la noche: "Hemos estado todo el día trabajando a destajo. No hemos parado ni un momento." Y así creemos que podemos estar tranquilos porque no se nos puede reprochar nada cuando lo hemos dado todo. Pero al final llega el día en que nos damos cuenta de que no hemos conseguido ninguna de las metas con las que soñábamos y nos preguntamos cómo fue posible, si estuvimos trabajando tanto.

Cuando veo a alguien que se pasa los días súper atareado haciendo mil cosas sin parar como si no hubiera un mañana tengo la tentación de preguntarle:
  • ¿Qué estás haciendo ahora mismo?
  • ¿Cómo lo estás haciendo? ¿Se podría hacer de otra forma que te llevara menos tiempo?
  • Y, sobre todo, ¿para qué lo estás haciendo? ¿Qué vas a conseguir con ello?
  • ¿Podrías estar haciendo otra cosa que realmente te aportara más de lo que estás haciendo ahora mismo?
Pero siempre me contengo y, al final, no digo nada. Al fin y al cabo, ellos están muy ocupados y probablemente no tendrán tiempo para escuchar a nadie.
Continue Reading

Regreso al futuro... o cómo probar propiedades temporales sin perder la cabeza

Supongamos que estamos construyendo un sistema para la gestión de clientes donde, entre otras cosas, vamos a registrar las compras que realizan. Empezamos con un modelo muy básico, creando una clase para nuestros clientes que nos ofrecerá la funcionalidad necesaria para cumplir nuestra primera regla de negocio:

  • Son clientes senior aquellos que tienen más de año de antiguedad
Aquí tenemos algo de código:

Hemos empezado con un test que falla y continuamos desarrollando la implementación que hace que el test pase:

Seguimos con nuestro modelo. Como dijimos, los clientes compran productos, para los que tenemos un código y la fecha de compra (dicha fecha se establecerá en el momento en que la compra se haga efectiva, no pudiendo modificarse posteriormente). Así que introducimos una nueva clase para productos que ofrecerá una funcionalidad, bastante básica también, con otra regla de negocio:

  • Un producto ha sido comprado recientemente si la compra se produjo en el último mes
Y creamos un test para dicha funcionalidad:

A continuación, añadimos la lógica que hace que nuestro test pase:

Ahora que tenemos clientes y productos, vamos a añadir la lógica que permitirá a los clientes comprar productos. Empezamos, como siempre, por un test que falla:

Y, después de haber creado el test, implementamos dicha lógica y hacemos que el test pase:

Con el código que tenemos a estas alturas, podemos empezar a hacer cosas más interesantes. Nos proponemos hacer ofertas especiales a algunos clientes y de ahí sale nuestra tercera regla de negocio:

  • Sólo los clientes senior pueden recibir ofertas especiales.
Así que añadimos una nueva funcionalidad en nuestra clase de clientes, empezando con tests que fallan:

Y, a continuación, implementamos esta funcionalidad de acuerdo con nuestra regla de negocio para hacer que los tests pasen:

Nuestro código pasa a produción, la gente empieza a comprar y nosotros hacemos ofertas a nuestros clientes para aumentar las ventas. Al cabo del tiempo, decidimos cambiar las condiciones para hacer ofertas a clientes y modificamos nuestra tercera regla de negocio con una nueva versión:

  • Sólo los clientes senior que hayan realizado alguna compra recientemente pueden recibir ofertas especiales.
Como siempre, empezamos por los tests, así que creamos un nuevo test que nos asegure que esta nueva versión de nuestra regla de negocio se cumple. Pero, con el código de test que hemos creado hasta ahora, nos encontramos con la primera dificultad. La clase Product fue diseñada para ser inmutable, por lo que ni el código del producto comprado ni obviamente la fecha de compra deberían poder ser cambiados. Con nuestro código actual, necesitamos establecer una fecha de compra con más de un mes de antelación para poder construir un test que compruebe que los clientes que

  • tienen más de un año de antigüedad pero
  • no han comprado recientemente
no reciben ofertas especiales.

Aquí empiezan las dificultades pues tenemos que, para poder probar nuestro sistema, hay que violar el diseño que nosotros mismos habíamos definido. No pasa nada, hacemos un pequeño ajuste en la clase Product y, a partir de ahí, resulta mucho más fácil probarlo todo:

Ahora ya podemos implementar nuestra nueva regla en la clase Customer:

Y esto es lo que llaman Design for testability, ¿verdad? Hemos modificado nuestro diseño para poder probar el código correctamente. ¡Falso! Esto no es más que un diseño pobre que hemos enmendado de mala manera para poder crear unas mínimas pruebas unitarias, pero dista mucho de ser una buena solución y el problema no hará más que agravarse con el tiempo. El diseño para la prueba no tiene nada que ver con romper el principio de encapsulación y exponer el estado interno de un objeto permitiendo que actores externos puedan alterarlo sin ningún control.

Por supuesto, podríamos intentar usar algún tipo de objeto mock para los productos que compran los clientes, lo cual requeriría configurar dichos mocks para devolver las fechas de compra que necesitamos en nuestra prueba y modificar la propia clase Customer para poder inyectar dichos mocks como productos comprados por el cliente. Sin embargo, esto sería un engorro que nos va a hacer perder mucho tiempo cada vez que queramos probar algo y ya sabemos que hacer que las pruebas sean complicadas de programar supone abandonar la práctica de programar pruebas automáticas.

El problema no está en si la clase Product es inmutable o no, en cómo modificar la fecha de compra de los productos o en cómo injectar instancias mocks de productos en la clase Customer. El auténtico fallo de diseño en nuestro código está en que hemos dejado en manos de la clase Cutomer la creación de la fecha de compra de los productos. Es decir, cuando un cliente compra un producto, establecemos la fecha de compra del product (que debe ser el instance actual, es decir, ahora) y dejamos que sea la propia clase Customer la que decida cuándo es ahora.

El verdadero problema está en que la clase Customer es  responsable de determinar cuándo es ahora, es decir, de instanciar Date.
Un verdadero diseño que facilite las pruebas consiste en delegar la lógica que determina cuándo es ahora, es decir, crear una nueva instancia de la clase Date en el instante actual, a una clase externa que sirva como proveedor de tiempo. Con esta idea, la implementación no podría ser más sencilla:

Ahora ya no es Customer la clase responsable de crear objetos Date sino que los solicita a una clase externa, TimeProvider. De hecho, a partir de ahora ninguna clase en nuestro sistema debería jamás crear sus propias instancias de Date sino pedirlas siempre a TimeProvider.

Habréis notado algo raro en esta implementación de TimeProvider. ¿Qué es ese campo offset y para qué lo queremos? Offset no es ni más ni menos que nuestra implementación del concepto de design for testability. Veamos cómo funciona.

Supongamos, como caso más sencillo, que offset toma un valor de 0. A continuación, solicitamos a TimeProvider la fecha y hora actual llamando al método now(). Pero supongamos que offset tuviera como valor el incremento de tiempo correspondiente a tres días (en milisegundos) y le volvemos a pedir a TimeProvider la fecha y ahora actual, es decir, volvemos a llamar a now(). ¿Qué recibimos? La fecha y hora de dentro de tres días. ¿Y si todo nuestro sistema estuviera conectado a TimeProvider y cada vez que requiriese la hora actual llamara al método now()? Entonces nuestro sistema estaría viviendo en el futuro, tres días en el futuro, para ser exactos.

Para conseguir esto, vamos a construir una clase que solamente pueda ser utilizada por nuestro código de pruebas. Dicha clase nos permitirá ajustar el valor de offset dentro de TimeProvider de forma que siempre sume (o reste) una constante de tiempo al instante actual. Así es como acabamos de crear la máquina del tiempo:

Básicamente esta clase sirve para establecer el valor de offset en TimeProvider. Además le he añadido algunos métodos adicionales para permitir especificar el instante de tiempo al que queremos viajar como Date, LocalDateTime, etc. pero la idea básica es esa: cambiar el offset de TimeProvider. Ahora podemos reescribir nuestro código de pruebas para la nueva versión de nuestra regla de negocio de una forma más clara y sencilla:

Ya no necesitamos cambiar la fecha de compra de los productos ya que ésta se va a establecer en el instante correcto cuando llamamos al método buy(). Nuestra clase Product vuelve a ser inmutable tal y como queríamos. De hecho, todos los tests que hemos creado hasta ahora pueden ser reescritos usando TimeMachine, lo que nos va a ayudar a hacerlos más legibles:

Sólo hay un detalle final que tenemos que tener en cuenta. Como debemos mantener las ejecuciones de los distintos tests independientes unas de otras, es preciso que offset vuelva a cero cada vez que terminamos con una prueba (y también asegurarnos de que es cero cuando una prueba comienza). Para eso, TimeMachine nos proporciona un método reset() que se encarga de ello.

En resumen, hemos llegado a una solución con un mejor reparto de responsabilidades entre las distintas clases del sistema. Esta solución nos ha ayudado a mantener el diseño inmutable que queríamos para nuestra clase Product y ha conseguido que los tests sean más fáciles de escribir y de leer.

Incluso en el caso de que no estemos demasiado interesados en la inmutabilidad de objetos, seguir principios de Design Driven Domain o sencillamente no nos importe llenar nuestras clases de métodos de acceso getters y setters (bueno, hay gente para todo), el tener un proveedor centralizado de fecha y hora para todo el sistema nos puede resultar muy útil cuando estamos construyendo lógica fuertemente ligada a una cronología de eventos y acontecimientos (por ejemplo, cuando estamos implementando un flujo de trabajo y queremos probarlo de forma integral).

Aquí termina la presentación de mi máquina del tiempo particular, una ligera variación del arcano conocimiento que me fue transmitido por Dmytro Iaroslavskyi, un auténtico fenómeno. Espero que os haya resultado interesante esta solución. Por supuesto, cualquier comentario sobre cómo podéis aplicarla, qué modificaciones haríais, qué puntos débiles le veis o cualquier otra cosa que os venga a la mente será más que bienvenida.

Tenéis el código fuente utilizado como ejemplo para esta entrada en github.
Continue Reading

Buscando un cielo más azul

Hay algo que está muy extendido entre los desarrolladores de software: la tendencia natural que tenemos a pensar siempre que existe un lugar de trabajo mejor que donde estamos. Sólo tenemos que encontrarlo para ser felices para siempre, al menos, laboralmente.

Lo cual, traducido a nuestras carreras profesionales, tiene su traducción en creer firmemente que la empresa donde trabajamos es una basura, que las condiciones son horribles, que el trabajo se hace mal (y que, en otra parte, existe una empresa donde las cosas se hacen realmente bien, donde nos sentiremos valorados, nos pagarán como nos merecemos y donde las condiciones serán tan buenas que ir a trabajar será mil veces mejor que irnos un mes de vacaciones a las Maldivas).

Normalmente este pensamiento no aparece instantáneamente nada más empezar a trabajar en una empresa. Algunas veces es un hecho concreto lo que lo dispara (una promoción que se nos ha escapado, un aumento de sueldo que no ha llegado, una discusión con nuestro jefe) aunque lo más habitual es que sencillamente se vaya formando en nuestra mente poco a poco, conforme la rutina empieza a impregnar cada aspecto de nuestro trabajo diario y la magia de estar en un sitio nuevo donde descubres algo todos los días se va perdiendo.

A partir de entonces empezamos a disfrutar cada vez menos de nuestro trabajo y, probablemente por ello, nos sentimos menos motivados. Lo que suele resultar, en casi todos los casos, en que nuestros resultados son peores. A veces, mucho peores (he visto a desarrolladores a los que consideraba genios hacer auténticas chapuzas y darme cuenta al final de que el problema estaba en que no se sentían motivados cuando las hicieron). Por supuesto, conforme los resultados son peores, la valoración que otros tienen de nosotros es peor, la promoción que esperábamos no llegará, tendremos cada vez más broncas con nuestro jefe, nuestro sueldo seguirá siendo igual de miserable. Y todo ello hará que cada vez nos sintamos más resentidos con nuestra empresa y más amargados en nuestro trabajo.

Pero no pasa nada, porque sabemos que existe ese otro sitio donde las cosas nos irían mucho mejor instantáneamente por la sencilla razón de que es un lugar mejor. Y entonces empezamos a buscar otro trabajo… de nuevo. Porque no es la primera vez que andamos buscando ese sitio. Es la misma búsqueda que hicimos la última vez que nos pusimos a buscar un nuevo trabajo (y que a lo mejor pensamos que había terminado cuando encontramos el trabajo donde estamos ahora). Si eres sólo un poco más listo que la mayoría de la gente ya te habrás dado cuenta de que tampoco va a ser la última vez que lo hagas.

Y así nos pasamos muchos de nosotros nuestra vida profesional, saltando de una empresa a otra regateando beneficios sin ser feliz en ninguna parte, hasta que llega el momento en el que empezamos a valorar más otras cosas como la estabilidad -por convencimiento o por pura necesidad. Y dejamos de buscar, no porque pensemos que hemos llegado al lugar correcto, sino sencillamente porque sabemos que ya no podemos seguir más adelante. Y terminamos en un lugar que no nos gusta, donde no disfrutamos ni un solo segundo que pasamos en él y de donde sabemos que ya no vamos a salir fácilmente.

Vale, estoy siendo un poco dramático pero quién puede decir que no reconoce un patrón parecido en su carrera o la carrera profesional de otros. Y, ¿realmente tiene sentido que sea así?

Rompiendo el círculo

Lo primero que tenemos que hacer para romper esta espiral de insatisfacción es darnos cuenta de una verdad: no existe la empresa perfecta. Sólo tienes que pensar en que la gente se va de Google porque está absolutamente quemada. Sí, los mismos que trabajan en esas oficinas tan chulas con salas de reuniones super guapas, restaurante propio donde te dan de comer chefs y te prepara el café que quieras un barista profesional, además de cobrar una pasta gansa, se terminan marchando porque están insatisfechos, aburridos, estresados, quemados.

¿Crees que serías más feliz si en vez de trabajar en tu empresa actual lo hicieras en Google? Pregúntales a los que se marcharon de Google (posiblemente te dirán que se fueron para trabajar en una empresa mejor). ¿Crees que vas a ser más feliz por trabajar en otra empresa? Puede que no porque las condiciones son sólo una parte pequeña de la ecuación de la satisfacción laboral. Cuanto antes te des cuenta de eso más tiempo tendrás para realmente planificar cómo quieres que evolucione tu carrera.

Conócete a ti mismo


Empieza por preguntarte qué es lo que realmente quieres conseguir de tu carrera profesional. ¿Te gustaría por encima de todo trabajar para una gran empresa? ¿Valoras más poder compartir tu lugar de trabajo con gente super inteligente? ¿Te gustaría dedicarte a gestionar en lugar de a hacer cosas por ti mismo? ¿Quieres trabajar en tu propia idea?

Mucha gente con la que he hablado no sabe en qué quiere que se convierta su carrera profesional. Muchos tienen dudas, les cuesta decidir, algunos ni siquiera se lo han preguntado. Y eso es lo que realmente nos hace infelices en el trabajo la mayoría de las veces.

Por eso, da igual lo que quiera que escojas, debes escoger algo y tener presente que no se puede tener todo. Si nos hicieran por separado cualquiera de las siguientes preguntas:

  • ¿Te gustaría que tus compañeros de trabajo fueran personas super inteligentes de las que pudieras aprender todos los días?
  • ¿Te gustaría trabajar en algo con un impacto global que potencialmente afecte a millones de usuarios?
  • ¿Te gusta ver que tu trabajo diario tiene un impacto directo en los resultados de tu compañía?
  • ¿Quieres ascender la escalera corporativa y llegar a ocupar el cargo con mayor responsabilidad posible?
  • ¿Quieres desarrollar tu propia idea (revolucionaria) y cambiar el mundo?

Posiblemente responderíamos que sí a un buen número de ellas. Pero, puestas todas juntas, es imposible decir que sí a todas a la vez (al menos, de una forma realista). Sencillamente son cosas diferentes, en muchos casos contradictorias, que requieren seguir caminos divergentes para alcanzarlas. Así que tenemos que decir que sí a unas pocas cosas y empezar a decir que no a muchas otras.

Y empieza a moverte


Y cuando ya sepas adónde quieres ir entonces es el momento de empezar a moverte, de buscar siempre mejores oportunidades para seguir desarrollándote de la forma que has decidido hacerlo. Puede ser que un trabajo te dure unos pocos meses hasta que cambies a otro o bien que continues en la misma empresa durante años. En cualquier caso, cada paso que des lo harás convencido de que te acerca a tu objetivo. Y si tu objetivo cambia, no pasa nada, ajusta tu plan y empieza a ponerlo en marcha.

Los planes son inútiles, pero planificar lo es todo.
Dwight D. Eisenhower

No ocurre nada si cuando llegas a una entrevista de trabajo presentas un curriculum que demuestra que durante 5 años quisiste ser experto en inteligencia artificial y durante los dos años siguientes cambiaste porque querías ser scrum master o agile coach. No se puede decir lo mismo si cambiaste diez veces de empresa para conseguir dos días más de vacaciones o una silla más cómoda.

Para quien navega sin rumbo ningún viento es favorable.
Séneca
Continue Reading