Codice Buggy Rails: i 10 errori più comuni che fanno gli sviluppatori Rails

Pubblicato: 2022-03-11

Ruby on Rails ("Rails") è un popolare framework open source, basato sul linguaggio di programmazione Ruby che si sforza di semplificare e snellire il processo di sviluppo delle applicazioni web.

Rails è costruito sul principio della convenzione sulla configurazione. In poche parole, ciò significa che, per impostazione predefinita, Rails presume che i suoi sviluppatori esperti seguano le convenzioni di best practice "standard" (per cose come denominazione, struttura del codice e così via) e, se lo fai, le cose funzioneranno per te "auto -magicamente” senza che tu debba specificare questi dettagli. Sebbene questo paradigma abbia i suoi vantaggi, non è privo di insidie. In particolare, la "magia" che accade dietro le quinte nel framework a volte può portare a falsificazioni, confusione e "che diavolo sta succedendo?" tipi di problemi. Può anche avere ramificazioni indesiderabili per quanto riguarda la sicurezza e le prestazioni.

Di conseguenza, mentre Rails è facile da usare, non è nemmeno difficile da usare in modo improprio. Questo tutorial esamina 10 problemi comuni di Rails, incluso come evitarli e i problemi che causano.

Errore comune n. 1: inserire troppa logica nel controller

Rails si basa su un'architettura MVC. Nella comunità di Rails, è da un po' che si parla di fat model, skinny controller, eppure diverse applicazioni Rails recenti che ho ereditato hanno violato questo principio. È fin troppo facile spostare la logica di visualizzazione (che è meglio alloggiata in un helper) o la logica di dominio/modello nel controller.

Il problema è che l'oggetto controller inizierà a violare il principio di responsabilità singola, rendendo difficili e soggette a errori le modifiche future alla base di codice. In generale, gli unici tipi di logica che dovresti avere nel tuo controller sono:

  • Gestione sessioni e cookie. Ciò potrebbe includere anche l'autenticazione/autorizzazione o qualsiasi ulteriore elaborazione dei cookie necessaria.
  • Selezione del modello. Logica per trovare l'oggetto modello corretto dati i parametri passati dalla richiesta. Idealmente questa dovrebbe essere una chiamata a un singolo metodo di ricerca che imposta una variabile di istanza da utilizzare in seguito per eseguire il rendering della risposta.
  • Richiedi la gestione dei parametri. Raccolta dei parametri di richiesta e chiamata a un metodo del modello appropriato per mantenerli.
  • Rendering/reindirizzamento. Rendering del risultato (html, xml, json, ecc.) o reindirizzamento, a seconda dei casi.

Anche se questo spinge ancora i limiti del principio di responsabilità singola, è una sorta di minimo indispensabile che il framework Rails ci richiede di avere nel controller.

Errore comune n. 2: mettere troppa logica nella vista

Il motore di creazione modelli Rails pronto all'uso, ERB, è un ottimo modo per creare pagine con contenuto variabile. Tuttavia, se non stai attento, potresti presto ritrovarti con un file di grandi dimensioni che è un mix di codice HTML e Ruby che può essere difficile da gestire e mantenere. Questa è anche un'area che può portare a molte ripetizioni, portando a violazioni dei principi DRY (non ripetere te stesso).

Questo può manifestarsi in diversi modi. Uno è l'uso eccessivo della logica condizionale nelle viste. Come semplice esempio, considera un caso in cui abbiamo un metodo current_user disponibile che restituisce l'utente attualmente connesso. Spesso, ci saranno strutture logiche condizionali come questa nei file di visualizzazione:

 <h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>

Un modo migliore per gestire qualcosa del genere è assicurarsi che l'oggetto restituito da current_user sia sempre impostato, indipendentemente dal fatto che qualcuno sia connesso o meno, e che risponda ai metodi utilizzati nella vista in modo ragionevole (a volte indicato come null oggetto). Ad esempio, potresti definire l'assistente current_user in app/controllers/application_controller questo modo:

 require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end

