Vés al contingut

Bones pràctiques en el desenvolupament de software: integrant els principis SOLID

La qualitat del codi determina no només l’eficiència i funcionalitat del producte final, sinó també la seva capacitat d’adaptar-se i evolucionar davant de nous requisits i desafiaments tecnològics. En aquest context, els principis SOLID emergeixen com una guia fonamental de bones pràctiques en el disseny i la programació orientada a objectes, oferint un marc teòric i pràctic per construir aplicacions més robustes, mantenibles i escalables.

Introduïts per Robert C. Martin, conegut afectuosament com a “Uncle Bob”, aquests cinc principis fonamentals proporcionen als desenvolupadors eines per afrontar alguns dels problemes més habituals en el desenvolupament de software, com ara l’acoblament excessiu, la baixa cohesió i la dificultat de fer proves eficients.

Al llarg d’aquest article, explorarem cadascun dels principis SOLID en detall, analitzant-ne la importància i l’aplicabilitat amb exemples concrets, i mostrant com la seva integració pot transformar el procés de desenvolupament en una pràctica més elegant, eficient i sostenible.

Principi de responsabilitat única (SRP)

El Principi de Responsabilitat Única (Single Responsibility Principle, SRP) és un dels fonaments dels principis SOLID. Estableix que cada classe dins d’un sistema de software hauria de tenir una única raó de canvi, és a dir, hauria d’estar enfocada en una sola funcionalitat o responsabilitat concreta dins del sistema.

La rellevància d’aquest principi rau en la seva capacitat per fomentar un disseny net i modular. Si cada classe compleix una única responsabilitat, el codi esdevé més llegible, més fàcil de mantenir i molt més senzill de testejar amb proves unitàries. A més, aquest enfocament redueix la complexitat general del sistema i evita efectes col·laterals inesperats quan s’hi introdueixen canvis o ampliacions.

Implementació del SRP

Per complir amb el SRP, és fonamental fer una anàlisi detallada dels requisits i les funcionalitats del sistema per identificar i separar les diferents responsabilitats. Cada classe hauria de dissenyar-se de manera que encapsuli una única funcionalitat o aspecte concret del sistema.

Si es detecta que una classe està assumint més d’una responsabilitat, cal plantejar una refactorització que divideixi aquesta classe en altres més petites, cadascuna centrada en una única tasca o funció.

Trampes comunes i anti-patrons

Com hem comentat, sovint les classes es sobrecarreguen amb responsabilitats addicionals. Per exemple, una classe “Llibre” podria contenir no només propietats i mètodes relacionats amb el llibre en si, sinó també la lògica per desar aquest llibre en una base de dades. Aquesta barreja de lògica de domini amb la lògica de persistència és un anti-patró habitual que viola el SRP.

Per respectar el SRP, es recomana separar la lògica de persistència en una altra classe diferent, garantint que cada classe tingui una sola raó per canviar. Aquest enfocament no només fa que el codi sigui més mantenible sinó també més escalable.

Exemple pràctic

A continuació, comentarem un exemple en Python aplicat a un sistema de gestió d’empleats en una empresa. (L’exemple està en castellà.)

Inicialment, tenim una classe “Empleado” que gestiona tant la informació personal de l’empleat com les operacions de càlcul del seu salari.

principios solid 1
principis SOLID

En aquest disseny, la classe “Empleado” té múltiples responsabilitats: mantenir la informació de l’empleat, gestionar el seu salari i també la persistència d’aquests dades. Això viola el Principi de Responsabilitat Única (SRP), ja que hi ha més d’una raó per canviar aquesta classe.

Per adaptar-nos al SRP, podem dividir aquesta classe en dues: una que s’ocupi exclusivament de la informació personal i el salari de l’empleat, i una altra que gestioni la persistència de les dades de l’empleat.

solid principios solid 2
principis SOLID

En aquest enfocament, “Empleado” es centra en les propietats i comportaments directament relacionats amb l’empleat, mentre que “GestorDePersistenciaEmpleado” s’encarrega de totes les interaccions amb la base de dades relacionades amb els empleats. Aquesta separació de responsabilitats fa que el codi sigui més modular, més fàcil de mantenir i més comprensible.

