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, no voy a hablar de nada relacionado con los principios de desarrollo S.O.L.I.D., que, en cualquier caso, recomiendo a cualquier programador conocer en profundidad y, sobre todo, ponerlos en práctica.

En los últimos meses he tenido que leer bastante documentación sobre licitaciones y también he estado inmerso en propuestas de nuevos proyectos en colaboración con otras compañías.

En esas licitaciones, algunas para gobiernos autonómicos de mi país, llama la atención la falta de rigor en las especificaciones. Es más, concretamente en alguna se indicaba que "las tareas a realizar se plantearán con exactitud cuando el proyecto sea adjudicado al licitante". En otra, decía que el licitante debía adaptarse a las metodologías de trabajo más comunes, y mencionaba CMMi, Métrica 3, etc. En fin...

Al mismo tiempo, la mayoría de esos proyectos consisten en evolucionar aplicaciones que ya están funcionando en consejerías o departamentos de esas regiones.

¿Cómo poder estimar el esfuerzo para evolucionar algo sin ver su estado actual? ¿Estará la solución hecha unos zorros? ¿Será el típico proyecto espagueti o en cambio todo se habrá hecho con conciencia, mejoras continuas, limpieza de código, etc.?

Miedo me da encontrarme con algo así. 

En esos proyectos se hablaban de tecnologías que ya existían hacer quince años y que siguen muy presentes aunque bajo revisiones más recientes.

En otro, que no tiene nada que ver con los anteriores, se hablaba de trabajar a muy bajo nivel con cierto tipo de dispositivos, empleando el protoloco DLMS.

Y para variedad, estoy trabajando como colaborador externo para una magnífica compañía nacional, líder en su sector, y de nombre Altafonte. Utilizan un sistema basado en PHP, base de datos MemSQL, etc. A sus desarrolladores los considero unos héroes, por haber sabido avanzar en la solución, la base y el core de su negocio, mientras éste crece con multitud de nuevos requerimientos continuamente.

Pensando en todos esos proyectos, ¿qué es lo que pueden tener en común?

En realidad, tecnológicamente hablando, puede parecer que nada, son completamente diferentes, y sin embargo, desde un punto de vista más integral (holístico dirían en otros contextos), el éxito en todos esos proyectos tan dispares, sólo depende de principios que son comunes y antiguos, y que además no son exclusivos de nuestro sector de desarrollo de software.

¿Qué sería de todos esos proyectos sin una correcta organización y planificación?

Para cualquier cosa que haya que hacer, escribir un libro, ahorrar a largo plazo, realizar un proyecto software cuya solución va a durar años, todo medianamente complejo y que se expande en el tiempo, requiere de organizar y planificar.

Y eso es un trabajo en sí mismo. Lo he visto muchas veces, me temo, se organiza al principio, pero ya después las cosas van cayendo en el olvido. 

Cualquier esfuerzo de organización debe ser continuo y hay que dedicarle un tiempo recurrente, es una tarea más que hay que meter entre nuestras actividades.

El proyecto, aún sin la mejor organización, se puede terminar, claro, pero con toda seguridad se habrá tenido que dedicar un esfuerzo mucho mayor y la gente habrá soportado mayores niveles de estrés.

Todos esos proyectos, ¿se podrían enfocar igual sin una mejora continua?

En cualquier proyecto largo, sobre todo si lo actual depende de lo anterior, hay que tener una actitud de mejora continua sobre todo lo que ya se ha realizado. De no ser así, el tema va creciendo y creciendo sobre bases que no están bien, y llegará el momento en que todo se desmorone.

Hay que realizar restrospectivas cada cierto tiempo; las lecciones que nos dan tienen muchísimo valor para seguir avanzando con pasos seguros.

En software, ¿se mejoran todos tipos de assets?

Se tiende a pensar que las técnicas de refactorización sólo afectan al código de producción.

Pues no, de ningún modo; aunque refactorizar es un término que se aplica al código, el resto de assets pueden y deben ser mejorados al mismo ritmo, sin olvidar que la calidad de las pruebas debe ser la misma que la calidad del código de producción.

