“Jeg har haft søvnløse nætter i forsøget på at tilføje funktioner i den kode, vi har købt fra en anden virksomhed. Jeg har med den reneste form for Legacy Code at gøre”

“Jeg har virkelig svært ved at håndtere sammenfiltret, ustruktureret kode, som jeg skal arbejde med, men som jeg ikke forstår en døjt af. Legacy Code !”

Legacy code er et begreb, som nok har mange forskellige definitioner som -kode erhvervet fra en anden, kode skrevet af en anden, kode, der er svær at forstå eller kode skrevet i forældede teknologier. Uanset hvad definitionen er, mener de fleste af os, at legacy kode er skræmmende.

Spørgsmål> Hvordan vil du definere legacy kode?

Definition af legacy kode

Michael Feathers definerer i sin bog “Working Effectively with Legacy Code” legacy kode som, kode uden tests.

Kode uden tests er en dårlig kode. Det er ligegyldigt hvor godt skrevet det er; hvor godt struktureret det er; hvor godt indkapslet det er. uden tests er der ingen måde at fortælle, om vores kode bliver bedre eller dårligere.

En let modificeret version af denne definition er “kode uden unit tests kaldes legacy kode”. Det er altid bedre at have tests så tæt på koden som muligt (enhedstest > integrationstest > UI-test > UI-test). Så det ville ikke være uretfærdigt at kalde en kode uden enhedstests for legacy-kode.

Arbejde med legacy-kode

Spørgsmål> Hvilken fremgangsmåde vil du vælge, hvis du skal foretage en ændring i legacy-kode?

De fleste af os vil måske sige: “Jeg foretager ændringen og så er det slut, hvorfor bekymre mig om at forbedre koden”. Rationale bag denne tankegang kunne være –

  • Jeg har ikke tid nok til at refaktorisere koden, Jeg vil foretrække at foretage en ændring og afslutte min historie
  • Hvorfor risikere at ændre strukturen i den kode, der har kørt i produktion i lang tid
  • Hvad er den overordnede fordel ved at refaktorisere legacy-kode

Michael Feathers kalder denne stil med at foretage en ændring for Edit and Pray. Du planlægger og laver dine ændringer, og når du er færdig, beder og beder du mere for at få dine ændringer rigtigt.

Med denne stil kan man kun bidrage til at øge Legacy-kode.

Der er en anden stil at lave ændringer, som er Cover and Modify. Byg et sikkerhedsnet, lav ændringer i systemet, lad sikkerhedsnettet give feedback og arbejd på disse feedbacks.

Det kan roligt antages, at Cover and Modify er en vej at gå for at håndtere legacy-kode.

Spørgsmål> Men bør man overhovedet bruge tid på at skrive tests i legacy-kode eller overhovedet tænke på at refaktorisere en legacy-kode?

Bør man overhovedet bruge tid på at tænke på at refaktorisere Legacy-kode?

Spejderreglen

Ideen bag spejderreglen er, som onkel Bob sagde, ret enkel: Efterlad koden renere, end du fandt den! Hver gang du rører ved en gammel kode, skal du rense den ordentligt. Du skal ikke bare anvende en genvejsløsning, som vil gøre koden sværere at forstå, men i stedet behandle den med omhu. Det er ikke nok at skrive koden godt, koden skal holdes ren over tid.

Vi får et meget stærkt budskab, når spejderreglen anvendes på ældre kode: “Efterlad et spor af forståelse bag dig, så andre kan følge dig”, hvilket betyder, at vi vil refaktorisere koden, så den bliver mere forståelig. Og for at refaktorere vil vi bygge Safety Net omkring den.

Nu hvor vi forstår, at vi ikke kan tage genveje, er den eneste mulighed, der er tilbage for os, at skrive nogle tests, refaktorere kode og fortsætte med udviklingen. Spørgsmål>

  • Hvilke tests skal vi skrive?
  • Hvor meget skal vi refaktorisere?

