“Ho passato notti insonni cercando di aggiungere funzioni nel codice che abbiamo acquisito da un’altra azienda. Ho a che fare con la forma più pura di Legacy Code”

“Sto avendo una vera difficoltà a trattare con un codice intricato e non strutturato con cui devo lavorare ma di cui non capisco nulla. Legacy Code!”

Legacy code è un termine che probabilmente ha un sacco di definizioni diverse come -codice acquisito da qualcun altro, codice scritto da qualcun altro, codice che è difficile da capire o codice scritto in tecnologie obsolete. Qualunque sia la definizione, la maggior parte di noi crede che il codice legacy faccia paura.

Domanda> Come definiresti il codice legacy?

Definizione del codice legacy

Michael Feathers nel suo libro “Working Effectively with Legacy Code” definisce il codice legacy come codice senza test.

Codice senza test è un cattivo codice. Non importa quanto bene sia scritto; quanto bene sia strutturato; quanto bene sia incapsulato.Senza test non c’è modo di dire se il nostro codice sta migliorando o peggiorando.

Bene, una versione leggermente modificata di questa definizione è “il codice senza test di unità è chiamato codice legacy”. È sempre meglio avere dei test il più vicino possibile al codice (test unitari > test di integrazione > test UI). Quindi, non sarebbe ingiusto chiamare un codice senza test unitari un codice legacy.

Lavorare con il codice legacy

Domanda>Quale approccio adotterai se dovessi fare un cambiamento nel codice legacy?

Molti di noi potrebbero dire, “farò il cambiamento e lo chiamerò un giorno, perché preoccuparsi di migliorare il codice”. La motivazione dietro questo processo di pensiero potrebbe essere –

  • Non ho abbastanza tempo per rifattorizzare il codice, Preferirei fare un cambiamento e completare la mia storia
  • Perché rischiare di cambiare la struttura del codice che è stato in produzione per molto tempo
  • Qual è il beneficio generale del refactoring del codice legacy

Michael Feathers chiama questo stile di fare un cambiamento come Edit and Pray. Pianifichi e fai le tue modifiche e quando hai finito, preghi e preghi ancora di più per ottenere le modifiche giuste.

Con questo stile, si può solo contribuire ad aumentare il codice Legacy.

C’è uno stile diverso di fare modifiche che è Cover and Modify. Costruisci una rete di sicurezza, apporta modifiche al sistema, lascia che la rete di sicurezza fornisca un feedback e lavora su questi feedback.

Si può tranquillamente supporre che Cover and Modify sia un modo per affrontare il codice legacy.

Domanda> Ma, si dovrebbe spendere tempo a scrivere test nel codice legacy o anche solo pensare di rifattorizzare un codice legacy?

Si dovrebbe passare del tempo a pensare di rifattorizzare il codice legacy?

La regola dei boy scout

L’idea dietro la regola dei boy scout, come affermato dallo zio Bob, è abbastanza semplice: Lascia il codice più pulito di come l’hai trovato! Ogni volta che tocchi un vecchio codice, dovresti pulirlo correttamente. Non limitatevi ad applicare una soluzione-scorciatoia che renderà il codice più difficile da capire, ma trattatelo con cura. Non basta scrivere bene il codice, il codice deve essere mantenuto pulito nel tempo.

Abbiamo un messaggio molto forte quando la regola dei Boy Scout viene applicata al codice legacy “lascia una traccia di comprensione dietro di te perché altri la seguano”, il che significa che rifattorizzeremo il codice per renderlo più comprensibile. E per rifattorizzare, costruiremo Safety Net intorno ad esso.

Ora che abbiamo capito che non possiamo prendere scorciatoie l’unica opzione che ci rimane è scrivere alcuni test, rifattorizzare il codice e procedere con lo sviluppo. Domande>

  • Quali test dovremmo scrivere?
  • Quanto dovremmo rifattorizzare?

Quali test scrivere

In quasi tutti i sistemi legacy, quello che il sistema fa è più importante di quello che dovrebbe fare.

