Următoarea serie scurtă de articole este destinată dezvoltatorilor și începătorilor cu experiență Ruby puțin experimentați. Am avut impresia că codul miroase și refactorizările pot fi foarte descurajante și intimidante pentru începători - mai ales dacă nu sunt în situația fericită de a avea mentori care pot transforma conceptele de programare mistice în becuri strălucitoare.
Având în mod evident mers în aceste pantofi de mine, mi-am amintit că se simțea inutil de ceață pentru a obține în cod mirosuri și refactorizări.
Pe de o parte, autorii se așteaptă la un anumit nivel de competență și, prin urmare, s-ar putea să nu se simtă super-obligați să ofere cititorului aceleași contexte pe care un nou-ar putea avea nevoie să se scufunde în mod confortabil în această lume mai devreme.
Ca o consecință, poate, începătorii, pe de altă parte, formează impresia că ar trebui să aștepte un pic mai mult până când vor fi mai avansați să învețe despre mirosuri și refactorizări. Nu sunt de acord cu această abordare și cred că, făcând acest subiect mai accesibil, îi va ajuta să proiecteze mai bine un software mai devreme în cariera lor. Cel puțin sper că ajută la furnizarea de peeps cu un start bun.
Deci, ce vorbim despre exact când oamenii menționează codul miroase? Este întotdeauna o problemă în codul dvs.? Nu neaparat! Puteți să le evitați complet? Nu cred! Vrei să spui că mirosul de cod conduce la cod rupt? Ei bine, uneori și uneori nu. Ar trebui să fie prioritatea mea să le rezolv imediat? Același răspuns, mă tem că uneori da și uneori, cu siguranță, ar trebui să prune mai întâi pești mai mari. Esti nebun? Întrebare corectă în acest moment!
Înainte de a continua să scufundați în această afacere mirositoare, amintiți-vă să luați un singur lucru din toate acestea: Nu încercați să remediați orice miros pe care îl întâlniți - aceasta este, cu siguranță, o pierdere de timp!
Mi se pare că mirosurile de coduri sunt greu de încheiat într-o cutie bine etichetată. Există tot felul de mirosuri cu diferite opțiuni diferite pentru a le aborda. De asemenea, diferite limbi de programare și cadre sunt predispuse la diferite mirosuri - dar există cu siguranță o mulțime de tulpini "genetice" comune între ele. Încercarea mea de a descrie mirosurile de cod este să le comparăm cu simptomele medicale care vă spun că ați putea avea o problemă. Ele pot indica toate tipurile de probleme latente și pot avea o mare varietate de soluții dacă sunt diagnosticate.
Din fericire, ele nu sunt la fel de complicate în general ca și tratarea corpului uman - și psihicul, desigur. Este totuși o comparație corectă, totuși, pentru că unele dintre aceste simptome trebuie tratate imediat, iar altele vă oferă suficient timp pentru a găsi o soluție care să fie cea mai bună pentru bunăstarea generală a pacientului. Dacă aveți cod de lucru și vă confruntați cu ceva mirositor, va trebui să luați decizia dificilă dacă merită să găsiți o soluție și dacă refactorizarea îmbunătățește stabilitatea aplicației dvs..
Acestea fiind spuse, dacă vă împingeți după codul pe care îl puteți îmbunătăți imediat, este un sfat bun să lăsați codul în spatele unui pic mai bun decât înainte - chiar și un mic pic mai bine se adaugă substanțial în timp.
Calitatea codului dvs. devine discutabilă dacă includerea unui nou cod devine mai greu, cum ar fi decizia de a pune codul nou este o durere sau vine cu o mulțime de efecte deranjante în codul dvs. de bază, de exemplu. Aceasta se numește rezistență.
Ca îndrumare pentru calitatea codului, îl poți măsura mereu după cât de ușor este să introduci modificări. Dacă acest lucru devine din ce în ce mai greu, este cu siguranță timpul să refăctor și să luăm ultima parte din roșu-verde-Refactor mai serios în viitor.
Hai să începem cu ceva fantezist - "clasele lui Dumnezeu" - pentru că cred că sunt foarte ușor de înțeles pentru începători. Clasele lui Dumnezeu sunt un caz special de un miros de cod numit Clasa Mare. În această secțiune mă voi adresa ambelor persoane. Dacă ați petrecut puțin timp în pământ Rails, probabil că ați văzut atât de des că vă par normal.
Îți amintești cu siguranță "modelele grase, controlerul slab" mantra? De fapt, slab este bun pentru toate aceste clase, dar ca îndrumare este un sfat bun pentru începători presupun.
Clasele lui Dumnezeu sunt obiecte care atrag tot felul de cunoștințe și comportament ca o gaură neagră. Suspecții obișnuiți includ cel mai adesea modelul de utilizator și orice problemă (sperăm!) Pe care aplicația dvs. încearcă să o soluționeze - cel puțin în primul rând. O aplicație todo ar putea să se înmulțească pe Todos model, o aplicație pentru cumpărături Produse, o aplicație foto pe Fotografii-veți obține driftul.
Oamenii îi numesc clase de Dumnezeu deoarece știu prea mult. Au prea multe legături cu alte clase - mai ales pentru că cineva le modela leneș. Este totuși o muncă grea, pentru a păstra clasele lui Dumnezeu sub control. Îi face cu adevărat mai ușor să-ți dai mai multe responsabilități, și cum ar atesta o mulțime de eroi greci, este nevoie de un pic de pricepere pentru a împărți și a cuceri "zeii".
Problema cu ei este că ele devin mai grele și mai greu de înțeles, mai ales pentru noii membri ai echipei, mai greu de schimbat, iar reutilizarea lor devine din ce în ce mai puțin o opțiune, cu atât mai multă gravitate le-au acumulat. Da, ai dreptate, testele tale sunt inutil de greu de scris, de asemenea. Pe scurt, nu există într-adevăr un avantaj pentru a avea clase mari, și în special clase de Dumnezeu.
Există câteva simptome / semne comune în care clasa dvs. are nevoie de eroism / intervenție chirurgicală:
De asemenea, dacă vă uitați la clasă și gândiți-vă "Eh? Ew! "S-ar putea să fiți și pe ceva. Dacă tot ceea ce sună familiar, șansele sunt bune că te-ai găsit un specimen bun.
"clasa rubinie CastingInviter EMAIL_REGEX = /\A([^@\s]+)@((?[[-a-z0-9]+.)+[-z]2)\z/
attr_reader: message,: invitees,: casting
def initialize (atributele = ) @message = atribute [: message] || "@invitees = atribute [: invitees] ||" @sender = atribute [: expeditor]
def valid? valid_message? && valid_invitees? sfarsit def livra daca este valabil? invitee_list.each nu | email | invitație = create_invitation (e-mail) Mailer.invitation_notification (invitație, @message) termină altceva error_message = Mesajul "Your # # @casting" nu a putut fi trimis. (invitație, fail_message) end end private def invalid_invitees @invalid_invitees || = invitee_list.map nu | item | cu excepția cazului în care item.match (EMAIL_REGEX) sfârșitul elementului end.compact end def invitee_list @invitee_list || = @ invitees.gsub (/ \ s + /, ") divizat (/ [\ n;;] + /) end def valid_message? message.present? end def valid_invitees? invalid_invitees.empty? end
def create_invitation (e-mail) Invitation.create (casting: @casting, expeditor: @sender, invitee_email: email, status: 'în așteptare'
Tipul urât, nu? Poți să vezi cât de mult este îngrozită aici? Bineînțeles că am pus o cireșă deasupra, dar mai devreme sau mai tîrziu vei trece în cod ca asta. Să ne gândim la ce responsabilități sunt acestea CastingInviter
clasa trebuie să jongleze.
Ar trebui ca toate acestea să fie aruncate într-o clasă care doar dorește să transmită un apel prin intermediul unui turnător livra
? Cu siguranta nu! Dacă metoda dvs. de schimbare a invitației vă puteți aștepta să faceți o operație de pușcă. CastingInviter nu are nevoie să cunoască majoritatea acestor detalii. Aceasta este mai mult responsabilitatea unei clase care este specializată în abordarea chestiunilor legate de e-mail. În viitor, veți găsi multe motive pentru a vă schimba codul aici.
Deci, cum ar trebui să facem acest lucru? Adesea, extragerea unei clase este un model de refactorizare la îndemână, care se va prezenta ca o soluție rezonabilă la astfel de probleme cum ar fi clasele mari, complexe - mai ales atunci când clasa respectivă se ocupă de multiple responsabilități.
Metodele private sunt adesea candidați buni pentru a începe cu - și marcaje ușoare, de asemenea. Uneori va trebui să extragi mai mult de o clasă de la un băiat așa de rău - nu fa totul într-un singur pas. Odată ce găsiți o cantitate suficientă de carne coerentă care pare să aparțină unui obiect de specialitate propriu, puteți extrage această funcție într-o clasă nouă.
Creați o nouă clasă și mutați treptat funcționalitatea peste una câte una. Mutați fiecare metodă separat și redenumiți-i dacă vedeți un motiv. Apoi se face referire la noua clasă în cea originală și se deleagă funcționalitatea necesară. Bine că aveți acoperire de testare (sperăm!), Care vă permite să verificați dacă lucrurile continuă să funcționeze corect la fiecare pas al drumului. Scopul este de a reuși să reutilizați clasele extrase. Este mai ușor să vezi cum se face în acțiune, deci hai să citim un cod:
"clasa rubinie CastingInviter
attr_reader: message,: invitees,: casting
def initialize (atributele = ) @message = atributele [: message] || "@invitees = atribute [: invitees] ||" @casting = atribute [: casting] @sender =
def valid? casting_email_handler.valid? Sfârșit
def delivering_email_handler.deliver final
privat
def casting_email_handler @casting_email_handler || = CastingEmailHandler.new (mesaj: mesaj, invitați: invitați, casting: turnare, expeditor: @sender) sfârșitul final "
"clasa rubinie CastingEmailHandler EMAIL_REGEX = /\A([^@\s]+)@((?[[-a-z0-9]+.)+[-z]2)\z/
def initialize (attr = ) @message = attr [: message] || "@invitees = attr [: invitees] ||" @casting = attr [: casting] @sender =
def valid? valid_message? && valid_invitees? Sfârșit
def livrați dacă este valid? invitee_list.each nu | email | invitație = create_invitation (e-mail) Mailer.invitation_notification (invitație, @message) final altceva failure_message = Mesajul "Your # # @casting" nu a putut fi trimis. Invitații e-mailuri sau mesaje sunt nevalide "invitație = create_invitation (@sender) Mailer.invitation_notification (invitație, fail_message) final sfârșit
privat
def invalid_invitees @invalid_invitees || = invitee_list.map nu | item | cu excepția cazului în care elementul item.match (EMAIL_REGEX) se termină la capătul end.compact
def invitee_list @invitee_list || = @ invitees.gsub (/ \ s + /,) divizat (/ [\ n,;] + /) sfârșit
def valid_invitees? invalid_invitees.empty? Sfârșit
def valid_message? @ Message.present? Sfârșit
def create_invitation (e-mail) Invitation.create (casting: @casting, expeditor: @sender, invitee_email: email, status: 'în așteptare'
În această soluție, veți vedea nu numai modul în care această separare a preocupărilor afectează calitatea codului dvs., ci citește și mai mult și devine mai ușor de digerat.
Aici delegăm metode unei noi clase care este specializată în a face față trimiterii acestor invitații prin e-mail. Aveți un loc dedicat care verifică dacă mesajele și invitații sunt valide și cum trebuie să fie livrate. CastingInviter
nu are nevoie să știe nimic despre aceste detalii, așa că am delegat aceste responsabilități unei noi clase CastingEmailHandler
.
Cunoașterea modului de livrare și de verificare a valabilității acestor e-mailuri de invitație de difuzare este acum cuprinsă în noua noastră clasă extrasă. Avem mai mult cod acum? Pariezi! A meritat să se preocupe separat? Destul de sigur! Putem merge dincolo de asta și de refactor CastingEmailHandler
mai mult? Absolut! Fa-te praf!
În cazul în care vă întrebați valabil?
metoda pe CastingEmailHandler
și CastingInviter
, aceasta este pentru RSpec pentru a crea un personalizat personalizat. Acest lucru îmi permite să scriu ceva de genul:
ruby așteaptă (casting_inviter) .to be_valid
Destul de la îndemână, cred.
Există mai multe tehnici pentru a trata clasele mari / obiectele dumnezeiești, iar pe parcursul acestei serii veți învăța câteva moduri de a refăca astfel de obiecte.
Nu există nicio prescripție fixă pentru a trata aceste cazuri - depinde întotdeauna și este vorba de un apel de judecată de la caz la caz în cazul în care trebuie să aduceți armele mari sau dacă tehnicile de refactorizare mai mici, incrementale, sunt cele mai bune. Știu, uneori frustrant. În conformitate cu principiul responsabilității unice (SRP) va merge un drum lung, totuși, și este un nas bun de urmat.
Având metode care au un pic mare este unul dintre cele mai comune lucruri pe care le întâlniți ca dezvoltator. În general, vrei să știi dintr-o privire ce ar trebui să facă o metodă. Ar trebui să aibă și un singur nivel de cuibărit sau un nivel de abstractizare. Pe scurt, evitați scrierea unor metode complicate.
Știu că sună greu, și de multe ori este. O soluție care apare frecvent este extragerea unor părți ale metodei într-una sau mai multe funcții noi. Această tehnică de refactorizare este numită metoda extracției-este una dintre cele mai simple, dar totuși foarte eficientă. Ca un efect secundar frumos, codul dvs. devine mai ușor de citit dacă vă denumiți în mod corespunzător metodele.
Să aruncăm o privire la specificațiile de caracteristici în care veți avea nevoie de această tehnică foarte mult. Îmi amintesc că am fost introdus în metoda extracției în timp ce scriu astfel de caracteristici și cât de uimitor sa simțit atunci când becul a continuat. Datorită faptului că astfel de caracteristici sunt ușor de înțeles, ele sunt un bun candidat pentru demonstrație. În plus, veți trece în scenarii similare din când în când, când vă scrieți specificațiile.
spec / caracteristici / some_feature_spec.rb
"ruby necesită 'rails_helper'
M_mi6.com 'click_button' Trimite 'vizita missions_path click_on' Creare misiune 'fill_in' Nume de misiune ', cu:' Proiect Moonraker 'click_button' Trimite '
în "li: conține (" Project Moonraker ")" click_on "Misiunea finalizată" se așteaptă (pagina) .to_message_message.php "
După cum puteți vedea cu ușurință, în acest scenariu se întâmplă multe lucruri. Du-te la pagina de index, conectați-vă și creați o misiune pentru configurare, apoi exercițiul prin marcarea misiunii ca fiind completă și, în final, verificați comportamentul. Nu știință de rachete, dar, de asemenea, nu este curată și cu siguranță nu este compusă pentru reutilizare. Putem face mai bine decât asta:
spec / caracteristici / some_feature_spec.rb
"ruby necesită 'rails_helper'
caracteristica "M marchează misiunea ca completă" face scenariul "cu succes" face semn_in_as "[email protected]" create_classified_mission_named "Project Moonraker"
mark_mission_as_complete "Project Moonraker" agent_sees_completed_mission "End of Project Moonraker"
def create_classified_mission_named (mission_name) vizitați missions_path click_on 'Creare misiune' fill_in 'Mission Name', cu: mission_name click_button 'Trimite' sfârșit
def mark_mission_as_complete (nume_plicație) în cadrul "li: conține ('# mission_name')" faceți clic pe 'Finalizarea misiunii' sfârșitul final
def agent_sees_completed_mission (misiune_name) așteptați (pagina) .to have_css 'ul.missions li.mission-name.completed', text: mission_name end
def sign_in_as (e-mail) vizitați root_path fill_in 'E-mail', cu: email click_button 'Submit' end '
Aici am extras patru metode care pot fi reutilizate cu ușurință în alte teste acum. Sper că este clar că am lovit trei păsări cu o singură piatră. Caracteristica este mult mai concisă, citește mai bine și este alcătuită din componente extrase fără duplicare.
Să ne imaginăm că ați scris tot felul de scenarii similare fără a extrage aceste metode și ați vrut să schimbați o anumită implementare. Acum doriți să vă fi făcut timpul pentru a vă reface testele și a avut un loc central pentru a vă aplica modificările.
Sigur, există o modalitate chiar mai bună de a trata specificațiile caracteristicilor, cum ar fi obiectele din această pagină, de exemplu - dar acest lucru nu este scopul nostru astăzi. Cred că asta e tot ce trebuie să știți despre extragerea metodelor. Puteți aplica acest model de refactorizare peste tot în codul dvs., nu numai în specificații, desigur. În ceea ce privește frecvența de utilizare, presupun că este o tehnică numărul unu pentru a îmbunătăți calitatea codului. A se distra!
Să închidem acest articol cu un exemplu în care vă puteți reduce parametrii. Ea devine plictisitoare destul de repede când trebuie să vă hrăniți metodele cu mai mult de unul sau două argumente. Nu ar fi frumos să renunți la un obiect? Exact asta puteți face dacă introduceți a parametru obiect.
Toți acești parametri nu sunt doar o durere de scriere și de păstrare în ordine, ci pot duce și la dublarea codului - și cu siguranță dorim să evităm acest lucru ori de câte ori este posibil. Ceea ce îmi place mai ales în legătură cu această tehnică de refactorizare este modul în care aceasta afectează și alte metode din interior. De multe ori puteți scăpa de o mulțime de junk de parametri în lanțul alimentar.
Să trecem peste acest exemplu simplu. M poate atribui o nouă misiune și are nevoie de un nume de misiune, de un agent și de un obiectiv. M este, de asemenea, capabil să comute statusul dublu al agenților 0, ceea ce înseamnă că licența lor este de a ucide.
"rubră clasa M def assign_new_mission (nume_plată, agent_name, object, license_to_kill: nil) print" Misiunea # mission_name a fost atribuită # # agentului cu obiectivul de a "#" obiectiv "dacă license_to_kill print" a fost acordată. "alt tip de imprimare" Licența de a ucide nu a fost acordată "
m = M.new m.assign_new_mission ("Octopussy", "James Bond", "găsiți dispozitivul nuclear", license_to_kill: true) # => Misiunea Octopussy a fost atribuită lui James Bond cu scopul de a găsi dispozitivul nuclear. Licența de a ucide a fost acordată. "
Când te uiți la asta și întrebi ce se întâmplă atunci când "parametrii" misiunii cresc în complexitate, te afli deja în ceva. Acesta este un punct de durere pe care îl puteți rezolva numai dacă treceți într-un singur obiect care are toate informațiile de care aveți nevoie. De cele mai multe ori, acest lucru vă ajută să vă opriți de la modificarea metodei dacă obiectul parametru se modifică din anumite motive.
"clasa ruby Misiune attr_reader: mission_name,: agent_name,: objective,: license_to_kill
def initialize (nume_locatie: nume_sunt, agent_name: agent_name, obiectiv: obiectiv, license_to_kill: license_to_kill) @mission_name = nume_sunt @agent_name = nume_activitate @obiectiv = obiectiv @licence_to_kill =
def assign print "Misiunea # mission_name a fost asignată la # agent_name cu scopul de a # objective." if license_to_kill print "Licența de a ucide a fost acordată." alt tip de imprimare "Licența de a ucide nu a fost acordată." sfârșitul capătului final
clasa M def assign_new_mission (misiune) mission.assign end end
m = M.new mission = Mission.new (numele misiunii: "Octopussy", agent_name: "James Bond", obiectiv: "găsiți dispozitivul nuclear", license_to_kill: true) m.assign_new_mission (misiune) # => Misiunea Octopussy a fost alocat lui James Bond cu scopul de a găsi dispozitivul nuclear. Licența de a ucide a fost acordată. "
Așa că am creat un obiect nou, Misiune
, care se axează exclusiv pe furnizarea M
cu informațiile necesare pentru a atribui o nouă misiune și a le oferi #assign_new_mission
cu un obiect de parametru singular. Nu este nevoie să-i transmiteți pe voi înșiși acești parametri plictisitori. În schimb, spuneți obiectului să dezvăluie informațiile de care aveți nevoie în cadrul metodei în sine. În plus, am extras un anumit comportament - informațiile despre cum să tipăriți în noul Misiune
obiect.
De ce ar trebui M
trebuie să știți cum să imprimați misiunile misiunii? Noul #atribui
de asemenea, a beneficiat de extracție pierzând o anumită greutate, deoarece nu am avut nevoie să trecem în parametrul obiect - deci nu este nevoie să scrieți chestii ca mission.mission_name
, mission.agent_name
si asa mai departe. Acum ne folosim attr_reader
(e), care este mult mai curat decât fără extragere. Sapi?
Ce este de asemenea la îndemână pentru asta este asta Misiune
ar putea colecta tot felul de metode sau stări suplimentare care sunt încapsulate într-un singur loc și sunt gata pentru a vă accesa.
Cu această tehnică veți sfârși cu metode mai concise, care tind să citiți mai bine și evitați repetarea aceluiași grup de parametri peste tot. Destul de bun! Îndepărtarea grupurilor identice de parametri este, de asemenea, o strategie importantă pentru codul DRY.
Încercați să căutați extragerea mai mult decât a datelor. Dacă poți să faci un comportament și în noua clasă, vei avea obiecte care sunt mai utile - în caz contrar, vor începe să miroasă și mai repede.
Sigur că, de cele mai multe ori, veți fi difuzate în versiuni mai complicate ale acestui lucru - și testele dvs. vor fi, de asemenea, necesare adaptate simultan în timpul acestor refactorizări - dar dacă aveți un exemplu simplu sub centură, veți fi gata de acțiune.
O să mă uit la noul Bond acum. A auzit că nu este așa de bun, deși ...
Actualizare: Saw Spectre. Verdictul meu: în comparație cu Skyfall - care a fost MEH imho-Spectre a fost wawawiwa!