“Ik heb slapeloze nachten als ik functies probeer toe te voegen in de code die we van een ander bedrijf hebben overgenomen. Ik heb te maken met de zuiverste vorm van legacy code.”

“Ik heb het erg moeilijk met verwarde, ongestructureerde code waar ik mee moet werken, maar die ik helemaal niet begrijp. Legacy code !”

Legacy code is een term die waarschijnlijk veel verschillende definities heeft, zoals -code die van iemand anders is overgenomen, code die door iemand anders is geschreven, code die moeilijk te begrijpen is of code die is geschreven in verouderde technologieën. Wat de definitie ook is, de meesten van ons zijn van mening dat legacy code eng is.

Vraag> Hoe zou u legacy code definiëren?

Definiëren van legacy code

Michael Feathers definieert in zijn boek “Effectief werken met legacy code” legacy code als, code zonder tests.

Code zonder tests is een slechte code. Het maakt niet uit hoe goed het geschreven is; hoe goed het gestructureerd is; hoe goed het ingekapseld is.Zonder tests is er geen manier om te vertellen of onze code beter of slechter wordt.

Wel, een iets aangepaste versie van deze definitie is “code zonder unit tests wordt legacy code genoemd”. Het is altijd beter om tests zo dicht mogelijk bij de code te hebben (unit tests > integratietests > UI-tests). Het zou dus niet oneerlijk zijn om een code zonder unit tests een legacy code te noemen.

Werken met legacy code

Vraag> Welke benadering kiest u als u een wijziging in legacy code wilt aanbrengen?

De meesten van ons zullen misschien zeggen: “Ik breng de wijziging aan en dan is het klaar, waarom zou ik me nog druk maken over het verbeteren van de code”. De redenering achter deze gedachte zou kunnen zijn –

  • Ik heb niet genoeg tijd om de code te refactoren, Ik zou liever een verandering aanbrengen en mijn verhaal afmaken
  • Waarom het risico nemen de structuur te veranderen van de code die al lang in productie is
  • Wat is het algemene voordeel van het refactoren van legacy code

Michael Feathers noemt deze stijl van veranderen “Edit and Pray”. Je plant en voert je wijzigingen door en als je klaar bent, bid je nog harder om je wijzigingen goed te krijgen.

Met deze stijl kan men alleen maar bijdragen aan het vergroten van legacy code.

Er is een andere stijl van wijzigingen aanbrengen en dat is Cover and Modify. Bouw een vangnet, breng wijzigingen aan in het systeem, laat het vangnet feedback geven en werk aan die feedback.

Het kan veilig worden aangenomen dat Cover and Modify een manier is om met legacy code om te gaan.

Vraag> Maar, moet je wel tijd besteden aan het schrijven van tests in legacy code of zelfs maar denken aan het refactoren van een legacy code?

Moet je überhaupt tijd besteden aan het refactoren van legacy code?

De padvindersregel

De gedachte achter de padvindersregel, zoals die door oom Bob is geformuleerd, is vrij eenvoudig: Laat de code schoner achter dan je hem hebt gevonden! Als je een oude code aanraakt, moet je hem goed schoonmaken. Pas niet zomaar een snelkoppeling toe die de code moeilijker te begrijpen maakt, maar behandel hem met zorg. Het is niet genoeg om code goed te schrijven, de code moet in de loop der tijd ook schoon worden gehouden.

We krijgen een heel sterke boodschap als de padvindersregel wordt toegepast op oude code “laat een spoor van begrip achter zodat anderen het kunnen volgen”, wat betekent dat we de code gaan refactoren om het begrijpelijker te maken. En om te kunnen refactoren, zullen we er een Safety Net omheen bouwen.

Nu we begrijpen dat we geen kortere weg kunnen nemen, blijft ons als enige optie over om enkele tests te schrijven, code te refactoren en verder te gaan met de ontwikkeling. Vragen>

  • Welke tests moeten we schrijven?
  • Hoeveel moeten we refactoren?

