Marii dezvoltatori știu când și cum să refactoreze codul șinelor
Publicat: 2022-03-11Refactorizați la scară largă: De ce ați face vreodată așa ceva?
Dacă nu este stricat, nu-l repara.
Este o frază bine cunoscută, dar după cum știm, cea mai mare parte a progresului tehnologic uman a fost făcut de oameni care au decis să repare ceea ce nu este stricat. În special în industria de software, s-ar putea argumenta că cea mai mare parte a ceea ce facem este să reparăm ceea ce nu este stricat.
Remedierea funcționalității, îmbunătățirea interfeței de utilizare, îmbunătățirea vitezei și a eficienței memoriei, adăugarea de funcții: toate acestea sunt activități pentru care este ușor de văzut dacă merită făcute, iar noi, ca dezvoltatori experimentați Rails, argumentăm pentru sau împotriva ne petrecem timpul cu ele. Cu toate acestea, există o activitate, care în cea mai mare parte se încadrează într-o zonă gri – refactorizarea standard și, în special, refactorizarea codului la scară largă.
Termenul de refactorizare la scară largă merită explicat. Exact ceea ce poate fi considerat „la scară mare” va varia de la caz la caz, deoarece termenul este puțin vag, dar consider că orice lucru afectează semnificativ mai mult decât doar câteva clase sau mai mult decât un singur subsistem, iar interfața sa este „mare”. .” Pe de altă parte, orice refactorizare Rails care rămâne ascunsă în spatele interfeței unei singure clase ar fi cu siguranță „mică”. Desigur, există multă zonă gri între ele. În cele din urmă, ai încredere în instinctul tău, dacă îți este frică să o faci, atunci probabil că este „mare”.
Refactorizarea, prin definiție, nu produce nicio funcționalitate vizibilă, nimic din ce puteți arăta clientului, niciun livrabil. În cel mai bun caz, ar putea produce îmbunătățiri mici ale vitezei și utilizării memoriei, dar acesta nu este scopul principal. S-ar putea spune că scopul principal este codul de care ești mulțumit. Dar pentru că rearanjați codul în așa fel încât să aibă consecințe de mare anvergură în întreaga bază de cod, există șansa ca iadul să se dezlănțuie și să apară probleme. De aici vine, bineînțeles, teama pe care am menționat-o. Ați introdus vreodată pe cineva nou în baza de cod și după ce a întrebat despre o bucată de cod organizat în mod special, ați răspuns cu ceva de genul:
Yeeeaahh, acesta este un cod vechi care avea sens la momentul respectiv, dar specificațiile s-au schimbat și acum este prea scump să-l reparăm?
Poate chiar le-ai aruncat o privire foarte serioasă și le-ai spus să lase și să nu-l atingi.
Întrebarea „De ce am vrea să facem asta?” este una firească și poate fi la fel de importantă ca și procesul de refactorizare, deoarece destul de des există alte persoane pe care trebuie să-i convingi pentru a-ți permite să-ți petreci timpul scump pe refactorizare. Deci, să luăm în considerare cazurile în care ați dori să o faceți și beneficiile care trebuie câștigate:
Imbunatatiri ale performantei
Sunteți mulțumit de organizarea curentă a codului dvs. din punct de vedere al mentenanței, dar încă provoacă probleme de performanță. Este prea greu să optimizați modul în care este configurat în prezent, iar modificările ar fi foarte fragile.
Există un singur lucru de făcut aici și este să-l profilați pe larg. Rulați benchmark-uri și faceți estimări despre cât veți câștiga și apoi încercați să estimați cum se va traduce în câștiguri concrete. Uneori ați putea chiar să realizați că refactorizarea de cod propusă nu merită. Alteori, veți avea date reci pentru a vă susține cazul.
Îmbunătățiri arhitecturale
Poate că arhitectura este în regulă, dar oarecum depășită, sau poate că este atât de rău încât să te înfioți de fiecare dată când atingi acea parte a bazei de cod. Funcționează bine și rapid, dar este greu să adăugați funcții noi. În această durere se află valoarea de business a refactorizării. „Durerea” înseamnă, de asemenea, că procesul de refactorizare va dura mai mult pentru a adăuga noi caracteristici, poate mult mai mult.
Și există un beneficiu de câștigat. Faceți estimări ale costului/beneficiului pentru câteva caracteristici eșantion, cu și fără refactorizarea mare propusă de dvs. Explicați că diferențe similare se vor aplica pentru majoritatea caracteristicilor viitoare care ating acea parte a sistemului acum și pentru totdeauna în viitor, în timp ce sistemul este în curs de dezvoltare. Este posibil ca estimările dvs. să fie greșite, deoarece acestea sunt adesea în dezvoltarea de software, dar ratele lor vor fi probabil pe teren.
Aducerea la zi
Uneori, codul este inițial bine scris. Ești extrem de mulțumit de el. Este rapid, eficient în memorie, întreținut și bine aliniat cu specificațiile. Inițial. Dar apoi specificațiile se schimbă, obiectivele de afaceri se schimbă sau înveți ceva nou despre utilizatorii tăi finali, care infirmă ipotezele tale inițiale. Codul încă funcționează bine și ești încă destul de mulțumit de el, dar ceva este pur și simplu ciudat când te uiți la el în contextul produsului final. Lucrurile sunt plasate în subsistemul ușor greșit sau proprietățile sunt situate în clasa greșită, sau poate că unele nume nu mai au sens. Ei îndeplinesc acum un rol care este numit în termeni de afaceri complet diferit. Cu toate acestea, este încă foarte greu de justificat orice tip de refactorizare a șinelor la scară largă, deoarece munca implicată va fi la scară cu oricare dintre celelalte exemple, dar beneficiile sunt mult mai puțin tangibile. Când te gândești la asta, nici măcar nu este atât de greu să o întreții. Trebuie doar să vă amintiți că unele lucruri sunt de fapt altceva. Trebuie doar să vă amintiți că A înseamnă de fapt B și proprietatea Y de pe A se referă de fapt la C.
Și aici constă adevăratul beneficiu. În domeniul neuro-psihologiei există multe experimente care sugerează că memoria noastră pe termen scurt sau de lucru este capabilă să dețină doar 7+/-2 elemente, unul dintre ele fiind experimentul Sternberg. Când studiem un subiect începem cu elemente de bază și, inițial, când ne gândim la concepte de nivel superior trebuie să ne gândim la definițiile lor. De exemplu, luați în considerare un termen simplu „parolă SHA256 sărată”. Inițial, trebuie să păstrăm în memoria noastră de lucru definiții pentru „sărat” și „SHA256” și poate chiar o definiție pentru „funcție hash”. Dar odată ce înțelegem pe deplin termenul, acesta ocupă un singur slot de memorie pentru că îl înțelegem intuitiv. Acesta este unul dintre motivele pentru care trebuie să înțelegem pe deplin conceptele de nivel inferior pentru a putea raționa despre cele de nivel superior. Același lucru este valabil și pentru termenii și definițiile specifice proiectului nostru. Dar dacă trebuie să ne amintim de traducerea în sensul real de fiecare dată când discutăm despre codul nostru, acea traducere ocupă încă unul dintre acele locuri prețioase de memorie de lucru. Produce încărcătură cognitivă și îngreunează raționarea prin logica din codul nostru. La rândul său, dacă este mai greu de raționat, înseamnă că există o șansă mai mare ca să trecem cu vederea un punct important și să introducem un bug.
Și să nu uităm de efecte secundare mai evidente. Există șanse mari de confuzie atunci când discutăm despre schimbări cu clientul nostru sau cu cineva familiarizat cu termenii de afaceri corecti. Oamenii noi care se alătură echipei trebuie să se familiarizeze atât cu terminologia de afaceri, cât și cu omologii săi din cod.
Cred că aceste motive sunt foarte convingătoare și justifică costul refactorizării în multe cazuri. Totuși, aveți grijă, pot exista o mulțime de cazuri marginale în care trebuie să vă folosiți cea mai bună judecată pentru a determina când și cum să refactorați.
În cele din urmă, refactorizarea la scară largă este bună din aceleași motive pentru care mulți dintre noi ne bucură să înceapă un nou proiect. Te uiți la acel fișier sursă gol și o lume nouă și curajoasă începe să se învârtească prin minte. De data aceasta o veți face bine, codul va fi elegant, va fi atât frumos aranjat, cât și rapid și robust și ușor de extensibil și, cel mai important, va fi o bucurie să lucrați cu el în fiecare zi. Refactorizarea, la scară mică și mare, vă permite să recaptați acel sentiment, să dați o nouă viață unei baze de cod vechi și să plătiți acea datorie tehnică.
În cele din urmă, cel mai bine este ca refactorizarea să fie condusă de planuri pentru a facilita implementarea unei anumite caracteristici noi. În acest caz, refactorizarea va fi mai concentrată și o mare parte din timpul petrecut cu refactorizarea va fi, de asemenea, recuperată imediat prin implementarea mai rapidă a caracteristicii în sine.
Pregătirea
Asigurați-vă că acoperirea testului este foarte bună în toate zonele bazei de cod pe care este posibil să le atingeți. Dacă vedeți anumite părți care nu sunt bine acoperite, mai întâi petreceți ceva timp pentru a crește acoperirea testului. Dacă nu aveți teste deloc, atunci ar trebui să petreceți mai întâi timp creând acele teste. Dacă nu puteți crea o suită de teste adecvată, concentrați-vă pe teste de acceptare și scrieți cât mai multe și asigurați-vă că scrieți teste unitare în timp ce refactorați. Teoretic, puteți face refactorizarea codului fără o acoperire bună de testare, dar vă va cere să faceți multe teste manuale și să o faceți des. Va dura mult mai mult și va fi mai predispus la erori. În cele din urmă, dacă acoperirea dvs. de testare nu este suficient de bună, costul efectuării unei refactorări Rails la scară largă ar putea fi atât de mare încât, din păcate, ar trebui să vă gândiți să nu o faceți deloc. În opinia mea, acesta este un beneficiu al testelor automate, care nu este subliniat suficient de des. Testele automate vă permit să refactorați des și, mai important, să fiți mai îndrăzneți în privința asta.
După ce v-ați asigurat că acoperirea testului este bună, este timpul să începeți să vă mapați modificările. La început nu ar trebui să faci nicio codificare. Trebuie să mapați aproximativ toate schimbările implicate și să urmăriți toate consecințele prin baza de cod, precum și să încărcați cunoștințele despre toate acestea în mintea dvs. Scopul tău este să înțelegi exact de ce schimbi ceva și rolul pe care îl joacă acesta în baza de cod. Dacă treci prin asta schimbând lucrurile doar pentru că par să fie schimbate sau pentru că ceva s-a stricat și asta pare să o rezolve, probabil vei ajunge pe o alee moartă. Noul cod pare să funcționeze, dar incorect, iar acum nici măcar nu vă puteți aminti toate modificările pe care le-ați făcut. În acest moment, s-ar putea să fie nevoie să renunțați la munca pe care ați făcut-o la refactorizarea codului la scară largă și, în esență, v-ați pierdut timpul. Așa că fă-ți timp și explorează codul pentru a înțelege ramificațiile fiecărei modificări pe care urmează să o faci. Va plăti frumos până la urmă.

