S.O.L.I.D.

Un artículo de Rafa G. Blanes

¿Cómo conseguir que un proyecto software sea mantenible y que se pueda modificar con sencillez?

Esta pregunta tiene muchas respuestas, según la naturaleza del mismo proyecto, claro. No obstante, existen una serie de principios que, de seguirlos, la vida (y estructura) de un proyecto puede cambiar de una forma radical.

Demasiado a menudo veo proyectos, sin ir más lejos, muchos repositorios en GitHub, en los que es imposible discernir cómo se han implementado los principios de diseño más básicos en ingeniería del software, y no es que esté mal, claro, todo depende de la vida que vaya a tener ese proyecto.

No hace falta que conozcamos al detalle todos y cada uno de los patrones de diseño que más o menos son conocidos y aceptados por la industria y muy bien explicados en libros clásicos como este, pero, desde luego, si lo que queremos es que nuestro proyecto pueda ser mejorado continuamente sin demasiada dificultad, entonces sí que hay que seguir unas mínimas normas de desarrollo.

Aquí es donde entran los principios S.O.L.I.D.

Si tu proyecto tienen clases o funciones extraordinariamente largas del orden de cientos de líneas de código, si hay métodos con innumerables if o con muchos bloques anidados, o hay métodos o funciones con muchos parámetros, entonces tu aplicación camina hacia una solución espagueti difícil de mantener y evolucionar. 

Si es así, sigue leyendo...

¿Qué diferencia un principio de diseño de un patrón?

Mientras que un patrón es una solución técnica y clara a un problema particular, un principio es una guía que debe cumplir tu código a nivel de diseño. Los mejores frameworks de desarrollo son los que mejor cumplen con estos principios, y estoy pensando sin ir más lejos en Seneca, un framework que me gusta mucho para construir microservicios en NodeJS, aunque hay muchos otros ejemplos.

A grandes rasgos, los principios S.O.L.I.D. permiten diseñar una aplicación de un modo que:

  • Tenga un alto grado de desacoplamiento entre sus partes funcionales.
  • Se pueda extender en funcionalidad con sencillez, sin grandes cambios en la aplicación.
  • Es testeable.
  • Mejora la reusabilidad del código.

Aunque su implementación es más natural en programación orientada a objetos, se pueden aplicar en lenguajes funcionales. En el contexto de este artículo, hablaremos de entidades software para referirnos a clases, módulos, métodos, funciones, etc.

A continuación indico un breve resumen de estos cinco principios, junto con una técnica igualmente relevante, y que es la inversión de control.

Single-responsability principle (SRP o principio de responsabilidad única)

Una clase se debe dedicar a resolver un único problema, esto es, no se debe implementar una clase cuya funcionalidad mezcle ámbitos de la aplicación diferentes.

Si se utiliza bien este principio, entonces se podrán generar clases más o menos pequeñas y, por tanto, más reutilizables y fáciles de modificar y probar.

En el contexto de este principio, se entiende por responsabilidad como la razón para cambiar, esto es, si en una clase existe más de un motivo por el que puede cambiar, entonces es que no sigue este principo.

Quizá no sea simple de ver al comienzo, pero en este snippet de código indico un ejemplo sencillo (y algo académico), escrito en javascript para NodeJS.

Open-closed principel (OCP o principio abierto/cerrado)

Según este principio, una clase se debe diseñar de modo que sea fácilmente extendible sin modificar su funcionalidad actual. Fácil de describir, pero para implementarlo hace falta cierto grado de abstracción. Según la característica de la aplicación en la que se esté trabajando este principio se puede aplicar o no.

No niego que este principio sea algo sutil, pero si se entiende bien, en realidad lo que está indicando es que la entidad software debe abstraer al máximo el core de la funcionalidad que implementa.

Lo que persigue conseguir OCP es extender la funcionalidad de una entidad software sin modificar su código, y para ello, como digo, hace falta cierta abstracción.

En este otro snippet de código indico cómo aplicar este principio en el clásico ejemplo de validación de entradas de usuario.

Liskov substitution principle (LSP o principio de sustitución de Liskov)

Este es quizá el principio más sutil de entender, pero igualmente importante. Con LSP lo que se pretende es asegurarnos de que se implementa correctamente la herencia entre clases.

Entendemos como clase hija o extendida aquella que deriva de otra.

La definición es un tanto académica, de modo que haré una descripción más sencilla; lo que viene a decir LSP es que si un programa utiliza una clase, entonces deberá seguir siendo válido (= funcionará bien) si se la sustituye por una nueva clase que derive de ella. Si un programa utiliza la clase P, y en su lugar sustituimos P por O (donde O deriva de P), entonces, el programa deberá seguir funcionando correctamente.

Y aquí la definición de la autora (Bárbara Kiskov): si o1 es un objeto de tipo S, y o2 es un objeto de tipo T, si el comportamiento de un programa (función, método, etc.) basado en objetos T, usando o1 y o2 no cambia, entonces necesariamente S es un subtipo de T.

Puff..., en realidad está indicando cómo hay que implementar la herencia entre clases.

Sencillo de decir, no tan obvio de saber implementar, aunque con este principio nos garantizamos que la herencia se hace bien y además las clases base (padre) y sus derivadas (hijas) son más fácilmente testeables.

LSP está muy relacionado con el principio OCP, ya que establece también que una clase hija debe extender el comportamiento de la padre, no modificarlo.

Para implementar LSP hace falta un grado alto de abstracción de la solución que pretendemos resolver; si se rompe este principio en nuestro diseño, entonces es que no se está abstrayendo lo suficientemente bien.

Aquí dejo un ejemplo ilustrativo que cumple LSP.

Por último, una forma de forzar que se cumple este principio es usar diseño por contratos, en el que se especifican interfaces y las clases base las implementan.

Interface segregation principle (ISP o principio de segregación de interfaz)

Este principio pretende evitar la creación de interfaces extensas con métodos que no tienen nada que ver unos con otros, de modo que una clase que derive de ellas tenga que dejar sin implementar algunos de esos métodos porque no los necesita. 

Con ISP lo que se sugiere es dividir esta interfaz (fat interface) en interfaces más pequeñas y coherentes, de modo que las clases que deriven de ellas lo hagan porque necesitan todos y cada uno de sus métodos.

La consecuencia de este principio es que necesariamente las interfaces que se definen en el proyecto tienen un alto grado de abstracción y de granularidad.

En este otro ejemplo, se puede ver cómo aplicar este principio.

Dependency inversion principle (DIP o principio de inversion de dependencias)

Este principo es, quizá, el más popular de todos y el más fácil de implementar y en ocasiones se le confunde con la inyección de dependencias.

Lo que viene a decir es que nuestro código (en un módulo, una clase, etc.) debe depender de abstracciones, no de instancias concretas de las mismas.

Entendemos por abstracción la definición que debe implementar una clase concreta, lo que viene a ser lo mismo que clases abstractas e interfaces en OOP.

Con DIP conseguimos que el código esté más desacoplado y que las unidades de funcionalidad sean más pequeñas (y, por tanto, reutilizables y testeables).

Inversion of control (IoC o inversión de control)

La inversión de control no está dentro del conjunto S.O.L.I.D pero sin embargo está en la línea de conseguir código más desacoplado y mantenible, sin embargo; no es aplicable a cualquier tipo de proyectos.

Tradicionalmente, podemos decir muy simplificadamente que la implementación de un programa consiste en la ejecución de una llamada a un método o función tras otro. Sin embargo, a medida que este programa crece, es necesario estructurar la aplicación en módulos, entran en juego diversas arquitecturas, etc.

Es decir, el flujo habitual de una aplicación consiste en llamar a procedimientos (funciones o métodos) de una biblioteca.

Se implementa la inversión de control cuando se invierte ese flujo, esto es, cuando es la misma biblioteca la que llama al código del usuario que es el que implementa realmente la aplicación.

Parece enrevesado, pero hay multitud de ejemplos que implementan inversión de control y, además, es el enfoque natural cuando necesitamos que nuestra aplicación sea fácilmente extensible. Sin embargo, insisto en decir que no siempre es necesario implementar este mecanismo, depende de la naturaleza del proyecto.

IoC y DIP (inversión de control e inversión de dependencias) suelen ir de la mano, ya que es sencillo implementar IoC utilizando DIY:

Best Practices

Artículo disponible en epub y pdf:

Cómo usar repositorios de datosCómo usar repositorios de datos epub

 

 

Me llama mucho la atención cómo a lo largo de los años puedo haber trabajado en aplicaciones con .NET en C#, con javascript utilizando Angular y NodeJS, y también con PHP cuando estaba más centrado a hacer módulos en Drupal, y cómo con todos esos lenguajes con sus respectivos entornos de desarrollo se pueden aplicar las mismas buenas prácticas.

Hay veces que éstas se refieren a procedimientos de trabajo o al modo de estructurar un proyecto, otras hacen referencia a aplicar patrones de diseño e incluso antipatrones para detectar precisamente lo que no se hace bien. En otras ocasiones, son sencillamente prácticas habituales que hace la mayoría de la gente que usa una tecnología en particular (de ahí, por ejemplo, los proyectos de scaffolding).

Un buen proyecto software debe oler a buenas prácticas, de principio a fin, desde la arquitectura del mismo hasta la evolución de las funcionalidades que se le van añadiendo poco a poco.

Nada peor que una aplicación, proyecto o sistema en el que no se pueda ver claramente cómo están implementadas esas buenas prácticas.

Y es que además tienen algo en común: la mayoría de ellas son fáciles de implementar.

Y como me gusta escribir... pues voy a ir hablando de ellas, de aquellas con las que yo directamente he visto que tienen más impacto en la mejora de la calidad de un proyecto software.

Un aviso a navegantes: en muchas ocasiones los beneficios de estas recomendaciones no se ven claramente, ni siquiera en el corto plazo, y su impacto positivo a medio y largo plazo es más una cuestión sutil que sólo se aprende cuando has cometido todos los errores posibles y entonces se hace la luz y dices algo como, "ahora sí que lo entiendo".

Para mí la implementación de repositorios de datos es una de las prácticas más útiles en cualquier proyecto software.

El principio general de implementar un repositorio de datos vendría a ser el siguiente:

Todos los accesos a los datos que utiliza una aplicación (vengan de bases de datos, entidades de datos del mundo cloud, datos en caché, etc.) deben realizarse desde un único punto.

Dependiendo de la tecnología y del tipo de aplicación que se esté desarrollando, ese único punto se entiende por un módulo con sus clases, una librería, una API, un servicio REST, etc. Lo importante es que todos esos accesos estén centralizados y bien localizados en el proyecto.

¿Qué beneficios se obtiene de esto? Son muchos, muchísimos, algunos de ellos no fáciles de ver en un principio.

Por resumir, centralizando el acceso a los datos conseguimos:

  • Cumplimos con el principio SoC, (separation of concerns separación se intereses), básico en el desarrollo ágil. De este modo tenemos una parte muy concreta y localizada del proyecto especializada en acceder a los datos que utiliza.
  • Se evitan duplicar código repitiendo los mismos accesos a los mismos datos a lo largo de la solución. En lugar de ejecutar algo como un "select * from dbo.users where userId = 20" cada vez que se necesita la información de un usuario, tenemos algo mucho más general como "data.getUserById(20)" (esto es un sencillo ejemplo en pseudocódigo, eh!).
  • Se simplifica la solución y se reduce el número de líneas de código. Al estar encapsulado el acceso a los datos en métodos muy concretos, no necesitamos ensuciar u ofuscar la lógica de negocio de la aplicación con detalles de acceso a los datos (tales como abrir una conexión a la base de datos, cerrarla, etc.), y ni hablar cuando esos accesos con más complejos y necesitamos transacciones o joins anidados.
  • Es más fácil ejecutar un análisis sencillo de profiling: tan sólo repasando en el objeto repositorio a qué datos se accede y cómo, podemos determinar qué índices hacen falta, en el caso de bases de datos relacionales e implementar tests de rendimiento que se puedan ejecutar continuamente.
  • Conseguimos desacoplar la solución de cómo ésta accede a los datos. ¿Y si mañana cambiamos de motor de base de datos? ¿Y si por escalabilidad se decide distribuir los datos en diversas bases de datos incluso con diversas tecnologías? Nada peor que empañar todas las partes de la solución con los detalles de acceso a los datos. Puesto que todo está encapsulado en el repositorio, tan sólo hay que tocar éste. El resto de la aplicación, ni se entera del cambio.
  • Con la implementación del repositorio, al estar éste desacoplado, se pueden crear tests específicos, unitarios y de rendimiento.
  • Es trivial también crear mocks. ¿Qué pasará cuando la tabla "usuarios" tenga cien mil registros? ¿Aguantará el front-end? Con un repositorio de datos es fácil simular diversas situaciones en previsión de problemas de escalabilidad pero sobre todo, que esas situaciones pueden estar respaldadas por pruebas.
  • Al estar todos los accesos centralizados, es más fácil para la persona encargada del desarrollo de la base de datos y su acceso trabajar en esa área especializada.
  • Al no estar dispersos por la solución todos los accesos al repositorio de datos, las migraciones del mismo no terminan en pesadilla...

Hace un tiempo se hablaba en ciertas tecnologías de la capa DAL (data access layer), que vendría a ser el mismo concepto pero más ligado a bases de datos relacionales.

Si se utiliza algún tipo de ORM que crea objetos alrededor de las entidades de datos, ¿deberían esos objetos viajar a lo largo de la lógica de negocio? Esta pregunta me la encuentro recurrentemente, y mi opinión es que no, puesto que de ese modo la aplicacíon tiene una dependencia clara de la tecnología ORM utilizada. Los objetos que se deberían distribuir a lo largo de la lógica de negocio serán similares pero no ligados a la tecnología ORM usada (son los que se llaman los data transfers objects o DTOs).

A ver, al final hay que aplicar el sentido común (con un poco de experiencia y hasta de suerte...): si trabajamos en un proyecto cuyo ciclo de vida es muy claro y sabemos que se terminará y que no va a evolucionar, pues nos podemos tomar cierto tipo de licencias, pero si tenemos entre manos un producto o proyecto que crecerá y evolucionará en funcionalidad a lo largo de muchos años, entonces sí hay que cuidar mucho este tipo de decisiones y dependencias, así como aplicar con mayor rigor aún todas las buenas prácticas posibles.

Sí, ya sé, es que a veces no hay tiempo, el proyecto viene muy cerrado en horas, etc. De acuerdo, razón de más para aplicar este tipo de buenas prácticas desde el principio, porque sólo así se podrá avanzar en el proyecto con productividad y mayor rapidez.

Todo esto es más fácil de implementar que de describir; sin embargo, un buen proyecto software que necesita de cualquier mecanismo para persistir datos, tiene que acceder a ellos a través de un objeto / módulo / librería que implemente esta buena práctica.

Algunos enlaces de interés:

The Repository Pattern

Repository Pattern Done Right

No me gusta repetir trabajo ni tareas pesadas que se hacen una y otra vez y que de algún modo se pueden automatizar o al menos indicar lo pasos a realizar para que la siguiente vez no pierdas tanto el tiempo en recordar cómo se hacía ésto o aquéllo. Si te encuentras en esta situación a menudo, que sepas que puedes evitarlo.

Si es verdad que debemos poner el foco en aquello que aporta valor, en las tareas verdaderamente creativas y productivas, entonces tenemos que encontrar la forma de no tener que reinventar la rueda continuamente. No estoy hablando de tareas que hay que hacer sí o sí (como un parte de trabajo) y hablo en sentido general, no sólo desde el punto de vista del desarrollo de software, sino de hacer todo lo posible para que estas tareas ocupen el menor tiempo posible y, sobre todo, no tengamos que pensar reiteradamente cómo se hacían.

