Een snelle inleiding op refactoring voor beginners
Marta Swiatkowska
Junior Software Engineer
Misschien schrijf ik over iets dat voor velen duidelijk is, maar misschien niet voor iedereen. Refactoring is, denk ik, een ingewikkeld onderwerp omdat het gaat om het veranderen van de code zonder de werking ervan te beïnvloeden.
Daarom is het voor sommigen onbegrijpelijk dat refactoring is eigenlijk een gebied van programmeren, en het is ook een zeer belangrijk onderdeel van het werk van de programmeur. De code is altijd in ontwikkeling, hij wordt aangepast zolang de mogelijkheid bestaat om nieuwe functionaliteiten toe te voegen. De code kan echter een vorm aannemen waarbij het niet langer mogelijk is om effectief nieuwe functionaliteiten toe te voegen en het eenvoudiger zou zijn om het hele programma te herschrijven.
Wat is refactoring?
Meestal is het antwoord dat je hoort dat het de structuur van de code verandert door een reeks refactoring transformaties toe te passen zonder het waarneembare gedrag van de code te beïnvloeden. Dit is waar. Onlangs kwam ik ook een definitie tegen van Martin Fowler in zijn boek "Het ontwerp van bestaande code verbeteren". waar hij beschrijft refactoring als "grote veranderingen maken in kleine stappen". Hij beschrijft refactoring als een codewijziging die geen invloed heeft op de werking, maar hij benadrukt dat het in kleine stappen moet gebeuren.
Het boek pleit er ook voor dat refactoring geen invloed heeft op de werking van de code en wijst erop dat het op geen enkel moment invloed heeft op het slagen voor de tests. Het beschrijft stap voor stap hoe je veilig refactoring. Ik vond zijn boek leuk omdat het eenvoudige trucs beschrijft die in het dagelijks werk kunnen worden gebruikt.
Waarom hebben we refactoring nodig?
Meestal heb je het nodig wanneer je een nieuwe functionaliteit wilt introduceren en de code in de huidige versie dit niet toelaat of het moeilijker zou zijn zonder wijzigingen aan de code. Het is ook nuttig in gevallen waarin het toevoegen van meer functies tijdtechnisch niet rendabel is, dat wil zeggen, het zou sneller zijn om de code vanaf nul te herschrijven. Ik denk dat soms vergeten wordt dat refactoring kan de code schoner en leesbaarder maken. Martin schrijft in zijn boek hoe hij refactoring uitvoert als hij onaangename geuren in de code voelt en, hoe hij het zegt, "het laat altijd ruimte voor het betere". En hij verraste me hier door refactoring te zien als een onderdeel van het dagelijkse codewerk. Voor mij zijn de codes vaak onbegrijpelijk, het lezen ervan is een beetje een ervaring omdat de code vaak onintuïtief is.
Het onderscheidende kenmerk van een goed ontworpen programma is zijn modulariteitwaardoor het voldoende is om slechts een klein deel van de code te kennen om de meeste wijzigingen door te voeren. Modulariteit maakt het ook makkelijker voor nieuwe mensen om in te stappen en efficiënter aan de slag te gaan. Om deze modulariteit te bereiken, moeten verwante programma-elementen worden gegroepeerd, waarbij de verbindingen begrijpelijk en gemakkelijk te vinden zijn. Er is geen eenduidige vuistregel over hoe dit gedaan kan worden. Naarmate je meer en meer weet en begrijpt van hoe de code zou moeten werken, kun je de elementen groeperen, maar soms moet je ook testen en controleren.
Een van de regels van refactoring in YAGNIHet is een acroniem voor 'You Aren't Gonna Need It' en komt van eXtreme Programmeren (XP) voornamelijk gebruikt in Agilesoftwareontwikkeling teams. Lang verhaal kort, YAGNI zegt dat alleen actuele dingen gedaan moeten worden. Dit betekent in feite dat zelfs als iets in de toekomst nodig zou kunnen zijn, het nu nog niet gedaan zou moeten worden. Maar we kunnen ook verdere uitbreidingen niet verpletteren en dit is waar modulariteit belangrijk wordt.
Als je het hebt over refactoringmoet een van de meest essentiële elementen, namelijk testen, worden genoemd. In refactoringmoeten we weten dat de code nog steeds werkt, omdat refactoring verandert niet hoe het werkt, maar de structuur, dus alle tests moeten worden doorstaan. Het is het beste om tests uit te voeren voor het deel van de code waar we aan werken na elke kleine transformatie. Het geeft ons een bevestiging dat alles werkt zoals het zou moeten en het verkort de tijd van de hele operatie. Dit is waar Martin het over heeft in zijn boek - zo vaak mogelijk testen uitvoeren om niet een stap terug te doen en tijd te verspillen aan het zoeken naar een transformatie die iets kapot heeft gemaakt.
Code refactoring Zonder testen is het lastig en er is een grote kans dat er iets fout gaat. Als het mogelijk is, zou het het beste zijn om op zijn minst wat basistests toe te voegen die ons een beetje zekerheid geven dat de code werkt.
De transformaties hieronder zijn slechts voorbeelden, maar ze zijn echt nuttig bij het dagelijkse programmeren:
Functie-extractie en variabele-extractie - als de functie te lang is, controleer dan of er kleine functies zijn die geëxtraheerd kunnen worden. Hetzelfde geldt voor lange regels. Deze transformaties kunnen helpen bij het vinden van duplicaties in de code. Dankzij Small Functions wordt de code duidelijker en begrijpelijker,
Hernoemen van functies en variabelen - het gebruik van de juiste naamgevingsconventie is essentieel voor goed programmeren. Goed gekozen namen van variabelen kunnen veel vertellen over de code,
De functies groeperen in een klasse - deze wijziging is handig als twee klassen vergelijkbare bewerkingen uitvoeren, omdat het de lengte van de klasse kan verkorten,
De geneste verklaring opheffen - als de voorwaarde klopt voor een speciaal geval, geef dan een retouropmerking wanneer de voorwaarde optreedt. Naar dit soort tests wordt vaak verwezen als de guard clause. Het vervangen van een geneste voorwaardelijke instructie door een afsluitende instructie verandert de nadruk in de code. De if-else constructie kent aan beide varianten evenveel gewicht toe. Voor de persoon die de code leest, is het een boodschap dat elk van beide even waarschijnlijk en belangrijk is,
Een speciaal geval introduceren - als je sommige voorwaarden vaak in je code gebruikt, kan het de moeite waard zijn om er een aparte structuur voor te maken. Hierdoor kunnen de meeste controles van speciale gevallen worden vervangen door eenvoudige functie-aanroepen. Vaak is de algemene waarde die speciale verwerking vereist null. Daarom wordt dit patroon vaak het Nul Object genoemd. Deze aanpak kan echter in elk speciaal geval worden gebruikt,
Vervanging van het voorwaardelijke instructiepolymorfisme.
Voorbeeld
Dit is een artikel over refactoring en er is een voorbeeld nodig. Ik wil hieronder een eenvoudig voorbeeld van refactoring laten zien met behulp van De geneste verklaring overschrijven en Vervanging van het voorwaardelijke instructiepolymorfisme. Laten we zeggen dat we een programmafunctie hebben die een hash teruggeeft met informatie over hoe je planten in het echt water geeft. Zulke informatie zou waarschijnlijk in het model zitten, maar voor dit voorbeeld hebben we het in de functie.
def besproei_info(plant)
resultaat = {}
if plant.is_a? Suculent || plant.is_a? Cactus
resultaat = {water_hoeveelheid: "Een klein beetje " , how_to: "Vanaf de bodem", water geven_duur: "2 weken" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = {water_hoeveelheid: "Grote hoeveelheid", hoe_te: "Zoals u wilt", water geven_duur: "5 dagen" }
elsif plant.is_a? Peperomia
result = {water_hoeveelheid: "Dicent hoeveelheid",
how_to: "Vanaf de onderkant! Ze houden niet van water op de bladeren",
watergift_duur: "1 week" }
anders
resultaat = {water_hoeveelheid: "Dicent amount",
how_to: "Zoals u verkiest",
bewatering_duur: "1 week"
}
einde
resultaat retourneren
einde
Het idee is om te veranderen als terug te keren:
Als plant.isa? Suculent || plant.isa? Cactus
result = {waterhoeveelheid: "Een beetje " , howto: "Vanaf de bodem",
Naar
return {water_hoeveelheid: "Een klein beetje " , how_to: "Vanaf de bodem" , watering_duration: "2 weken" } if plant.is_a? Suculent || plant.is_a? Cactus
return {waterhoeveelheid: "Een beetje" , hoenaar: "Vanaf de bodem",water gevenduur: "2 weken" } if plant.isa? Suculent || plant.is_a? Cactus
En zo verder tot we bij een functie komen die er als volgt uitziet:
def besproei_info(plant)
return result = {waterhoeveelheid: "Een klein beetje " , hoezo: "Vanaf de bodem", bewateringsduur: "2 weken" } if plant.isa? Suculent || plant.is_a? Cactus
return result = {waterhoeveelheid: "Grote hoeveelheid", hoezo: "Zoals u wilt", watergeefduur: "5 dagen" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = {water_hoeveelheid: "Dicent amount",
howto: "Vanaf de bodem! Ze houden niet van water op de bladeren",
watergeefduur: "1 week" } if plant.is_a? Peperomia
return result = {water_hoeveelheid: "Dicent amount",
how_to: "Zoals u verkiest",
bewatering_duur: "1 week".
}
einde
Helemaal aan het einde hadden we al een resultaat. En een goede gewoonte is om dit stap voor stap te doen en elke verandering te testen. Je zou dit if-blok kunnen vervangen door een switch-case en dan ziet het er meteen beter uit en hoef je niet elke keer alle ifs te controleren. Het zou er dan zo uitzien:
def besproei_info(plant)
swich plant.class.to_string
geval Suculent, Cactus
{waterhoeveelheid: "Een beetje " , hoezo: "Vanaf de bodem", water geven_duur: "2 weken" }
geval Alocasia, Maranta
{waterhoeveelheid: "Grote hoeveelheid", hoezo: "Zoals u wilt", water geven_duur: "5 dagen" }
geval Peperomia
{water_hoeveelheid: "Dicent hoeveelheid",
how_to: "Vanaf de onderkant! Ze houden niet van water op de bladeren",
watering_duration: "1 week" }
anders
{water_bedrag: "Dicent amount",
how_to: "Zoals u verkiest",
bewatering_duur: "1 week" }
einde
einde
En dan kun je de Het voorwaardelijke instructiepolymorfisme vervangen. Dit is om een klasse te maken met een functie die de juiste waarde teruggeeft en ze op de juiste plaats zet.
Klasse Suculent
...
def watergift_info()
return {waterhoeveelheid: "Een beetje " , hoezo: "Vanaf de bodem", watering_duration: "2 weken" }
einde
einde
klasse Cactus
...
def bewatering_info()
return {waterhoeveelheid: "Een klein beetje " , howto: "Vanaf de bodem", watering_duration: "2 weken" }
einde
einde
klasse Alocasia
...
def besproeiing_info
return {wateramount: "Grote hoeveelheid", howto: "Zoals u verkiest", watering_duration: "5 dagen" }
einde
einde
klasse Maranta
...
def besproeiing_info
return {wateramount: "Grote hoeveelheid", howto: "Zoals u verkiest", watering_duration: "5 dagen" }
einde
einde
klasse Peperomia
...
def watergift_info
return {water_bedrag: "Dicent amount",
how_to: "Vanaf de onderkant! Ze houden niet van water op de bladeren",
watering_duration: "1 week" }
einde
einde
Klasse Plant
...
def besproeiing_info
return {water_bedrag: "Dicent amount",
how_to: "Zoals u verkiest",
bewatering_duur: "1 week" }
einde
einde
En in de hoofdwatering_infofunctie ziet de code er als volgt uit:
def besproei_info(plant)
plant.map(&:besproeiing_info)
einde
Natuurlijk kan deze functie worden verwijderd en vervangen door zijn inhoud. Met dit voorbeeld wilde ik de algemene patroon van refactoring.
Samenvatting
Refactoring is een groot onderwerp. Ik hoop dat dit artikel een stimulans was om meer te lezen. Deze vaardigheden in refactoring u helpen bugs te vinden en uw schone code-workshop te verbeteren. Ik raad aan om het boek van Martin te lezen (Improving the Design of Existing Code), dat een vrij basale en nuttige verzameling regels bevat van refactoring. De auteur toont verschillende transformaties stap voor stap met een volledige uitleg en motivatie en tips om fouten te vermijden in refactoring. Door zijn veelzijdigheid is het een heerlijk boek voor frontend en ontwikkelaars van backends.