Dumm bauen, intelligent umgestalten: Wie man Probleme aus Ruby-on-Rails-Code herausarbeitet

Veröffentlicht: 2022-03-11
Daniel Lewis ist seit über 4 Jahren ein professioneller Ruby on Rails-Entwickler und arbeitet an etwa einem Dutzend stark frequentierter Webanwendungen, viele davon über Toptal.

Manchmal 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.

Manchmal mögen wir eine Funktionsanfrage nicht, weil der einfachste Weg, sie zu lösen, darin besteht, schlechten Code zu schreiben.
Twittern

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:

Diesem Ruby on Rails-Refaktorisierungsprozess folge ich, um Rails-Probleme zu lösen.

Für eine alternative Perspektive ist hier das Git-Commit-Protokoll für eine Funktion, die Schritt für Schritt umgestaltet wurde:

Dieses Protokoll demonstriert einen Rails-Refaktor, der Schritt für Schritt durchgeführt 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:

  1. Sie können diese Änderung in weniger als 15 Minuten inszenieren.
  2. Wenn dieser Block belassen wird, kann er leicht zwischengespeichert werden.
  3. 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.

Etwas offensichtlich Dummes zu reparieren ist immer einfacher, als eine schlecht implementierte, fehlerhafte Abstraktion zu reparieren.
Twittern

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.

Siehe auch : Timestamp Truncation: A Ruby on Rails ActiveRecord Tale