Voy a poner varios ejemplos:

  • El proceso de publicar una nueva noticia en la web, ya sabes, crear el contenido, fijar bien la url, revisar el contenido, comprobar la estructura del mismo, revisar faltas de ortografía o de expresión, pedir la aprobación del mismo si es necesario, etc. Son varios pasos, sí, pero si este trabajo lo haces una vez cada varios meses, terminar olvidándolo y preguntándote ¿cómo había que poner el formato de las url?
  • Si mantienes varias webs, como es mi caso, y además todas con Drupal, y dado que no las actualizo continuamente (actualizaciones de módulos, mejoras de diseño, etc.), puedes encontrarte con que cada vez que vas a actualizar una, tienes que volver a investigar cómo se hacía esto o aquello, como por ejemplo, cómo y dónde guardar una copia de seguridad, cómo etiquetar la nueva versión de la web en GIT o similar, cómo comprobar que la nueva actualización no ha roto nada, cómo desplegar para que no te quede ningún cabo suelto, comprobar la seguridad, etc.
  • Llevo dos años usando la herramienta Scrivener para la creación de libros. Su proceso de compilación genera un fichero en format eBook, pdf, docx, etc. Sin embargo, después de generar el fichero tienes que comprobar varias cosas según tu flujo de trabajo y el propósito que tengas. Por ejemplo, si generas un eBook para Kindle, tienes (o deberías), revisar que el epub pasa las pruebas de validación con una herramienta como epubcheck, después tienes (o deberías) abrirlo con la herramienta Kindle Previewer y revisar página a página, y no digamos le proceso de publicación en Amazon.

En fin, son trabajos que realizas que requieren de varios pasos para su realización. Salvo que tengas una memoria prodigiosa, cada vez que los vayas a enfrentar algo se te quedará en el tintero.

¿Resultado? No se terminará del todo bien y se tardará más en realizarlo, aparte de lo tedioso que resulta tener que investigar algo porque lo has olvidado... Y si te fijas bien, parte de tu trabajo lo pasas realizando tareas que bien podrían estar bajo el paraguas de un procedimiento.

Llevo años procedimentando este tipo de tareas e intentando establecer una cultura y hábito en procedimentar en los ámbitos laborales en lo que me muevo.

Crear un procedimiento no es más, en esencia, que establecer en una simple guía los pasos para realizar una tarea. No estoy hablando de esas compañías que para hacerlo todo tienen un repositorio de procedimientos o normas más largos que El Señor de los Anillos, sino de sencillas guías que te puedes hacer tú mismo para evitar tener que recordar continuamente las mismas cosas y así hacerlas con mayor rapidez y seguridad.

Y esto se aplica a cualquier ámbito personal o profesional. ¿Qué sería si no la gestión de la configuración un proyecto software sin un procedimiento entendido por todos los desarrolladores?

Si lo tuyo es el marketing, es posible que tengas establecido cómo documentar tus próximas acciones comerciales así como sus resultados (= procedimientos).

Es sorprendente la cantidad de tiempo que se gana cuando tienes guías sencillas y prácticas para realizar tareas más o menos complejas.

Pero por otra parte, ¿cómo si no muchas personas de una organización, por pequeña o grande que sea, podrían trabajar sin seguir ciertas normas o esquemas de trabajo (= procedimientos)?

Lamentablemente en muchas organizaciones, se terminan aprendiendo las cosas no porque las leas en un documento de bienvenida o algo así, sino porque a algún compañero o compañera le endosan la tarea de explicarte cómo se hacen las cosas (por lo cual también es una pérdida de productividad para él).

Estos son algunos de mis procedimientos que más uso:

  • Generación y revisión en la generación de epubs y pdf de cara a su publicación.
  • Procedimiento de crear contenidos (como este) en las distintas webs así como su difusión.
  • Uso de los repositorios de código para los proyectos en los que trabajo.
  • Actualización y mantenimiento de mis webs con Drupal.
  • Desarrollo de nuevas ideas y propuestas.
  • etc.

Si en tu organización no hay procedimientos de trabajo de algún tipo, chungo. Y si en tu día a día, en la forma de organizarte o de afrontar los distintos proyectos en los que trabajas, pero aún.

Estoy convencido de que las personas más exitosas y las organizaciones que funcionan mejor tienen una fuerte cultura de establecer y usar procedimientos de trabajo ágiles, claro, porque su abuso puede suponer al final todo lo contrario de lo que se desea obtener.

Productivity

Me encanta prototipar, probar conceptos, ideas y ponerlas en marcha sencillamente para validarlas o por profundizar en tecnologías que me gustan. Esto creo que es uno de los vicios de muchos desarrolladores, la necesidad sana de aprender cómo funciona tal framework, cómo desplegar con Heroku o cómo funciona el mecanismo de plugins de cualquier software, por poner algunos ejemplos.

Ahora bien, si queremos probar una idea no podemos perdernos en los detalles y desviarnos así del asunto relevante que queremos conseguir. No obstante, veo lo fácil que es perderse en esos detalles y no centrarse en lo importante realmente, lo que aporta valor al proyecto, producto o idea que estamos probando.

De ahí el conocido mantra en nuestra profesión de poner el foco completamente en la funcionalidad particular de un proyecto de modo que la general debe ser cubierta por el entorno a usar en forma de librerías reutilizables, plugins, extensiones, plantillas, etc, cualquier cosa, en definitiva, reutilizable.

Es como si se nos planteara realizar una plataforma para la gestión empresarial y decidiéramos hacerlo todo desde cero y no contar con algunos de los productos que ya existen como base en el mercado. Sería, cuanto menos, inviable en términos de tiempo y esfuerzo.

Sin embargo no siempre pensamos en adquirir alguna solución de tipo comercial ya existente y comenzar a partir de ella y, de nuevo, caemos en el error de reinvertar la rueda una y otra vez, sin evaluar realmente el coste de esto. Una cosa es investigar e implementar por simple gusto o curiosidad en tu tiempo libre y otra muy distinta justificar decisiones en tu compañía que supongan costes extra que se pueden evitar.

Vicio, mala práctica o pura ignorancia, este es uno de los déficits habituales de muchos desarrolladores que, además, es de camino inverso: no saber seleccionar componentes del mercado o de código abierto y también no saber desacoplar lo suficiente lo que se hace para reutilizarlo en un futuro.

No sólo estoy pensando en grandes productos comerciales que hay que configurar y adaptar a necesidades específicas, sino en pequeños componentes desde plugins gráficos, templates, engines de búsqueda, etc.

Hoy día existe un auténtico mercado en el que se pueden adquirir a un precio que resulta irrelevante para el esfuerzo que tendríamos que dedicar a hacer algo similar (y seguramente, no con la misma calidad por no ser tan expertos en ese tema en concreto).

Sí, para casi todo existe algo open source y de excelente calidad, por lo que también es una alternativa a considerar aunque no podemos cerrar puertas y usar algo libre (y gratuito) porque sí, cuando podemos encontrar cosas por pocos euros de mejor calidad. ¿Qué son unos cuantos euros cuando te pueden ahorrar decenas de horas de trabajo?

Recientemente hemos adquirido un tema para un landing page de un nuevo producto que hemos lanzado: necesitábamos una página de inicio muy atractiva, sencilla, con pocos contenidos y que resultara moderna, así que decidimos usar parallax que tanto se ve en los últimos dos años. Algunos buenos ejemplos de este tipo efectos aquí.

Moda o no, la realidad es que implementar una página de inicio así y que además sea responsiva tiene muchísimo trabajo y hay que ser un buen experto en HTML5 y CSS3 para realizar una buena implementación, sin hablar de los aspectos estéticos y de diseño. Además, no me quiero ni imaginar el esfuerzo de asegurar que la implementación se lleva bien con todos (o la mayoría) de navegadores y dispositivos... Imposible de asegurar a no ser que dispongas de muchísimo tiempo para probarlo.

¿Por qué centrarte en ese desarrollo (que no es el core de tu sistema) cuando puedes adquirir por poco dinero una solución profesional y ya implementada, usada por cientos de usuarios y extraordinariamente depurada? ¿No es como contratar a alguien muy bueno en su nicho por poco dinero y tenerlo a tu disposición? Muchos fracasamos en algunos proyectos por no poder vencer esa necesidad (un poco egocéntrica) de querer hacerlo todo uno mismo, no saber delegar (adquirir componentes externos es una forma de delegación) y no saber centrarnos realmente en lo importante del proyecto.

Decidimos usar un paquete desde Envato Market que nos costó.....8 dólares, cantidad de dinero que además te da la oportunidad de preguntar al mismo autor y ver multitud de ejemplos. Es decir, unos cuantos cafés por algo que al menos yo tardaría en implementar semanas y de lejos tan bien como un experto en esas técnicas.

¿No es eso una muestra de productividad? Otra cosa es personalizar el componente lo suficiente para que todos tus trabajos no parezcan iguales.

