Build Dumb, Refactor Smart: come eliminare i problemi dal codice Ruby on Rails
Pubblicato: 2022-03-11A volte, i clienti ci inviano richieste di funzionalità che non ci piacciono davvero. Non è che non ci piacciono i nostri clienti, amiamo i nostri clienti. Non è che non ci piaccia la funzionalità: la maggior parte delle funzionalità richieste dai clienti sono perfettamente allineate con i loro obiettivi aziendali e le entrate.
A volte non ci piace una richiesta di funzionalità perché il modo più semplice per risolverla è scrivere codice errato e non abbiamo una soluzione elegante in testa. Questo invierà molti di noi sviluppatori Rails a fare ricerche infruttuose attraverso RubyToolbox, GitHub, i blog degli sviluppatori e StackOverflow alla ricerca di una gemma, di un plugin o di un codice di esempio che ci faccia sentire meglio con noi stessi.
Refactor Ruby on Rails Code
Bene, sono qui per dirti: va bene scrivere codice errato. A volte, un codice Rails errato è più facile da rifattorizzare in un codice bello rispetto a una soluzione mal concepita implementata in tempi ridotti.
Questo è il processo di refactoring di Rails che mi piace seguire quando risolvo i problemi con le mie orribili soluzioni di cerotto:
Per una prospettiva alternativa, ecco il log di commit Git per una funzionalità che è stata rifattorizzato passo dopo passo:
Ed ecco un altro articolo interessante sul refactoring su larga scala di un collega della rete Toptal.
Vediamo come è fatto.
Le visualizzazioni
Passaggio 1. Inizia nelle Viste
Supponiamo che stiamo iniziando un ticket per una nuova funzionalità. Il cliente ci dice: "I visitatori dovrebbero essere in grado di visualizzare un elenco di Progetti attivi nella pagina di benvenuto".
Questo ticket richiede una modifica visibile, quindi un posto ragionevole per iniziare a lavorare sarebbe nelle Views. Il problema è semplice e tutti siamo stati addestrati a risolvere più volte. Lo risolverò nel modo sbagliato e dimostrerò come riorganizzare la mia soluzione nelle aree appropriate. Risolvere un problema The Wrong Way può aiutarci a superare il problema di non conoscere la soluzione giusta.
Per iniziare, supponiamo di avere un modello chiamato Project
con un attributo booleano chiamato active
. Vogliamo ottenere un elenco di tutti i Projects
in cui active
è uguale a true
, quindi possiamo usare Project.where(active: true)
e scorrerci sopra con each
blocco.
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
So cosa stai dicendo: "Non passerebbe mai una revisione del codice" o "Il mio cliente mi licenzierebbe sicuramente per questo". Sì, questa soluzione interrompe la separazione delle preoccupazioni Model-View-Controller, potrebbe causare chiamate vaganti al database difficili da tracciare e potrebbe diventare difficile da mantenere in futuro. Ma considera il valore di farlo nel modo sbagliato :
- Puoi ottenere questa modifica durante lo staging in meno di 15 minuti.
- Se lasciato, questo blocco è facile da memorizzare nella cache.
- La risoluzione di questo problema di Rails è semplice (potrebbe essere data a uno sviluppatore junior).
Passaggio 2. Parziali
Dopo averlo fatto nel modo sbagliato , mi sento male con me stesso e voglio isolare il mio codice errato. Se questa modifica fosse chiaramente solo una preoccupazione del livello Visualizza, potrei riformulare la mia vergogna in modo parziale.
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
È un po' meglio. Chiaramente, stiamo ancora commettendo l'errore di una query Model in una vista, ma almeno quando un manutentore arriva più tardi e vede il mio orribile parziale, avrà un modo semplice per affrontare quel problema di codice Rails in particolare. Riparare qualcosa di ovviamente stupido è sempre più facile che riparare un'astrazione mal implementata e buggata.
Passaggio 3. Aiutanti
Gli helper in Rails sono un modo per creare un DSL (Domain Specific Language) per una sezione delle tue viste. Devi riscrivere il tuo codice usando content_tag invece di haml o HTML, ma ottieni il vantaggio di poter manipolare le strutture dati senza che altri sviluppatori ti guardino male per 15 righe di codice Visualizza non stampabili.
Se dovessi usare gli helper qui, probabilmente farei il refactoring del tag 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
I controllori
Passaggio 4. Spostalo sui controller
Forse il tuo codice terribile non è solo un problema di visualizzazione. Se il tuo codice ha ancora odore, cerca le query che puoi passare dalle visualizzazioni ai controller.

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
Passaggio 5. Filtri del controller
Il motivo più ovvio per spostare il codice in un Controller before_filter
o after_filter
è per il codice che hai duplicato in più azioni del Controller. Puoi anche spostare il codice in un filtro Controller se desideri separare lo scopo dell'azione Controller dai requisiti delle tue viste.
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
Passaggio 6. Controller dell'applicazione
Supponiamo che tu abbia bisogno che il tuo codice venga visualizzato in ogni pagina o che tu voglia rendere disponibili le funzioni di supporto del controller a tutti i controller, puoi spostare la tua funzione in ApplicationController. Se le modifiche sono globali, potresti voler modificare anche il layout dell'applicazione.
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
I modelli
Passaggio 7. Refactoring al modello
Come recita il motto di MVC: Fat Model, Skinny Controllers e Dumb Views . Ci si aspetta che refactoring tutto ciò che è possibile nel modello, ed è vero che le funzionalità più complesse alla fine diventeranno associazioni di modelli e metodi di modello. Dovremmo sempre evitare di formattare/visualizzare cose nel Modello, ma è consentito trasformare i dati in altri tipi di dati. In questo caso, la cosa migliore da refactoring nel Modello sarebbe la clausola where(active: true)
, che possiamo trasformare in uno scope. L'uso di un ambito è prezioso non solo perché rende la chiamata più carina, ma se mai decidessimo di aggiungere un attributo come delete
o outdated
, possiamo modificare questo ambito invece di dare la caccia a tutte le nostre clausole 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)
Passaggio 8. Filtri modello
Non abbiamo un uso particolare per i after_save filters
before_save
o after_save di un modello in questo caso, ma il passaggio successivo che faccio di solito è spostare la funzionalità dai controller e dai metodi del modello nei filtri del modello.
Supponiamo di avere un altro attributo, num_views
. Se num_views > 50
, il progetto diventa inattivo. Potremmo risolvere questo problema nella vista, ma apportare modifiche al database in una vista non è appropriato. Potremmo risolverlo nel Controller, ma i nostri Controller dovrebbero essere il più sottili possibile! Possiamo risolverlo facilmente nel Modello.
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
Nota: dovresti evitare di chiamare self.save
in un filtro Modello, poiché ciò causa eventi di salvataggio ricorsivi e il livello di manipolazione del database della tua applicazione dovrebbe comunque essere il Controller.
Passaggio 9. Biblioteche
Occasionalmente, la tua funzione è abbastanza grande da poter garantire la propria libreria. Potresti volerlo spostare in un file di libreria perché è riutilizzato in molti posti, oppure è abbastanza grande da volerlo sviluppare separatamente.
Va bene memorizzare i file di libreria nella directory lib/ , ma man mano che crescono, puoi trasferirli in una vera RubyGem! Uno dei principali vantaggi dello spostamento del codice in una libreria è che puoi testare la libreria separatamente dal tuo modello.
Ad ogni modo, nel caso di un elenco di progetti, potremmo giustificare lo spostamento della chiamata scope :active
dal modello di Project
in un file di libreria e riportarlo in 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
Nota: il metodo self.included
viene chiamato quando una classe Rails Model viene caricata e passa nell'ambito della classe come variabile k
.
Conclusione
In questo tutorial sul refactoring di Ruby on Rails abbiamo impiegato meno di 15 minuti, abbiamo implementato una soluzione e l'abbiamo messa in fase di test per gli utenti, pronta per essere accettata nel set di funzionalità o rimossa. Entro la fine del processo di refactoring, abbiamo un pezzo di codice che definisce un framework per l'implementazione di elementi elencabili e attivabili su più modelli che supererebbero anche il processo di revisione più rigoroso.
Nel tuo processo di refactoring Rails, sentiti libero di saltare alcuni passaggi della pipeline se sei sicuro di farlo (ad esempio, passa da View a Controller o Controller a Model). Tieni solo a mente che il flusso del codice va da View a Model.
Non aver paura di sembrare stupido. Ciò che distingue i linguaggi moderni dalle vecchie applicazioni di rendering di modelli CGI non è che facciamo ogni volta tutto nel modo giusto , ma che ci prendiamo il tempo per refactoring, riutilizzare e condividere i nostri sforzi.