Welke tests moeten we schrijven

In bijna elk legacysysteem is het belangrijker wat het systeem doet dan wat het zou moeten doen.

Karakteriseringstests, de tests die we nodig hebben als we gedrag willen behouden, worden karakteriseringstests genoemd. Een karakteriseringstest is een test die het werkelijke gedrag van een stuk code karakteriseert. Er is geen “Wel, het zou dit moeten doen” of “Ik denk dat het dat doet”. De tests documenteren het werkelijke gedrag van het systeem.

Schrijven van karakteriseringstest

Een karakteriseringstest documenteert per definitie het werkelijke gedrag van het systeem op exact dezelfde manier als waarop het in de productieomgeving draait.

Laten we een karakteriseringstest schrijven voor een Customer-object dat een tekstverklaring genereert voor enkele films die door een klant zijn gehuurd.

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

Deze test probeert de “Tekstverklaring”-generatie te begrijpen (of te karakteriseren) voor een klant die een kinderfilm heeft gehuurd voor 3 dagen. Omdat we het systeem niet begrijpen (althans niet vanaf nu), verwachten we dat de verklaring leeg is of een willekeurige dummy waarde bevat.

Laten we de test uitvoeren en laten we hem mislukken. Als dat gebeurt, weten we wat de code onder die omstandigheid doet.

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

Nu we het gedrag van de code kennen, kunnen we de test aanpassen.

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

Wacht eens even, hebben we de uitvoer van de code gekopieerd en in onze test geplaatst? Ja, dat is precies wat we deden.

We proberen nu geen bugs te vinden. We proberen een mechanisme in te bouwen om later bugs te vinden, bugs die zich manifesteren als verschillen van het huidige gedrag van het systeem. Als we dit perspectief aannemen, is onze kijk op tests anders: Ze hebben geen morele autoriteit; ze zitten daar gewoon te documenteren wat het systeem werkelijk doet. In dit stadium is het erg belangrijk om die kennis van wat het systeem werkelijk doet ergens te hebben.

Vraag> Wat is het totale aantal tests dat we schrijven om een systeem te karakteriseren?

Antwoord> Het is oneindig. We zouden een groot deel van ons leven kunnen wijden aan het schrijven van case na case voor elke klasse in een legacy code.

Vraag> Wanneer stoppen we dan? Is er een manier om te weten welke gevallen belangrijker zijn dan andere?

Antwoord> Kijk naar de code die we karakteriseren. De code zelf kan ons ideeën geven over wat het doet, en als we vragen hebben, zijn tests een ideale manier om ze te stellen. Schrijf op dat punt een test of tests die een voldoende groot deel van de code bestrijken.

Vraag> Bestrijkt dat alles in de code?

Antwoord> Het zou kunnen van niet. Maar dan doen we de volgende stap. We denken na over de veranderingen die we in de code willen aanbrengen en proberen uit te vinden of de tests die we hebben problemen zullen opmerken die we kunnen veroorzaken. Als dat niet het geval is, voegen we meer tests toe totdat we er zeker van zijn dat ze dat wel zullen doen.

Hoeveel te refactoren?

Er is zoveel te refactoren in legacy code en we kunnen niet alles refactoren. Om dit te beantwoorden moeten we terug naar het doel van het refactoren van de legacy code.

We willen legacy code refactoren om het schoner achter te laten dan wat het was toen het bij ons kwam en om het begrijpelijk te maken voor anderen.

Met dat gezegd, we willen het systeem beter maken en de focus op de taak houden. We willen ons niet gek laten maken door refactoring en proberen het hele systeem in een paar dagen te herschrijven. Wat we willen doen is “de code refactoren die in onze weg komt bij het implementeren van elke nieuwe verandering”. We zullen proberen dit beter te begrijpen met een voorbeeld in het volgende artikel.

Conclusie

Articles

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.