Etiquetas: , ,

SOLID. Con este acronimo se resumen cinco principios, planteados en el paper Design Principles and Design Patterns escrito en el 2000 (si, hace más de dos decadas) por Robert C. Martin y que posteriormente poplarizó Michael Feathers y, en especial, Bob Martin en su archiconocido libro clean code. Y ahora que nos hemos quitado de encima las presentaciones y derechos de autor, vamos a lo importante: ¿Qué significa cada letra? y ¿Por qué debería importarme?

Letrita a letrica

SOLID

Cómo ya hemos mencionado, SOLID es un acronimo, así que analicemos que significa cada letra:

  • S – Single Responsibility Principle
  • O – Open/Closed Principle
  • L – Liskov Substitution Principle
  • I – Interface Segregation Principle
  • D – Dependency Inversion Principle

¿Te has quedado como estabas? Bueno, pues como dijo Jack el destripador, vamos por partes:

La S : Principio de responsabilidad única (Single Responsibility Principle)

Bajo este nombre tan bonito se esconde un principio tan importante como simple: Una clase o una función debería hacer solamente una cosa. Si yo tuviera que escribir este principio a mi manera, lo definiria como “Cada una de tus clases o funciones deberían hacer una sola cosa, peor hacerla muy bien”. Lo que pasa es que mi manera tiene peor marketing. Tomando nota Esto no siempre es sencillo de implementar, pero hacerlo tiene sus beneficios. Cuando tengas que cambiar o evolucionar algo, tendrás que tocar solamente en la función que hace ese trabajo, el resto debería seguir funcionando sin afectación. O, en el peor de los casos, con ajustes minimos. Esto permite que las modificaciones en el código sean mucho más sencillas de realizar, el código más sencillo de leer y que de algo menos de miedo tocar algo que ya funciona.

Todos, a poca experiencia que tengamos en este mundillo, nos hemos encontrado con funciones de cientos de líneas en las que nos hemos sentido como en esas escenas en las que el protagonista se encuentra con una bomba en mitad de la ciudad, rezando por cortar el cable correcto y no tocar ninguna otra parte que pueda hacer que todo explote. Este principio en parte busca evitar este tipo de situaciones (lo siento por los amantes de la adrenalina). Y crear código que sea más mantenible y reutilizable (a fin de cuentas, cuanto más simple es una función más probable será que lo que hace te encaje en otros proyectos, al tener menos dependencias y cubrir casos de uso en lugar de funcionalidades concretas de la aplicación).

Esta es probablemente una de las más importantes de las 5, sino la que más. Si solo vas a leer hasta aquí, me alegro que te quedes con esto.

La O : Principio de apertura y cierre (Open/Closed Principle)

Ojo, que empiezan las curvas. Desarrollando un poco este principio, lo que viene a decir es que una clase debería estar abierta para extenderse y cerrada para modificarse. ¿te quedas un poco igual? No me extraña, este principio lo planteo en un primero momento Bertran Meyer en 1988, y en esa epoca casi todo lo que se publicaba en informatica era duro de leer (Esta gente no habría logrado sobrevivir en tiktok, esta claro)

Por decirlo de otra manera, deberíamos poder ampliar la funcionalidad de una clase o función sin necesidad de modificarla, creo que viendola con un ejemplo se entiende mejor.

Voy a crear una función super avanzada, de esas que probablemente nunca hayais visto como ejemplo en ninguna parte:

def matematicas(num_1, num_2):
    return num_1 + num_2

sarcamos a parte, evidentemente es una función que recibe dos números y devuelve su suma. Nada de otro mundo. En un ejercicio de imaginación vamos a suponer que es lo unico que tiene que hacer nuestro programa. Pero mañana viene el cliente y nos dice que ya no quiere que sea una suma, sino una multiplicación.

Podríamos entrar al código y hacer esto:

def matematicas(num_1, num_2):
    return num_1 * num_2

Y estariamos cumpliendo los requerimientos del cliente, pero incumpliendo la O de SOLID. Ya que habríamos hecho una modificación para realizar un evolutivo, en lugar de extender la funcionalidad. Esto supone que si por ejemplo otra parte de nuestro código estaba haciendo uso de la función matematicas, esa parte dejará de funcionar o tendrá un funcionamiento erroneo.

Para evitar esto, y aplicar el principio de apertura y cierre lo que podriamos hacer es lo siguiente:

def matematicas2(num_1, num_2):
    resultado = 0
    for i in range(num2):
        resultado = matematicas(resultado, num_1)
    return resultado

Lo que hemos hecho es mantener el uso de la función matematicas sin modificarla y ahora nuestra aplicación es capaz de multiplicar. En este caso, podríamos decir (con toda la razón) que estamos haciendo exageramente complejo un problema tremendamtente trivial, y estaríamos en lo cierto. Pero se trata solamente de un ejemplo para entender el concepto, no un caso de uso real. Lo importante es que nos quedemos con lo que hay detrás: Si funciona no lo toques, evolucionalo.