Principi d’Obertura/C tancament (OCP)

El Principi d’Obertura/C tancament (Open/Closed Principle, OCP) estableix que les entitats de software (classes, mòduls, funcions, etc.) han d’estar obertes a l’extensió, però tancades a la modificació. Això significa que un component de software ha de poder ampliar-se sense necessitat de modificar el codi font ja existent.

Tenint això en compte, l’OCP permet que el software creixi i s’adapti a requisits nous o canviants, minimitzant el risc d’introduir errors en el codi que ja està provat i funciona correctament. Això redueix tant el cost com l’esforç associats a la implementació de noves funcionalitats o a l’adaptació a diferents contextos o requisits.

Implementació de l’OCP

La implementació de l’OCP sovint s’aconsegueix mitjançant l’ús d’abstraccions (com ara interfícies o classes abstractes) i preferint la composició per sobre de l’herència. Això permet que els comportaments s’estenguin modificant la manera com es compon un objecte (per exemple, a través de la injecció de dependències) en lloc de canviar el comportament ja existent.

Exemple pràctic

A continuació, comentarem un exemple en Python aplicat a un sistema de processament de pagaments on cal gestionar diferents tipus de pagament (targeta de crèdit, PayPal, criptomonedes, etc.). El codi de l’exemple està en castellà.

Sense l’OCP, podríem estar constantment afegint noves condicions i mètodes a una classe de processament de pagaments cada vegada que cal suportar un nou mètode de pagament, cosa que viola el principi d’estar “tancat per modificació“.

Una solució que respecta l’OCP podria ser definir una interfície “IPago” amb un mètode “procesarPago()“.

Després, per a cada mètode de pagament, crearies una classe que implementi la interfície “IPago” (com ara “PagoTarjetaCredito“, “PagoPayPal“, “PagoCriptomoneda“), tal com es mostra a continuació:

principios solid 3
principis SOLID

Aquest disseny permet afegir nous mètodes de pagament al sistema simplement creant noves classes que implementin “IPago“, sense necessitat de modificar el codi existent que processa els pagaments. Això compleix amb l’OCP, ja que el sistema està tancat a modificacions però obert a extensions.

Principi de Substitució de Liskov (LSP)

El Principi de Substitució de Liskov (Liskov Substitution Principle, LSP), formulat per Barbara Liskov el 1987, és un concepte fonamental en el disseny orientat a objectes. Aquest principi afirma que, si una classe S és un subtip d’una classe T, aleshores els objectes de tipus T en un programa poden ser substituïts per objectes de tipus S (és a dir, objectes de la subclasse) sense alterar cap de les propietats desitjables del programa (correcció, tasca que realitza, etc.).

L’LSP és important per al disseny de sistemes robustos i mantenibles, ja que promou la interoperabilitat i la reutilització del codi. En adherir-se a l’LSP, els desenvolupadors poden estendre i modificar sistemes amb confiança, sabent que els nous subtipus no introduiran errors ni comportaments inesperats.

Implementació de l’LSP

Per a complir amb l’LSP, és essencial assegurar-se que les subclases no canviïn el comportament de les superclasses d’una manera que pugui sorprendre l’usuari del codi. Això inclou respectar les invariants de la superclasse, complir amb els contractes dels mètodes (incloent-hi els requisits dels paràmetres i els valors de retorn) i no introduir noves excepcions que no es puguin gestionar.

Exemple pràctic

A continuació, mostrem com es podria introduir una solució basada en interfícies que distingeixi entre les aus que poden volar i les que no, utilitzant Python. El codi de l’exemple està en castellà.

Primer, definim una interfície general per a “Ave” i una altra per a les aus que poden volar, “AveVoladora“. Després, implementem classes específiques per a diferents tipus d’aus, assegurant-nos que només les aus que poden volar implementin la interfície “AveVoladora“, com es pot veure a continuació:

principios solid 4
principis SOLID