Test di caratterizzazione, i test di cui abbiamo bisogno quando vogliamo preservare il comportamento sono chiamati test di caratterizzazione. Un test di caratterizzazione è un test che caratterizza il comportamento effettivo di un pezzo di codice. Non c’è un “Beh, dovrebbe fare questo” o “Penso che faccia quello”. I test documentano l’effettivo comportamento attuale del sistema.

Scrivere test di caratterizzazione

Un test di caratterizzazione per definizione documenta l’effettivo comportamento attuale del sistema nello stesso esatto modo in cui è in esecuzione in ambiente di produzione.

Scriviamo un test di caratterizzazione per un oggetto Cliente che genera una dichiarazione di testo per alcuni film noleggiati da un cliente.

import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){ Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented); john.addRental(rental); String statement = john.generateTextStatement();
assertEquals("", statement);
}

Questo test cerca di capire (o caratterizzare) la generazione della “Dichiarazione di testo” per un cliente dato un film per bambini noleggiato per 3 giorni. Poiché non capiamo il sistema (almeno per ora), ci aspettiamo che l’estratto conto sia vuoto o che contenga un qualsiasi valore fittizio.

Eseguiamo il test e lasciamo che fallisca. Quando lo fa, abbiamo scoperto cosa fa effettivamente il codice in quella condizione.

java.lang.AssertionError: 
Expected :""
Actual :Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points.

Ora che conosciamo il comportamento del codice, possiamo andare avanti e cambiare il test.

import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){
String expectedStatement = "Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points"; Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented);
john.addRental(rental); Sting statement = john.generateTextStatement();
assertEquals(expectedStatement, statement);
}

Aspettate, abbiamo appena copiato l’output generato dal codice e messo nel nostro test. Sì, è esattamente quello che abbiamo fatto.

Non stiamo cercando di trovare bug in questo momento. Stiamo cercando di mettere un meccanismo per trovare i bug più tardi, bug che si mostrano come differenze dal comportamento attuale del sistema. Quando adottiamo questa prospettiva, la nostra visione dei test è diversa: non hanno alcuna autorità morale; stanno semplicemente seduti lì a documentare ciò che il sistema fa realmente. In questa fase, è molto importante avere quella conoscenza di ciò che il sistema fa realmente da qualche parte.

Domanda> Qual è il numero totale di test che scriviamo per caratterizzare un sistema?

Risposta> È infinito. Potremmo dedicare una buona parte della nostra vita a scrivere caso dopo caso per qualsiasi classe in un codice legacy.

Domanda> Quando ci fermiamo allora? C’è un modo per sapere quali casi sono più importanti di altri?

Risposta> Guardate il codice che stiamo caratterizzando. Il codice stesso può darci idee su ciò che fa, e se abbiamo domande, i test sono un modo ideale per farle. A quel punto, scriviamo uno o più test che coprono una porzione abbastanza buona del codice.

Domanda> Questo copre tutto il codice?

Risposta> Potrebbe non farlo. Ma poi facciamo il passo successivo. Pensiamo ai cambiamenti che vogliamo fare nel codice e cerchiamo di capire se i test che abbiamo individueranno i problemi che possiamo causare. Se non lo faranno, aggiungiamo altri test fino a quando ci sentiamo sicuri che lo faranno.

Quanto Rifattorizzare?

C’è così tanto da rifattorizzare nel codice legacy e non possiamo rifattorizzare tutto. Per rispondere a questo abbiamo bisogno di tornare a capire il nostro scopo di rifattorizzare il codice legacy.

Vogliamo rifattorizzare il codice legacy per lasciarlo più pulito di quello che era quando ci è arrivato e per renderlo comprensibile agli altri.

Detto questo, vogliamo rendere il sistema migliore mantenendo il focus sul compito. Non vogliamo impazzire con il refactoring cercando di riscrivere l’intero sistema in pochi giorni. Quello che vogliamo fare è “rifattorizzare il codice che ci intralcia nell’implementazione di ogni nuovo cambiamento”. Cercheremo di capire meglio questo con un esempio nel prossimo articolo.

Conclusione

Articles

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.