Build Dumb, Refactor Smart: Cum să masați problemele din codul Ruby on Rails
Publicat: 2022-03-11Uneori, clienții ne oferă solicitări de funcții care chiar nu ne plac. Nu este că nu ne plac clienții – ne iubim clienții. Nu este că nu ne place caracteristica – cele mai multe funcții solicitate de clienți sunt aliniate perfect cu obiectivele și veniturile lor de afaceri.
Uneori, nu ne place o solicitare de caracteristici, deoarece cea mai simplă modalitate de a o rezolva este să scriem cod prost și nu avem o soluție elegantă în capul nostru. Acest lucru îi va trimite pe mulți dintre noi, dezvoltatorii Rails, la căutări inutile prin RubyToolbox, GitHub, bloguri pentru dezvoltatori și StackOverflow, în căutarea unei bijuterii sau plugin sau exemplu de cod care să ne facă să ne simțim mai bine cu noi înșine.
Refactorează codul Ruby on Rails
Ei bine, sunt aici să vă spun: este în regulă să scrieți cod prost. Uneori, codul Rails prost este mai ușor de refactorizat într-un cod frumos decât o soluție prost gândită implementată în condiții de criză de timp.
Acesta este procesul de refactorizare Rails pe care îmi place să-l urmăresc atunci când rezolv problemele din oribilele mele soluții de bandă:
Pentru o perspectivă alternativă, iată jurnalul de comitere Git pentru o caracteristică care a fost refactorizată pas cu pas:
Și iată un alt articol interesant despre refactorizarea la scară largă de la un coleg din rețeaua Toptal.
Să vedem cum se face.
Vederile
Pasul 1. Începeți în Vizualizări
Să presupunem că începem un bilet pentru o nouă funcție. Clientul ne spune: „Vizitatorii ar trebui să poată vedea o listă de proiecte active pe pagina de bun venit.”
Acest bilet necesită o modificare vizibilă, așa că un loc rezonabil pentru a începe lucrul ar fi în Vizualizări. Problema este simplă și pe care toți am fost instruiți să o rezolvăm de mai multe ori. O să o rezolv în mod greșit și să demonstrez cum să refactorez soluția mea în zonele corespunzătoare. Rezolvarea unei probleme Calea greșită ne poate ajuta să trecem peste cocoașa de a nu ști soluția corectă.
Pentru început, presupunem că avem un model numit Project
cu un atribut boolean numit active
. Vrem să obținem o listă cu toate Projects
în care active
este egal cu true
, astfel încât să putem folosi Project.where(active: true)
și să trecem peste el cu each
bloc.
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
Știu ce spui: „Asta nu ar trece niciodată de o revizuire a codului” sau „Clientul meu m-ar concedia cu siguranță pentru asta.” Da, această soluție întrerupe separarea problemelor Model-View-Controller, ar putea avea ca rezultat apeluri rătăcite de baze de date care sunt greu de urmărit și poate deveni dificil de menținut în viitor. Dar luați în considerare valoarea de a face acest lucru în mod greșit :
- Puteți obține această schimbare în scenă în mai puțin de 15 minute.
- Dacă este lăsat, acest bloc este ușor de stocat în cache.
- Remedierea acestei probleme Rails este simplă (ar putea fi dată unui dezvoltator junior).
Pasul 2. Parțiale
După ce am făcut-o The Wrong Way , mă simt rău pentru mine și vreau să-mi izolez codul prost. Dacă această schimbare ar fi în mod clar doar o preocupare a stratului View, mi-aș putea refactoriza rușinea într-o parțială.
app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
E puțin mai bine. În mod clar, încă facem greșeala unei interogări de model într-o vizualizare, dar cel puțin atunci când un întreținător vine mai târziu și va vedea parțialul meu oribil, va avea o modalitate simplă de a rezolva problema codului Rails în special. Repararea a ceva evident prost este întotdeauna mai ușoară decât a remedia o abstracție prost implementată.
Pasul 3. Ajutoare
Helpers in Rails reprezintă o modalitate de a crea un DSL (Domain Specific Language) pentru o secțiune a vizualizărilor dvs. Trebuie să vă rescrieți codul folosind content_tag în loc de haml sau HTML, dar aveți avantajul că vi se permite să manipulați structurile de date fără ca alți dezvoltatori să vă privească cu privirea pentru 15 rânduri de cod Vizualizare care nu se imprimă.
Dacă ar fi să folosesc ajutoare aici, probabil că aș refactoriza eticheta li
.
app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end
Controlorii
Pasul 4. Mutați-l în controlere
Poate că codul tău îngrozitor nu este doar o problemă View. Dacă codul dvs. încă miroase, căutați interogări pe care le puteți trece de la Vizualizări la Controlere.

app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end
Pasul 5. Filtre de controler
Cel mai evident motiv pentru a muta codul într-un Controller before_filter
sau after_filter
este pentru codul pe care l-ați duplicat în mai multe acțiuni Controller. De asemenea, puteți muta codul într-un filtru Controller dacă doriți să separați scopul acțiunii Controller de cerințele vizualizărilor dvs.
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
Pasul 6. Controller de aplicație
Presupuneți că aveți nevoie de codul dvs. să apară pe fiecare pagină sau doriți să puneți la dispoziție funcțiile de ajutor Controller pentru toate controlerele, vă puteți muta funcția în ApplicationController. Dacă modificările sunt globale, poate doriți să modificați și aspectul aplicației.
app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end
Modelele
Pasul 7. Refactorizarea la model
După cum spune motto-ul MVC: Fat Model, Skinny Controllers și Dumb Views . Se așteaptă să refactorăm tot ce putem în Model și este adevărat că cele mai complexe funcționalități vor deveni în cele din urmă asociații de model și metode de model. Ar trebui să evităm întotdeauna să facem formatarea/vizualizarea lucrurilor în Model, dar transformarea datelor în alte tipuri de date este permisă. În acest caz, cel mai bun lucru de refactorat în Model ar fi clauza where(active: true)
, pe care o putem transforma într-un domeniu. Folosirea unui domeniu este valoroasă nu numai pentru că face apelul să pară mai frumos, dar dacă am decis vreodată să adăugăm un atribut precum delete
sau outdated
, putem modifica acest domeniu în loc să urmărim toate clauzele noastre where
.
app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)
Pasul 8. Model Filtre
Nu avem o utilizare specială pentru after_save filters
before_save
ale unui Model în acest caz, dar următorul pas pe care îl fac de obicei este mutarea funcționalității de la Controlere și metodele Model în filtrele Model.
Să presupunem că avem un alt atribut, num_views
. Dacă num_views > 50
, proiectul devine inactiv. Am putea rezolva această problemă în vizualizare, dar efectuarea modificărilor bazei de date într-o vizualizare este inadecvată. Am putea rezolva asta în Controller, dar Controllerele noastre ar trebui să fie cât mai subțiri posibil! O putem rezolva cu ușurință în Model.
app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end
Notă: ar trebui să evitați apelarea self.save
într-un filtru Model, deoarece acest lucru provoacă evenimente recursive de salvare, iar stratul de manipulare a bazei de date al aplicației dvs. ar trebui să fie oricum Controller.
Pasul 9. Biblioteci
Ocazional, caracteristica dvs. este suficient de mare încât ar putea garanta propria bibliotecă. Poate doriți să-l mutați într-un fișier de bibliotecă, deoarece este reutilizat în multe locuri sau este suficient de mare încât ați dori să-l dezvoltați separat.
Este bine să stocați fișierele bibliotecii în directorul lib/ , dar pe măsură ce acestea cresc, le puteți transfera într-un RubyGem adevărat! Un avantaj major al mutarii codului într-o bibliotecă este că puteți testa biblioteca separat de modelul dvs.
Oricum, în cazul unei liste de proiecte, am putea justifica mutarea scope :active
call din modelul de Project
într-un fișier de bibliotecă și să-l aducem înapoi în Ruby:
app/models/project.rb: class Project < ActiveRecord::Base include Activeable before_filter :deactivate_if_over_num_views end lib/activeable.rb: module Activeable def self.included(k) k.scope :active, k.where(active: true) end def deactivate_if_over_num_views if num_views > 50 self.active = false end end end
Notă: metoda self.included
este apelată atunci când o clasă Rails Model este încărcată și trece în domeniul de aplicare al clasei ca variabilă k
.
Concluzie
În acest tutorial de refactorizare Ruby on Rails, ne-am luat mai puțin de 15 minute și am implementat o soluție și am pus-o în staging pentru testarea utilizatorilor, gata să fie acceptată în setul de caracteristici sau eliminată. Până la sfârșitul procesului de refactorizare, avem o bucată de cod care stabilește un cadru pentru implementarea elementelor listabile și activabile în mai multe modele care ar trece chiar și cel mai strict proces de revizuire.
În propriul proces de refactorizare Rails, nu ezitați să săriți câțiva pași în jos, dacă aveți încredere în acest lucru (de exemplu, săriți de la vizualizare la controler sau de la controler la model). Rețineți că fluxul de cod este de la vizualizare la model.
Nu-ți fie frică să arăți prost. Ceea ce separă limbajele moderne de vechile aplicații de redare a șabloanelor CGI nu este faptul că facem totul în mod corect de fiecare dată – este faptul că ne facem timp pentru a refactoriza, reutiliza și împărtăși eforturile noastre.