En aquest disseny, “Pájaro” es capaç de menjar i volar, per això implementa tant “Ave” como “AveVoladora“. En canvi, “Pingüino“, que pot menjar però no volar, només implementa “Ave“. Això compleix amb el LSP, ja que podem substituir qualsevol “Ave” per un “Pájaró” o un “Pingüino” sense alterar el comportament esperat pel que fa a la capacitat de menjar. No obstant això, només podem esperar que una “AveVoladora” voli.

Aquest enfocament garanteix que les nostres classes s’adhereixin al LSP, evitant que les subclases (com “Pingüino“) es vegin forçades a implementar mètodes (com “volar“) que no poden utilitzar, mantenint així la coherència i evitant comportaments incorrectes o inesperats.

Principi de Segregació d’Interfícies (ISP)

El Principi de Segregació d’Interfícies (ISP) tracta sobre com s’han d’estructurar les interfícies en un software per fomentar un disseny net i modular. En essència, l’ISP suggereix que “els clients no han de ser obligats a dependre d’interfícies que no utilitzen”.

L’aplicació de l’ISP és fonamental per evitar la “inflació” d’interfícies, on una interfície conté massa mètodes que no són rellevants per a tots els seus consumidors. Això pot portar a la creació d’implementacions que depenen de parts de la interfície que no necessiten, fet que pot fer el codi difícil de mantenir i d’entendre, a més d’incrementar el risc d’errors en temps d’execució.

Implementació de l’ISP

Per adherir-se a l’ISP, les interfícies han de ser específiques als requisits dels seus clients, en lloc de ser genèriques. Això significa dividir interfícies grans i extensives en conjunts més petits i específics que siguin rellevants per als seus respectius clients. Aquesta pràctica afavoreix un disseny més net, on les implementacions només necessiten conèixer i dependre de les interfícies que realment utilitzen.

Exemple pràctic

Imagina un sistema de gestió per a una biblioteca que inclou operacions com imprimir rebuts de préstec, enviar notificacions per email i guardar registres de préstecs. Inicialment, podries tenir una interfície gran “IBibliotecaServicios” amb mètodes per a cadascuna d’aquestes operacions. No obstant això, no tots els clients d’aquesta interfície necessitaran totes aquestes operacions. Per exemple, una classe que només gestiona la impressió de rebuts no hauria de necessitar implementar mètodes per enviar emails o guardar registres.

Per aplicar el ISP al sistema de gestió de la biblioteca, dividirem la interfície gran “IBibliotecaServicios” en interfícies més petites i especialitzades, com ara “IReciboServicio“, “INotificacionServicio” i “IRegistroServicio“, definint-les segons les seves funcionalitats. Cada client implementaria només les interfícies rellevants per a les seves necessitats, promovent així un disseny més net i modular.

A continuació, implementem classes específiques que només necessiten implementar les interfícies rellevants per a les seves responsabilitats. El codi de l’exemple està en castellà:

principios solid 5
principis SOLID

En aquest disseny, cada servei es centra en una única responsabilitat i només implementa la interfície corresponent a aquesta responsabilitat. Per exemple, “ReciboServicio” només s’encarrega d’imprimir rebuts i, per tant, només implementa “IReciboServicio“. Això significa que si una part del sistema només necessita gestionar la impressió de rebuts, pot dependre exclusivament de “IReciboServicio“, sense preocupar-se per les altres funcionalitats.

Aquest enfocament no només compleix amb el ISP, sinó que també facilita el manteniment del codi i millora la seva extensibilitat, ja que afegir noves funcionalitats o modificar les existents té un impacte mínim en les altres parts del sistema.

Dependency Inversion Principle (DIP)

El Principi d’Inversió de Dependències (DIP) se centra en la manera com s’estructuren les dependències dins d’un sistema de software, promovent una estructura que facilita el manteniment i la flexibilitat. El DIP estableix que:

  • Els mòduls d’alt nivell no han de dependre dels mòduls de baix nivell. Tots dos han de dependre d’abstraccions.
  • Les abstraccions no han de dependre dels detalls. Els detalls han de dependre de les abstraccions.

