Una delle sfide più importanti nello sviluppo di applicazioni software critiche per le aziende è la progettazione di valide architetture che siano in grado di assicurare requisiti non funzionali quali l’estensibilità, la manutenibilità, la testabilità e la leggibilità del codice nel tempo.
Alcuni dei principii da seguire quando si sviluppa software o si effettua il refactoring di sistemi legacy sono sintetizzati nell’acronimo SOLID (Single responsibility, Open- closed, Liskov substitution, Interface segregation, Dependency inversion), un insieme di importanti concetti della programmazione ad oggetti che assieme alle suite di test aiutano a mantenere un’elevata qualità del software.
2. Una delle sfide più importanti nello sviluppo di applicazioni
software critiche per le aziende è la progettazione di valide
architetture che siano in grado di assicurare requisiti non
funzionali quali l’estensibilità, la manutenibilità, la
testabilità e la leggibilità del codice nel tempo.
2Innoteam
3. Quando i clienti ci chiedono infatti di modificare o riscrivere le
loro applicazioni, ciò avviene spesso perchè si ritrovano
con una base di codice sviluppata male che ha costi e
tempi di manutenzione troppo elevati per via delle
(mancate) scelte progettuali; in questi casi riscrivere o
effettuare il refactoring dell’applicazione con adeguati principi
architetturali diventa una scelta da valutare molto seriamente
dopo un accurato audit.
3Innoteam
4. Alcuni di questi principii che seguiamo quando lavoriamo
sulle applicazioni legacy dei clienti, e non solo, sono
sintetizzati nell’acronimo SOLID (Single responsibility, Open-
closed, Liskov substitution, Interface segregation,
Dependency inversion), un insieme di importanti concetti
della programmazione ad oggetti che assieme alle suite di
test aiutano a mantenere un’elevata qualità del software.
4Innoteam
5. Applichiamo questi concetti sia quando riscriviamo ex novo
l’applicazione che quando procediamo al refactoring. Infatti,
sebbene si sia spesso tentati di cestinare la vecchia base di
codice per ripartire da una nuova, non sempre questa è la
soluzione più economica; in molti casi è infatti sufficiente
riutilizzare il codice sufficientemente buono e applicare
l’approccio SOLID riscrivendo le parti attorno e quando
possibile dentro a quelle porzioni di codice.
5Innoteam
6. Questi sono i principi SOLID:
• Single Responsibility Principle (SRP): una classe deve farsi carico di una sola
responsabilità;
• Open Closed Principle (OCP): l’estensione dovrebbe essere preferita rispetto
alla modifica;
• Liskov Substitution Principle (LSP): un oggetto di una classe parente dovrebbe
essere in grado di fare riferimento a oggetti figli tramite polimorfismo;
• Interface Segregation Principle (ISP): un client non dovrebbe essere obbligato a
utilizzare un’interfaccia se non ne ha bisogno, è meglio avere tante interfacce
specifiche piuttosto che una generica per tutti gli scopi;
• Dependency Inversion Principle (DIP): il codice di più alto livello non deve
dipendere dall’implementazione di codice di più basso livello ma dovrebbe
dipendere dalle astrazioni (es. tramite iniezione delle dipendenze).
6Innoteam
7. Il principio della “Single Responsibility” stabilisce che ogni classe deve
avere una sola responsabilità e che questa deve essere gestita
internamente.
Ad esempio, una classe Prodotto che salva i suoi attributi sul database
non dovrebbe definire il proprio codice per aprire la connessione al
database ed effettuare il logging di eventuali errori, ma dovrebbe
affidarsi ad apposite classi dedicate rispettivamente al database e al
logging.
Le classi che violano il principio della singola responsabilità tendono ad
essere difficili da sottoporre a unit testing.
Single Responsibility Principle
7Innoteam
8. Questo principio stabilisce che le classi dovrebbero essere aperte per
l’estensione ma chiuse per la modifica.
“Aperte per l’estensione” significa che le classi andrebbero progettate in modo che
nuove funzionalità possano essere aggiunte tramite ereditarietà man mano che
arrivano nuovi requisiti.
“Chiuse per la modifica” significa che una volta che la classe è stata implementata
non dovrebbe essere più modificata, se non per correggere i bug.
Utilizzando interfacce e classi astratte, e strutturando correttamente le dipendenze,
nuove funzionalità possono essere aggiunte creando nuove classi che implementano
tali interfacce. Un beneficio collaterale di questo principio è che usare le interfacce
per definire le dipendenze comporta la riduzione di accoppiamento e l’ottenimento di
codice orientato ai componenti.
Open Closed Principle
8Innoteam
9. Il principio di sostituzione di Liskov si applica all’ereditarietà, specificando che le
classi devono essere progettate in modo tale che le dipendenze del client
possano essere sostituite con altre sottoclassi senza che il client si accorga
della variazione.
In sostanza, quindi, tutte le sottoclassi devono operare nello stesso modo della
superclasse. Le funzionalità di una sottoclasse possono essere ovviamente differenti
ma devono essere conformi al comportamento che ci si aspetta dalla superclasse.
Liskov Substitution Principle
9Innoteam
10. Questo principio stabilisce che le interfacce che sono diventate molto grandi (in modo
simile alle “God class”) devono essere suddivise in interfacce più piccole, in modo che
il codice client non debba essere forzato a dipendere da metodi dell’interfaccia che non
usa e favorendo la composizione rispetto all’ereditarietà.
In questo modo, quando è necessario modificare alcune di queste interfacce non è necessario
aggiornare larghe porzioni di codice che in realtà non dovrebbero dipendere da tali modifiche.
Inoltre si ottiene un maggiore disaccoppiamento e il refactoring, il redeploy e la modifica di
codice diventano più facili.
Questo principio nasce in Xerox, dove un nuovo sistema di gestione di stampanti con
funzionalità aggiuntive come il fax era costruito attorno ad un’unica classe centralizzata
invocata per ogni attività.
Questa classe conteneva quindi il codice per inviare un fax anche quando veniva istanziata per
effettuare una stampa, rendendo arduo aggiungere e correggere funzionalità, oltre che ad
applicare aggiornamenti. La soluzione applicata prevedeva la suddivisione del codice in più
classi specializzate.
Interface Segregation Principle
10Innoteam
11. Il principio di inversione delle dipendenze stabilisce infine che moduli di alto livello
non devono dipendere da moduli di basso livello, devono invece dipendere da
astrazioni. Inoltre, le astrazioni non devono dipendere dai dettagli, mentre i
dettagli devono dipendere dalle astrazioni.
Questo principio è molto importante per ottenere un forte disaccoppiamento del
codice e per renderlo facile da testare in modo isolato, poiché dettagli come il tipo di
database non sono “hard coded” ma passati come “plugin”.
Dependency Inversion Principle
11Innoteam
12. I principi SOLID sono strumenti preziosi che dovrebbero essere presi in
considerazione sia quando si scrive nuovo codice che quando si
effettua il refactoring di sistemi legacy. In particolare dimostrano la loro
potenza quando sono usati in combinazione.
Sebbene la sensazione dei programmatori meno esperti che si
imbattono in codice e framework progettati con questi principi sia quello
di avere a che fare con sistemi “over-engineered”, la realtà è che non
usare pienamente questi principi porta a pentirsi di non averlo fatto
quando si realizza che il debito tecnico accumulato rende il codice non
più mantenibile in modo efficace.
Conclusione
12Innoteam