Focalizate en el problema, no en la solución: Desmitificando la arquitectura de software
En su momento, se escuchaban muchas tonterías como "el diseño es subjetivo" o "el diseño es cuestión de gusto". Existen diseños mejores y peores. Estos criterios no son perfectos, pero sirven para descartar algunas obviedades y, lo que es más importante, puedes evaluarlos en el momento. Los verdaderos criterios de calidad de diseño, "minimizan el coste (incluido el coste de la demora) y maximizan el beneficio a lo largo de la vida del software", solo pueden evaluarse a posteriori y, aun así, cualquier evaluación estará sujeta a una gran cantidad de sesgos cognitivos. Kent Beck [1]
Uno de los pasos que marca una mejora de calidad en la carrera de un ingeniero de software es el uso de patrones de diseño. Cuando empiezas a leer sobre paradigmas como SOLID, DRY, patrones de diseño sofisticados como los expuestos en "Design Patterns" de GangOfFour, o anti-patrones como STUPID, quieres aplicarlos de la mejor manera posible. Además, existe una cierta aura alrededor de aquellos ingenieros de software que utilizan estos patrones, siendo casi como un conocimiento irrefutable.
No tiene sentido descartar este conocimiento por completo, pero podría ser bueno desmitificarlo. Es útil para resolver muchos problemas complejos, pero permíteme cambiar la perspectiva pensando en lo que queremos lograr y no en cómo queremos resolverlo.
No debemos ignorar el hecho de que los patrones de diseño introducen complejidad accidental y aquí está la clave: cómo equilibrar los beneficios frente a la complejidad involucrada en su implementación y el mantenimiento posterior.
No debemos pensar en aplicar patrones o construir arquitecturas de manera elegante o sofisticada. Del mismo modo, es un error pensar en un caso de uso y sus cientos de casos límite. Tampoco es una buena idea desarrollar pensando en un futuro que tal vez nunca llegue.
El primer objetivo y lo que debemos tener en cuenta al construir una solución es hacer un código amigable. Estás creando un amigo para toda la vida. La vida no te ofrece estas oportunidades, así que piensa que mañana lo volverás a ver y quieres seguir pasando un buen rato con él.
Esto puede significar muchas cosas diferentes, pero para ser más específico: si tuviera que elegir cuándo es necesario aplicar o replantear un cambio arquitectónico en mi código, me guiaría por los siguientes puntos.
Efecto Cadena
Cuando en un código, una modificación implica cambiar otras líneas de código dispersas por otras funciones o archivos, es una clara señal de que estamos generando un problema de acoplamiento. Este problema se agrava cuando ese punto de modificación es un punto central de nuestro dominio, ya que constantemente vamos a tener puntos de fuga en nuestro código. Esto es muy común con funciones de módulos como utilidades o clases muy generales en OOP.
Este problema es relativamente fácil de detectar si tenemos una buena suite de pruebas. Si cada vez que realizamos un cambio, se rompen pruebas que no están relacionadas con el caso de uso actual, tenemos un alto acoplamiento en nuestro código. Esto suele suceder mediante el uso abusivo de abstracciones o aplicando indiscriminadamente el patrón DRY.
Una regla general sería crear una abstracción o reutilizar código cuando el caso es claramente código repetitivo o similar, cuando este caso no incluye casos límite para cada función que lo utiliza y, sobre todo, no crear abstracciones anecdóticas.
Pruebas intuitivas. Fáciles de crear y fáciles de leer.
Una aplicación está bien construida y bien arquitectada cuando es fácil construir pruebas sobre ella. Y me refiero a construir pruebas legibles de una manera sencilla. Con la introducción de ciertos patrones de diseño o marcos para desarrollar rápidamente, se ha evitado una separación mínima de capas.
Por eso es importante el mantra TDD, porque ayuda a definir pruebas simples y se construye con esa capa de independencia en mente. También es importante probar utilizando la menor cantidad de herramientas complejas de la suite de pruebas como sea posible. Con esto quiero decir que no se creen simulaciones con herramientas de prueba sofisticadas, sino que se utilice nuestra separación de capas.
Concéntrate en probar tus casos de uso, ya que son el verdadero núcleo de tu aplicación. Piensa en el mañana, estarás agradecido si puedes ir a la suite de pruebas y saber todo sobre tus casos de uso con solo un vistazo. Sé que es un sueño hecho realidad.
También es importante mencionar que, aunque TDD es una metodología perfecta para mantener tu arquitectura en buena forma (mediante refactorizaciones constantes y sin anticipar diseños), las pruebas escritas a través de TDD deben considerarse pruebas que garantizan que tu código funcione como se espera, no pruebas que validen que nuestra aplicación no tiene errores.
Una revisión de código debe comenzar con las pruebas, desde donde se documenta todo lo que hace y/o no hace nuestro código. Por eso, las pruebas son el índice de nuestro código y a veces se llaman especificaciones.
Presta especial atención cuando se reutiliza código
La reutilización, ya sea a través de abstracciones o con soluciones generales, nos hace diseñar soluciones excesivamente complicadas. Inicialmente, muchas veces puede parecer que estamos violando el principio DRY y que buscamos repetir el menor código posible. Además, Kent Beck habla de la no duplicación. Pero espera un momento, no te confundas.
La realidad es que muchas veces se crea una generalización a través de un caso anecdótico. La no duplicación no se trata de líneas de código, sino de casos de uso o lógica empresarial.
Tendemos a pensar en agrupar código similar; cuanto más general sea el código, más podremos reutilizarlo. La realidad es diferente, el código debe adaptarse a las particularidades de cada submódulo y eso dificulta su mantenimiento.
El código se lee de manera fácil por otros colegas
Cuando Kent Beck desarrolló Extreme Programming a principios de los años 90, ya mencionaba que debemos tener en cuenta que el código revela intención.
Esto significa que el código es fácil de entender. Los lectores de tu código deben comprender cuál es el propósito del software que has escrito.
Puede parecer fácil, pero en realidad es lo más vago y difícil de aplicar. Esto se debe a que no depende de nosotros, sino de los demás. Cuando alguien no entiende cómo resolvimos el problema, puede ser una buena razón para replantear nuestra solución.
Por eso es necesario establecer un buen diálogo con los revisores, un diálogo de confianza y apertura al cambio.
Por otro lado, deja que el código descanse. El código no se ve igual después de un día. Relee tu código y trata de entender a tu yo del pasado.
Olores de código
Podemos hacer que nuestra revisión de código sea más compleja y dar un paso más allá a través de los olores de código. Los olores de código son un compendio de "olores" que Martin Fowler compiló en su excelente libro "Refactoring".
Con esos olores, somos capaces de detectar posibles problemas cuando miramos un código. Como ya hemos mencionado antes, son suposiciones y no axiomas, por lo que no siempre un olor de código conducirá a un código deficiente.
En el desarrollo de software, no se trata de ver más allá al resolver un problema, sino de resolver el problema actual de la manera más comprensible, intencional y comprobable posible. Enfócate en el problema, no en la solución.
Miembro fundador de The Crafters Lab
Rubén es desarrollador de software y miembro fundador de The Crafters Lab.