Ciò consentirebbe quindi di sostituire l'esempio di codice di visualizzazione precedente con questa semplice riga di codice:

 <h3>Welcome, <%= current_user.name -%></h3>

Un paio di ulteriori best practice Rails consigliate:

  • Usa i layout di visualizzazione e le parziali in modo appropriato per incapsulare le cose che si ripetono nelle tue pagine.
  • Usa presentatori/decoratori come la gemma Draper per incapsulare la logica di costruzione della vista in un oggetto Rubino. È quindi possibile aggiungere metodi a questo oggetto per eseguire operazioni logiche che altrimenti avresti potuto inserire nel codice di visualizzazione.

Errore comune n. 3: inserire troppa logica nel modello

Data la guida per ridurre al minimo la logica nelle viste e nei controller, l'unico posto rimasto in un'architettura MVC per inserire tutta quella logica sarebbe nei modelli, giusto?

Beh, non proprio.

Molti sviluppatori di Rails in realtà commettono questo errore e finiscono per attaccare tutto nelle loro classi di modelli ActiveRecord portando a file mongo che non solo violano il principio di responsabilità singola ma sono anche un incubo di manutenzione.

Funzionalità come la generazione di notifiche e-mail, l'interfacciamento con servizi esterni, la conversione in altri formati di dati e simili non hanno molto a che fare con la responsabilità principale di un modello ActiveRecord che dovrebbe fare poco più che trovare e mantenere i dati in un database.

Quindi, se la logica non dovrebbe andare nelle viste, e non dovrebbe andare nei controller, e non dovrebbe andare nei modelli, allora, dove dovrebbe andare?

Inserisci i semplici vecchi oggetti Ruby (PORO). Con un framework completo come Rails, i nuovi sviluppatori sono spesso riluttanti a creare le proprie classi al di fuori del framework. Tuttavia, spostare la logica fuori dal modello nei PORO è spesso proprio ciò che il medico ha ordinato per evitare modelli eccessivamente complessi. Con i PORO, puoi incapsulare elementi come notifiche e-mail o interazioni API nelle proprie classi anziché inserirli in un modello ActiveRecord .

Quindi, con questo in mente, in generale, l'unica logica che dovrebbe rimanere nel tuo modello è:

  • Configurazione ActiveRecord (cioè, relazioni e convalide)
  • Semplici metodi di mutazione per incapsulare l'aggiornamento di una manciata di attributi e salvarli nel database
  • Accedere ai wrapper per nascondere le informazioni sul modello interno (ad esempio, un metodo full_name che combina i campi first_name e last_name nel database)
  • Query sofisticate (cioè più complesse di una semplice find ); in generale, non dovresti mai usare il metodo where , o qualsiasi altro metodo di creazione di query simile, al di fuori della classe del modello stessa
Correlati: 8 domande essenziali per l'intervista a Ruby on Rails

Errore comune n. 4: utilizzare classi helper generiche come discarica

Questo errore è davvero una sorta di corollario dell'errore n. 3 sopra. Come discusso, il framework Rails pone l'accento sui componenti nominati (ad esempio, modello, vista e controller) di un framework MVC. Esistono definizioni abbastanza buone del tipo di cose che appartengono alle classi di ciascuno di questi componenti, ma a volte potremmo aver bisogno di metodi che non sembrano rientrare in nessuno dei tre.

I generatori di Rails creano convenientemente una directory di supporto e una nuova classe di supporto per ogni nuova risorsa che creiamo. Diventa troppo allettante, tuttavia, iniziare a inserire in queste classi di supporto qualsiasi funzionalità che non si adatta formalmente al modello, alla vista o al controller.

Sebbene Rails sia certamente incentrato su MVC, nulla ti impedisce di creare i tuoi tipi di classi e aggiungere directory appropriate per contenere il codice per quelle classi. Quando hai funzionalità aggiuntive, pensa a quali metodi raggruppano e trova buoni nomi per le classi che contengono quei metodi. L'uso di un framework completo come Rails non è una scusa per lasciare che le buone pratiche di progettazione orientata agli oggetti vadano nel dimenticatoio.