Por 8 dólares tienes a tu disposición un desarrollador que ha trabajado mucho en ese paquete que estás comprando y que precisamente por estar más especializado en esa área lo va a realizar seguramente mejor que tú. El desarrollador gana porque su componente se vende cientos o miles de veces.

El concepto de reutilización está en las entrañas y en la esencia del software, pero no siempre lo tenemos 100% presente.

A ver, es que me he encontrado con ciertas actitudes de desprecio cuando uno comenta que recurre a Bootstrap o cualquier otra cosa como si eso fuese poco profesional: lo profesional es aprovechar tus recursos (tiempo) lo más productivamente posible. La última palabra la tiene siempre el cliente, que es el que dice si le gusta o no y realmente lo no profesional es suponer un coste de un mes de trabajo a tu empresa cuando ese mes se puede reducir a unas pocas horas por invertir unos cuantos euros en un componente ya existente.

Ahora mismo existe un gran ecosistema de componentes de modo que muchos desarrolladores que trabajan en proyectos, más que desarrollar software, pasan la mayor parte de su tiempo integrando distintas piezas de software reutilizables para conseguir la funcionalidad particular que le pide el cliente, lo cual no deja de ser un aspecto interesante de nuestra profesión.

Yo no veo que esto sea malo ni muchísimo menos, sino que es una forma maravillosa de no realizar trabajo doble y además puedes participar sin ningún problema en todo ese mercado de venta de componentes y librerías ofreciendo tus propios desarrollos: plugins, themes, etc., lo cual, además, te puede dar cierto prestigio como profesional.

Wunderlist sampleComo muchas veces he cometado en este blog, trabajar bien, generar un resultado de calidad en tiempo y sin necesidad de que los equipos de trabajo estén estresados continuamente, es más una cuestión de organización que de excesos de horas delante del ordenador. Cuando las horas extras dejan de ser puntuales y pasan a ser crónicas, evidencian un problema grave de planificación y productividad, no hay más.

No obstante, veo continuamente cómo se sigue abusando del correo electrónico para gestionar tareas con una lista interminable de replies y cómo se usa el Outlook o el Gmail como almacén documental; también veo a menudo cómo se usa el excel como herramienta para planificar el trabajo...

Digamos que aunque hablamos mucho de la sociedad del conocimiento y que todos tenemos acceso a ordenadores, portátiles, smartphones, etc., ciertas cosas se siguen haciendo igual que en los noventa, es decir, que apenas se sale del correo electrónico y el paquete ofimático de turno.

A estas alturas, gestionar las tareas de un equipo usando exclusivamente el correo y hojas en excel, no es que esté obsoleto, sino que revela una falta de productividad o una inercia total de la gente que los usa abusivamente sin querer encontrar opciones más apropiadas y más ágiles. Lo siento si tu jefe o manager se organiza así, toda su desorganización terminará afectándote de un modo u otro.

Hay que sustituir el uso (abuso) de esas herramientas por otras cuyo propósito es el de gestionar proyectos o tareas y además tienes que definir tus propios flujos de trabajo. El uso de cualquier herramienta para gestionar tareas viene después de que tengas claro cómo organizas tu trabajo (flujos de trabajo).

La cuestión no es en realidad qué tareas hacer en cada momento o en los próximos días o semanas, sino de qué modo gestionas tu tiempo.

No, el correo electrónico no es:

  • Un repositorio de documentos para su control de versiones.
  • Un sitio donde recordarte lo que tienes que hacer.
  • De ningún modo es la herramienta para generar informes que algún día tendrán que ser rescatadoss
  • No es una herramienta para sustituir sesiones de brainstorming a la hora de definir detalles de un proyecto

Del mismo modo, en cuanto al excel:

  • No es para gestionar tareas entre los miembros de un mismo equipo.
  • Mucho menos para gestionar las fases de un proyecto.
  • Es un desastre cuando varias personas intentan editar a la vez el mismo documento...

Sí, parece evidente que abusamos de esas herramientas y en realidad son el estándar de facto con las que la mayoría de la gente se organiza, me temo.

Aunque mejor eso que nada: la gente más estresada y víctima de la ansiedad en su trabajo y en el día a día suelen ser las que intentan llevarlo todo “en la cabeza”, las que no se paran un segundo para priorizar porque perciben que todo es urgente, las que desconfían de pararse un segundo y preguntarse a sí mismas ¿cómo puedo gestionar mi tiempo y mi trabajo algo mejor?.

La cabeza es muy mala aliada para gestionar la sobrecarga de trabajo, porque:

Tendemos a magnificar en la mente la mayoría de las tareas, sobre todo aquellas sobre las que aún no hemos reflexionado. Cuántas veces le he estado dando vueltas a la cabeza a algún asunto que al final se ha resuelto en menos de una hora...

La mente no prioriza bien: para priorizar hay que pararse sosegadamente un rato y ver pormenorizadamente todos los asunto que tienes abiertos y los compromisos (si es que no se te olvida ninguno).

Del mismo modo, la mente no es una agenda, tendemos a olvidar cosas.

El intentar llevarlo todo con la cabeza es la principal fuente de estrés, ansiedad y al final de la cadena falta de productividad. Por tanto, hay que “sacar” de ella todas las tareas que tenemos pendientes.

Desde hace años utilizo herramientas que te permitan gestionar fácilmente las tareas que tengo pendientes y uso esas mismas herramientas para planificar mi trabajo; lo mejor de todo es que existen varias opciones más o menos populares que además son gratuitas con funcionalidad más que suficiente.

Es absolutamente liberador cuando no tienes que rebuscar en la mente qué hacer en cada momento (con la sospecha de olvidar algo importante) y recurrir tranquilamente a tu gestor de tareas.

Existen muchas opciones, yo particularmente llevo mucho tiempo usando Wunderlist (incluso para las cosas de mis hijas y de casa).

Trello logoDesde no hace mucho he comenzado a usar Trello, más orientado a la gestión de proyectos en el que participan varias personas y me ha sorprendido gratamente. Aunque, como digo, existen muchas opciones más:

¿Qué conseguimos parándonos un momento y organizar nuestras tareas (o las del equipo que gestionamos) en cualquiera de esas herramientas? Lo primero que consigues es que percibes que tienes el trabajo “bajo control” y que no se te escapa nada, al menos nada importante.

Yo siempre digo que malo cuando no sabes en qué vas a estar trabajando en las próximas semanas. El buen trabajo no se improvisa, se planifica.

Por otra parte, visualizar de un modo gráfico todos tus proyectos, su estado actual y las tareas pendientes te permite encajar mejor desviaciones o asuntos imprevistos.

Es reconfortante cada vez que haces “clic” en algunas de las tareas que tienes pendiente para marcarla como completada, es como si hubieses dado un paso adelante. ¡Trabajo terminado!

El tener las tareas bien identificadas y definidas de la manera más concreta posible te permite además aprovechar esos huecos que surgen en casa o fines de semana. Si en ese momento te tienes que parar un momento para pensar qué hacer, seguramente termines haciendo algo de lo que más te apetece en ese momento, no lo más prioritario o importante. Casi nunca podemos avanzar en cualquier tipo de proyecto haciendo por capricho lo que nos viene en gana en cada momento.

Así las cosas, es mejor usar el típico excel y abusar de tu correo electrónico para gestionar tu trabajo que no usar nada, mucho mejor usar alguna herramienta web o un conjunto de ellas para gestionar, planificar y organizar tu trabajo y el de los demás, pero aún mejor, por encima de todo lo anterior, lo mejor es trabajar con tu propio workflow o flujo de trabajo bien definido.

Al final, cuando tienes una lista de tareas, estas han surgido “a partir” de un modo de trabajo particular, ese flujo de trabajo lo tienes que definir tú mismo e intentar adaptarlo para cada tipo de proyecto o circunstancia. En otras palabras, el modo en que usas cualquier gestor de tareas es una consecuencia de tener flujos de trabajo bien definidos.

