Dumm bauen, intelligent umgestalten: Wie man Probleme aus Ruby-on-Rails-Code herausarbeitet
Veröffentlicht: 2022-03-11Manchmal geben uns Kunden Feature-Requests, die uns wirklich nicht gefallen. Es ist nicht so, dass wir unsere Kunden nicht mögen – wir lieben unsere Kunden. Es ist nicht so, dass uns die Funktion nicht gefällt – die meisten von Kunden gewünschten Funktionen sind perfekt auf ihre Geschäftsziele und ihr Einkommen abgestimmt.
Manchmal mögen wir eine Funktionsanfrage nicht, weil der einfachste Weg, sie zu lösen, darin besteht, schlechten Code zu schreiben, und wir keine elegante Lösung im Kopf haben. Dies wird viele von uns Rails-Entwicklern auf vergebliche Suchen durch RubyToolbox, GitHub, Entwicklerblogs und StackOverflow schicken, auf der Suche nach einem Schmuckstück, Plugin oder Beispielcode, der uns ein besseres Gefühl von uns selbst geben wird.
Ruby on Rails-Code umgestalten
Nun, ich bin hier, um Ihnen zu sagen: Es ist in Ordnung, schlechten Code zu schreiben. Manchmal lässt sich schlechter Rails-Code leichter in schönen Code umgestalten als eine schlecht durchdachte Lösung, die unter Zeitdruck implementiert wird.
Dies ist der Rails-Refactoring-Prozess, dem ich gerne folge, wenn ich Probleme aus meinen schrecklichen Pflasterlösungen massiere:
Für eine alternative Perspektive ist hier das Git-Commit-Protokoll für eine Funktion, die Schritt für Schritt umgestaltet wurde:
Und hier ist noch ein interessanter Artikel über Refactoring im großen Maßstab von einem Kollegen aus dem Toptal-Netzwerk.
Mal sehen, wie es gemacht wird.
Die Ansichten
Schritt 1. Beginnen Sie in den Ansichten
Angenommen, wir beginnen ein Ticket für ein neues Feature. Der Kunde teilt uns mit: „Besucher sollten auf der Willkommensseite eine Liste aktiver Projekte sehen können.“
Dieses Ticket erfordert eine sichtbare Änderung, daher wäre ein angemessener Ort, um mit der Arbeit zu beginnen, die Ansichten. Das Problem ist einfach und eines, für dessen Lösung wir alle mehrfach trainiert wurden. Ich werde es The Wrong Way lösen und zeigen, wie ich meine Lösung in die entsprechenden Bereiche umgestalten kann. Ein Problem lösen The Wrong Way kann uns dabei helfen, den Buckel zu überwinden, die richtige Lösung nicht zu kennen.
Nehmen wir zunächst an, wir haben ein Modell namens Project
mit einem booleschen Attribut namens active
. Wir möchten eine Liste aller Projects
erhalten, bei denen active
gleich true
ist, sodass wir Project.where(active: true)
verwenden und mit einem each
-Block durchlaufen können.
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
Ich weiß, was Sie sagen: „Das würde niemals eine Codeüberprüfung bestehen“ oder „Mein Kunde würde mich dafür sicher feuern.“ Ja, diese Lösung durchbricht die Trennung von Modell, Ansicht und Controller, sie kann zu schwer nachzuverfolgenden streunenden Datenbankaufrufen führen und in Zukunft schwierig zu warten sein. Aber bedenken Sie den Wert, es auf die falsche Weise zu tun:
- Sie können diese Änderung in weniger als 15 Minuten inszenieren.
- Wenn dieser Block belassen wird, kann er leicht zwischengespeichert werden.
- Das Beheben dieses Rails-Problems ist unkompliziert (könnte einem Junior-Entwickler übergeben werden).
Schritt 2. Teiltöne
Nachdem ich es The Wrong Way gemacht habe, fühle ich mich schlecht und möchte meinen schlechten Code isolieren. Wenn diese Änderung eindeutig nur eine Angelegenheit der Ansichtsebene wäre, könnte ich meine Schande in einen Teil umwandeln.
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
Das ist etwas besser. Natürlich machen wir immer noch den Fehler einer Model-Abfrage in einer View, aber zumindest, wenn später ein Betreuer hereinkommt und mein schreckliches Partial sieht, wird er eine einfache Möglichkeit haben, insbesondere dieses Rails-Code-Problem anzugehen. Etwas offensichtlich Dummes zu reparieren ist immer einfacher, als eine schlecht implementierte, fehlerhafte Abstraktion zu reparieren.
Schritt 3. Helfer
Helfer in Rails sind eine Möglichkeit, eine DSL (Domain Specific Language) für einen Abschnitt Ihrer Views zu erstellen. Sie müssen Ihren Code mit content_tag anstelle von haml oder HTML neu schreiben, aber Sie haben den Vorteil, dass Sie Datenstrukturen manipulieren dürfen, ohne dass andere Entwickler Sie für 15 Zeilen nicht druckbaren View-Code anstarren.
Wenn ich hier Helfer verwenden würde, würde ich wahrscheinlich das li
-Tag umgestalten.
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
Die Controller
Schritt 4. Verschieben Sie es zu den Controllern
Vielleicht ist Ihr schrecklicher Code nicht nur ein Anliegen von View. Wenn Ihr Code immer noch stinkt, suchen Sie nach Abfragen, die Sie von den Ansichten zu den Controllern überführen können.

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
Schritt 5. Controller-Filter
Der offensichtlichste Grund, Code in einen Controller before_filter
oder after_filter
zu verschieben, ist Code, den Sie in mehreren Controller-Aktionen dupliziert haben. Sie können Code auch in einen Controller-Filter verschieben, wenn Sie den Zweck der Controller-Aktion von den Anforderungen Ihrer Ansichten trennen möchten.
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
Schritt 6. Anwendungscontroller
Angenommen, Sie möchten, dass Ihr Code auf jeder Seite angezeigt wird, oder Sie möchten Controller-Hilfsfunktionen für alle Controller verfügbar machen, dann können Sie Ihre Funktion in den ApplicationController verschieben. Wenn die Änderungen global sind, möchten Sie möglicherweise auch Ihr Anwendungslayout ändern.
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
Die Models
Schritt 7. Umgestaltung des Modells
Wie das MVC-Motto lautet: Fat Model, Skinny Controllers, and Dumb Views . Von uns wird erwartet, dass wir alles, was wir können, in das Modell umgestalten, und es stimmt, dass die komplexeste Funktionalität schließlich zu Modellzuordnungen und Modellmethoden wird. Wir sollten immer vermeiden, Dinge im Modell zu formatieren/anzuzeigen, aber die Umwandlung von Daten in andere Datentypen ist zulässig. In diesem Fall wäre das Beste, was wir in das Modell umgestalten könnten, die where(active: true)
-Klausel, die wir in einen Gültigkeitsbereich umwandeln können. Die Verwendung eines Geltungsbereichs ist nicht nur deshalb wertvoll, weil er den Aufruf schöner aussehen lässt, sondern wenn wir uns jemals entschieden haben, ein Attribut wie delete
oder outdated
hinzuzufügen, können wir diesen Geltungsbereich ändern, anstatt alle unsere where
-Klauseln zu suchen.
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)
Schritt 8. Modellfilter
Wir haben in diesem Fall keine besondere Verwendung für die before_save
oder after_save filters
eines Modells, aber der nächste Schritt, den ich normalerweise unternehme, besteht darin, die Funktionalität von Controllern und Modellmethoden in Modellfilter zu verschieben.
Angenommen, wir hätten ein anderes Attribut, num_views
. Wenn num_views > 50
, wird das Projekt inaktiv. Wir könnten dieses Problem in der Ansicht lösen, aber das Vornehmen von Datenbankänderungen in einer Ansicht ist unangemessen. Wir könnten es im Controller lösen, aber unsere Controller sollten so dünn wie möglich sein! Wir können es einfach im Modell lösen.
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
Hinweis: Sie sollten es vermeiden, self.save
in einem Modellfilter aufzurufen, da dies zu rekursiven Speicherereignissen führt und die Datenbankmanipulationsschicht Ihrer Anwendung sowieso der Controller sein sollte.
Schritt 9. Bibliotheken
Gelegentlich ist Ihr Feature groß genug, dass es eine eigene Bibliothek rechtfertigen könnte. Vielleicht möchten Sie es in eine Bibliotheksdatei verschieben, weil es an vielen Stellen wiederverwendet wird oder weil es groß genug ist, dass Sie es separat entwickeln möchten.
Es ist in Ordnung, Bibliotheksdateien im lib/-Verzeichnis zu speichern, aber wenn sie wachsen, können Sie sie in ein echtes RubyGem übertragen! Ein großer Vorteil des Verschiebens Ihres Codes in eine Bibliothek besteht darin, dass Sie die Bibliothek getrennt von Ihrem Modell testen können.
Wie auch immer, im Fall einer Projektliste könnten wir rechtfertigen, den Aufruf scope :active
aus dem Project
in eine Bibliotheksdatei zu verschieben und ihn zurück in Ruby zu bringen:
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
Hinweis: Die self.included
Methode wird aufgerufen, wenn eine Rails-Model-Klasse geladen wird, und übergibt den Klassenbereich als Variable k
.
Fazit
In diesem Ruby on Rails-Refactoring-Tutorial haben wir uns weniger als 15 Minuten Zeit genommen und eine Lösung implementiert und für Benutzertests bereitgestellt, damit sie in den Funktionsumfang aufgenommen oder entfernt werden kann. Am Ende des Refactoring-Prozesses haben wir einen Code, der einen Rahmen für die Implementierung auflistbarer, aktivierbarer Elemente in mehreren Modellen vorgibt, der selbst den strengsten Überprüfungsprozess bestehen würde.
In Ihrem eigenen Rails-Refactoring-Prozess können Sie gerne ein paar Schritte in der Pipeline überspringen, wenn Sie sich sicher sind (z. B. von View zu Controller oder von Controller zu Model springen). Denken Sie nur daran, dass der Codefluss von der Ansicht zum Modell erfolgt.
Haben Sie keine Angst, dumm auszusehen. Was moderne Sprachen von alten Anwendungen zum Rendern von CGI-Vorlagen unterscheidet, ist nicht, dass wir jedes Mal alles auf die richtige Art und Weise machen – sondern dass wir uns die Zeit nehmen, umzugestalten, wiederzuverwenden und unsere Bemühungen zu teilen.