L’importanza di una base SOLIDa

Un titolo come “i principi SOLID” è fin troppo inflazionato, sebbene sia di questo che si parlerà in questo articolo. Di cosa si tratta esattamente? SOLID è un acronimo, e sta ad indicare 5 principi da seguire quando si vuole (aspirare a) scrivere del “clean code“:

  • S: single responsibility principle
  • O: open/closed principle
  • L: Liskov substitution principle
  • I: interface segregation principle
  • D: dependency inversion principle

Vediamoli nel dettaglio, tenendo presente che stiamo parlando di OOP. Per una trattazione completa vi rimando al libro “Agile Software Development, Principles, Patterns, and Practices” (o “Agile Software Development, Principles, Patterns, and Practices in C#, se siete più familiari col mondo .NET) di Robert “Uncle Bob” Martin (prendete la foto che c’è su Wikipedia, stampatela e mettetela sulla scrivania a mo’ di “Uncle Bob vi guarda”).

S: Single responsibility principle

“Una classe dovrebbe avere solo un motivo per cambiare”.

Avete presenti le catene di montaggio? Ogni operaio in una particolare linea ha una sola responsabilità (verniciare la portiera, assemblare quel componente del motore e così via). Pensate al vostro codice come ad una catena di montaggio, e a voi come al Megapresidentegalattico dell’azienda. Sareste contenti di vedere un operaio della catena che fa più cose? Io non lo sarei molto.

Il nome di questo principio è autoesplicativo: ogni elemento di un software (che sia una classe, un metodo o anche una semplice variabile) deve avere una singola responsabilità, e tale responsabilità deve essere interamente incapsulata dall’elemento stesso. L’elemento non deve assolutamente offrire servizi non allineati con la sua responsabilità, per nessun motivo al mondo.

Perché siamo così fissati con la singola responsabilità? Perché siamo dei pessimi programmatori e non sappiamo gestire la complessità di avere più responsabilità? Forse, ma già il fatto di aver nominato la “complessità” ci dovrebbe dare da pensare. Il motivo alla base però è un altro: secondo una responsabilità è un motivo per cambiare, e da qui ne consegue che se un elemento ha, supponiamo, 15 responsabilità (ma anche 2 sarebbe male, al pari dell’incrocio dei flussi) avrebbe anche 15 possibili motivi per cambiare. Se state rabbrividendo siete sulla strada giusta. Se riuscite a pensare a più di un motivo per cui una classe può cambaire, allora quella classe ha più di una responsabilità.

Chiudo con un altro paragone col mondo reale, un oggetto tanto caro a noi sviluppatori: la macchina del caffè (moka o quel che è). E’ un oggetto che noi ammiriamo tantissimo, fa una cosa (il caffè) e (si spera) fatta bene. Se la nostra macchina del caffè ci portasse anche al lavoro la mattina potremmo avere dei problemi.

Attenzione però: un motivo di cambiamento è tale solo quando il cambiamento c’è. Non è saggio applicare il SRP se non ci sono sintomi di cambiamento.

O: Open/closed principle

Il nostro codice deve essere aperto alle estensioni ma chiuso alle modifiche

Principio “aperto/chiuso”. No, non siamo finiti dentro una puntata di Boris, con Duccio che o fa il buio o apre (smarmella) tutto. Prendete ad esempio la solita moka: è indubbiamente aperta alle estensioni (prima c’era solo quella che va sul fornello, poi è venuta quella col fornello elettrico incorporato, o ancora quella che ti sveglia accendendosi e facendo il caffè (potrebbe sembrare una violazione del SRP, ma anche no). Il funzionamento della moka però è sempre lo stesso, è stata solo estesa, non modificata.

Un inciso: se prima avevate il dubbio che io potessi essere un caffeinomane (mi avete già offerto un caffè col link a fondo pagina, vero?) ora il dubbio ve lo siete sicuramente tolto.

Tornando al codice: quando si vuole estendere il proprio codice, non dovrebbe essere necessario andare a scavare nei dettagli implementativo (chiuso alle modifiche: i cambiamenti al codice non sono necessari), ma dovrebbe essere possibile estenderlo semplicemente aggiungendo nuove classi e funzionalità (aperto all’estensione: nuovi comportamenti possono essere aggiunti in futuro) senza dover andare a mettere le mani nel motore.

Ora, come si fa a seguire questo principio? La parola d’ordine è “ereditarietà”, basarsi sulle astrazioni. Introducendo l’astrazione (tramite classi astratte o interfacce) non c’è limite al numero di implementazioni di ciascuna astrazione, e al tempo stesso non serve modificare il codice che già usa queste astrazioni. Questo principio fa il paio col principio di sostituzione di Liskov.

L: Liskov substitution principle

Definizione cervellotica del principio di sostituzione di Liskov:

“Se  q(x) è una proprietà che si può dimostrare essere valida per oggetti  x di tipo  T, allora  q(y) deve essere valida per oggetti y di tipo S dove  S è un sottotipo di T.” (Wikipedia)

Traduzione: è sempre possibile sostituire un sottotipo col suo supertipo. Esempio pratico: chitarra, tamburo e tromba (sottotipo, ovvere classi più specializzate) sono strumenti musicali (supertipo, ovvero classi meno specializzate). Se io sto parlando di una chitarra sto parlando di uno strumento musicale, ma se sto parlando di uno strumento musicale non sto necessariamente parlando di una chitarra. Il sottotipo è sempre sostituibile in tutti i contesti in cui ci si aspetta un supertipo. Traduciamo tutto in un codice molto semplificato:

 

public interface IStrumento
    {
        void Suona();
    }

    public class Chitarra :IStrumento
    {

        public void Suona()
        {
            //suona la chitarra
        }
    }

    public class Tamburo:IStrumento
    {
        public void Suona()
        {
            //suona il tamburo
        }
    }

    public class Tromba:IStrumento
    {

        public void Suona()
        {
            //suona la tromba
        }
    }

Adesso io in qualunque parte della mia applicazione potrò scrivere:

Strumento s = new Chitarra();
s.Suona();

Dove mi aspettavo un riferimento al supertipo ho potuto utilizzare un riferimento al sottotipo. Easy come, easy go.
Adesso qualcuno si chiederà: ma da dove viene il tipo concreto che stiamo usando? Chi decide se è una chitarra, una tromba o un hang drum? A questa domanda risponderà in seguito la D.

I: Interface segregation principle

Questo principio dice che un client (più in generale una classe) non dovrebbe dipendere da metodi che non usa, perciò è preferibile che le interfacce siano molte (in linea di principio), piccole e coese. Esistono un’infinità di marche, di qualità e di miscele di caffè, ma non dobbiamo dipendere da tutte (soprattutto dal Kopi Luwak, molto costoso e dalla produzione non proprio gustosa).

Tornando all’esempio degli strumenti musicali io posso avere strumenti a corda e a fiato. Mettendo tutto nella stessa interfaccia Strumento un codice che suona esclusivamente la chitarra dipende anche da metodi che con la chitarra non hanno nulla a che fare (ad esempio SostituisciAncia()). Oltre ad avere la situazione bizzarra di avere una chitarra a cui si può sostituire l’ancia le classi che suonano solo strumenti a corda dipenderanno anche dalla sostituzione dell’ancia.

D: Dependency inversion principle

Ultimo, ma non ultimo (anche perché è uno dei principi più importanti nella OOP), il principio di inversione delle dipendenze. Uncle Bob lo descrive come una combinazione del principio aperto/chiuso e del principio di sostituzione di Liskov, e lo definisce così:

I moduli di alto livello non devono dipendere da quelli di basso livello. Entrambi devono dipendere da astrazioni;
Le astrazioni non devono dipendere dai dettagli; sono i dettagli che dipendono dalle astrazioni.

Come se ciò non bastasse, è un concetto correlato a quello dell’inversione del controllo (o IoC). Per questo motivo ho deciso di trattare l’argomento in un articolo separato, ma già in redazione.

Per approfondire

In rete è presente molto materiale sui principi SOLID. Due buoni punti di partenza sono il già citato libro di Robert Martin (Agile Software Development, Principles, Patterns, and Practices in C#, qui il link per Amazon) e il corso SOLID Principles of Object Oriented Design di Steve Smith, su Pluralsight.

Se l’articolo vi è piaciuto non esitate a contattarmi sui miei profili social (trovate i link qui accanto) oppure, se preferite, offritemi un caffè (chi mi conosce sa che sono un caffeinomane).

Pierpaolo Paris

Sono uno dei tanti che sono entrati nel girone infernale di Ingegneria Informatica, e che poi ne è anche uscito. Attualmente lavoro come sviluppatore presso una società di consulenza. Ogni tanto mi diletto con la fotografia.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *