Table of Contents
Introduzione: Perché la ricostruzione e la SOLID vanno a mani in mano
Ogni sistema software che è stato in sviluppo attivo per più di pochi mesi accumula inevitabilmente il debito tecnico. Correzioni rapide, requisiti di cambiamento, e la pressione per spedire nuove caratteristiche spesso portano a codice che è fragile, difficile da capire, e difficile da estendere.
Il rifattore è la tecnica disciplinata di ristrutturazione del codice esistente senza alterarne il comportamento esterno. Non corregge bug o aggiunge funzionalità; invece, migliora la struttura interna in modo che i cambiamenti futuri diventino più sicuri e più veloci. I principi SOLID, presentati da Robert C. Martin, forniscono una serie di linee guida di progettazione che, quando seguiti, hanno ottenuto il codice di resa manutenbile, testabile e resiliente a cambiare.
In pratica, molti team di sviluppo lottano per applicare i principi SOLID retroattivamente. Il codice originale può essere monolitico, strettamente accoppiato, o disseminato con logica condizionale. Senza un approccio sistematico, lo sforzo di "fare SOLID" si sente schiacciante. Questo è dove le tecniche di rifattore brillano.
Comprendere i principi SOLID
Prima di immergersi in tecniche di rifattori, una breve ricapitolazione dell'acronimo SOLID metterà la fase:
- Principio di responsabilità individuale (SRP): Una classe dovrebbe avere solo un motivo per cambiare – cioè dovrebbe avere una responsabilità unica e ben definita.
- Principio aperto/permesso (OCP):[ Le entità software (classi, moduli, funzioni) devono essere aperte per l'estensione ma chiuse per la modifica.
- Liskov Substitution Principle (LSP):[] Gli oggetti di una superclasse devono essere sostituibili con oggetti di una sottoclasse senza influire sulla correttezza del programma.
- Principio di segregazione dell'interfaccia (ISP):[ I clienti non dovrebbero essere costretti a dipendere da interfacce che non utilizzano.
- Dependency Inversion Principio (DIP):[ I moduli di alto livello non dovrebbero dipendere da moduli di basso livello; entrambi dovrebbero dipendere dalle astratti.
Ogni principio affronta un tipo specifico di odore di codice. SRP combatte le classi che “sapranno troppo”. OCP combatte le fragili catene condizionali. LSP impedisce le fragili gerarchie ereditarie. ISP combatte le interfacce grasse che forzano inutili implementazioni di metodo. DIP affronta il stretto accoppiamento alle implementazioni concrete.
Refactoring for the Single Responsibility Principi
Identificare le violazioni
Il sintomo più comune di una violazione SRP è una classe che ha più di un motivo per cambiare. Ad esempio, una classe chiamata [] che calcola i totali, formatta la fattura per il display, lo salva in un database, e invia una email ha almeno quattro responsabilità. Qualsiasi cambiamento al calcolo fiscale, formattazione HTML, schema di archiviazione o contenuto di posta elettronica costringerà un cambiamento alla stessa classe di test.
Per individuare queste violazioni, cercare nomi di classe che includono parole come “Manager,” “Processor,” “Helper,” o “Util.” Questi nomi spesso nascondono responsabilità multiple. Inoltre, esaminare le firme metodo della classe: se alcuni metodi prendono parametri che sono irrilevanti ad altri metodi, che è un altro indizio.
Tecniche di rifattore
Il rifattore principale per SRP è ]Extract Class]. Identifichi un insieme di campi e metodi correlati che formano un concetto coessivo e li trasferiscono in una nuova classe. Ad esempio, dalla classe , puoi estrarre , , e ogni nuova classe ha un cambiamento.
Se la logica è sparsa in pochi metodi piuttosto che in una classe intera, usare [Extract Method[] per isolare un certo pezzo di comportamento. Ciò rende la responsabilità più visibile e prepara il terreno per una futura classe di estratto. Una tecnica correlata è Move Method quando un metodo sembra appartenere più logicamente ad un host corrente.
Un'altra tecnica preziosa è Sostituisci il codice in linea con la funzione chiamata[] quando noti una logica ripetuta che appartiene a un dominio diverso. Spostando quella logica a una funzione o classe dedicata, si riduce l'area superficiale della classe primaria e si fanno delle responsabilità esplicite. L'obiettivo è che ogni classe può essere descritta in una sola frase senza usare la parola "e".
Applicare il Rifattore per il Principio Aperto/Chiuso
Sostituzione di Condizioni con Polimorfismo
Il codice che viola OCP contiene spesso grandi [] o dichiarazioni che controllano un certo tipo o modalità. Ad esempio, un metodo che calcola il costo di spedizione basato su una stringa [, ], o []]] è chiuso a nuovi metodi di spedizione.
Il rifattore standard qui è ] Sostituisci condizionale con il polimorfismo[]. Si crea una classe di base astratta o un'interfaccia (ad esempio ]) con un metodo . Ogni metodo di spedizione diventa una sottoclasse concreta. Il codice client originale utilizza l'astrazione, e nuovi metodi di spedizione sono aggiunti creando una nuova sottoclasse chiusa – aperta per la modifica per la sottoclasse.
Utilizzo dei modelli di strategia e decoratore
Il modello Strategy[] è il classico driver OCP. Nel passo rifattore, si inizia solitamente definendo l'interfaccia di strategia, quindi spostare i rami condizionali in classi di strategia separate. Infine, si inietta la strategia appropriata nel client a runtime.
Il modello aiuta quando è necessario aggiungere il comportamento ad un oggetto senza cambiare la sua classe di base. Ad esempio, se si dispone di una classe che genera testo normale, è possibile decorare con ] o ]] senza modificare .
Anche senza schemi di progettazione formale, il principio di favorire la composizione su eredità aiuta OCP. Quando è necessario variare il comportamento, comporre la classe da parti più piccole intercambiabili piuttosto che riempire la logica nella classe stessa.
Rifattore per sostenere il principio di sostituzione di Liskov
Contratti di sottotitolazione e comportamentali
Le violazioni LSP spesso si estendono come metodi in una sottoclasse che gettano eccezioni inaspettate, ritornano dove la classe base restituisce un oggetto valido, o indeboliscono le condizioni precondizioni e rafforzano le condizioni postali. Un esempio classico è una classe che eredita da ma viola il ]]]] / contratto perché una piazza deve mantenere entrambe le dimensioni uguali.
[FLT]] [[LT]]]][[FLT]]]][[[FLT]]]]][[FLT]]]]][[FLT]]]]][[FLT]]]][FLT:]]Richiedi l'eccezione con il controllo precondizionale]]] per rendere esplicito i contratti impliciti.
Utilizzo di Interfacce per Enforce LSP
[LT] [[[FLT]]][LT]] [[[[FLT]]]]]] [[LT]]]]]] [[LT]]]]] [[LT]]]] [[[LT]]]]]] [[[[[[LT]]]]]]]]]]]] [[[[LT]]]]]]]]] [[[[[[[[[[[[[[[[[LT]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]][[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
Un altro utile rifattore è Push Down Method[]: se un metodo in una superclasse ha senso solo per alcune sottoclassi, spostarlo verso quelle sottoclassi. Questo elimina il rischio di una sottoclasse ereditando un metodo inappropriato.
Implementazione della separazione delle interfacce tramite Refactoring
Spalato Interfacce Grasse
[LT] L'interfaccia [LT:37]] è spesso violata quando un'unica interfaccia accumula troppi metodi. Ad esempio, un interfaccia con , [, ], e costringe una semplice stampante di testo per implementare metodi di stub.
Quando si dividono, cercare gruppi di metodi che sono spesso utilizzati insieme da clienti specifici. Un errore comune è diviso in molte piccole interfacce prematuramente. Mirare per interfacce di ruolo: un'interfaccia che rappresenta una singola capacità che un cliente può desiderare. Per esempio, un interfaccia potrebbe avere e ]; questi due metodi sono logicamente accoppiati e improbabili.
Refactoring Existing Codice Cliente
Una volta che l'interfaccia grassa è divisa, è necessario rifare ogni cliente per implementare solo l'interfaccia pertinente. Questo è un mix di Metodo di registrazione] (per accettare l'interfaccia più stretta) e Rinominare la classe (per riflettere il nuovo ruolo).
Rifattore per il principio di inversione di dipendenza
Dipendenze astratto
[LT:50], che istantaneo una classe di basso livello come . Questo costringe a dipendere dall'implementazione del database concreto, rendendo difficile testare e scambiare con un repository in-memory.
Iniezione di dipendenza e inversione di controllo
La tecnica di rifattore Sostituisci il costruttore con il metodo di fabbrica[] può essere utilizzata quando non puoi cambiare facilmente i costruttori. In alternativa, usa Richiedi un riferimento globale con il parametro se la dipendenza è ottenuta da un singolo o un locator di servizio statico.
Una volta che si ha iniezione costruttore, si consideri l'applicazione []Extract Method Object se le dipendenze iniettate sono utilizzate in molti metodi – che può essere un segno che la classe stessa ha ancora troppe responsabilità. Inoltre, cercare classi che dipendono da astrazione multiple diverse ma solo utilizzare un sottoinsieme dei loro metodi; che può indicare una violazione ISP insieme a DIP.
Questo è noto come il Inversione di Proprietà]. Quando rifattore, definire l'astrazione (interfaccia) nello stesso pacchetto come il modulo ad alto livello che lo utilizza, non nel modulo a basso livello. Questo assicura che il modulo ad alto livello non dipende da qualcosa che il modulo a basso livello di controllo del modulo di basso livello noto.
Conclusione: Fare la Refactoring a Habit
Il rafforzamento dei principi SOLID attraverso la rifacimento non è un'attività di una volta ma una disciplina continua. Le tecniche descritte qui – Extract Class, Sostituire Condizionato con Polimorfismo, Estrarre Interface, Introdurre Parameter, e molti altri – sono i blocchi di costruzione che permettono di rimodellare gradualmente un codebase senza romperlo. Ogni piccolo passo riduce il debito tecnico, rende il codice più comprensibile e apre la porta per una più facile estensione e test.
Per approfondire la vostra pratica, studiate il catalogo dei refactoring in Martin Fowler Rifacendo il sito web . Per un trattamento dettagliato dei principi SOLID, Robert C. Martin’s Clean Code blog] fornisce spiegazioni eccellenti. Infine, ricordate che il refactoring senza test è pericoloso.
Iniziare piccolo: scegliere una classe che viola SRP, applicare la classe di estratto, e vedere come il resto del sistema risponde. La fiducia che si ottiene vi incoraggerà ad affrontare il principio successivo. Con una pratica coerente, si internizzerà queste rifattori e iniziare a progettare il codice che naturalmente rispetta SOLID dall'inizio.