El tener tu propio conjunto de workflows te permite no tener que pensar en cada momento cómo gestionar cada nuevo trabajo o tarea que te llega, sencillamente le aplicar el flujo de trabajo habitual; por poner algunos ejemplos de mi día a día:

  • Si surje una tarea que puedes terminar en menos de dos o tres minutos, la haces inmediatamente. Esto parece una tontería, pero así vacías aún más tu lista de tareas mental y la de la herramienta que uses.
  • Cada vez que identifiques una nueva tarea, al anotarla indica también cuándo debe estar terminada. Esto no es una trivialidad, sabemos que las tareas que no tienen fecha de finalización o se hacen al final o sencillamente nunca se hacen…
  • Revisas el estado de las tareas al menos dos veces al día: al inicio de tu jornada laboral y al final. Este trabajo te permitirá reordenar si es necesario la prioridad de las tareas según las circustancias y además terminas tu jornada laboral con una idea clara de lo que harás al día siguiente (así le das oportunidad al subconsciente de ir trabajando en segundo plano…)
  • Las tareas deben estar suficientemente concretadas para poder comenzarlas y terminarlas sin tener que saltar a otras cosas en medio. Nada peor para la productividad que intentar hacer varias cosas a la vez. Si una tarea te puede llevar muchas horas, divídiles en otras más concretas y pequeñas.

Esto no es más que un ejemplo, aunque lo importante es que si tenemos esa foto fija de lo que tenemos entre manos, nos liberamos de la típica sensación abrumadora de no tener tiempo para hacerlo todo, sabiendo priorizar en cada momento y sabiendo distinguir lo importante de lo urgente. Es más, seguramente no llegues nunca a tener asuntos “urgentes” porque estos eran antes importantes y se realizaron en el momento correcto.

En software ocurre que no hay una misma respuesta para cada situación, para cada proyecto en el que cambia el número de personas involucradas en el equipo, la complejidad del mismo proyecto e incluso las tecnologías usadas. No obstante, lo que sí es cierto en todos los casos es que si se deja para el final el despliegue del sistema en un entorno lo más parecido o idéntico al de producción, nos encontraremos con muchos, muchísimos problemas.

Hay que desplegar en un entorno de producción lo más parecido al del cliente final tan pronto como se tenga alguna funcionalidad terminada pero completa, aunque sea simple.

Incluso si contamos con un entorno de integración fantástico y una gestión de la configuración para él magnífica, esto no nos garantizará que todo funcionará de maravilla en producción. Se podría escribir un libro entero sobre este tema, dependiendo de la naturaleza del proyecto e incluso del equipo, aunque me temo que casi nunca se tiene en cuenta el despliegue final hasta... ¡el final!, momento en el que nos podemos llevar muchas sorpresas.

Si no lo hacemos así, si no pensamos en el despliegue desde el principio, la brecha que nos encontraremos al final del proyecto cuando creemos que lo tenemos todo casi terminado, no hará más que agrandarse.

Un caso extremo de esto es cuando desarrollamos el proyecto en la misma máquina del cliente final..., llegando al caso de que fuera de él el proyecto requiere de una auténtica reingeniería (...) Esto, en software, es casi un pecado capital, al igual que probar contra bases de datos en producción o dispositivos que están en clientes finales. Vale sí, si hay que hacerlo, pues se hace cuando no haya más remedio, pero al menos tengamos en cuenta las consecuencias que puede tener.

Estas son las razones por las que opino que hay que publicar el proyecto en un entorno, llamémosle de pre-producción, lo antes posible.

Lo habitual es que un entorno de producción (= entorno que usará finalmente el cliente para usar nuestro proyecto) no tenga nada que ver con el entorno de desarrollo local que solemos usar. Eso es trivial y hasta frívolo decirlo, pero hasta que no sufrimos las consecuencias no nos damos cuenta de que muchas veces los SDKs que tienes en local y que no estarán en producción resulta que son imprescindibles, la configuración de una base de datos en producción puede que no sea la misma que la que tú usas en desarrollo (o incluso puede que sea hasta una versión diferente), la configuración del servidor o destino final puede que no tenga nada que ver con el entorno de los desarrolladores, sólo por poner unos ejemplos.

Es también corriente no detectar ciertos cuellos de botella porque en tu máquina local, al estar todo en el mismo equipo, todo va como la seda, con conexiones locales a la base de datos, si es que usas alguna, sin tener en cuenta la latencia que puede haber en ese punto si en producción la base de datos no está en local, está accesible remotamente o incluso el motor de base de datos es compartido, como suele ocurrir en diversos entornos.

Si se trata de una aplicación con una interfaz de usuario web, en local difícilmente vas a detectar problemas de rendimiento al cargar una simple página al no tener minimizados y concatenados los ficheros css y javascript. Eso sí puede ser un problema en un entorno real.

Hasta en entornos de alto nivel como .NET puede pasar que ciertas cosas funcionen a la perfección en modo debug y fallen de alguna manera inesperada en modo release, ¿sorprendido?

En local, en tu estación de trabajo, sueles tener a mano toda la información o trazas de cualquier cosa que ocurre en el sistema, ¿nos hemos asegurado de que en producción existe la misma trazabilidad? ¿Cuando algo falle, tendremos las herramientas necesarias para averiguar qué pasa en cada momento?

Hasta aquí comento muy por encima lo que podría ocurrir una vez el proyecto lo tienes desplegado, pero, ¿seremos capaces de desplegarlo todas las veces que haga falta? En otras palabras, debemos contar con una guía de despliegue que nos indique:

  • El software base que debe estar preinstalado, versiones y su configuración (como Apache, IIS, node, SqlServer, MySql, Nginx, frameworks, Redis, etc.)
  • Ubicación exacta de dónde se deben publicar todos los artefactos del proyecto. Esto es de vital importancia cuando esperamos publicar el proyecto en más de un cliente. Nada peor que tener algunas cosas por aquí y por allá y según la máquina de cada cliente, esto puede volver loco a quien le toque el mantenimiento (lo que equivale a una falta total de productividad)
  • Pruebas finales que nos aseguren que todo está funcionado y en marcha. Estas no son pruebas unitarias o de integración, sino pruebas aunque sean manuales para garantizar que todo debería estar funcionando correctamente. Comúnmente este tipo de tests se llaman de validación y según qué proyectos los puede realizar el mismo cliente final para dar el ok definitivo al sistema.

Quizá suena un poco agorero todo lo anterior, pero es algo habitual que si no todo, gran parte de lo que he descrito te haya pasado en alguna ocasión.

La forma de evitarlo es ver el despliegue no como algo que se hace al final del proyecto, sino asumir la guía de despliegue como un artefacto más del mismo desde el primer momento. De esta forma, esta guía y sus actualizaciones a lo largo del desarrollo del proyecto nos permitirá avanzar en el mismo desde el primer momento de manera sólida, garantizando que la funcionalidad que tenemos hasta ahora, funciona tanto en los entornos de trabajo o de integración como en los entornos finales. Si tenemos suerte y el sistema hay que desplegarlo en veinte clientes nuevos, entonces ni tú o la persona encargada tendrá que pensar demasiado al estar todo documentado en esa guía de despliegue.

En mi opinión, la existencia de una guía de despliegue para cualquier proyecto es una síntoma de calidad.

¿A quién no le han pedido alguna vez que ponga en marcha un proyecto a partir del código fuente del mismo, y nada más? Terrorífico.

 

Esta es una de las cosas más difíciles de resolver cuando afrontamos la creación de un nuevo proyecto: distinguir claramente entre qué debe hacer de cómo se debe hacer.

El "qué" está relacionado con requisitos, especificaciones, historias de usuario, todo aquello que nos haga entender lo que el cliente quiere, su problema a resolver.

El "cómo" tiene que ver con las tecnologías que van a resolver ese problema que el cliente nos plantea, en otras palabras, el cómo abarca desde las tecnologías a usar, metodología, evidencias a entregar hasta dónde se desplegaría el proyecto final.

Hay una confusión tremenda entre esa separación de conceptos, hasta el punto de tener que forzar o usar mal tecnologías mal elegidas para el propósito indicado en una sencilla especificación (cuando no se le dice a un cliente que "eso no se puede hacer").

Si ya es difícil extraer al comienzo todos los requisitos y conseguir que estos estén descritos con buena calidad y que sean entendibles, más complicado aún el elegir ese cómo a partir de todo lo anterior.

De cómo se gestione esto y las decisiones que se tomen en ese momento dependerá en gran medida la calidad del proyecto que se entregue, su coste final y su facilidad de mantenimiento y evolución. Errores en esta fase hacen que el proyecto termine en un completo fracaso o haya que tirarlo al cabo de algunos años.

Lo peor de todo es que en muchas ocasiones confundimos requisitos objetivos, indicados o no por un cliente o intermediario, con aquellas tecnologías que nos gustaría usar porque sí o bien porque son las únicas que conocemos bien y no queremos salir de nuestra zona natural de confort. 

Traducido al desarrollo de proyectos software, esto viene a ser algo así como empezar la casa por el tejado: si antes de afrontar un nuevo proyecto, sea del tipo de que sea, ya estamos diciendo que lo vamos a hacer con una base de datos en SQL Server, ya estamos levantando paredes en el laberinto que se formará durante su desarrollo, o dicho en otras palabras, en lugar de solucionar, estamos poniéndonos a nosotros mismos obstáculos.

Este no es que sea un error más, el confundir qué se tiene que hacer con cómo se debe hacer, sino que es un error paradigmático de una profesión tanto si a ella llegan gente titulada con uno u otro título académico o intrusos sin formación formal relacionada (sin ánimo de menospreciar a nadie, al fin de al cabo sólo valen el talento de cada cual y el trabajo bien hecho).

Este error clásico, de libro, consiste en forzar el uso de un conjunto de tecnologías para una solución sin hacer el ejercicio intelectual previo de validar si esas tecnologías son las adecuadas o no para ese tipo de proyecto.

Las razones que hacen que caigamos en este error recurrente, en mi opinión, siempre son algunas de las siguientes:

  • Por pura pereza: puesto que conozco mejor las tecnologías x, y o z, no me voy a molestar en aprender o indagar otro tipo de cosas que quizá serían más convenientes para este proyecto.
  • Porque la política de la empresa es trabajar con tecnologías de x, sí o sí y no hay que darle más vueltas.
  • Porque en la estimación del esfuerzo, no hay cabida para incluir una curva de aprendizaje en nuevas tecnologías más apropiadas para ese nuevo trabajo.
  • O lo que es peor todavía: porque me gusta la tecnología x y quiero jugar con ella, ahora tengo la oportunidad, valga o no valga para el proyecto en el que me han metido, así por fin puedo jugar con ella a costa de mi trabajo en la empresa, aunque el resultado sea penoso.

En cualquier caso, las consecuencias de no hacer ese ejercicio previo antes de lanzar a un equipo de desarrollo a construir una nueva solución, son malas y a veces catastróficas.

Sin ir más lejos, en el último año he visto cómo se ha tirado a la basura la solución inicial, pero ya en explotación en una gran compañía eléctrica, para rehacerla completamente con tecnologías más apropiadas para el escenario que se planteaba. De este modo vemos que el no considerar ese aspecto del software al comenzar un nuevo proyecto tiene un coste económico que alguien tendrá que pagar, tu propia empresa o tus propios clientes (encareciendo tus servicios y perdiendo así oportunidades competitivas).

Esto ocurre en pequeñas empresas, pero también en esas que llamamos grandes, con recursos suficientes para hacer todas las auditorías del riesgo del mundo...

Hay que preguntarse siempre y justificar al máximo posible si es conveniente o no usar ASP.NET MVC o cualquier otro framework para interfaces de usuario web en el contexto de un proyecto particular, si es mejor AngularJS o Backbone, si es necesario o no una base de datos relacional (MySql, SQL Server, etc.) o repositorios de información no estructurada (MongoDB, Cassandra, Amazon Simple DB, SQL Tables de Azure, etc.). 

No toda tecnología es adecuada para cualquier tipo de proyecto. Esto parece una obviedad, pero el modo en que veo que en ocasiones se elige qué emplear en cada proyecto me da muchísimo que pensar, cuando no esa motivación nace de la última moda...

En mi opinión, esta debe ser una decisión del líder técnico del proyecto, si es que lo hay, ya que sólo se puede aproximar a una solución adecuada si cuentas con suficiente experiencia y si conoces en alguna medida todas las posibilidades tecnológicas existentes que podrían dar solución a un proyecto en particular.

No sé si es acertado o no, pero por esta razón me gusta promover dentro de la delegación de trabajo que dirijo sesiones internas y completamente desinteresadas en las que cada miembro del equipo expone algo que le interesa, tenga que ver o no con los proyectos que gestionamos en el día a día. Esas sesiones internas nos las preparamos en su mayor parte fuera del horario laboral, pero el resultado es que ampliamos así el acervo tecnológico existente y cuando nos llega algo que afrontar totalmente nuevo, tenemos, cómo decirlo, un horizonte más claro por haber tenido más contacto con un conjunto de tecnologias más amplio. Evidentemente esto requiere de un esfuerzo difícil de valorar, pero que aporta un valor intangible extraordinario en lo que hacemos, o eso creo.

En cualquier caso, el cliente nunca te va a pagar la curva de aprendizaje que necesitaría al comenzar con una nueva tecnología que desconoces, aspecto que distingue radicalmente nuestra profesión de otras: si intentamos repercutir en el cliente ese coste, seremos más caros y por tanto, más débiles de cara a la competencia.

Todo esto que en cierto modo no deja de ser sutil (porque sus consecuencias son difícilmente medibles); es algo con lo que tenemos que enfrentarnos queramos o no. A lo mejor si trabajas en una gran empresa donde alguien te dice con pelos y señales lo que tienes que hacer y cómo, los tiempos, etc., esta discusión sea irrelevante, pero la mayoría de desarrolladores se tienen que enfrentar a estas decisiones continuamente.

No hace mucho he tenido que retomar un desarrollo que comencé hace año y medio y que después continuó uno de mis compañeros. A veces programamos pensando que nos vamos a acordar de todos y cada uno de los detalles de lo que hacemos cuando la realidad es que a los pocos días se nos olvidan sin poder evitarlo y a los meses casi ni reconocemos que esto o aquello los hiciste tú mismo. 

Para cada clase incluimos una sección de comentarios indicando el autor de la misma y su propósito, como manera rápida de identificar esa información; en ocasiones me cuesta mucho recordar que una clase concreta la comencé yo mismo... No es mala memoria (que también, quién sabe), aunque me consuelo diciéndome que es el efecto de haber escrito miles de líneas de código en los últimos años.

Con frecuencia se cae en el error de programar para solucionar algo ahora y sin tener en cuenta que hay que solucionarlo para siempre. Pero, ¿cómo medir más o menos objetivamente que algo está mejor resuelto para el largo plazo? O nos interesamos por los resultados a corto plazo o a largo plazo, el resultado en uno y otro caso no tienen nada que ver.

Al retomar aquel proyecto pude comprobar una vez más cómo escribir software de manera limpia, clara, sencilla y con una buena cobertura de pruebas te puede salvar de malgastar días y semanas de trabajo sencillamente retomando aquello que hiciste hace mucho tiempo. Lo contrario es acumular deuda técnica que te pasará factura tarde o temprano, o lo que es peor, tendrás a un compañero acordándose de tus propias chapuzas; sí, seguramente sea ese que cuando te lo cruzas viniendo del office te mira con mala cara, quién sabe.

Del mismo modo no siempre he podido trabajar tanto en el detalle, en encontrar la mejor solución y sé de sobra el resultado: malos trabajos que se entregan y que terminan empeorando con el tiempo.

Llamamos deuda técnica a todos esos detalles que vamos dejando de lado sin resolver del todo bien pero que cuando se acumulan, terminan en una solución difícil de evolucionar y mantener. Es algo malo, muy malo, como eso de acumular karma negativo... Es la pesadilla de cualquier desarrollador: el tener que asumir un proyecto que ha hecho otro y que no hay quien lo entienda, que está cogido con pinzas.

Lo habitual es que se trabaje dejando muchísima deuda técnica, y esto es así porque es muy difícil evaluar el impacto en tiempo y falta de productividad que produce meses o años después cuando el gestor de un proyecto (tu jefe, vaya), lo que quiere son resultados inmediatos (o sea, pan para hoy y hambre para mañana).

Como el jefe soy yo mismo (aunque suene mal decirlo), una de las máximas que sigo en los proyectos que desarrollamos es que las cosas se tienen que terminar limpias y claras; si de lo que se trata es de reducir riesgos (cosa que se entiende mejor en otros contextos de la compañía) entonces esto es así como una garantía para el futuro: reducimos riesgos refactorizando diseños, limpiando código y simplificando. Curiosamente, el intentar trabajar así no nos ha hecho fallar en ninguna fecha de entrega acordada, lo cual es buena señal.

