Principi SOLID: le cinque regole d’oro della OOP
I principi SOLID rappresentano le linee guida per un codice pulito, sostenibile e flessibile nell’ambito della programmazione orientata agli oggetti. Applicando e rispettando tali principi, è possibile progettare software in modo semplice e comprensibile anche nell’arco di lunghi periodi di sviluppo. Questo permette non solo di scrivere un codice migliore, ma anche di mantenere più efficacemente il codice esistente.
Che cosa sono i principi SOLID?
Un buon codice sorgente è caratterizzato da regole, paradigmi di programmazione e uno stile di programmazione appropriato in grado di garantire un codice efficiente e pulito. I cinque principi SOLID sono stati coniati da Robert C. Martin, Bertrand Meyer e Barbara Liskov proprio allo scopo di perseguire queste caratteristiche del codice. Seguendo questi principi nell’ambito della programmazione orientata agli oggetti (OOP) con linguaggi come Python o Java, non solo è possibile scrivere un codice migliore, ma si ottiene anche una manutenzione del codice più efficiente, una progettazione del software flessibile e longeva, oltre che una maggiore sicurezza a lungo termine.
I principi SOLID rappresentano dunque una solida base per tutti coloro che desiderano imparare a programmare. Il nome SOLID, ideato da Micheal Feathers, non è altro che un acronimo delle prime lettere di ciascuno dei cinque principi:
- Single Responsibility Principle (principio di singola responsabilità): una classe dovrebbe avere una singola responsabilità (non un compito) e un solo motivo per cambiare.
- Open Closed Principle (princpio aperto/chiuso): le classi devono essere aperte per l’estensione e chiuse per la modifica.
- Liskov Substitution Principle (principio di sostituzione di Liskov): le sottoclassi devono essere in grado di ereditare e implementare tutti i metodi e le proprietà della superclasse.
- Interface Segregation Principle (principio di segregazione delle interfacce): le interfacce non devono contenere più metodi di quelli richiesti per l’implementazione delle classi.
- Dependency Inversion Principle (principio di inversione delle dipendenze): le classi non devono dipendere da altre classi, ma da interfacce o classi astratte.
Quali vantaggi offrono i principi SOLID?
“Dove non ci sono regole, regna il caos”. Questo è particolarmente vero nell’ambito della programmazione. Quando si programma basta un piccolo errore, un’imprecisione o una lacuna per rendere un buon codice sorgente inutilizzabile. Sono sufficienti classi eccessivamente complesse, che rendono più difficile l’implementazione, o sottoclassi che mancano di singole proprietà delle loro superclassi. I principi SOLID assicurano che il codice da riparare mediante refactoring sia il meno possibile.
I vantaggi dell’applicazione dei principi SOLID includono:
- Chiarezza, pulizia e eleganza: il software e i codici sono più facili da capire, più comprensibili, più efficaci e più gradevoli anche a livello estetico.
- Facilità di manutenzione: la struttura chiara e ordinata rende più semplice mantenere e aggiornare il codice, sia nuovo che obsoleto, anche in caso di team di sviluppo composti da più persone.
- Personalizzabile, estensibile, riutilizzabile: grazie alla migliore leggibilità, alla diminuzione della complessità e delle responsabilità, nonché alla riduzione delle dipendenze delle classi, il codice si presta per essere facilmente modificato e personalizzato, esteso attraverso le interfacce e riutilizzato in modo flessibile.
- Meno soggetto a errori: un codice pulito con una struttura semplice significa che le modifiche apportate a una parte non influiscono involontariamente su altre aree o funzioni.
- Più sicuro e affidabile: un numero minore o nullo di vulnerabilità, incompatibilità o errori migliora la funzionalità e l’affidabilità dei sistemi e quindi anche la sicurezza.
I principi SOLID in sintesi
I principi SOLID rappresentano leregole d’oro per una buona programmazione e dovrebbero fare parte del bagaglio di conoscenze di chiunque si occupi di programmazione a oggetti. Ti presentiamo i cinque principi nel dettaglio.
SRP: Single Responsibility Principle (principio di singola responsabilità)
Come affermato da Robert C. Martin in “Agile Software Development: Principles, Patterns and Practices”:
“Non dovrebbe mai esistere più di un motivo per modificare una classe.”
Il principio di responsabilità unica afferma che per ogni classe della programmazione orientata agli oggetti deve essere applicata una sola responsabilità. Pertanto, deve esserci un solo motivo alla base delle modifiche apportate alla classe. Contrariamente a quanto si possa credere, questo non significa che una classe o ogni modulo possano avere un solo compito. Dovrebbero però essere responsabili soltanto di compiti specifici, in modo da non sovrapporsi ad altre aree.
Una sovrapposizione di responsabilità, ad esempio fornendo funzioni per diverse aree aziendali, in caso di modifiche a un’area può avere un impatto sulla funzionalità dell’intera classe. A causa di molteplici responsabilità e troppe dipendenze, una modifica all’interno di un’area può comportare diverse altre modifiche o errori di codice.
Il principio di singola responsabilità mira quindi a sviluppare moduli coerenti che siano responsabili di un compito ben comprensibile o di oggetti chiaramente definiti. Grazie alla forma ben strutturata, con dipendenze ridotte e implementazioni disaccoppiate, le modifiche e le modulazioni successive possono essere effettuate in modo più semplice, rapido e senza complicazioni.
Le classi, che definiscono i tipi di oggetti, sono l’elemento centrale della programmazione orientata agli oggetti. Possono essere intese come un progetto con attributi per oggetti reali e simili da costruire in oggetti software. Pe questo motivo le classi, note anche come moduli, sono spesso paragonate ai tipi di file.
OCP: Open Closed Principle (principio aperto/chiuso)
Per descrivere il principio aperto/chiuso, riprendiamo questa citazione di Bertrand Meyer e Robert C. Martin in “Object Oriented Software Construction”:
“Le entità software (classi, moduli, funzioni, ecc.) devono essere sia aperte per le estensioni che chiuse per le modifiche.”
Questo principio fa in modo che non sia necessario riscrivere il software nel suo nucleo per implementare le modifiche. In caso contrario, qualora siano necessarie modifiche approfondite del codice, si rischiano errori impercettibili e code smell. Un codice ben strutturato deve poter essere dotato di interfacce che permettano di estenderlo con funzioni aggiuntive. La parola chiave in questo caso è ereditarietà delle classi.
Le nuove funzionalità e le estensioni con nuove funzioni e metodi da implementare possono essere semplicemente agganciate a una superclasse sotto forma di sottoclassi tramite un’interfaccia. In questo modo, non è necessario “armeggiare” con il codice scritto e stabile. Così facendo viene semplificata la manutenzione e la cura dei programmi e la riutilizzabilità degli elementi di codice stabile viene progettata in modo molto più efficiente grazie alle interfacce.
LSP: Liskov Substitution Principle (principio di sostituzione di Liskov)
Citando Barbara H. Liskov e Jeannette M. Wing in “Behavioural Subtyping Using Invariants and Constraints”, il principio afferma quanto segue:
“Se q (x) è una proprietà dell’oggetto x di tipo T, allora q (y) dovrebbe applicarsi a tutti gli oggetti di tipo S, dove S è un sottotipo di T.”
Un concetto che a prima vista potrebbe sembrare criptico, ma in realtà è piuttosto semplice: le sottoclassi collegate o estese devono funzionare come le loro superclassi o classi di base. Ciò significa che ogni sottoclasse deve mantenere le proprietà della rispettiva superclasse tramite l’ereditarietà e queste proprietà non devono essere modificate nella sottoclasse. In linea di principio, devono essere sostituibili: da qui il nome “principio di sostituzione”. Le superclassi, invece, possono essere modificate tramite cambiamenti.
Il principio di sostituzione di Liskov può essere spiegato in maniera comprensibile anche utilizzando il classico esempio del rettangolo e del quadrato di Robert C. Martin. In geometria, tutti i quadrati sono rettangoli, ma non tutti i rettangoli sono quadrati. Un quadrato condivide la proprietà “lati ad angolo retto” con la superclasse dei rettangoli, ma possiede la proprietà aggiuntiva “lati di uguale lunghezza”.
Nella programmazione le cose funzionano diversamente: qui, il presupposto che classi simili o apparentemente identiche si relazionino tra loro o dipendano l’una dall’altra porta a errori, fraintendimenti e codice poco chiaro. Per questo motivo, nella programmazione, la classe “rettangolo” non è un quadrato e la classe “quadrato” non è un rettangolo. Entrambe le classi sono “disaccoppiate” e implementate separatamente. Senza un collegamento integrato tra le classi, un’incomprensione non può portare a errori incrociati tra le classi. Questo aumenta la sicurezza e la stabilità, permettendo di sostituire le implementazioni nelle sottoclassi o nelle superclassi senza ripercussioni.
ISP: Interface Segregation Principle (principio di segregazione delle interfacce)
La definizione di principio di segregazione delle interfacce è stata data da Robert C. Martin in “The Interface Segregation Principle”:
“I client non dovrebbero essere costretti a dipendere da interfacce che non utilizzano.”
Il principio afferma che l’utente non dovrebbe avere l’obbligo di utilizzare interfacce di cui non ha bisogno. In altre parole: per fornire ai client le funzioni di alcune classi, è bene adattare nuove interfacce più piccole a requisiti specifici. In questo modo si evita che le interfacce diventino troppo grandi e si creano forti dipendenze tra le classi. Il vantaggio è che un software con classi disaccoppiate e diverse piccole interfacce adattate a requisiti specifici risulta più facile da mantenere.
DIP: Dependency Inversion Principle (principio di inversione delle dipendenze)
Come riportato da Robert C. Martin in “The Dependency Inversion Principle”, il quinto e ultimo dei principi SOLID afferma:
“A. I moduli di alto livello non dovrebbero dipendere dai moduli di basso livello. Entrambi dovrebbero dipendere da astrazioni. B. Le astrazioni non devono dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.”
Il principio di inversione delle dipendenze assicura che le funzioni e le dipendenze concrete nei livelli del codice sorgente siano basate su interfacce astratte, anziché l’una sull’altra. In altre parole: le architetture software possono essere suddivise approssimativamente in livelli utente superiori e livelli astratti inferiori o più profondi. Logicamente, si può supporre che la base astratta determini il comportamento dei livelli superiori. Ciononostante, il principio afferma che ciò sia soggetto a errori, in quanto i livelli superiori sono dipendenti dai livelli inferiori.
Invece di collegare i livelli superiori a quelli inferiori, le classi dei livelli alti e bassi dovrebbero dipendere da interfacce astratte intermedie. Le interfacce recuperano le funzionalità richieste ai livelli superiori dai livelli inferiori e le rendono disponibili. In questo modo, si può evitare una “gerarchia di dipendenze bottom-to-top (dall’alto verso il basso)”, che nel tempo potrebbe portare a errori nel codice. Questo facilita la riutilizzabilità dei moduli e consente di apportare modifiche alle classi inferiori senza influenzare i livelli superiori.
- Certificato SSL e protezione DDoS
- Velocità, flessibilità e scalabilità
- Dominio e consulente personale
- 1 anno gratis del gestionale di fatturazione elettronica FlexTax
Cosa succede quando i principi SOLID non vengono rispettati?
Un codice elegante e di facile lettura, che renda la manutenzione il più semplice possibile, dovrebbe essere l’obiettivo di chiunque si occupi di sviluppo di software. Trascurando aspetti fondamentali come le linee guida dettate dai principi SOLID, il codice invecchierà male a causa di vulnerabilità, ridondanze, errori accumulati e troppe dipendenze. Nel peggiore dei casi, col passare del tempo il codice diventerà completamente inutilizzabile. Questo rappresenta un serio problema soprattutto nell’ambito dello sviluppo agile del software, che solitamente prevede la collaborazione simultanea di più persone a un codice complesso.
Le conseguenze di un codice non pulito o di una scarsa manutenzione del codice includono:
- Code smell: le vulnerabilità derivanti da codice non pulito sono conosciute come code smell. Solitamente si sviluppano a causa di errori funzionali e programmi incompatibili.
- Code rot: se il codice non viene mantenuto attraverso refactoring o costose revisioni del codice può “marcire” fino a perdere completamente la sua funzionalità. Un altro termine per indicare un codice illeggibile, confuso e ingarbugliato è “spaghetti code”.
- Falle di sicurezza: oltre a guasti, problemi nella manutenzione e incompatibilità, trascurare i principi SOLID può anche portare a falle di sicurezza e vulnerabilità che potrebbero aprire la strada ad attacchi informatici tramite exploit, come gli exploit zero day.
Chi ha sviluppato i principi SOLID?
L’origine dei principi SOLID risiede in diversi principi introdotti per la prima volta da Robert C. Martin (“Uncle Bob”), uno degli iniziatori della programmazione agile, nel suo saggio “Design Principles and Design Patterns” del 2000. A coniare il gruppo dei cinque principi SOLID sono stati Robert C. Martin, Bertrand Meyer e Barbara Liskov. L’acronimo accattivante composto dalle cinque lettere iniziali dei principi è stato ideato da Michael Feathers.
Esistono principi di programmazione simili?
Nell’ambito dello sviluppo software, i principi rappresentano linee guida generali o specifiche e raccomandazioni di azione. In questo contesto, i principi SOLID offrono una serie di principi per il paradigma della programmazione orientata agli oggetti. Altri principi di programmazione per clean code includono:
- Principio DRY (Don’t repeat yourself), che descrive l’importanza di funzioni non ripetute, ovvero rappresentate in maniera unica e singola
- Principio KISS (Keep it simple, stupid), che detta le linee guida per un codice costruito nel modo più semplice possibile