A medida que un proyecto crece, hay que mejorar su estructura, su diseño, la documentación, etc.

Estoy hablando de proyectos software que deben ser evolucionados en un periodo de tiempo muy largo, años.

Todo, absolutamente todo, influye para el resultado final sea mejor.

¿Y qué hay de los flujos de trabajo?

Me encuentro que las organizaciones se hacen las cosas de un modo determinado, que puede funcionar mejor o peor, sí, pero no existe la conciencia de establecer claramente un flujo de trabajo para cada proceso que necesita emplear la compañía. Basta con poner por escrito de algún cómo se debe hacer esto o aquello y consensuarlo con la gente implicada.

De ese modo, con el tiempo podemos comprobar las deficiencas, trazarlas y mejorar esos mismos flujos de trabajo.

Podemos estar usando tecnologías de hace diez años u otras más recientes, podemos diseñar una nueva solución orientada desde un principio a alguna infraestructura cloud, una nueva aplicación móvil con la úlima API de Android, o entornos como Xamarin o PhoneGap, o bien actualizar una solución de NodeJS basada en la versión 0.12.x al nuevo salto de nivel con la versión 5.x.x., etc.

Sin embargo, cada vez estoy más convencido de que todas esas tecnologías que evolucionan, van y vienen, son la parte de un puzle mayor. Aplicando correctamente estos principios sólidos a la hora de afrontar tu trabajo, podrás usar una tecnología casi obsoleta o el stack más de moda en la actualidad, pero tendrás así muchas más posibilidades de éxito y adaptación a nuevos escenarios.

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á?

Trabajar en distintos ámbitos tiene algo de fascinante, terminas dándote cuenta de cómo lo que haces en uno enriquece los demás y al revés.

Ahora mismo estoy trabajando en un proyecto basado en MEAN que al mismo tiempo me está permitiendo sentar las bases de un proyecto de I+D que estamos realizando en la compañía para la que trabajo y que desarrollaremos durante 2015 y 2016; este proyecto consiste en un sistema de detección y diagnóstico de incidencias para el despliegue de cuatro millones de smart meters, de la mano de una compañía de distribución eléctrica nacional. Big-data con Hadoop, su despliegue en la nube con Azure, el análisis y explotación de esos datos más una capa de servicios que consumirán terceros sistemas serán las partes esenciales del proyecto y en cuyas tecnologías nos vamos a meter de lleno en los próximos meses.

¿Qué puede garantizar que tendremos éxito en ese nuevo proyecto reconociendo que algunas de las tecnologías que vamos a usar son relativamente nuevas para nosotros?

Si crees conocer muy bien la tecnología X, ¿qué te hace pensar que serás capaz de sacar provecho a otra totalmente distinta?, ¿te asegura acaso que vas a tener éxito con un nuevo stack que debes usar para un nuevo proyecto cuya naturaleza no tiene nada que ver con lo que has hecho hasta ahora?

Para un responsable de proyectos, ese es un riesgo que hay que considerar a fondo.

Un desarrollador de software o un equipo de trabajo sólo pueden tener éxito en cualquier proyecto si son capaces de mantener firmemente una serie de principios de desarrollo y métodos que se pueden aplicar a cualquier tipo de tecnología.

Por tanto, aún teniendo valor, me da siempre qué pensar cuando escucho afirmaciones del tipo "soy experto en C#", "nivel avanzado de administración de Sql Server", o "desarrollador front-end con AngularJS", etc, cosa que se suele ver en todos los currículums que me llegan (y por qué no, en el mío propio hasta hace pocos años). Todo eso es fantástico, pero no suficiente...

Si dominas una serie de principios serás bueno y tendrás éxito en cualquier proyecto software independientemente de las tecnologías que se usen.

Para mí, el currículum ideal de un desarrollador profesional de software estaría compuesto de afirmaciones de este tipo:

  • Participación activa en los siguientes proyectos:....
  • Experto en patrones de diseños y antipatrones.
  • Experiencia en enfoques ágiles para proyectos software.
  • Conocimiento sobre principios de diseño como inyección de dependencias, inversión de control, DRY, KISS, SoC y un largo etcétera.
  • Desarrollo con integración continua y cobertura total con pruebas unitarias y de integración.
  • Experiencia de análisis de calidad de código.
  • Habituado a refactorizar código y desarrollo de código limpio.
  • Experto en realizar código mantenimible y evolucionable.
  • Dominio de arquitecturas software escalables.
  • Conocimiento de principios de usabilidad en interfaces de usuario.
  • etc.

Aunque sólo son un ejemplo, todas esas aptitudes son mucho más importantes que dominar cualquier tecnología en particular: con cualquier lenguaje, sistema operativo, framework o entorno de desarrollo se pueden aplicar esas técnicas, principios y métodos impresindibles para generar productos software de calidad.

Cualquier desarrollador con dominio de todos esos temas va a seguir aplicándolos con cualquier nueva tecnología con la que tenga que comenzar a trabajar.

Aparte de una actitud positiva hacia el trabajo y buena disposición a trabajar en equipo, a lo anterior añadiría afirmaciones de tipo "Mis últimas lecturas técnicas han sido...", "Asistencia en el último año a eventos como...", "Participación en el blog X", etc. Aunque ya todo esto sería mucho pedir.

Es difícil, lo sé, cuando muchas compañías piden "expertos programadores en java", por poner un ejemplo, sin darse cuenta de que cualquier desarrollador mínimamente experimentado puede acercarse al mundo java con un par de semanas de estudio y dos o tres buenos manuales, aunque no por eso va a hacer un buen trabajo necesariamente. Sí va a hacer un buen trabajo si está habituado a implantar todos los conceptos anteriores con cualquier otra tecnología. Otra cosa es que los gestores de proyecto tengan claros ese tipo de consideraciones...

En cierto sentido, todo lo anterior es como la gramática y la ortografía necesarias para escribir una buena novela, en donde el saber escribir una palabra tras otra vendría a ser a conocer bien la tecnología X.

¿Sabes sencillamente escribir aunque tengas un buen vocabulario sin tener claras la gramática que nos permite generar un trabajo de calidad?

A lo largo de esta web así como de El Libro Negro del Programador, se insiste mucho acerca de la necesidad de que una solución software sea mantenible. Es más, si no es mantenible fácilmente, el proyecto puede ser todo un fracaso y traerá consigo un cliente muy cabreado y enojado (que seguramente nunca volverá a confiar en ti) o una compañía que tiene un agujero económico que cubrir.

No se hace software para que funcione ahora, sino para que pueda ser evolucionado y matenido fácilmente con el tiempo. Esto para mí es un mantra que repito y un principio irrenunciable.

Mantener un proyecto software en producción es en definitiva conocer cómo se comporta, tener identificadas claramente las actividades de mantenimiento, poder detectar con sencillez dónde ocurren los problemas pero, sobre todo, tener la capacidad de hacer todo eso de manera eficiente y en poco tiempo.

Administrar, por su parte, está relacionado con lo anterior y es igualmente importante: si la administración de cualquier producto software es ineficiente, estaremos poniendo en peligro su viabilidad en producción.

Por muy bien que funcione aparentemente nuestro proyecto, si este no se puede ni mantener ni administrar correcta y eficientemente, habremos realizado en realidad un proyecto más bien pobre. Para que un software sea mantenible y administrable tiene que desarrollarse desde el principio pensando en su sencillez de mantenimiento y administración.

Realizando recientemente algunas actividades de mantenimiento de la web de la compañía en la que dirijo proyectos software, volví a tener presente este carácter intrínseco tan importante del desarrollo. La web está montada en Drupal y se tenían que actualizar algunos módulos desde hacía algún tiempo. A los que me conocen no les extraña en absoluto que dirija la realización de proyectos software al mismo tiempo que me meto de lleno es aspectos de desarrollo de bajo nivel, ¿acaso se puede dirigir algo sin conocerlo suficientemente?

Existe un proyecto que el mundo drupalero conoce muy bien y que permite realizar las actividades de mantenimiento, supervisión y administración de una web basada en Drupal muy fácilmente y, sobre todo, de manera muy eficaz; se trata, claro está, de drush.

Por poner un ejemplo, si tenemos que actualizar el Core de Drupal de un 7.xx a la última versión (en el momento de escribir esto la 7.32) podríamos implementar los pasos oficiales sobre una instancia stage, que, más o menos, son estos:

  • Realizar un backup de nuestro sistema (ficheros y base de datos).
  • Descargar la última versión de Drupal.
  • Copiarla correctamente manteniendo nuestra carpeta "/sites".
  • Ejecutar el script "/update.php".
  • Por último comprobar que todo ha ido bien...

Esto es algo habitual y, en mi opinión, que existan tantas revisiones de Drupal 7 para mí es bueno, puesto que cada revisión mejora aspectos del Core o realiza actualizaciones de seguridad.

No obstante, lo anterior puede ser tedioso incluso si se tiene que realizar cada dos o tres meses. En cambio con drush todo lo anterior se puede hacer con:

$ drush up drupal

En una simple línea de comandos realizamos todos los pasos anteriores, por lo que de ser una actividad pesada se convierte con drush en la ejecución de una única orden.

Del mismo modo, con:

$ drush status

, obtenemos una visión general de la instancia de Drupal.

Comento esto como un maravilloso ejemplo de facilidad de manteniemiento: con el primer enfoque podemos tardar 10/30 minutos, con el segundo menos de un minuto. ¿No es esto productividad? Me pregunto si no llenamos nuestro día con actividades similares que podríamos optimizar.

Salvo que estemos haciendo proyectos de prueba o de evaluación y no profesionales que en ningún momento se pasarán a producción, personalmente me preocupo mucho de que lo que se haga ofrezca suficientes posbilidades, información y herramientas para un fácil y ágil mantenimiento y administración. En este punto está la clave del éxito de muchos proyectos software.

¿De qué sirve un producto que ha costado desarrollar X € si el mantenimiento del mismo cuesta el triple? ¿Tiene sentido proyectos en los que existen hasta varias personas dedicadas íntegramente al mantenimiento del mismo? ¿No es esto una evidencia de que las cosas no se han hecho del todo bien durante su desarrollo? Recuerdo hace algunos años cuando dedicaba parte de mi jornada laboral a mantener un sistema de telegestión en un cliente y se me iban las horas buceando en la base de datos con consultas muy tediosas para descubrir y resolver ciertos problemas.

Me pregunto cuántas veces en este tipo de situaciones no tiene efecto esa frase tan española y castiza de "preocuparse por la peseta pero no por el duro" (un duro equivalía en la era pre-euro a cinco pesetas), expresión que en versión tecnológica vendría a ser algo así como "intentar que el desarrollo a corto plazo sea muy barato y no ver que su mantenimiento será muy caro".

En cualquier producto software, se desarrolla una vez pero se mantiene continuamente (al menos hasta que nos quedemos sin clientes).

Del mismo modo, cualquier cliente en su sano juicio antes de comprar algo debería preocuparse por lo que le va a costar el mantenerlo y si su administración es eficiente y ágil.

Es más, podríamos convertir esta misma capacidad en un atractivo más de valor para nuestro producto: su sencillez (y poco costoso) mantenimiento.

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.

Me he encontrado en ocasiones algunos desarrolladores de software que, al menos aparentemente, se obstinan en hacer las cosas exageradamente complicadas. Tanto es así que parece que lo hacen como seña de identidad: cuanto más abstruso más demuestro lo bueno que soy. Digo yo que lo mismo iban por ahí lo tiros.

No obstante, la pregunta que hay que hacerse cuando escribimos una nueva pieza de código es, "¿lo entenderemos nosotros mismos dentro de un tiempo?", si la respuesta es afirmativa, la siguiente pregunta sería "¿lo entenderán los demás, aquellos que hereden para lo bueno o para lo malo este trabajo?".