Veți avea nevoie de un ajutor pentru procesul de refactorizare. Poate preferați altceva, dar îmi place o simplă bucată de hârtie goală și un stilou. Încep prin a scrie modificarea inițială pe care vreau să o fac în partea stângă sus a hârtiei. Apoi încep să caut toate locurile afectate de schimbare și le notez sub modificarea inițială. Aici este important să vă folosiți raționamentul. În cele din urmă, notele și diagramele de pe hârtie sunt acolo pentru tine, așa că alege un stil care se potrivește cel mai bine memoriei tale. Scriu fragmente de cod scurt cu marcatori sub ele și o mulțime de săgeți care duc la alte astfel de note care indică lucruri care depind de el direct (săgeată completă) sau indirect (săgeți întrerupte). De asemenea, adnot săgețile cu semne scurte ca un memento la un lucru specific pe care l-am observat în baza de cod. Amintiți-vă, veți reveni la acele note doar în următoarele zile în timp ce efectuați modificările planificate în ele și este perfect să folosiți mementouri foarte scurte și criptice, astfel încât acestea să ocupe mai puțin spațiu și să fie mai ușor de aranjat pe hârtie. . De câteva ori îmi curățam biroul luni după o refactorizare Rails și am găsit una dintre acele hârtii. Era o farsă completă, nu aveam absolut nicio idee ce înseamnă ceva de pe acea hârtie, cu excepția faptului că ar fi putut fi scris de cineva înnebunit. Dar știu că acea bucată de hârtie a fost indispensabilă în timp ce lucram la problemă. De asemenea, nu credeți că trebuie să scrieți fiecare schimbare. Le puteți grupa și urmări detaliile într-un mod diferit. De exemplu, pe lucrarea principală puteți observa că trebuie să „redenumiți toate aparițiile lui Ab în Cd” și apoi puteți urmări detaliile în mai multe moduri diferite. Le puteți scrie pe toate pe o bucată de hârtie separată, puteți planifica din nou să efectuați o căutare globală pentru toate aparițiile acesteia sau pur și simplu puteți lăsa deschise toate fișierele sursă în care modificarea trebuie făcută în editorul ales. și notează mental să le revii după ce ai terminat de mapat modificările.
Când mapați consecințele schimbării dumneavoastră inițiale, prin natura ei fiind la scară largă, cel mai probabil veți identifica modificări suplimentare care au ele însele consecințe ulterioare. Repetați analiza și pentru ei, notând toate modificările dependente. În funcție de dimensiunea modificărilor, le puteți scrie pe aceeași foaie de hârtie sau puteți alege una nouă goală. Un lucru foarte important de încercat și de făcut în timp ce mapați modificările este să încercați să identificați limitele în care puteți opri efectiv modificările de ramificare. Doriți să limitați refactorizarea la cel mai mic set de modificări sensibile, rotunjite. Dacă vedeți un punct în care vă puteți opri și lăsa restul așa cum este, faceți-o chiar dacă vedeți că ar trebui refactorizat, chiar dacă este legat în concept de celelalte modificări ale dvs. Încheiați această rundă de refactorizare a codului, testați temeinic, implementați și reveniți pentru mai multe. Ar trebui să căutați în mod activ acele puncte pentru a menține dimensiunea modificărilor gestionabilă. Desigur, ca întotdeauna, faceți un apel de judecată. Destul de des am ajuns la un punct în care aș putea întrerupe procesul de refactorizare adăugând niște clase de proxy pentru a face un pic de traducere a interfeței. Am început chiar să le implementez când mi-am dat seama că vor fi la fel de multă muncă ca și împingerea refactorizării puțin mai departe până la punctul în care va exista un punct de „oprire naturală” (adică aproape că nu este nevoie de cod proxy). Apoi am dat înapoi, revenind ultimele mele modificări și refactorizat. Dacă totul sună un pic ca o cartografiere a unui teritoriu neexplorat, este pentru că simt că este, cu excepția faptului că hărțile teritoriale sunt doar bidimensionale.
Execuţie
Odată ce ați făcut pregătirea pentru refactorizare, este timpul să executați planul. Asigurați-vă că concentrarea este ridicată și asigurați-vă un mediu fără distracție. Uneori merg până la schimbarea completă a conexiunii la internet în acest moment. Chestia este că, dacă te-ai pregătit bine, ai un set bun de însemnări pe hârtie lângă tine, iar concentrarea a crescut! De multe ori puteți trece foarte repede prin schimbări în acest fel. În teorie, cea mai mare parte a lucrărilor a fost făcută în prealabil, în timpul pregătirii.
Odată ce refactorizați codul, acordați atenție fragmentelor ciudate de cod care fac ceva foarte specific și pot părea cod prost. Poate că sunt cod prost, dar destul de des se ocupă de fapt de un caz ciudat de colț care a fost descoperit în timp ce investiga o eroare în producție. De-a lungul timpului, majoritatea codului Rails crește „pări” sau „negi” care se ocupă de erori ciudate ale carcasei de colț, de exemplu, un cod de răspuns ciudat aici, care poate fi necesar pentru IE6 sau o condiție de acolo care gestionează o eroare ciudată de sincronizare. Ele nu sunt importante pentru imaginea de ansamblu, dar sunt încă detalii semnificative. În mod ideal, acestea sunt acoperite în mod explicit cu teste unitare, dacă nu încercați să le acoperiți mai întâi. Odată am fost însărcinat să port o aplicație de dimensiuni medii de la Rails 2 la Rails 3. Eram foarte familiarizat cu codul, dar era puțin dezordonat și erau multe modificări de luat în considerare, așa că am optat pentru reimplementare. De fapt, nu a fost o reimplementare reală, deoarece aceasta nu este niciodată o mișcare inteligentă, dar am început cu o aplicație Rails 3 goală și am refactorizat felii verticale ale aplicației vechi în cea nouă, folosind aproximativ procesul descris. De fiecare dată când am terminat o felie verticală, am trecut prin vechiul cod Rails, uitându-mă la fiecare linie și verificând de două ori dacă are omologul său în noul cod. În esență, aleg toate vechile „părți” de cod și le reproduc în noua bază de cod. În cele din urmă, noua bază de cod a avut toate cazurile de colț abordate.
Asigurați-vă că efectuați teste manuale suficient de des. Ambele vă vor forța să căutați „pauze” naturale în procesul de refactorizare, care vă vor permite să testați o parte a sistemului, precum și să vă ofere încredere că nu ați spart nimic din ceea ce nu vă așteptați să spargeți în acest proces. .
Încheiați-l
După ce ați terminat de refactorizat codul Rails, asigurați-vă că ați revizuit toate modificările pentru ultima dată. Uită-te la întreaga diferență și analizează-l. Destul de des, veți observa lucruri subtile pe care le-ați omis la începutul refactorizării, deoarece nu aveați cunoștințele pe care le aveți acum. Este un avantaj frumos al refactorizării la scară largă: obțineți o imagine mentală mai clară a organizării codului, mai ales dacă nu ați scris-o inițial.
Dacă este posibil, cereți și unui coleg să o revizuiască. Nici măcar nu trebuie să fie foarte familiarizat cu acea parte exactă a bazei de cod, dar ar trebui să aibă o familiaritate generală cu proiectul și codul acestuia. A avea un ochi nou asupra schimbărilor poate ajuta foarte mult. Dacă nu poți convinge un alt dezvoltator să se uite la ele, va trebui să te prefaci că ești unul. Dormiți bine și revizuiți-l cu o minte proaspătă.
Dacă îți lipsește QA, va trebui să porți și acea pălărie. Din nou, luați o pauză și îndepărtați-vă de cod, apoi reveniți pentru a efectua testarea manuală. Tocmai ați suferit echivalentul cu a intra într-un dulap aglomerat de cablaje electrice cu o grămadă de unelte și ați sortat totul, eventual tăind și recablat lucruri, așa că trebuie să aveți puțină mai multă grijă decât de obicei.
În cele din urmă, bucură-te de roadele muncii tale având în vedere toate schimbările planificate, care vor fi acum mult mai curate și mai ușor de implementat.
Când nu ai face-o?
Deși există multe beneficii în efectuarea regulată a refactorizării la scară largă pentru a menține codul de proiect proaspăt și de înaltă calitate, este totuși o operațiune foarte costisitoare. Există și cazuri în care nu ar fi recomandabil:
Acoperirea dvs. de testare este slabă
După cum sa menționat: o acoperire foarte slabă a testului ar putea fi o mare problemă. Folosiți-vă propria judecată, dar ar putea fi mai bine pe termen scurt să vă concentrați pe creșterea acoperirii în timp ce lucrați la noi funcții și efectuați cât mai multe refactorizări localizate la scară mică. Acest lucru vă va ajuta foarte mult odată ce vă decideți să faceți pasul și să sortați părți mai mari ale bazei de cod.
Refactorizarea nu este condusă de o nouă caracteristică și baza de cod nu s-a schimbat de mult timp
Am folosit timpul trecut în loc să spun „baza de cod nu se va schimba” în mod intenționat. Judecând după experiență (și prin experiență vreau să spun că greșești de multe ori) aproape niciodată nu te poți baza pe predicțiile tale cu privire la momentul în care o anumită parte a bazei de cod va trebui schimbată. Așadar, faceți următorul lucru cel mai bun: priviți trecutul și presupuneți că trecutul se va repeta. Dacă ceva nu a fost schimbat de mult timp, probabil că nu trebuie să îl schimbați acum. Așteptați ca schimbarea să apară și lucrați la altceva.
Ești presat de timp
Întreținerea este cea mai costisitoare parte a ciclului de viață al proiectului, iar refactorizarea o face mai puțin costisitoare. Este absolut necesar ca orice afacere să folosească refactorizarea pentru a reduce datoria tehnică pentru a face întreținerea viitoare mai ieftină. În caz contrar, riscă să intre într-un cerc vicios în care devine din ce în ce mai costisitoare adăugarea de noi funcții. Sper că este de la sine înțeles de ce este rău.
Acestea fiind spuse, refactorizarea la scară largă este foarte, foarte imprevizibilă când vine vorba de cât timp va dura și nu ar trebui să o faceți la jumătate. Dacă din orice motive interne sau externe sunteți presat de timp și nu sunteți sigur că veți putea termina în acel interval de timp, atunci este posibil să trebuiască să abandonați refactorizarea. Presiunea și stresul, în special cele induse de timp, conduc la un nivel mai scăzut de concentrare, care este absolut necesar pentru refactorizarea la scară largă. Lucrați pentru a obține mai mult „asumare” din partea echipei dvs., pentru a rezerva timp pentru asta și pentru a analiza calendarul pentru o perioadă în care veți avea timp. Nu este necesar să fie o perioadă continuă de timp. Desigur, veți avea alte probleme de rezolvat, dar acele pauze nu ar trebui să fie mai lungi de o zi sau două. Dacă da, va trebui să-ți reamintești propriul plan, deoarece vei începe să uiți ce ai învățat despre baza de cod și exact unde te-ai oprit.
Concluzie
Sper că v-am dat câteva îndrumări utile și v-am convins de beneficiile, și îndrăznesc să spun necesitate, de a efectua refactorări la scară largă în anumite ocazii. Subiectul este foarte vag și, desigur, nimic din ceea ce se spune aici nu este un adevăr cert, iar detaliile vor varia de la proiect la proiect. Am încercat să dau un sfat, care, după părerea mea, este aplicabil în general, dar, ca întotdeauna, luați în considerare cazul dvs. particular și folosiți propria experiență pentru a vă adapta provocărilor sale specifice. Succes la refactorizare!