Así es, como buena estudiante en constante absorción de conocimientos, tengo como objetivo aprender buenas prácticas a la hora de programar, por eso le quiero dedicar un artículo a los principios de diseño de Javascript.
Javascript es un lenguaje de programación funcional y como tal, los principios hablan mucho de funciones y de cómo deberían usarse.
Lo que hay que evitar
Para hablar de principios primero vamos a centrarnos en lo que NO hay que hacer. Son aquellas características que se repiten cuando programamos sin tener en cuenta que otros puedan entender nuestro código. En cierto modo es hablar de falta de empatía, aunque no seamos conscientes de ello.
En este sentido, debemos evitar que nuestro código tenga:
- Rigidez: con efectos cascada (tocas algo y, como si de un efecto dominó se tratara, se rompen otras cosas), con lo cual, es difícil de mantener
- Fragilidad: tiene mucho que ver con el anterior punto, en el sentido de que lo frágil se destruye fácilmente en cuanto tocas algo.
- Inmovilidad: no se puede reutilizar.
- Viscosidad: tendencia a escribir tu propio código, haciendo copia-pega o haciendo “apaños” y condicionales, en vez de solucionar el problema usando el código ya existente y refactorizándolo o adaptándolo a las necesidades actuales.
- Repetitividad: abuso del copia y pega (aunque esto en cantidades moderadas no supone un problema si hace que el código sea más entendible).
- Incoherencia: el código dice una cosa y hace otra.
Principios de diseño
Por otro lado, como comentábamos al principio, existen unos principios que nos pueden guiar a la hora de crear nuestro código o, más en concreto, de refactorizarlo (ya que que requieren un nivel de abstracción que suele ser más fácil de ver cuando tenemos ya un código sobre el que trabajar).
Estos principios se han englobado en las siglas S.O.L.I.D.
S-Single Responsibility. Que no haga más cosas de las que dice, que tenga una finalidad sencilla y concreta.
O-Open-closed. Abierto en cuanto a poder usarse ampliamente, pero cerrado en cuanto a modificaciones (para que no se rompa).
L-Liskov Substitution
I-Interface Segregation
D-Dependency-Inversion. El objetivo es conseguir desacoplar el código. Las funciones de más alto nivel (que son las más cercanas al usuario, al lenguaje humano), saben que necesitan de otras funciones de más bajo nivel, pero no saben exactamente lo que hacen, pues esa información se mantiene de forma privada o en otra función aparte.
Conceptos que me guiarán en este camino
Para entender mejor nuestro objetivo de programar de forma limpia y entendible de acuerdo con estos principios, vamos a desengranar varios conceptos que vienen de la mano:
Acomplamiento.
Las funciones sabemos que tienen que depender unas de otras en muchas ocasiones, a eso es a lo que se le llama acoplamiento. Sin embargo, debemos tratar de conseguir que dicho acoplamiento sea lo más débil posible, esto quiere decir que, aunque sepa que tiene que llamar a otra función, no sepa en cambio lo que hace exactamente (es lo que hemos comentado más arriba acerca de la inversión de dependencia).
Para conseguir esto, debemos tratar de que nuestras funciones sean en la medida de lo posible, cajas negras, y que no sepamos mucho acerca de cómo están hechas. Sabemos lo que hacen, pero no cómo lo hacen. Por ejemplo, sé que suma dos items, porque ejecuto una función que se llama sumaDosItems, pero no sé cómo lo hace, porque no conozco sus entrañas.
Cohesión.
Que sea coherente. Cuanto más parecido a una barra de pan entera, mejor se podrá transportar. Esto quiere decir que es más fácil usar una función cuyos elementos hablan de una misma cosa, por ejemplo, de medición de tiempo, a otra que, además del tiempo, también hable de sumas, de interfaz de usuario, etc. Esto último no sería coherente.
Abstracción y nombramiento.
Así dicho, suena raro, pero he querido unir estas dos palabras para explicar la importancia de elegir los nombres de las funciones o variables, en tanto en cuanto supone alcanzar un nivel de abstracción que va a marcar el camino a la hora de diseñar nuestra arquitectura funcional.
¿Query o Action?
Hay que tener claro si nuestra función va a ser tipo query, es decir, que nos devuelve un resultado, o tipo action, es decir, que modifica algo (el estado, el sistema). Este ejercicio de separación es lo que se conoce como CQS (command query separation).
Esto también va a determinar nuestro diseño funcional, ya que las funciones tipo query se pueden usar por ejemplo como callbacks o como parámetro, elevando así el nivel de abstracción y permitiendo por ejemplo tener funciones más puras, ya que no modifican el estado. Este tipo de funciones (query), se acercan más a la programación funcional.
Testeable.
Las unidades más pequeñas y con responsabilidades sencillas y concretas (como cuando conseguimos desacoplar funciones) son más fáciles de testear y mantener.