Y por cierto, estos nombres son horribles para las funciones, si quieres ver algunos consejos sobre como nombrar tus funciones, variables, constantes… etc, te recomiendo que mires esta otra entrada.

La L : Principio de sustitución de Liskov

Las clases derivadas deben poder sustituirse por sus clases base. Eso decia Barbara Liskov, cuyo apellido se hace referencia en esta L.

Decir que si utilizamos una clase podríamos cambiarla por una de sus subclases y que todo siga funcionando igual es un poco abstracto, vamos a intentar aterrizarlo un poco. Pero quedate con la idea de que detrás de esto hay un intento de que las cosas sean lo más sencillas y generales posibles.

Vamos a suponer que nuestra aplicación tiene que gestionar algo relacionado con personas. Por lo tanto necesitamos un modelo de datos que soporte el concepto de una persona, y nosotros, ni cortos ni perezosos nos ponemos a definirla. Y decimos que una persona es:

class persona:
    nombre:str
    apellido:str
    dni:str
    cuenta_bancaria:str
    fecha_nacimiento:str

Y nos sentimos satisfechos. Y seguimos con el desarrollo, y salimos a producción. Y entonces un usuario de ocho años intenta usar nuestra aplicación, pero por su edad no tiene ni DNI, ni cuenta bancaria, ni profesión. La aplicación crashea y solo nos queda ver el mundo arder… Pero la culpa no la tiene el usuario, ese precoz niñó que ha querido usar nuestra aplicación es una persona. Hemos sido nosotros los que hemos cometido un error que ahora tenemos que solucionar.

Podriamos llegar y redefinir nuestra clase persona:

class persona:
    nombre:str
    apellido:str
    fecha_nacimiento:str

pero entonces probablemente nuestra aplicación se rompa por todas partes, ya que esos campos serían utilizados en otros sitios. ¿Como tendríamos que hacer esto?

Bueno, una solución podría ser hacer clases lo bastante sencillas y pequeñas como para que ningún caso se salga de ellas, en este caso mantendriamos la clase persona con los campos minimos para que sirvan a cualquier persona:

Y creariamos otra clase, llamada adulto, por ejemplo, que contenga los campos que no tiene por qué tener cualquier individuo que entre en la categoria de persona, pero que si tendrá cualquier adulto

class adulto
    persona: persona
    dni:str
    cuenta_bancaria:str

De esta forma, podemos extender la clase original (persona) con tantas clases hija como queramos sin que nada se rompa, y en un momento dado, si neceistamos cambiar “persona” por “adulto” no debería romper nada, ya que contiene los campos de persona y sencillamente no accederemos a los de adulto si no lo necesitamos.

Como comentaba antes, este principio es algo complejo de ver en un primero momento, pero fundamentalmente lo que busca es que atomizar todo lo posible las definiciones de las calses para que sean más sencillas.

Recuerda que, por lo general, sencillez implica facilidad de mantemiento y evolución.

La I : Principio de segregación de la interfaz

Aquí se delata un poco que todo esto se hizo pensando en Java, y aunque todos los principios son validos en cualquier lenguaje de programación, el contepto “interfaz” está muy ligado al lenguaje de la taza de café.

Lo que mantiene le principio es que es mejor tener muchas interfaces con poco métodos, que pocas interfaces con muchos metodos que luego no se usan.

En cualquier caso, podemos intentar extenderlo a cualquier lenguaje diciendo que es mejor tener muchas funciones pequeñas, que unas pocas muy grandes.

La D : Principio de inversión de dependencias

Aunque la explicación oficial es que deberiamos depender de abtracciones y no de clases concretas, que a su vez estas abtracciones no deberían depender de detalles, sino que los detalles deberían ser lo que dependan de las abstracciones, Podrimos resumirlo en “Las dependencias son malas”. Intenta, en la medida de lo posible, en la medida de lo posible, es recomendable evitar el acoplamiento.

Y esto… ¿para qué?

Gato seniority

Los principios de SOLID no harán que tu código sea más rápido, ni que funcione mejor. Sus objetivos son crear un software que sea robusto, estable, con un código limpio, flexible, reutilizable y fácil de mantener y escalar.

Muchas veces lo complicado no es crear un programa que funcione y haga su cometido. Lo que es realmente complejo es crear software que sea barato de mantener, que se pueda escalar para ampliar sus funcionalidades o se pueda modificar para adaptarlo a las circunstancias que puedan darse a lo largo de su vida. Y esto es lo que se busca con los principios de SOLID, dar una serie de recomendaciones que ayuden a programar con este objetivo en la cabeza.

Como siempre en este tipo de cosas, hay que tener en cuenta que son consejos, criterios y buenas practicas que en la mayoría de los casos tenemos que adaptar a nuestras circunstancias y necesidades. No tiene sentido - en mi opinión - forzarnos a hacer uso de todas estas prácticas si esto complica el desarrollo, o nos impide cumplir con los plazos comprometidos. Pero si son buenas guias y mejoras que se pueden ir realiando en nuestros desarrollos.

Recuerda:

“It is not enough for code to work.” – Robert C. Martin.

Categorías: