Kanskje skriver jeg om noe som er åpenbart for mange, men kanskje ikke for alle. Refaktorering er et komplisert tema, synes jeg, fordi det handler om å endre koden uten å påvirke hvordan den fungerer.
Derfor er det uforståelig for noen at refaktorisering er faktisk et område innen programmering, og det er også en svært viktig del av programmererens arbeid. Koden er i stadig utvikling, og den vil bli endret så lenge det er mulig å legge til ny funksjonalitet. Det kan imidlertid hende at den tar en form som ikke lenger gjør det mulig å legge til ny funksjonalitet på en effektiv måte, og da er det enklere å skrive om hele programmet.
Hva er refaktorisering?
Vanligvis får du til svar at det dreier seg om å endre strukturen i koden ved å bruke en rekke refaktoriseringstransformasjoner uten å påvirke kodens observerbare atferd. Dette er sant. Nylig kom jeg også over en definisjon av Martin Fowler i sin bok "Forbedring av utformingen av eksisterende kodeverk" hvor han beskriver refaktorisering som "å gjøre store endringer med små skritt". Han beskriver refaktorisering som en kodeendring som ikke påvirker driften, men han understreker at det må gjøres i små skritt.
Boken tar også til orde for at refaktorisering ikke påvirker driften av koden, og påpeker at det ikke har noen innvirkning på bestått test på noe tidspunkt. Den beskriver trinn for trinn hvordan du trygt kan utføre refaktorisering. Jeg likte boken fordi den beskriver enkle triks som kan brukes i det daglige arbeidet.
Hvorfor trenger vi refaktorisering?
Oftest trenger du det når du ønsker å introdusere en ny funksjonalitet og koden i sin nåværende versjon ikke tillater det, eller det ville være vanskeligere uten endringer i koden. Det er også nyttig i tilfeller der det er ulønnsomt å legge til flere funksjoner, det vil si at det ville være raskere å skrive om koden fra bunnen av. Jeg tror det noen ganger glemmes at refaktorisering kan gjøre koden renere og mer lesbar. Martin skriver i boken sin hvordan han utfører refaktorering når han kjenner ubehagelig lukt i koden, og, som han uttrykker det, "det gir alltid rom for det bedre". Og han overrasket meg her ved å se refaktorering som et element i det daglige kodearbeidet. For meg er kodene ofte uforståelige, det er litt av en opplevelse å lese den fordi koden ofte er uintuitiv.
Det som kjennetegner et godt utformet program, er dets modularitetDet er derfor nok å kunne bare en liten del av koden for å kunne gjøre de fleste endringer. Modularitet gjør det også lettere for nye personer å komme inn og begynne å jobbe mer effektivt. For å oppnå denne modulariteten må beslektede programelementer grupperes sammen, og forbindelsene må være forståelige og lette å finne. Det finnes ingen tommelfingerregel for hvordan dette kan gjøres. Etter hvert som du vet og forstår mer og mer av hvordan koden er ment å fungere, kan du gruppere elementene bedre, men noen ganger må du også teste og sjekke.
En av reglene for refaktorering i YAGNIDet er et akronym for "You Aren't Gonna Need It" og stammer fra eXtreme-programmering (XP) hovedsakelig brukt i Smidigprogramvareutvikling lag. For å gjøre en lang historie kort, YAGNI sier at bare oppdaterte ting skal gjøres. Det betyr at selv om noe kan bli nødvendig i fremtiden, bør det ikke gjøres akkurat nå. Men vi kan heller ikke knuse ytterligere utvidelser, og det er her modularitet blir viktig.
Når man snakker om refaktoriseringmå et av de viktigste elementene, nemlig testene, nevnes. I refaktoriseringmå vi vite at koden fortsatt fungerer, fordi refaktorisering endrer ikke hvordan den fungerer, men strukturen, så alle tester må bestås. Det beste er å kjøre tester for den delen av koden vi jobber med, etter hver lille endring. Det gir oss en bekreftelse på at alt fungerer som det skal, og det forkorter tiden for hele operasjonen. Det er dette Martin snakker om i boken sin - kjør tester så ofte som mulig for å slippe å gå tilbake og kaste bort tid på å lete etter en transformasjon som ødela noe.
Refaktorisering av kode Uten testing er det vanskelig, og det er stor sjanse for at noe går galt. Hvis det er mulig, er det best å legge til i det minste noen grunnleggende tester som gir oss en liten forsikring om at koden fungerer.
Transformasjonene nedenfor er bare eksempler, men de er svært nyttige i den daglige programmeringen:
Funksjonsekstraksjon og variabelekstraksjon - hvis funksjonen er for lang, bør du sjekke om det finnes mindre funksjoner som kan trekkes ut. Det samme gjelder for lange linjer. Disse transformasjonene kan hjelpe deg med å finne dupliseringer i koden. Takket være Small Functions blir koden tydeligere og mer forståelig,
Omdøping av funksjoner og variabler - riktig navngivning er avgjørende for god programmering. Velvalgte variabelnavn kan fortelle mye om koden,
Gruppering av funksjonene i en klasse - denne endringen er nyttig når to klasser utfører lignende operasjoner, ettersom den kan forkorte lengden på klassen,
Overstyring av den nestede setningen - hvis betingelsen sjekker ut for et spesialtilfelle, utsteder du en retursetning når betingelsen inntreffer. Denne typen tester kalles ofte for guard clause. Hvis du erstatter en nestet betinget setning med en exit-setning, endrer du vektleggingen i koden. If-else-konstruksjonen tildeler begge variantene like stor vekt. For den som leser koden, er det et budskap om at hver av dem er like sannsynlige og viktige,
Innføring av et spesialtilfelle - hvis du bruker noen betingelser i koden din mange ganger, kan det lønne seg å lage en egen struktur for dem. Dermed kan de fleste spesialtilfeller erstattes med enkle funksjonsanrop. Ofte er null den vanligste verdien som krever spesialbehandling. Derfor kalles dette mønsteret ofte for nullobjektet. Denne tilnærmingen kan imidlertid brukes i alle spesialtilfeller,
Erstatning av den betingede instruksjonspolymorfismen.
Eksempel
Dette er en artikkel om refaktorisering og det er behov for et eksempel. Jeg vil vise et enkelt refaktoriseringseksempel nedenfor med bruk av Overstyring av den nestede setningen og Erstatning av den betingede instruksjonspolymorfismen. La oss si at vi har en programfunksjon som returnerer en hash med informasjon om hvordan man vanner planter i virkeligheten. Slik informasjon ville sannsynligvis ligget i modellen, men i dette eksempelet har vi den i funksjonen.
def vanning_info(anlegg)
resultat = {}
if plant.is_a? Suculent || plant.is_a? Kaktus
resultat = { vann_mengde: "Litt " , how_to: "Fra bunnen", vanning_varighet: "2 uker" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
resultat = { vann_mengde: "Stor mengde", how_to: "Som du foretrekker", vanning_varighet: "5 dager" }
elsif plant.is_a? Peperomia
result = { water_amount: "Dicent mengde",
how_to: "Fra bunnen! de liker ikke vann på bladene",
vanning_varighet: "1 uke" }
ellers
resultat = { vann_mengde: "Dicent mengde",
how_to: "Som du foretrekker",
vanning_varighet: "1 uke"
}
end
returner resultat
end
Og så videre med alt, helt til vi kommer til en funksjon som ser slik ut:
def vanning_info(anlegg)
return result = { vannmengde: "Litt " , howto: "Fra bunnen", vanningvarighet: "2 uker" } if plant.isa? Suculent || plant.is_a? Cactus
return result = { vannmengde: "Stor mengde", howto: "Som du foretrekker", vanningvarighet: "5 dager" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent mengde",
howto: "Fra bunnen! de liker ikke vann på bladene",
vanningvarighet: "1 uke" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent amount",
how_to: "Som du foretrekker",
vanning_varighet: "1 uke"
}
slutt
Helt til slutt hadde vi allerede et returresultat. Og en god vane er å gjøre dette steg for steg og teste hver endring. Du kan erstatte denne if-blokken med en switch case, og det vil se bedre ut umiddelbart, og du trenger ikke å sjekke alle ifs hver gang. Det ville se slik ut:
def vanning_info(anlegg)
swich plant.class.to_string
case Suculent, Kaktus
{ vannmengde: "Litt " , howto: "Fra bunnen", vanning_varighet: "2 uker" }
case Alocasia, Maranta
{ vannmengde: "Stor mengde", howto: "Som du foretrekker", vanning_varighet: "5 dager" }
case Peperomia
{ water_amount: "Dicent amount",
how_to: "Fra bunnen! De liker ikke vann på bladene",
vanning_varighet: "1 uke" }
ellers
{ water_amount: "Dicent amount",
how_to: "Som du foretrekker",
vanning_varighet: "1 uke" }
slutt
end
Og så kan du bruke Erstatter den betingede instruksjonspolymorfismen. Dette er for å lage en klasse med en funksjon som returnerer riktig verdi og bytter dem på riktig plass.
klasse Suculent
...
def vanning_info()
return { wateramount: "Litt " , howto: "Fra bunnen", vanning_varighet: "2 uker" }
end
end
klasse Kaktus
...
def vanning_info()
return { wateramount: "Litt " , howto: "Fra bunnen", vanning_varighet: "2 uker" }
end
end
klasse Alocasia
...
def vanning_info
return { wateramount: "Stor mengde", howto: "Som du foretrekker", vanning_varighet: "5 dager" }
end
end
klasse Maranta
...
def vanning_info
return { wateramount: "Stor mengde", howto: "Som du foretrekker", vanning_varighet: "5 dager" }
end
end
klasse Peperomia
...
def vanning_info
return { water_amount: "Dicent amount",
how_to: "Fra bunnen! De liker ikke vann på bladene",
vanning_varighet: "1 uke" }
slutt
end
klassen Plant
...
def vanning_info
return { water_amount: "Dicent amount",
how_to: "Som du foretrekker",
vanning_varighet: "1 uke" }
slutt
end
Og i hovedfunksjonen watering_infofunction vil koden se slik ut:
def vanning_info(anlegg)
plant.map(&:vanning_info)
end
Denne funksjonen kan selvfølgelig fjernes og erstattes med innholdet. Med dette eksemplet ønsket jeg å presentere den generelle refaktoriseringsmønster.
Sammendrag
Refaktorisering er et stort tema. Jeg håper denne artikkelen var et insentiv til å lese mer. Disse ferdigheter i refaktorisering hjelpe deg med å fange opp feil og forbedre din "clean code workshop". Jeg anbefaler å lese Martins bok (Improving the Design of Existing Code), som er et ganske grunnleggende og nyttig sett med regler for refaktorisering. Forfatteren viser ulike transformasjoner trinn for trinn med en full forklaring og motivasjon og tips om hvordan du kan unngå feil i refaktorisering. På grunn av sin allsidighet er det en herlig bok for frontend- og backend-utviklere.