Yo siempre digo que si el primer objetivo de un buen software es resolver algún problema y que le sea de utilidad a alguien, lo segundo es que debemos programar para que los demás puedan asumir fácilmente aquello que hacemos. Nada más ingenuo que pensar que cuanto más rebuscado hagamos el trabajo mayor la estima técnica que nos puedan tener.

Un cosa clara me ha demostrado la experiencia, algo además irrefutable: las mejores soluciones con las que más me he sorprendido, han sido claramente las más sencillas. Todos podemos deliberadamente hacer algo complejo, pero pocos, muy pocos, podemos dar con el medio o la solución más sencilla.

Este debería ser uno de los prinpios con los que trabajar en el día a día: intentar buscar siempre la solución más sencilla a cualquier problema, tanto de programación, de diseño, etc.

No sólo este principio mejorará nuestros productos, nuestro día a día en el futuro cuando tengamos que volver a modificar una aplicación, sino que con el tiempo te das cuenta de que el hábito constante de buscar siempre simplificar las cosas, precisamente te reprograma la cabeza para que de manera natural te surjan soluciones sencillas.

Recomiendo encarecidamente la lectura (y relectura) del libro Code Simplicity, el libro de Max Kanat-Alexander, ingeniero software en Google en donde reflexiona sobre estos conceptos que tanto pueden hacer por mejorar la calidad del trabajo que realizamos. Su blog también contiene entradas muy interesantes.

Cuanto más tiempo viva tu aplicación, más probable será que tenga que cambiar; así de sencillo y contundente.

En ocasiones me han preguntado por qué parezco obsesionado por programar y diseñar de la manera más simple y legible posibles, para qué preocuparse ahora por aquello que puede o no necesitar cambiar en el futuro. La pregunta en sí ya encierra cierta candidez acerca de los artefactos que generamos en nuestra profesión.

Una de las verdades sobre nuestro trabajo, entronada a modo de ley, nos dice que cuanto más tiempo viva nuestra aplicación, programa, sistema, plugin, framework, etc. más probable será que necesite cambios, mejoras, correcciones, etc. Ley o no, lo que sí puedo decir es que ningún software en el que he participado ha permanecido inmutable más de seis meses...

¿Tenemos presente en nuestro día a día esta realidad?. ¿Nos preocupamos realmente en refactorizar y mejorar a pequeños saltos continuamente?. ¿Nos esforzamos en encontrar la solución más simple a cualquier problema?.

Es cierto que podemos dejar las cosas tal y como nos salen la primera vez que las hacemos (total, funcionan, ¿no?); sin embargo, si vamos dejando cabos sueltos, duplicidades, errores conocidos y toda una estela de debes, con el tiempo se formará una montaña que nos caerá encima inevitablemente (o enterrará el producto en el olvido).

Cualquier software va a necesitar cambios en un futuro necesariamente: cualquier producto que se lance al mercado va a morir si no se renueva con mejoras continuamente (continuous delivery), cualquier cliente consolidado que tengamos nos pedirá mejoras o cambios con toda seguridad, por mucho esfuerzo que hayamos dedicado a probar nuestro código, finalmente el cliente encontrará nuevos errores o pequeños bugs, cuando no nos encontramos con el consabido problema de haber entendido mal algún requisito en concreto. En otras ocasiones, la evolución del mercado o tipología de clientes al que se dirige nuestro software determinará el tipo de cambios a introducir.

Demasiadas razones para no programar centrando nuestro trabajo en la sencillez de las soluciones; esta casi nunca se consigue a la primera, sino que poco a poco, mediante pequeñas refactorizaciones, auscultando el trabajo con tests y a medida que la aplicación crece, vamos mejorándola en todos los aspectos. Tampoco podemos obviar que el coste de evolucionar una aplicación debe ser el mínimo posible, de lo contrario estaremos fuera del mercado antes de lo que nos gustaría.
 

"Aunque suene paradójico, programar profesionalmente significa también estar dispuesto a destruir parte del trabajo realizado ¡precisamente para mejorarlo!. Si nuestro software no puede ser modificado y mejorado fácilmente, estará perdiendo rentabilidad y oportunidades de competir."

 

¿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

Archivo

Trabajo en...

Mis novelas...