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

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

Tienes que crear un framework

Siempre recomiendo utilizar cuantos más componentes ya disponibles mejor. Nunca construyas tu propio framework para:
  • Persistir datos
  • Mostrar información basándote en plantillas
  • Construir la navegación de un sitio web
  • Comunicar componentes remotos
Y tantos otros ejemplos que me vienen a la cabeza.

Sin embargo, hay un framework que siempre deberías construir en cualquier proyecto en el que vayas a participar. Es algo que deberías tener claro antes incluso de empezar. Cuanto antes lo construyas será mucho mejor y no hacerlo pone seriamente en riesgo tu proyecto.

Se trata de tu propio framework para automatizar tus pruebas. No es algo que puedas tomar de alguna otra parte, ni que puedas copiar y pegar de un proyecto pasado (aunque, evidentemente, haber construido uno en el pasado te ayudará a hacerlo ahora). Y no puedes por una sencilla razón: todavía no ha sido inventado. Y el único que puede inventarlo eres tú porque sólo tendrá sentido para ti.

Pero espera un momento entonces. Si sólo va a tener sentido para mí, ¿sigue siendo un framework? Sí y no, lo veremos más adelante. Ahora mejor empecemos con un ejemplo.

Registrando usuarios en una aplicación web

Imaginemos que hemos construido nuestra nueva aplicación web y, como sabemos que vamos a tener que iterar muchas veces sobre nuestro producto para dejarlo a punto, tenemos claro que necesitamos tantas pruebas automáticas como sea posible. De lo contrario, cada vez que cambiemos algo estaremos corriendo un serio riesgo de que el sistema falle (básicamente nos hemos leído algún libro del tipo The Lean Startup, algo sobre desarrollo ágil o, por lo menos, algún artículo en internet publicado en los últimos 10 años. Así que decidimos empezar creando una prueba para una parte fundamental de nuestra aplicación: el login de usuarios.

Como estamos empezando y todavía no sabemos muy bien cómo será el producto final, decidimos hacer una versión muy sencilla (ver ejemplo).

http://cdpn.io/yqihp

Lo sé, es tan fea que casi hace que duelan los ojos. Pero tampoco Google tenía muy buena pinta cuando empezó y fíjate dónde está ahora. Así que no te preocupes, posiblemente cuando vayas por la iteración número 15 podrás pensar en dejarla bonita. De momento, nos sirve. El código HTML de nuestra página sería:

Automatizando pruebas

Podemos crear una prueba automática que haga login en nuestra aplicación usando, por ejemplo, Selenium:

A partir de aquí, podríamos usar este mismo código en cualquier test que requiera el login del usuario y todo estaría bien... hasta que llegásemos al punto en el que queramos cambiar nuestra pantalla de login (iba a ser en nuestra iteración número 15, ¿no?) y tengamos que modificar cientos o, si hemos sido realmente buenos, miles de tests que se basaban en el código anterior.

Mejorando nuestras pruebas automáticas

El problema anterior básicamente tiene su origen en que hemos hecho nuestro código de prueba dependiente de algo que cambia de forma frecuente (especialmente en las etapas iniciales de un proyecto): la interfaz de usuario. Así que algo tan trivial como cambiar el identificador a los elementos HTML de la página de login puede hacer que cientos de tests empiecen a fallar.

La solución, como otras tantas veces, consiste en añadir un nivel de indirección que se encargue de manejar los detalles acerca de cómo está implementada la interfaz de usuario y, al mismo tiempo, ofrezca una interfaz consistente y homogénea con la que puedan interactuar todos los tests que construyamos. Así es como empezaremos a construir el framework que nos ayudará a automatizar nuestras pruebas.

En nuestro caso, aquellos tests que necesiten hacer un login de usuario se podrían escribir de la siguiente manera:

Una vez empecemos con la construcción de nuestro propio framework podemos ir más allá de la simple abstracción de la UI. Por ejemplo, podemos hacer cosas como:

  • Ofrecer un acceso rápido a usuarios ya creados para los que además conocemos determinados parámetros interesantes desde el punto de vista de las pruebas.
  • Implementar procesos largos que son repetidos por muchos tests.
En mi actual proyecto, por ejemplo, tenemos una serie de usuarios prototípicos que son utilizados repetidamente por toda nuestra suite de pruebas. Por ejemplo, Thomas Müller: como buen alemán medio que es, tiene contratado un seguro de responsabilidad civil (también tiene el nombre y el apellido más repetido en Alemania). Así que siempre que alguien necesita un usuario que tenga registrado un seguro de responsabilidad civil durante la construcción de una prueba sólo tiene que hacer:

TestUsersRegistry.getThomasMueller();

O si queremos crearlo desde cero:

TestUsersFactory.createThomasMueller();

O cuando es necesario registrar un usuario nuevo, sin contratos, para probar una funcionalidad disponible para usuarios recieén registrados (tenemos unas cuantas de ese tipo) sólo hace falta hacer:

TestUsersUtils.registerNewUser("John", "Doe", "john.doe@example.com");

En este caso, este método está sobrecargado con diferentes opciones para darnos la flexibilidad necesaria que nos permita registrar usuarios con todas las opciones que sean relevantes desde el punto de vista de los casos de prueba que estamos automatizando. Lo que nos lleva al siguiente punto importante cuando creas tu propio framework de automatización de pruebas: complejidad.

Características de un framework de automatización de pruebas

Por su propia naturaleza, el códido que escribimos cuando automatizamos pruebas tiene (o sería deseable que tuviera) las siguientes características:

  • Es bastante repetitivo. Muchas acciones se repiten a lo largo de múltiples tests (especialmente cuando implementamos diferentes escenarios para una misma funcionalidad)
  • Es lineal (o debería serlo; si no lo es entonces deberías revisar cómo estás implementando tus pruebas)
  • Es fácil de escribir (porque queremos que escribir tests sea algo sencillo que todo el mundo haga de forma regular)
  • Es fácil de leer. Esto es más importante aún porque:
    • Queremos podemos entender más fácilmente qué esperamos de una funcionalidad con echarle un ojo al código que la prueba
    • Cuando un test falla y queremos arreglarlo lo último que queremos es tener que perder un montón de tiempo averiguando qué es lo que el test estaba intentando hacer
  • Se aproxima lo máximo posible al lenguaje natural y utiliza cuantos más términos propios del dominio de nuestro sistema mejor. Esto es básicamente un corolario del punto anterior, pero quería resaltarlo para dejarlo bien claro.
Por todo esto, queda claro dónde tenemos que poner la complejidad: en nuestro framework de automatización de pruebas. Porque las cosas nunca son sencillas y, si queremos tener un código de prueba que sea super sencillo y fácil de entender, tiene que haber un sitio donde se encuentren todas las complejidades inherentes al código que estamos intentando crear.

Por eso, no es ningún problema que nuetro código de automatización de pruebas sea complicado. Tiene que serlo para que nuestras pruebas sean sencillas. La parte positiva es que será un código que escribirás una vez y lo reutilizarás miles de veces (el beneficio es claro).

Recuerda: no se trata de hacer las cosas más complicadas de lo necesario por el gusto de hacerlas complicadas. Pero no hay ningún problema si tu framework de automatización de pruebas empieza a serlo porque tu objetivo es hacer sencillas las miles de pruebas que quieres automatizar.

Esto nos lleva a las características deseables para nuestro framework de automatización de pruebas:

  • Permite escribir muchos tests con poco esfuerzo.
  • Ofrece interfaces claras que pueden ser utilzadas fácilmente por quienes tengan que escribir las pruebas automáticas.
  • Se acerca lo máximo posible al lengaje natural (lo que permite que las pruebas automáticas puedan ser escritas por más personas distintas a los propios desarrolladores del sistema).
  • Maneja todas las complejidades que puedan aparecer cuando tenemos que escribir el código de una prueba.
  • Es lo suficientemente flexible para facilitar la creación de pruebas para todos los escenarios que necesitemos (no sólo para los más sencillos).
  • Incorpora atajos que hagan la vida más cómoda a quienes escriben los tests. Por ejemplo, si hemos parametrizado el método que crea usuarios para que se puedan crear todos los tipos de usuarios con los que nos interesa probar, es perfectamente válida crear métodos adicionales que permitan crear determinados tipos de usuarios de forma fácil (sin tener que pasar ningún parámetro, por ejemplo) aunque eso pudiera considerarse redundante.
Al principio, crear tu framework de automatización de pruebas puede parecer una tarea complicada pero te aseguro que el beneficio es demasiado grande para ignorarla. Además, es parecido a ir al gimnasio: puede costar mucho al principio pero, cuando te acostumbras, no cuesta tanto y, finalmente, verás que lo haces con naturalidad.

Es también una excelente inversión porque, una vez hecho el esfuerzo inicial y puesto en marcha el framework, verás cómo cada vez inviertes menos tiempo en desarrollarlo y perfeccionarlo y, en cambio, pasas más tiempo usándolo y creando más y más pruebas automáticas lo que te ayuda a mejorar la calidad del sistema que estás construyendo (y, de paso, te hacer sentire mucho mejor como desarrollador y ser humano).

Comentarios finales

Aquellos que están familiarizados con temas como automatización de pruebas, TDD,  etc. puede que se hayan sentido un poco confundidos por haber dejado en el aire algunas cuestiones importantes. Como, ¿para qué tipos de pruebas es importante crear nuestro framework? ¿Sólo para pruebas de aceptación? ¿Sólo cuando estamos probando a través de la UI, como en el ejemplo que hemos visto con Selenium?

Lo cierto es que he sido deliberadamente impreciso en este aspecto. En cualquier caso, debemos crear nuestro framework para cualquier tipo de prueba que automaticemos en nuestro proyecto, no importa que sean unitarias o integradas, de aceptación, de sistema... incluso para pruebas de humo.

Siempre que estemos repitiendo el mismo código una y otra vez cada vez que creamos un test y/o que dicho código dependa de cosas que van a cambiar con frecuencia estamos recibiendo una señal de que necesitamos refactorizar nuestro código de pruebas y empezar a crear nuestro propio framework. Es algo que debemos hacer a múltiples niveles y que tenemos que hacer con naturalidad (casi como respirar).

Referencias

Mi libro favorito sobre este tema, y con el que aprendí a escribir pruebas automáticas de una forma sostenible en el tiempo (y no el lío de código espagueti en el que solía convertir mis tests), es xUnit Test Patterns de G. Meszaros. Casi diría que es una lectura obligatoria (de la que ya hablé anteriormente aquí. Si no lo has leído, puedes empezar echándole un ojo al sitio web del libro donde se puede también información muy útil.
Continue Reading

10 lecturas recomendadas si eres ingeniero de software

Hace un tiempo un amigo me pidió que le recomendara algunas lecturas apropiadas ahora que se iba a dedicar oficialmente a ejercer como arquitecto de software (¿todavía alguien quiere ser arquitecto de software?). Al final, nunca le envié la lista pese a que me pareció una pregunta de lo más interesante.
Ahí van mis diez lecturas recomendadas para cualquier ingeniero de software, en ningún orden en particular:


No es una lista exhaustiva ni son todos los libros que te recomendaría. Podría haber incluido en la lista xUnit Test Patterns: Refactoring Test Code o The Pragmatic Programmer: From Journeyman to Master. O tantos y tantos grandes libros que hay. Pero, sinceramente, creo que si llegas a leerte todos estos libros y te dedicas de forma habitual al desarrollo de software siguiendo lo que en ellos se dice, habrás alcanzado el punto en que te convendría mucho más leer cosas no relacionadas tan directamente con el desarrollo del software. Anque ése es otro tema del que hablaremos en el futuro.

Notas finales

Para que no sirva de excusa el tema del idioma, os dejo los siguientes con sus enlaces a la edición en español:


Continue Reading

Economía básica aplicada a tu carrera profesional: búscate un buen nombre

Supongamos que ves un anuncio de una empresa que busca programadores para incorporar a un proyecto que está a punto de empezar. Por lo que te cuentan, en la empresa se trabaja bien, el proyecto puede ser interesante y (¡emoción!) hasta el salario parece bueno. Te empieza a gustar la idea de trabajar allí y decides intentarlo. ¿Qué es lo peor que podrías poner en tu curriculum antes de enviarlo?

Nombre: Fulanito
Apellido: Picateclas
Programador
Y no, no tiene nada que ver con el nombre ni el apellido (bastante ridículos) y esta entrada no trata sobre cómo cambiarlos para tener mejores oportunidades en la vida (aunque, sin duda, eso también ayuda). En realidad, esto va sobre programadores.

Si alguien me preguntara qué he sido durante el tiempo que llevo trabajando le diría que muchas cosas y le hablaría de roles que he ido teniendo, entre los que nunca estaría programador. Sí, exacto, yo nunca he sido programador. Y tú tampoco (¿cómo? ¿tú sí lo has sido? no, no lo has sido, calla y sigue leyendo). ¿Por qué?

Vamos a empezar con algo fácil de entender. Una empresa puede hacer muchas cosas (normalmente el grado de estupidez de lo que hace crece de forma pareja al tamaño de la empresa) pero, fundamentalmente, todas quieren aumentar los ingresos y reducir costes. Y una consecuencia lógica de esto es que las empresas normalmente recompensan a sus trabajadores en función de cómo son capaces de hacer alguna de estas dos cosas. Sí, las empresas recompensan a los empleados que hacen que sus ingresos aumenten o que consiguen reducir costes. ¿Crees que no? ¿Te parece mal? Monta tu propia empresa y lo entenderás.

Ahora viene la parte que hace que esto tenga algo que ver con tu carrera profesional. La persona que va a leer tu currículum y decide si contratarte o no también está buscando aumentar beneficios o reducir los costes. Después de  lo que hemos visto arriba debería ser algo bastante evidente,  ¿por qué se nos olvida tantas veces? Y a la persona que te paga no le interesan las arquitecturas limpias, utilizar lenguajes de programación de moda, frameworks chulos, el software craftsmanship ni resolver problemas complejos que constituyen un reto para tu mente de ingeniero. Aumentar los ingresos. Reducir los costes. Ése es su interés y lo demás son sólo medios para conseguirlo. Así que no te sorprenda que te a ti te vea como una pieza de la que espera que, encajada dentro de su equipo, haga que algún coste se reduzca o que los ingresos aumenten.

Y tradicionalmente los ingenieros son en sí mismos centros de costes, y bastante caros, por cierto. Y cuando tú mismo eres un centro de costes muy caro para una empresa te estás poniendo una diana para que todas esas mentes maquiavélicas que tienen un MBA o creen que saben dirigir una empresa empiecen a pensar cómo quitarte de en medio. Así es como aparecen ideas tan interesantes como el outsourcing. (Por cierto, nunca he escuchado que ninguna empresa haya externalizado su departamento de ventas. ¿Empiezas a ver la diferencia?)

Si te pones la etiqueta "programador" lo que estás diciendo sobre ti es "soy un peón muy caro que pica teclas". Y creo que esa no es la mejor idea sobre ti y tu trabajo que quieres que alguien se quede, ¿verdad?. Al menos, no lo es si quieres ser valorado (o sea, ganar más dinero). En vez de eso, intenta describir cómo has ayudado a las empresas en las que has estado anteriormente a reducir costes o aumentar los ingreso. Y si no has tenido oportunidad de hacerlo aún, busca una descripción que sugiera que eres capaz de hacerlo en el nuevo puesto que ocupes. 

Analista es mejor descripción que programador (y, piénsalo, seguro que has estado haciendo un montón de análisis hasta ahora aunque nadie te haya llamado así). Desarrollador es mejor que programador (si encima has desarrollado la solución que ha hecho ganar un montón de dinero a esa empresa en la que trabajaste, dilo). Arquitecto es mejor que programador. Creo que lo único peor que programador es progamador Java (además de ser un coste, tienes un ámbito de trabajo limitado).

A lo mejor ha sido algo que no has tenido en cuenta. ¿Te ha ido bastante bien hasta ahora sin prestarle atención? Sencillamente has tenido suerte de que alguien te haya estado viendo como un activo en lugar de como un coste (aunque si ignorabas esto, seguramente tú no hayas puesto mucho de tu parte y podrías haber ganado mucho más dinero si lo hubieras hecho).

Creo que inconscientemente casi todos saben esto. Al fin y al cabo, muchos queríamos promocionar rápidamente a categorías como analista dentro de las consultoras porque, de alguna forma, pensábamos que era mejor. ¿Mejor trabajo? ¿Más prestigio? ¿Quién sabe? Definitivamente, más dinero, sí. Pero ahora ya no debería preocuparte tanto siempre que sepas explicar lo realmente importante.

Grábalo en tu mente: reducir costes y aumentar ingresos. Eso es lo que le interesa a quien te paga así que es mejor que también te interese a ti. Y que seas capaz de reflejar ese interés.
Continue Reading

Siempre habrá un arquitecto


Os presento una situación bastante corriente: hemos conseguido un contrato para el mantenimiento de una aplicación web Java, con almacenamiento en una base de datos Oracle, donde se hace un amplio uso de procedimientos almacenados, y un carga fuerte de JavaScript en la capa de presentación. Se trata de un proyecto largo (más de un año) para un equipo pequeño (digamos que el presupuesto no es muy amplio). Hacemos algunos cálculos, pensamos en los escenarios más probables que nos vamos a encontrar y llegamos a la conclusión de que vamos a dimensionar el equipo con tres presonas.

Tenemos ahora que pensar en quiénes formarán parte del equipo. ¿Sabéis una cosa? Lo más probable es que la inmensa mayoría de los responsables de proyecto o, al menos, los que suelen completar sus proyectos con éxito, decidirán tener en su equipo a:

* Un desarrollador Java
* Un experto en bases de datos Oracle
* Un programador web con amplios conocimientos de JavaScript

Parece una decisión bastante lógica, ¿no? Pues bien, no es más que la inversión de una regla bastante antigua aunque, a veces, desconocida en el desarrollo de software: la ley de Conway.

Cualquier organización que afronte el proceso de diseñar un sistema de información diseñará uno que copie las estructuras de comunicación de la organización.

O, dicho de otra forma:

La arquitectura de un sistema refleja la estructura del equipo que la diseñó.

Junta a un desarrollador Java con un programador JavaScript y un experto Oracle y tendrás una aplicación en tres capas, posiblemente con procedimientos almacenados en la base de datos y una interfaz rica en el navegador. ¿No quieres procedimientos almacenados? Quita al experto en Oracle del equipo. Organiza un equipo de tres desarrolladores para construir un compilador y tendrás un compilador en tres fases. Estos diseños serán utilizados incluso aunque no sean los más apropiados para el sistema que queremos construir.

Las decisiones sobre la arquitectura del sistema se empiezan a tomar incluso antes de que nadie haya dibujado el primer diagrama ni escrito la primera línea de código: las tomamos cuando decidimos el equipo que queremos para nuestro proyecto.

Se toman cuando menos información tenemos sobre el sistema (justo al principio del proyecto) y las toman las personas que menos conocimientos tienen para hacerlo (los jefes de proyecto).

¿Cómo podemos evitar esto?


En primer lugar, intenta empezar el proyecto con el equipo más pequeño posible. Ten en cuenta que cada persona que añadas al equipo del proyecto supone una carga más en la arquitectura del sistema. Y demasiada arquitectura puede ser incluso más perjudicial para el resutado que tener muy poca arquitectura. Formando un equipo pequeño al principio, estarás tomando las mínimas decisiones sobre la arquitectura final del proyecto.

En segundo lugar, busca los perfiles más generalistas a la hora de formar el equipo en vez de meter a personas con una única (o un reducido número de) habilidades. En este caso, es mejor ese desarrollador Java al que no le importa trabajar en la base de datos y que puede defenderse a la hora de hacer interfaces web que un gurú de Oracle o un ninja del JavaScript. Puede que, con el tiempo, llegues a necesitas esos perfiles, pero te aseguro que es mejor retrasar su incorporación hasta que la arquitectura esté completamente clara.

Como podéis ver, todos los proyectos tienen, al menos, un arquitecto. Uno que decide sobre el diseño del sistema de una forma muy peculiar: sin diagramas ni documentos de arquitectura sino asignando personas al equipo que se va a encargar de construirlo.

Mi recomendación para cualquier jefe de proyecto es que evite en la medida de lo posible convertirse en ese arquitecto. Por el bien de su proyecto.

Bibliografía



Continue Reading

Algo rápido para leer en 5*60*1000

Me ha quedado un título un poco friki pero a quienes programan en Java y han tenido que tratar alguna vez con unidades de tiempo les sonará. Y es que muchas veces seguimos haciendo las cosas como siempre sin fijarnos si existen ya alternativas que nos puedan ayudar un poco a que nuestro trabajo sea más sencillo.
Desde hace tiempo está disponible en el juego de librerías estándar de Java, concretamente desde la versión 1.5, la clase TimeUnit. Esta clase representa intervalos de tiempo con distinta granularidad y provee de métodos de conversión entre diferentes unidades.
De esta forma, si queremos crear una constante con el valor de cinco minutos en milisegundos para poder utilizarla en nuestra clase, podríamos hacer:

Además del método convert, podemos usar métodos propios de cada una de las unidades de tiempo para convertirlas a otra unidad con un código más fácilmente legible:

La segunda características más destacable de TimeUnit es poder para la ejecución del hilo actual de una forma sencilla. Así, frente al uso del método sleep de la clase Thread podemos hacer:

Como vemos, TimeUnit no hace nada nuevo que no pudiera hacerse antes. Sencillamente, lo hace de un forma más expresiva que hace que nuestro código transmita mejor su intención (para qué está hecho) al hacerlo más limpio y directo.
Continue Reading