Võib-olla kirjutan millestki, mis on paljudele ilmselge, kuid võib-olla mitte kõigile. Refaktooring on minu arvates keeruline teema, sest see hõlmab koodi muutmist, ilma et see mõjutaks selle toimimist.
Seetõttu on mõnede jaoks arusaamatu, et refaktooring on tegelikult üks programmeerimise valdkond ja see on ka väga oluline osa programmeerija tööst. Kood on pidevalt arenev, seda muudetakse niikaua, kuni on võimalus lisada uusi funktsioone. Samas võib see võtta sellise kuju, mis ei võimalda enam tõhusalt uusi funktsionaalsusi lisada ja lihtsam oleks kogu programm ümber kirjutada.
Mis on refaktooring?
Tavaliselt on vastus, mida te kuulete, et tegemist on koodi struktuuri muutmisega, rakendades rea refaktooring-transformatsioone, ilma et see mõjutaks koodi jälgitavat käitumist. See on tõsi. Hiljuti puutusin ma ka kokku määratlusega, mille autor on Martin Fowler oma raamatus "Olemasoleva koodeksi kujunduse parandamine" kus ta kirjeldab refaktooring kui "suurte muudatuste tegemine väikeste sammudega". Ta kirjeldab refaktooring koodimuudatusena, mis ei mõjuta selle toimimist, kuid ta rõhutab, et seda tuleb teha väikeste sammude kaupa.
Raamat toetab ka seda, et refaktooring ei mõjuta koodi toimimist ja juhib tähelepanu sellele, et see ei mõjuta mingil juhul testide läbimist. Selles kirjeldatakse samm-sammult, kuidas ohutult teostada refaktooring. Mulle meeldis tema raamat, sest selles kirjeldatakse lihtsaid nippe, mida saab kasutada igapäevatöös.
Miks me vajame refaktooringut?
Kõige sagedamini võib seda vaja minna siis, kui soovite võtta kasutusele uue funktsionaalsuse ja kood oma praeguses versioonis seda ei võimalda või oleks see ilma koodi muutmata keerulisem. Samuti on see kasulik juhtudel, kui uute funktsioonide lisamine ei ole ajaliselt tasuv, st oleks kiirem kirjutada kood nullist ümber. Ma arvan, et mõnikord unustatakse, et refaktooring võib muuta koodi puhtamaks ja loetavamaks. Martin kirjutab oma raamatus, kuidas ta teeb refaktooringut, kui ta tunneb koodis ebameeldivaid lõhnu ja kuidas ta seda väljendab, "see jätab alati ruumi paremale". Ja ta üllatas mind siin, nähes refaktoorimist kui igapäevase kooditöö elementi. Minu jaoks on koodid sageli arusaamatud, selle lugemine on natuke kogemus, kuna kood on sageli ebatäpne.
Hästi kavandatud programmi eripäraks on selle modulaarsus, tänu millele piisab enamiku muudatuste tegemiseks vaid väikese osa koodi tundmisest. Modulaarsus lihtsustab ka uute inimeste sisseelamist ja tõhusamat tööle asumist. Modulaarsuse saavutamiseks peavad omavahel seotud programmi elemendid olema grupeeritud, kusjuures seosed peavad olema arusaadavad ja kergesti leitavad. Ei ole olemas ühtset rusikareeglit, kuidas seda teha. Kui te teate ja mõistate üha paremini, kuidas kood peaks toimima, saate elemente rühmitada, kuid mõnikord tuleb ka testida ja kontrollida.
Üks refaktooringu reeglitest on YAGNI, see on akronüüm sõnast "You Aren't Gonna't Need It" ja tuleneb eXtreme programmeerimine (XP) kasutatakse peamiselt Agiilnetarkvaraarendus meeskonnad. Pikk lugu lühidalt, YAGNI ütleb, et teha tuleks ainult ajakohaseid asju. See tähendab põhimõtteliselt seda, et isegi kui midagi võib tulevikus vaja minna, ei tohiks seda praegu teha. Kuid me ei saa ka edasisi laiendusi maha suruda ja siinkohal muutub modulaarsus oluliseks.
Rääkides refaktooringtuleb mainida ühte kõige olulisemat elementi, st teste. Veebilehel refaktooring, peame teadma, et kood ikka veel töötab, sest refaktooring ei muuda selle tööd, vaid selle struktuuri, nii et kõik testid tuleb läbida. Kõige parem on pärast iga väikest ümberkujundamist käivitada testid selle koodi osa jaoks, millega me töötame. See annab meile kinnituse, et kõik töötab nii, nagu peab, ja lühendab kogu toimingu aega. Sellest räägib Martin oma raamatus - käivitage testid nii tihti kui võimalik, et mitte astuda sammu tagasi ja raisata aega, et otsida transformatsiooni, mis midagi rikkus.
Koodide refaktooring ilma katsetamiseta on valus ja on suur võimalus, et midagi läheb valesti. Kui see on võimalik, oleks parem lisada vähemalt mõned põhilised testid, mis annavad meile väikese kindlustunde, et kood töötab.
Allpool loetletud teisendused on ainult näited, kuid need on tõesti kasulikud igapäevases programmeerimises:
Funktsiooni väljavõtmine ja muutujate väljavõtmine - kui funktsioon on liiga pikk, kontrollige, kas on mingeid väiksemaid funktsioone, mida saaks välja võtta. Sama kehtib ka pikkade ridade kohta. Need teisendused võivad aidata leida koodist dubleerimisi. Tänu väikestele funktsioonidele muutub kood selgemaks ja arusaadavamaks,
Funktsioonide ja muutujate ümbernimetamine - hea programmeerimise seisukohalt on oluline kasutada õiget nimetamiskonventsiooni. Muutujate nimed, kui need on hästi valitud, võivad palju koodi kohta öelda,
Funktsioonide rühmitamine klassi - see muudatus on kasulik, kui kaks klassi täidavad sarnaseid operatsioone, sest see võib lühendada klassi pikkust,
Sisestatud avalduse ületamine - kui tingimus kontrollib erijuhtumi puhul, väljastage tingimuse ilmnemisel tagastusavalduse. Seda tüüpi teste nimetatakse sageli kaitseklausliks. Nested Conditional Statement'i asendamine Exit Statement'iga muudab koodi rõhuasetust. Konstruktsioon if-else omistab mõlemale variandile võrdse kaalu. Koodi lugeva inimese jaoks on see sõnum, et mõlemad on võrdselt tõenäolised ja olulised,
Erandjuhtumi tutvustamine - kui kasutate oma koodis mõnda tingimust palju kordi, võib tasuda nende jaoks luua eraldi struktuur. Selle tulemusena saab enamiku erijuhtumite kontrolle asendada lihtsate funktsioonikõnedega. Sageli on tavaline väärtus, mis nõuab eritöötlust, null. Seetõttu nimetatakse seda mustrit sageli nullobjektiks. Seda lähenemist võib aga kasutada igal erijuhtumil,
Tingimuslike juhiste polümorfismi asendamine.
Näide
See on artikkel, mis käsitleb refaktooring ja vaja on näidet. Ma tahan näidata allpool lihtsat refaktooringu näidet, mille puhul on kasutatud Sisestatud avalduse ületamine ja Tingimusjuhiste polümorfismi asendamine. Oletame, et meil on programmifunktsioon, mis tagastab hashi, mis sisaldab teavet selle kohta, kuidas taimi reaalselt kasta. Selline teave oleks tõenäoliselt mudelis, kuid selle näite puhul on see meil funktsioonis.
def watering_info(plant)
result = {}
if plant.is_a? Suculent || plant.is_a? Kaktus
result = { water_amount: ", how_to: "Altpoolt", kastmise_kestus: "2 nädalat" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = { water_amount: "Big amount", how_to: "Nagu soovite", kastmise_kestus: "5 päeva" }
elsif plant.is_a? Peperomia
result = { water_amount: "Dicent amount",
how_to: "Altpoolt! neile ei meeldi vesi lehtedel",
watering_duration: "1 week" }
else
result = { water_amount: "Dicent amount",
how_to: "Nagu soovite",
watering_duration: "1 nädal"
}
end
return result
end
Mõte on muuta, kui naasta:
if plant.isa? Suculent || plant.isa? Cactus
result = { wateramount: ", howto: ",
Ja nii edasi, kuni jõuame funktsioonini, mis näeb välja selline:
def watering_info(plant)
return result = { wateramount: ", howto: "Altpoolt", wateringduration: "2 weeks" } if plant.isa? Suculent || plant.is_a? Cactus
return result = { wateramount: "Big amount", howto: "As you prefer", wateringduration: "5 päeva" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent amount",
howto: "Altpoolt! nad ei armasta vett lehtedel",
wateringduration: "1 nädal" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent amount",
how_to: "Nagu soovite",
kastmise_kestus: "1 nädal"
}
end
Kõige lõpus oli meil juba tagastustulemus. Ja hea harjumus on teha seda samm-sammult ja testida iga muudatust. Selle if ploki võiks asendada switch case'iga ja see näeks kohe paremini välja ning ei peaks iga kord kõiki if'e kontrollima. See näeks välja nii:
def watering_info(plant)
swich plant.class.to_string
case Suculent, Cactus
{ wateramount: "Natuke " , howto: "Altpoolt", watering_duration: "2 nädalat" }
case Alocasia, Maranta
{ wateramount: "Big amount", howto: "Nagu soovite", watering_duration: "5 päeva" }
case Peperomia
{ water_amount: "Dicent amount",
how_to: "Altpoolt! neile ei meeldi vesi lehtedel",
kastmise_kestus: "1 nädal" }
else
{ water_amount: "Dicent amount",
how_to: "Nagu soovite",
kastmise_kestus: "1 nädal" }
end
end
Ja siis saate rakendada Tingimuslike juhiste polümorfismi asendamine. See on luua klass koos funktsiooniga, mis tagastab õige väärtuse ja lülitab need õigetele kohtadele.
klass Suculent
...
def watering_info()
return { wateramount: ", howto: "Alalt", watering_duration: "2 weeks" }
end
end
klass Cactus
...
def watering_info()
return { wateramount: ", howto: "Alalt", watering_duration: "2 weeks" }
end
end
klass Alocasia
...
def watering_info
return { wateramount: "Big amount", howto: "Nagu soovite", watering_duration: "5 days" }
end
end
klass Maranta
...
def watering_info
return { wateramount: "Big amount", howto: "Nagu soovite", watering_duration: "5 days" }
end
end
klass Peperomia
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Altpoolt! neile ei meeldi vesi lehtedel",
kastmise_kestus: "1 nädal" }
end
end
klass Plant
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Nagu soovite",
kastmise_kestus: "1 nädal" }
end
end
Ja põhilises watering_infofunction'is näeb kood välja selline:
def watering_info(plant)
plant.map(&:watering_info)
end
Loomulikult võib selle funktsiooni eemaldada ja asendada selle sisuga. Selle näitega tahtsin esitada üldise refaktooringu muster.
Kokkuvõte
Refaktooring on suur teema. Ma loodan, et see artikkel oli ajendiks rohkem lugeda. Need refaktooringu oskused aitab teil leida oma vigu ja parandada oma puhta koodi töökoda. Soovitan lugeda Martini raamatut (Improving the Design of Existing Code), mis on üsna põhiline ja kasulik reeglistik refaktooring. Autor näitab erinevaid ümberkujundusi samm-sammult koos täieliku selgituse ja motivatsiooniga ning nõuandeid, kuidas vältida vigu refaktooring. Tänu oma mitmekülgsusele on see meeldiv raamat frontendile ja backend arendajad.