Hvilke tests skal vi skrive

I næsten alle legacy-systemer er det, hvad systemet gør, vigtigere end det, det skal gøre.

Karakteriseringstests, de tests, som vi har brug for, når vi ønsker at bevare adfærd, kaldes som karakteriseringstests. En karakteriseringstest er en test, der karakteriserer den faktiske adfærd af et stykke kode. Der er ikke noget “Nå, den burde gøre dette” eller “Jeg tror den gør det”. Testene dokumenterer systemets faktiske aktuelle adfærd.

Skrivning af karakteriseringstest

En karakteriseringstest dokumenterer pr. definition systemets faktiske aktuelle adfærd på nøjagtig samme måde, som det kører i produktionsmiljøet.

Lad os skrive en karakteriseringstest for et Customer-objekt, som genererer en tekstudtalelse for nogle film, der er lejet af en kunde.

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);
}

Denne test forsøger at forstå (eller karakterisere) genereringen af “tekstudtalelse” for en kunde, der får en børnefilm, som er lejet i 3 dage. Da vi ikke forstår systemet (i hvert fald ikke lige nu), forventer vi, at erklæringen er tom eller indeholder en hvilken som helst dummy-værdi.

Lad os køre testen og lade den mislykkes. Når den gør det, har vi fundet ud af, hvad koden faktisk gør under denne betingelse.

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

Nu, hvor vi kender kodens adfærd, kan vi gå videre og ændre testen.

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);
}

Hold fast, har vi lige kopieret det output, der er genereret af koden og placeret i vores test. Ja, det er præcis det, vi gjorde.

Vi forsøger ikke at finde fejl lige nu. Vi forsøger at indføre en mekanisme til at finde fejl senere, fejl, der viser sig som forskelle i forhold til systemets nuværende adfærd. Når vi anlægger dette perspektiv, bliver vores syn på tests anderledes: De har ingen moralsk autoritet; de sidder bare der og dokumenterer, hvad systemet virkelig gør. På dette tidspunkt er det meget vigtigt at have denne viden om, hvad systemet faktisk gør, et eller andet sted.

Spørgsmål> Hvad er det samlede antal tests, som vi skriver for at karakterisere et system?

Svar> Det er uendeligt. Vi kunne dedikere en god del af vores liv til at skrive case efter case for enhver klasse i en legacy-kode.

Spørgsmål> Hvornår stopper vi så? Er der nogen måde at vide, hvilke cases der er vigtigere end andre?

Svar> Se på den kode, som vi karakteriserer. Selve koden kan give os ideer om, hvad den gør, og hvis vi har spørgsmål, er tests en ideel måde at stille dem på. På det tidspunkt skriver du en test eller test, der dækker en god nok del af koden.

Spørgsmål> Dækker det alt i koden?

Svar> Det gør det måske ikke. Men så gør vi det næste skridt. Vi tænker over de ændringer, vi vil foretage i koden, og forsøger at finde ud af, om de tests, vi har, vil fornemme de problemer, som vi kan forårsage. Hvis de ikke vil, tilføjer vi flere tests, indtil vi føler os sikre på, at de vil.

Hvor meget skal refaktoriseres?

Der er så meget at refaktorisere i legacy-kode, og vi kan ikke refaktorisere alt. For at besvare dette er vi nødt til at gå tilbage til at forstå vores formål med at refaktorisere legacy-koden.

Vi ønsker at refaktorisere legacy-kode for at efterlade den renere end den var, da den kom til os, og for at gøre den forståelig for andre.

Med det sagt, ønsker vi at gøre systemet bedre og holde fokus på opgaven. Vi ønsker ikke at gå amok med refactoring og forsøge at omskrive hele systemet på et par dage. Det vi ønsker at gøre er at “refaktorisere den kode, der kommer i vejen for at implementere enhver ny ændring”. Vi vil forsøge at forstå dette bedre med et eksempel i den næste artikel.

Konklusion

Articles

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.