En hurtig introduktion til refaktorering for begyndere
Marta Swiatkowska
Junior Software Engineer
Måske skriver jeg om noget, der er indlysende for mange, men måske ikke for alle. Refactoring er, synes jeg, et kompliceret emne, fordi det handler om at ændre koden uden at påvirke dens funktion.
Derfor er det uforståeligt for nogle, at refaktorering er faktisk et område inden for programmering, og det er også en meget vigtig del af programmørens arbejde. Koden er i konstant udvikling, den vil blive ændret, så længe der er mulighed for at tilføje nye funktioner. Men den kan antage en form, der ikke længere giver mulighed for effektivt at tilføje nye funktioner, og det ville være lettere at omskrive hele programmet.
Hvad er refaktorering?
Normalt er svaret, at det er at ændre kodens struktur ved at anvende en række refaktoriseringstransformationer uden at påvirke kodens observerbare adfærd. Det er også rigtigt. For nylig stødte jeg også på en definition af Martin Fowler i sin bog "Forbedring af designet af eksisterende regler" hvor han beskriver refaktorering som "at lave store forandringer med små skridt". Han beskriver refaktorering som en kodeændring, der ikke påvirker driften, men han understreger, at det skal ske i små skridt.
Bogen slår også til lyd for, at refaktorering ikke påvirker kodens funktion og påpeger, at det ikke har nogen effekt på at bestå testene på noget tidspunkt. Den beskriver trin for trin, hvordan man sikkert udfører refaktorering. Jeg kunne godt lide hans bog, fordi den beskriver enkle tricks, der kan bruges i det daglige arbejde.
Hvorfor har vi brug for refaktorering?
Oftest har du brug for det, når du vil indføre en ny funktion, og koden i sin nuværende version ikke tillader det, eller det ville være sværere uden ændringer i koden. Det er også nyttigt i tilfælde, hvor det ikke er rentabelt tidsmæssigt at tilføje flere funktioner, dvs. at det ville være hurtigere at omskrive koden fra bunden. Jeg tror, at man nogle gange glemmer, at refaktorering kan gøre koden renere og mere læsbar. Martin skriver i sin bog, hvordan han udfører refaktorering, når han føler ubehagelige lugte i koden, og, som han udtrykker det, "Det giver altid plads til det bedre". Og her overraskede han mig ved at se refaktorering som et element i det daglige kodearbejde. For mig er koderne ofte uforståelige, og det er lidt af en oplevelse at læse den, da koden ofte er uintuitiv.
Det, der kendetegner et veldesignet program, er dets ModularitetTakket være det er det nok kun at kende en lille del af koden for at indføre de fleste ændringer. Modularitet gør det også lettere for nye folk at komme ind og begynde at arbejde mere effektivt. For at opnå denne modularitet skal relaterede programelementer grupperes sammen, og forbindelserne skal være forståelige og lette at finde. Der findes ikke en enkelt tommelfingerregel for, hvordan det kan gøres. Efterhånden som du ved og forstår mere og mere af, hvordan koden skal fungere, kan du gruppere elementerne, men nogle gange er du også nødt til at teste og kontrollere.
En af reglerne for refaktorering i YAGNIDet er et akronym for 'You Aren't Gonna Need It' og stammer fra eXtreme programmering (XP) bruges hovedsageligt i Agilsoftwareudvikling hold. Lang historie kort, YAGNI siger, at kun aktuelle ting skal gøres. Det betyder dybest set, at selv om der kan blive brug for noget i fremtiden, skal det ikke gøres lige nu. Men vi kan heller ikke forhindre yderligere udvidelser, og det er her, modularitet bliver vigtig.
Når man taler om refaktoreringskal et af de vigtigste elementer, nemlig test, nævnes. I refaktoreringer vi nødt til at vide, at koden stadig virker, fordi refaktorering ændrer ikke, hvordan den fungerer, men dens struktur, så alle tests skal bestås. Det er bedst at køre tests for den del af koden, vi arbejder på, efter hver lille transformation. Det giver os en bekræftelse på, at alt fungerer, som det skal, og det forkorter tiden for hele operationen. Det er det, Martin taler om i sin bog - at køre tests så ofte som muligt for ikke at gå et skridt tilbage og spilde tid på at lede efter en transformation, der ødelagde noget.
Refaktorering af kode Uden test er det besværligt, og der er stor risiko for, at noget går galt. Hvis det er muligt, ville det være bedst at tilføje i det mindste nogle grundlæggende tests, der giver os lidt sikkerhed for, at koden fungerer.
De transformationer, der er anført nedenfor, er kun eksempler, men de er virkelig nyttige i den daglige programmering:
Funktionsudtræk og variabeludtræk - hvis funktionen er for lang, skal du tjekke, om der er mindre funktioner, der kan trækkes ud. Det samme gælder for lange linjer. Disse transformationer kan hjælpe med at finde dobbeltarbejde i koden. Takket være Small Functions bliver koden klarere og mere forståelig,
Omdøbning af funktioner og variabler - at bruge den korrekte navngivningskonvention er afgørende for god programmering. Variabelnavne kan, når de er velvalgte, fortælle meget om koden,
Gruppering af funktionerne i en klasse - denne ændring er nyttig, når to klasser udfører lignende operationer, da det kan forkorte klassens længde,
Overstyring af den indlejrede erklæring - hvis betingelsen er opfyldt i et særligt tilfælde, skal du udstede en retursætning, når betingelsen indtræffer. Denne type test kaldes ofte for guard clause. Hvis man erstatter en indlejret betinget erklæring med en exit-erklæring, ændres vægten i koden. If-else-konstruktionen tildeler begge varianter samme vægt. For den person, der læser koden, er det en besked om, at hver af dem er lige sandsynlige og vigtige,
Introduktion af et specialtilfælde - hvis du bruger nogle betingelser i din kode mange gange, kan det være værd at oprette en separat struktur til dem. På den måde kan de fleste specialtilfælde erstattes med simple funktionskald. Ofte er den almindelige værdi, der kræver særlig behandling, null. Derfor kaldes dette mønster ofte for nul-objektet. Men denne tilgang kan bruges i alle specialtilfælde,
Udskiftning af den betingede instruktionspolymorfisme.
Eksempel
Dette er en artikel om refaktorering og der er brug for et eksempel. Jeg vil gerne vise et simpelt refaktoreringseksempel nedenfor med brug af Overstyring af den indlejrede erklæring og Udskiftning af den betingede instruktionspolymorfi. Lad os sige, at vi har en programfunktion, der returnerer en hash med oplysninger om, hvordan man vander planter i det virkelige liv. Sådanne oplysninger ville sandsynligvis være i modellen, men i dette eksempel har vi dem i funktionen.
def vanding_info(plante)
resultat = {}
hvis plant.is_a? Suculent || plant.is_a? Kaktus
result = { water_amount: "En lille smule" , how_to: "Fra bunden", watering_duration: "2 uger" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = { water_amount: "Stor mængde", how_to: "Som du foretrækker", watering_duration: "5 dage" }
elsif plant.is_a? Peperomia
result = { water_amount: "Bestemt mængde",
how_to: "Fra bunden! De kan ikke lide vand på bladene",
watering_duration: "1 uge" }
ellers
result = { water_amount: "Dicent mængde",
how_to: "Som du foretrækker",
watering_duration: "1 uge"
}
slut
returner resultat
slut
Ideen er at ændre, hvis man skal vende tilbage:
hvis plant.isa? Suculent || plant.isa? Kaktus
resultat = { vandmængde: "En lille smule" , howto: "Fra bunden",
Til
return { water_amount: "En lille smule" , how_to: "Fra bunden",watering_duration: "2 uger" } if plant.is_a? Suculent || plant.is_a? Kaktus
return { vandmængde: "En lille smule" , hvordantil: "Fra bunden", vandingvarighed: "2 uger" } if plant.isa? Sukkulent || plant.is_a? Kaktus
Og så videre med alt, indtil vi kommer til en funktion, der ser sådan ud:
def vanding_info(plante)
return result = { wateramount: "En lille smule" , howto: "Fra bunden", vandingsvarighed: "2 uger" } if plant.isa? Suculent || plant.is_a? Kaktus
return result = { wateramount: "Stor mængde", howto: "Som du foretrækker", vandingsvarighed: "5 dage" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent mængde",
howto: "Fra bunden! De kan ikke lide vand på bladene",
wateringduration: "1 week" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent mængde",
how_to: "Som du foretrækker",
vanding_varighed: "1 uge"
}
slutning
Til allersidst havde vi allerede et returresultat. Og det er en god vane at gøre det trin for trin og teste hver ændring. Du kunne erstatte denne if-blok med en switch case, og det ville straks se bedre ud, og du ville ikke behøve at tjekke alle if'er hver gang. Det ville se sådan ud:
def vanding_info(plante)
swich plant.class.to_string
case Sukkulente, Kaktus
{ vandmængde: "En lille smule" , howto: "Fra bunden", vanding_varighed: "2 uger" }
tilfælde Alocasia, Maranta
{ vandmængde: "Stor mængde", howto: "Som du foretrækker", watering_duration: "5 dage" }
tilfælde Peperomia
{ water_amount: "Dicent mængde",
how_to: "Fra bunden! De kan ikke lide vand på bladene",
vanding_varighed: "1 uge" }
ellers
{ water_amount: "Dicent mængde",
how_to: "Som du foretrækker",
vandingsvarighed: "1 uge" }
slutning
slut
Og så kan du anvende Udskiftning af den betingede instruktionspolymorfisme. Det er for at skabe en klasse med en funktion, der returnerer den korrekte værdi og skifter dem på deres rette plads.
klasse Sukkulente
...
def vanding_info()
return { vandmængde: "En lille smule" , howto: "Fra bunden", watering_duration: "2 uger" }
slut
slut
klasse Kaktus
...
def vanding_info()
return { vandmængde: "En lille smule" , howto: "Fra bunden", watering_duration: "2 uger" }
slut
slut
klasse Alocasia
...
def vanding_info
return { vandmængde: "Stor mængde", howto: "Som du foretrækker", watering_duration: "5 dage" }
slut
slut
klasse Maranta
...
def vanding_info
return { vandmængde: "Stor mængde", howto: "Som du foretrækker", watering_duration: "5 dage" }
slut
slut
klasse Peperomia
...
def vanding_info
return { water_amount: "Dicent beløb",
how_to: "Fra bunden! De kan ikke lide vand på bladene",
vanding_varighed: "1 uge" }
slutning
slut
klasse Plante
...
def vanding_info
return { water_amount: "Dicent beløb",
how_to: "Som du foretrækker",
vandingsvarighed: "1 uge" }
slutning
slut
Og i hovedfunktionen watering_infofunction vil koden se sådan ud:
def vanding_info(plante)
plante.map(&:vanding_info)
slut
Denne funktion kan selvfølgelig fjernes og erstattes med dens indhold. Med dette eksempel ville jeg præsentere den generelle mønster for refaktorering.
Sammenfatning
Refaktorisering er et stort emne. Jeg håber, at denne artikel var et incitament til at læse mere. Disse færdigheder i refaktorering hjælpe dig med at fange dine fejl og forbedre din workshop med ren kode. Jeg anbefaler at læse Martins bog (Improving the Design of Existing Code), som er et ret grundlæggende og nyttigt sæt regler for refaktorering. Forfatteren viser forskellige transformationer trin for trin med en fuld forklaring og motivation og tip til, hvordan man undgår fejl i refaktorering. På grund af sin alsidighed er det en dejlig bog til frontend og backend-udviklere.