L’aplicació del DIP ajuda a evitar un acoblament rígid entre els mòduls de software, permetent un sistema més fàcil de modificar, ampliar i provar. En dependre d’abstraccions en lloc d’implementacions concretes, els mòduls d’alt nivell no estan lligats directament als detalls dels mòduls de baix nivell, cosa que fomenta un disseny més modular i reutilitzable.

Implementació del DIP

Implementar el DIP sovint implica l’ús d’interfícies o classes abstractes per definir les abstraccions que formen el contracte entre diferents mòduls d’un sistema. Els mòduls d’alt nivell, que contenen la lògica de negoci o les polítiques, defineixen les seves dependències en termes d’aquestes interfícies, mentre que els mòduls de baix nivell, que implementen detalls específics com operacions de base de dades o comunicacions de xarxa, implementen aquestes interfícies.

Exemple pràctic

Per aquest cas, posarem com a exemple una aplicació de comerç electrònic amb un mòdul d’alt nivell que gestiona les comandes dels clients i un mòdul de baix nivell que maneja l’accés a la base de dades de productes.

Sense el DIP, el mòdul de gestió de comandes podria dependre directament de la implementació concreta del mòdul d’accés a la base de dades, cosa que dificultaria, per exemple, canviar a una nova base de dades o font de dades.

Aplicat el DIP, introduiríem una abstracció (per exemple, una interfície “IRepositorioProducto“) que el mòdul de gestió de comandes faria servir per interactuar amb els productes.

A continuació, implementem el mòdul d’accés a la base de dades que maneja els productes, “RepositorioProducto“, el qual implementa la interfície “IRepositorioProducto“. Això podria representar, per exemple, un accés a una base de dades relacional. El codi de l’exemple està en castellà:

principios solid 6
principis SOLID

Ara, creem el mòdul de gestió de comandes, “GestorPedidos“, que depèn de l’abstracció “IRepositorioProducto” en lloc d’una implementació concreta, seguint el DIP:

principios solid 6
principis SOLID

Finalment, a la part de configuració de la nostra aplicació o en el punt d’entrada, injectem la dependència específica de “RepositorioProducto” a “GestorPedidos“:

principios solid 7
principis SOLID

Aquest disseny permet canviar fàcilment la implementació del repositori de productes sense modificar el “GestorPedidos“, per exemple, si decideixes canviar d’una base de dades relacional a una NoSQL. Només cal crear una nova classe que implementi “IRepositorioProducto” per a la nova base de dades i canviar la instància passada a “GestorPedidos“. Això demostra com el DIP facilita un disseny flexible i fàcilment mantenible.

Conclusions

L’adopció dels principis SOLID representa una inversió significativa en la qualitat i la sostenibilitat del desenvolupament de software. A través de l’exploració i aplicació pràctica de cada principi, els desenvolupadors poden assolir un major enteniment i apreciació per un disseny de software que no només compleix amb els requisits actuals de manera eficaç, sinó que també facilita l’adaptació i el creixement futur.

Com hem vist al llarg de l’article, la importància d’aquests principis rau en la seva capacitat per guiar cap a un codi més net, desacoblat i fàcilment extensible, preparant el terreny per a sistemes que puguin evolucionar de manera fluida davant els canvis dels requisits del negoci i dels avenços tecnològics.

Per tant, integrar els principis SOLID en el procés de desenvolupament no és només una pràctica recomanada, sinó una filosofia que promou l’excel·lència en el disseny de software, assegurant que cada línia de codi contribueixi a la construcció d’aplicacions robustes, mantenibles i escalables, que són, en última instància, el cor de la innovació tecnològica.

Vols seguir aprenent sobre programació? No et perdis aquests recursos!

A Block&Capital, especialistes en selecció de personal, treballem per crear oportunitats on el creixement i l’èxit siguin a l’abast de tothom. Si estàs preparat per fer un pas endavant en la teva carrera professional, no dubtis a contactar amb nosaltres.