Errore comune n. 5: usare troppe gemme

Ruby e Rails sono supportati da un ricco ecosistema di gemme che collettivamente forniscono praticamente qualsiasi capacità a cui uno sviluppatore possa pensare. Questo è ottimo per creare rapidamente un'applicazione complessa, ma ho anche visto molte applicazioni gonfie in cui il numero di gemme nel Gemfile dell'applicazione è sproporzionato rispetto alla funzionalità fornita.

Ciò causa diversi problemi di Rails. L'uso eccessivo di gemme rende la dimensione di un processo Rails più grande di quanto dovrebbe essere. Ciò può rallentare le prestazioni in produzione. Oltre alla frustrazione dell'utente, ciò può anche comportare la necessità di configurazioni di memoria del server più grandi e un aumento dei costi operativi. Ci vuole anche più tempo per avviare applicazioni Rails più grandi, il che rende lo sviluppo più lento e rende i test automatizzati più lunghi (e di norma, i test lenti semplicemente non vengono eseguiti così spesso).

Tieni presente che ogni gem che porti nella tua applicazione potrebbe a sua volta avere dipendenze da altre gemme e queste a loro volta potrebbero avere dipendenze da altre gemme e così via. L'aggiunta di altre gemme può quindi avere un effetto di composizione. Ad esempio, l'aggiunta della gemma rails_admin porterà 11 gemme in più in totale, oltre il 10% in più rispetto all'installazione di base di Rails.

Al momento della stesura di questo articolo, una nuova installazione di Rails 4.1.0 include 43 gemme nel file Gemfile.lock . Questo è ovviamente più di quanto è incluso in Gemfile e rappresenta tutte le gemme che la manciata di gemme Rails standard porta come dipendenze.

Considera attentamente se vale la pena spendere di più mentre aggiungi ogni gemma. Ad esempio, gli sviluppatori spesso aggiungono casualmente la gemma rails_admin perché essenzialmente fornisce un bel front-end web alla struttura del modello, ma in realtà non è molto più di un sofisticato strumento di navigazione del database. Anche se la tua applicazione richiede utenti amministratori con privilegi aggiuntivi, probabilmente non vorrai concedere loro l'accesso grezzo al database e saresti meglio servito sviluppando la tua funzione di amministrazione più snella che aggiungendo questa gemma.

Errore comune n. 6: ignorare i file di registro

Sebbene la maggior parte degli sviluppatori di Rails sia a conoscenza dei file di registro predefiniti disponibili durante lo sviluppo e in produzione, spesso non prestano sufficiente attenzione alle informazioni in quei file. Sebbene molte applicazioni si basino su strumenti di monitoraggio dei registri come Honeybagger o New Relic in produzione, è anche importante tenere d'occhio i file di registro durante tutto il processo di sviluppo e test dell'applicazione.

Come accennato in precedenza in questo tutorial, il framework Rails fa molta "magia" per te, specialmente nei modelli. Definire le associazioni nei tuoi modelli rende molto facile inserire le relazioni e avere tutto a disposizione delle tue opinioni. Tutto l'SQL necessario per riempire gli oggetti del modello viene generato per te. È fantastico. Ma come fai a sapere che l'SQL generato è efficiente?

Un esempio in cui ti imbatterai spesso è chiamato problema di query N+1. Sebbene il problema sia ben compreso, l'unico vero modo per osservarlo è esaminare le query SQL nei file di registro.

Supponiamo, ad esempio, di avere la seguente query in una tipica applicazione di blog in cui verranno visualizzati tutti i commenti per un insieme selezionato di post:

 def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end

Quando esaminiamo il file di registro di una richiesta che chiama questo metodo, vedremo qualcosa di simile al seguente, in cui viene eseguita una singola query per ottenere i tre oggetti post, quindi vengono eseguite altre tre query per ottenere i commenti di ciascuno di quegli oggetti:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

La capacità di caricamento ansioso di ActiveRecord in Rails consente di ridurre significativamente il numero di query consentendo di specificare in anticipo tutte le associazioni che verranno caricate. Questo viene fatto chiamando il metodo includes (o preload ) sull'oggetto Arel ( ActiveRecord::Relation ) in fase di compilazione. Con includes , ActiveRecord garantisce che tutte le associazioni specificate vengano caricate utilizzando il numero minimo possibile di query; per esempio:

 def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end

Quando viene eseguito il codice rivisto sopra, vediamo nel file di registro che tutti i commenti sono stati raccolti in una singola query anziché tre:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

Molto più efficiente.

Questa soluzione al problema N+1 è in realtà solo intesa come un esempio del tipo di inefficienze che possono esistere "sotto il cofano" nella tua applicazione se non stai prestando adeguata attenzione. Il punto è che dovresti controllare i tuoi file di registro di sviluppo e test durante lo sviluppo per verificare (e affrontare!) Inefficienze nel codice che crea le tue risposte.

La revisione dei file di registro è un ottimo modo per essere informati sulle inefficienze del codice e per correggerle prima che l'applicazione entri in produzione. Altrimenti, potresti non essere a conoscenza di un conseguente problema di prestazioni di Rails fino a quando il tuo sistema non sarà attivo, poiché è probabile che il set di dati con cui lavori in fase di sviluppo e test sia molto più piccolo che in produzione. Se stai lavorando su una nuova app, anche il tuo set di dati di produzione potrebbe iniziare in piccolo e la tua app sembrerà che funzioni correttamente. Tuttavia, man mano che il set di dati di produzione cresce, problemi di Rails come questo causeranno un'esecuzione sempre più lenta dell'applicazione.

Se trovi che i tuoi file di registro sono intasati da un mucchio di informazioni che non ti servono, ecco alcune cose che puoi fare per ripulirli (le tecniche lì funzionano per lo sviluppo così come i registri di produzione).

Errore comune n. 7: mancanza di test automatizzati

Ruby e Rails forniscono potenti funzionalità di test automatizzato per impostazione predefinita. Molti sviluppatori di Rails scrivono test molto sofisticati usando gli stili TDD e BDD e fanno uso di framework di test ancora più potenti con gemme come rspec e cucumber.

Nonostante sia facile aggiungere test automatizzati alla tua applicazione Rails, tuttavia, sono stato molto spiacevolmente sorpreso da quanti progetti ho ereditato o a cui ho partecipato in cui non c'erano letteralmente test scritti (o nella migliore delle ipotesi, molto pochi) dal precedente team di sviluppo. Sebbene vi sia un ampio dibattito su quanto dovrebbero essere completi i test, è abbastanza chiaro che almeno alcuni test automatizzati dovrebbero esistere per ogni applicazione.

Come regola generale, dovrebbe esserci almeno un test di integrazione di alto livello scritto per ogni azione nei controller. Ad un certo punto in futuro, altri sviluppatori Rails molto probabilmente vorranno estendere o modificare il codice, o aggiornare una versione di Ruby o Rails, e questo framework di test fornirà loro un modo chiaro per verificare che la funzionalità di base dell'applicazione sia Lavorando. Un ulteriore vantaggio di questo approccio è che fornisce ai futuri sviluppatori una chiara definizione dell'intera raccolta di funzionalità fornite dall'applicazione.

Errore comune n. 8: blocco delle chiamate a servizi esterni

I fornitori di servizi di Rails di terze parti in genere semplificano l'integrazione dei loro servizi nella tua applicazione tramite gemme che avvolgono le loro API. Ma cosa succede se il tuo servizio esterno ha un'interruzione o inizia a funzionare molto lentamente?

Per evitare di bloccare queste chiamate, invece di chiamare questi servizi direttamente nella tua applicazione Rails durante la normale elaborazione di una richiesta, dovresti spostarli in una sorta di servizio di accodamento dei lavori in background, ove possibile. Alcune gemme popolari utilizzate nelle applicazioni Rails per questo scopo includono:

  • Lavoro in ritardo
  • Rischiare
  • Sidekiq