Puesto que en esencia un proyecto se paga por tiempo dedicado a él, no siempre podemos pararnos todo lo que nos gustaría a hacerlo lo mejor posible. No obstante, mi opinión es que si te paras a tratar de reducir o eliminar esa deuda técnica, todo el tiempo que en ese momento pierdas lo recuperarás multiplicado más adelante.

También es habitual encontrarte con gente que resuelve rápido, pero la pregunta no es cómo de rápido trabajas en software, sino cómo de limpio y mantenible haces el trabajo que entregas. Todo esto suele ser muy sutil, subjetivo, difícil de evaluar, me temo.

En esta ocasión, a las dos horas de revisar lo que se hizo en su día, ya estaba en condiciones de estimar el tiempo que podríamos tardar en añadir la nueva funcionalidad requerida por uno de nuestros clientes.

No hay una varita mágica para llegar a crear un software que te permita asumirlo de nuevo con comodidad al cabo de algunos meses o años incluso, depende del tipo de proyecto y claro está, tu propia experiencia acumulada. Sin embargo, cuando se siguen unos simples principios durante toda la solución es mucho más fácil (y rentable) volver a trabajar en ella como en el primer día.

En este caso que comento, la varita mágica era básicamente lo siguiente:

  • Ese módulo a evolucionar seguía la arquitectura de diseño general de la solución. Nada de excepciones en el diseño alejado del general, las cosas estaban ahí donde un esperaba encontrarlas.
  • Las clases son suficientemente pequeñas como para percibir claramente qué hace cada una, qué propósito único resuelve y cómo encaja con el resto.
  • Las pruebas te permiten ver cómo se usa cada clase; los tests no sólo te sirven para saber si lo que has escrito funciona, también te permiten documentar el uso del código que generas.
  • La inyección de dependencias te permite localizar rápidamente dónde están definidas y cómo se ensamblan las partes inyectables de la solución. Si está bien planteada, una gran parte funcional del sistema será inyectable.
  • Ausencia total de código obsoleto o muerto (que nunca se ejecuta pero que a alguien le ha dado pena eliminar). Es decir, no hay basura que te distraiga de captar lo esencial.
  • Estructura de la aplicación extraordinariamente ordenada y entendible.
  • Nombre de métodos y clases claros y bien elegidos.

En ocasiones nos surge la pregunta de qué hacer cuando se está en una fase de definición o se tienen huecos de tiempo por llenar y que podrías usar productivamente. La respuesta está clara: siempre puede buscar aplicar todos los puntos anteriores a la solución en la que trabajas.

Para casi cualquier sección del código final, su calidad no surgió a la primera cuando se desarrolló hace algunos años, sino que es el producto de muchos refactorings y muchas fases de trabajo, algunas de las cuales tenían únicamente el propósito de mejorar la estructura interna y el diseño del sistema.

Quizá el mejor desarrollador no es aquel que rápidamente encuentra y resuelve cómo hacer algo; también es un magnífico desarrollador el que se para un momento para mejorar lo que ya funciona en algún aspecto, el que presta mucha atención a los detalles que nos permiten años después poder retomar fácil y cómodamente lo que hacemos ahora. Es algo así como dar un paso atrás para dar después un gran salto adelante.

Es ya muy antigua la polémica por la que un desarrollo software se debe considerar horroroso, decente, regular o magnífico, hay tantos puntos de vista como esas críticas contradictorias de cualquier película o la descripción de una misma noticia por varios periódicos con distintas líneas editoriales; es un factor también tremendamente subjetivo.

No obstante, de lo que sí podemos estar seguros es de que una solución que acumula mucha deuda técnica es bastante peor que aquella en la que no existe. La pregunta no es sólo ¿funciona o no funciona?, también ¿será fácil retomar esta aplicación en el futuro por alguien que ha participado en ella?

Como ocurre en muchos temas y como decía Benedetti: "Cuando creíamos que teníamos todas las respuestas, de pronto, cambiaron todas las preguntas". Según las preguntas que te hagas acerca del tipo de calidad que esperas en el software que desarrollas (¿es rápido, eficiente, usable, mantenible, de diseño limpio, traza bien los errores, etc.?) así será este.

¿Desarrollas pensando en el corto plazo o un poco más allá?

Este comienza a convertirse en uno de mis mantras favoritos: "refactorizar o morir".

Recientemente he vuelto a poner en práctica una de las mejores virtudes de realizar este tipo de trabajo continuamente. El resultado finalmente es muy satisfactorio después de muchos momentos tipo "esto está quedando fatal", "así sólo voy a llegar a una solución muy enmarañada", etc. El desánimo cunde rápido, sobre todo si se trata de un pequeño proyecto que haces a ratos por las noches o fines de semana.

No obstante, una mínima tenacidad (y seguramente tirarlo todo a la basura y volver a empezar en algún momento), te hace llegar a una buena solución que no sólo funciona, sino que además es ampliable y evolucionable con cierta facilidad.

Se habla mucho de productividad; para mí es muy sencillo describir qué es productivo y qué no en software: las soluciones fáciles son más productivas que las inextricables que sólo pueden entender sus autores, aquello que nos permite ganar velocidad de desarrollo es más productivo y con ello conseguiremos más con menos esfuerzo. Nada más. Así de contundente y simple.

Hay quienes se procupan de mejorar el código de una aplicación en alguno de sus aspectos en algún momento del trabajo: al final, cuando ya todo funciona, de vez en cuando... Sin embargo, las ventajas de incorporar estas tareas de mejora en todo momento no siempre se aprecian como productivas, mucho menos cuando nos acercamos peligrosamente a las fechas de entrega y comenzamos a dejar cabos sueltos (de los que nos acordaremos sin duda semanas o meses más tarde).

¿Por qué refactorizar cuando lo importante de una aplicación es que le funcione al cliente? Buena pregunta, y al mismo tiempo, ingenua. Quienes aún no ven claro las virtudes de invertir tiempo en este trabajo, deben saber que lo primero que se gana es velocidad de desarrollo, por tanto, productividad.

La velocidad con la que desarrollamos cualquier pieza de software viene a ser una medida de la productividad, sobre todo cuando ésta nos acerca al objetivo de entregar las funcionalidades que nos piden.

Tenemos que perseguir que el diseño o la estructura de la solución que vamos construyendo, con el tiempo nos permita avanzar más rápido y no lo contrario.

En esa aplicación en la que llevo trabajando un tiempo he ido aplicando continuamente todas las técnicas de mejora posibles: simplificaciones, abstracciones en clases de utilidad, mejora en la estructura de la solución, modularizaciones, etc, de modo que cada dos semanas he podido ver cómo el código de un commit de hace quince días no tenía nada que ver con el del último. Esto cobra especial relevancia cuando desde el principio no tenemos claro cómo vamos a implementar ciertos aspectos de la aplicación un poco complicados.

A medida que avanzamos en la solución y gracias a que aplicamos todo ese trabajo de mejora, vemos cómo poco a poco va surgiendo (emergiendo) un diseño cada vez mejor. "Mejor" es una palabra muy subjetiva y difícil de consensuar entre dos o más personas. Para mí, en software, algo es mejor si te permite desarrollar más rápido (invertir menos tiempo en la misma solución) sin perder calidad en todos los aspectos de esta.

Ese diseño elegante, sencillo, correcto, es el que nos permite a partir de un punto ganar velocidad. Llega un momento en que esto es así y es entonces cuando te das cuenta de que todo ese trabajo de mejora ha sido en realidad una buena inversión de tiempo que ahora se va a amortizar. Es como si tuviéramos una madeja de hijo que al principio cuesta mucho desenredar hasta que llega el momento en que es fácil y rápido sacar más y más hilo de ella.

En ese proyecto en concreto que estoy realizando, ha llegado un momento que he conseguido resolver todas esas partes más difíciles de manera tan sencilla y elegante que ahora lo que me queda es decorar el resto hasta finalizar la aplicación. Esto no habría sido posible si no me hubiera parado al principio con esa idea de mejorar la aplicación en todos sus aspectos.

Los comienzos de una aplicación en la que hay muchas dudas que resolver son duros, cuesta apreciar verdaderamente avances y tenemos la tentación de tirar por lo rápido y fácil, sin darnos cuenta que ese camino se volverá en nuestra contra con el tiempo.