Nei casi in cui non è pratico o non fattibile delegare l'elaborazione a una coda di processi in background, sarà necessario assicurarsi che l'applicazione disponga di disposizioni sufficienti per la gestione degli errori e il failover per quelle situazioni inevitabili in cui il servizio esterno si interrompe o si verificano problemi . Dovresti anche testare la tua applicazione senza il servizio esterno (magari rimuovendo il server su cui si trova la tua applicazione dalla rete) per verificare che non comporti conseguenze impreviste.

Errore comune n. 9: sposarsi con migrazioni di database esistenti

Il meccanismo di migrazione del database di Rails consente di creare istruzioni per aggiungere e rimuovere automaticamente tabelle e righe del database. Poiché i file che contengono queste migrazioni sono denominati in modo sequenziale, è possibile riprodurli dall'inizio del tempo per riportare un database vuoto nello stesso schema della produzione. Questo è quindi un ottimo modo per gestire modifiche granulari allo schema del database della tua applicazione ed evitare problemi di Rails.

Anche se questo funziona sicuramente bene all'inizio del tuo progetto, con il passare del tempo il processo di creazione del database può richiedere un po' di tempo e talvolta le migrazioni vengono smarrite, inserite fuori servizio o introdotte da altre applicazioni Rails che utilizzano lo stesso server di database.

Rails crea una rappresentazione dello schema corrente in un file chiamato db/schema.rb (per impostazione predefinita) che di solito viene aggiornato quando vengono eseguite le migrazioni del database. Il file schema.rb può anche essere generato quando non sono presenti migrazioni eseguendo l'attività rake db:schema:dump . Un errore comune di Rails consiste nel controllare una nuova migrazione nel repository di origine ma non nel file schema.rb corrispondentemente aggiornato.

Quando le migrazioni sono sfuggite di mano e richiedono troppo tempo per essere eseguite, o non creano più il database correttamente, gli sviluppatori non dovrebbero aver paura di cancellare la vecchia directory delle migrazioni, scaricare un nuovo schema e continuare da lì. La configurazione di un nuovo ambiente di sviluppo richiederebbe quindi un rake db:schema:load anziché il rake db:migrate su cui si basa la maggior parte degli sviluppatori.

Alcuni di questi problemi sono discussi anche nella Rails Guide.

Errore comune n. 10: controllo di informazioni riservate nei repository del codice sorgente

Il framework Rails semplifica la creazione di applicazioni sicure impermeabili a molti tipi di attacchi. Alcune di queste operazioni vengono eseguite utilizzando un token segreto per proteggere una sessione con un browser. Anche se questo token è ora archiviato in config/secrets.yml e quel file legge il token da una variabile di ambiente per i server di produzione, le versioni precedenti di Rails includevano il token in config/initializers/secret_token.rb . Questo file spesso viene erroneamente archiviato nel repository del codice sorgente con il resto dell'applicazione e, quando ciò accade, chiunque abbia accesso al repository ora può facilmente compromettere tutti gli utenti della tua applicazione .

Dovresti quindi assicurarti che il tuo file di configurazione del repository (ad esempio, .gitignore per gli utenti git) escluda il file con il tuo token. I tuoi server di produzione possono quindi prelevare il loro token da una variabile di ambiente o da un meccanismo come quello fornito da dotenv gem.

Conclusione del tutorial

Rails è un potente framework che nasconde molti dei brutti dettagli necessari per costruire un'applicazione web robusta. Sebbene ciò renda lo sviluppo di applicazioni Web Rails molto più veloce, gli sviluppatori dovrebbero prestare attenzione ai potenziali errori di progettazione e codifica, per assicurarsi che le loro applicazioni siano facilmente estensibili e gestibili man mano che crescono.

Gli sviluppatori devono anche essere consapevoli dei problemi che possono rendere le loro applicazioni più lente, meno affidabili e meno sicure. È importante studiare il framework e assicurarsi di comprendere appieno i compromessi di architettura, progettazione e codifica che si stanno effettuando durante il processo di sviluppo, per garantire un'applicazione di alta qualità e prestazioni elevate.

Correlati: quali sono i vantaggi di Ruby on Rails? Dopo due decenni di programmazione, utilizzo Rails