Llega un momento en que esa mejor arquitectura y diseño para nuestro propósito es tan maduro que ya sólo nos queda seguir esa coherencia para terminar la aplicación con éxito.

Lo mejor, además, es que llegaremos al final con una solución limpia y fácil de mantener y evolucionar.

Algunas de las técnicas que he empleado hasta el momento son las siguientes:

  • A medida que el código crecía, he ido adaptando la estructura y distribución de este mucho mejor.
  • Clases largas las he ido abstrayendo en clases más concretas y sencillas con una relación lógica entre ellas (principio SRP).
  • Duplicidades las he aislado correctamente en sus servicios correspondientes (el frontend está basado en AngularJS).
  • He ido buscando continuamente todo aquello inyectable, es decir, todas aquellas dependencias que se podían sacar para implementar Inyección de Dependencias.
  • He simplificado muchos bloques de código similares con sus correspondientes funciones de utilidad.
  • Métodos de más de tres o cuatro parámetros los he ido simplificando.
  • y un largo etcétera.

Al final, todo este tipo de trabajo constituye un entrenamiento por el que terminas haciéndolo de la manera más natural y ni te planteas conscientemente el realizar esas actividades como algo separado del trabajo de desarrollo, sino como algo consustancial al mismo.

Este verano me pidieron que evaluara una sencilla tienda online para estimar el coste y esfuerzo necesarios para añadirle nuevas características (y corregir de paso algunos detalles que no funcionaban bien) y, para mi sorpresa, me encontré con algo que es habitual en nuestro sector: aplicaciones que están mal planteadas, mal hechas, escritas de cualquier forma, a pesar de que con ella el cliente final había facturado en el último año más de veinte mil euros en pedidos.

Es ya recurrente el siguiente ejemplo: cuando compramos un coche nos interesamos por muchos de sus acabados, prestaciones y, cómo no, detalles de su mecánica interna (fabricante del motor, etc); del mismo modo para la construcción de una casa se necesita la firma de un arquitecto colegiado (al menos en España) cuya función se supone que es la de supervisar todos los aspectos técnicos de la construcción.

Me pregunto entonces por qué para un proyecto software a casi ningún cliente se le ocurre preguntar o preocuparse por la calidad del proyecto que se le entrega, sin saber en realidad que según esa calidad, el coste de evolucionar o mejorar en el futuro la aplicación será asequible y razonable o desorbitado (cuando no se termina por tirar el sistema a la basura y realizar otro de nuevo desde cero).

De este modo, en muchísimos casos en nuestra profesión, para clientes de pequeño tamaño, medianos o grandes corporaciones, se entregan aplicaciones que en realidad son una manzana envenenada que explotará más adelante en cuanto el cliente necesite modificaciones o cambios, que es lo normal para casi cualquier tipo de software.

¿Compraríamos un coche que no se puede reparar o mantener? Absurdo.

Me planteo varias preguntas en relación a esto. ¿Cómo demostrarle a un cliente la calidad del software que se le entrega?

¿Cómo destacarnos de los competidores garantizando al cliente que nuestra solución va a ser más mantenible ante cambios? Este es un tema que da para mucho y es uno de esos asuntos recurrentes con los que nos encontramos día sí, día no.

¿Estaría el cliente dispuesto a un mayor coste si se le garantiza mejor calidad interna en lo que se le ofrece? ¿Tendrían sentido auditorías externas de calidad para poder dar un producto por terminado con su correcto certificado acreditado de calidad software? Vale, sí, sé que exiten auditorías así y que se suelen usar en grandes proyectos para grandes compañías pero eso es una gota en el océano.

En el caso que comentaba al principio, un simple vistazo a la estructura de la aplicación me hacía temer lo peor...

A continuación describo brevemente lo que más me llamó la atención:

  1. Nada de documentación. Cuando digo nada, es que ni un sencillo comentario en ningún fichero de código, muchísimo menos algún documento sobre las pautas de diseño seguidas, etc. De este modo, lo único que nos queda hacer es bucear entre la aplicación e ir viendo o suponiendo cómo está todo organizado. Del mismo modo, ninguna guía de despliegue que te diera pistas sobre cómo poner la aplicación en marcha en un entorno de desarrollo o explotación.
  2. Ni rastro de ningún tipo de pruebas. Yo diría que esto es lo peor, ya que de este modo el tiempo que se tarda en probar manualmente todo es extraordinariamente mayor y cuando tocas algo no tienes ninguna garatía de que no estás rompiendo nada (¿pero hay que explicar esto?)
  3. Sin diseño de la base de datos. Lo único que existía era un script larguísimo exportado desde phpMyAdmin...
  4. Ausencia de evidencias del uso control de versiones. Esto en sí mismo y para retomar una aplicación no es del todo imprescindible, pero ya el hecho de que no se usara una herramienta de control de versiones te da una idea de cómo se hizo la aplicación en su día.
  5. Múltiples sitios donde configurar las mismas contraseñas. Esta dispersión de contraseñas hard-coded no es que esté mal, es que es una característica de neófito absoluto, qué le vamos a hacer. ¿Hay que indicar que si tienes las mismas contraseñas o configuraciones en X sitios, cuando las cambies, vas a tener que perder mucho tiempo en modificarlas en todos y cada uno de esos sitios (si es que no se te olvida alguno y después tendrás que perder aún más tiempo depurando?
  6. Mala estructura de la solución. Cuando digo mala quiero decir que no se distinguía dónde estaba la parte de la interfaz de usuario, dónde el back-end y tampoco se intuía dónde podía estar la lógica de negocio. Todo estaba mezclado y disperso por aquí y por allá. Si es la primera vez que usamos esas tecnologías, entonces es normal que andemos un poco perdidos sobre la mejor forma de organizar las cosas; no obstante, basta buscar en GitHub por boilerplate para encontrar propuestas de esqueletos para cualquier tipo de proyecto.
  7. Layout de la interfaz de usuario con errores y hecho desde cero. Esto no es que esté mal, pero en un mundo multi-navegador con multitud de versiones por cada tipo de navegador, el hacer tú mismo este tipo de cosas puede llevar muchísimo tiempo en garantizar que al menos el layout funcione bien para los navegadores que más se usan. ¿No es mejor usar algo así como 960 Grid System o multitud de propuestas maduras para este problema?
  8. Extensos ficheros css y js sin minificar ni concatenar. Esto tampoco es que sea imprescindible, pero existiendo herramientas que te permiten reducir todos los ficheros CSS y de javascript a un único fichero .css y .js (este último además ofuscado), ¿por qué no usarlo?, así la aplicación gana algo más de velocidad en cada request, entre otras ventajas.
  9. Agujeros de seguridad como claves sin encriptar en base de datos y campos de texto cuyos contenidos no son filtrados para evitar ataques XSS. Ningún comentario.
  10. Partes de la aplicación en completo desuso. Tampoco es que esto esté del todo mal, pero si mantenemos código muerto y partes que no se usan, nuestra aplicación será más difícil de organizar y mantener: sólo debe existir aquello que la aplicación usa. Si tenemos miedo de perderlo, ¿para qué entonces tenemos la herramienta de control de versiones?

Esto es la punta del iceberg, ya que podría añadir que la aplicación no está en absoluto modularizada y ordenada (las clases de acceso a datos diseminadas, el front-end mezclado con el back-end), ausencia total de estilos a la hora de escribir el código (variables privadas escritas de distinta forma, por poner un ejemplo) y un largo etcétera.

Nada más lejos de mi intención criticar, en absoluto (no quiero acumular karma negativo...), pero me pregunto hasta qué punto degrada nuestra profesión entregar trabajos así que después van a entrar en explotación para un cliente para el que parte de su facturación dependerá de este sistema. 

No sé si es un consejo que vale para todo el mundo, pero cuando entrego un nuevo trabajo intento garantizarle al cliente que se ha hecho mucho esfuerzo para que la calidad de lo que se le entrega sea alta (además de cumplir correctamente con todos los requerimientos funcionales); otra cosa es que esa calidad interna sea valorada o no por el mismo cliente.

Páginas

¿Por qué leer El Libro Negro del Programador?

Adquirir desde:
Amazon (kindle eBook / papel)
CreateSpace (papel)
PayHip (epub / mobi / pdf)

El libro negro del programador.com
Segunda Edición - 2017

El libro negro del programador.com
Now available in english!

Archivo

Trabajo en...

Mis novelas...