Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on Dezember 12, 2021 by admin

Gestern war ich in einem Vorstellungsgespräch für eine Stelle als Ruby-Entwickler bei einem Unternehmen hier in Denver, als mein Gesprächspartner mich vor eine gute Metaprogrammier-Herausforderung stellte: Er bat mich, einen automatisierten Methoden-Auditor zu schreiben.

Er beschrieb eine Klassenmethode, die den Namen einer Instanzmethode nehmen und jedes Mal, wenn diese Zielmethode aufgerufen wird, einen automatischen, protokollierten Papierpfad erzeugen würde. Etwa so:

Das würde eine Ausgabe erzeugen, die so aussieht:

Ich habe eine Lösung gefunden, die funktioniert, aber einige potenziell gefährliche Hacks für Object#send beinhaltet. Bevor ich meine Lösung präsentierte, sagte ich: „Das ist wahrscheinlich eine schlechte Idee, aber…“, und ich hatte Recht. Gute Ideen fangen selten so an.

Die Lösung, die er vorschlug, beinhaltete die Verwendung des oft übersehenen Module#prepend, und ich dachte, sie sei cool genug, um einen Bericht darüber zu rechtfertigen.

Die Strategie bestand darin, das Verhalten von Module#prepend zu verwenden, um eine dynamisch erzeugte Wrapper-Methode für Messenger#share zu erstellen.

In diesem Wrapper wird eine „Performing“-Meldung ausgelöst, bevor der Körper der Methode ausgeführt wird, die Methodenausführung selbst und dann eine abschließende „Exiting“-Meldung, wenn die Ausführung der Methode erfolgreich abgeschlossen ist.

Um zu verstehen, wie man das mit Module#prepend macht, muss man erst einmal verstehen, wie die Methodenvererbung in Ruby funktioniert.

  • Ahnenketten
  • Das Modul selbst
  • Alles zusammenbringen
  • Fazit

Ahnenketten

Jedes Ruby-Objekt steht am Ende einer sogenannten Ahnenkette. Dabei handelt es sich um eine Liste der Vorgängerobjekte, von denen das Objekt erbt. Ruby verwendet diese Ahnenkette, um zu bestimmen, welche Version einer Methode (wenn überhaupt) ausgeführt wird, wenn das Objekt eine Nachricht erhält.

Sie können den Ahnenbaum für jedes Objekt anzeigen, indem Sie Module#ancestors aufrufen. Hier ist zum Beispiel die Vorfahrenkette für unsere Klasse Messenger:

Wenn wir #include oder #prepend ein Modul in eine Klasse einfügen, nimmt Ruby Änderungen an der Vorfahrenkette dieser Klasse vor und ändert, welche Methoden gefunden werden und in welcher Reihenfolge. Der große Unterschied zwischen #include und #prepend besteht darin, wo diese Änderung vorgenommen wird. Erzeugen wir ein Modul namens Auditable, das (irgendwann) den Code enthält, der unsere Prüfungsmagie bewirkt:

Wenn wir Module#include verwenden, um die (derzeit nicht vorhandenen) Methoden aus Auditable zu importieren, wird Ruby dieses Modul als Vorfahre unserer Klasse Messenger einfügen. Sehen Sie selbst:

Wenn wir eine Methode von Messenger aufrufen, schaut Ruby nach, was in der Klasse Messenger definiert ist, und – wenn es keine Methode findet, die dem Aufruf entspricht – klettert es in der Ahnenkette bis zu Auditable, um erneut zu suchen. Wenn es auf Auditable nicht findet, was es sucht, geht es weiter zu Object und so weiter.

Das ist #include. Wenn wir stattdessen Module#prepend verwenden, um den Inhalt des Moduls zu importieren, erhalten wir einen völlig anderen Effekt:

#prepend macht Messenger zu einem Vorfahren von Auditable. Wait. Was?

Das könnte so aussehen, dass Messenger jetzt eine Superklasse von Auditable ist, aber das ist nicht genau das, was hier passiert. Instanzen der Klasse Messenger sind immer noch Instanzen der Klasse Messenger, aber Ruby sucht jetzt nach Methoden für Messenger im Modul Auditable, bevor es sie in der Klasse Messenger selbst sucht.

Und das, meine Freunde, ist es, was wir uns zunutze machen werden, um diesen Auditor zu bauen: Wenn wir eine Methode namens Auditable#share erstellen, wird Ruby diese finden, bevor es Messenger#share findet. Wir können dann einen super-Aufruf in Auditable#share verwenden, um auf die ursprüngliche Methode zuzugreifen (und sie auszuführen!), die auf Messenger definiert ist.

Das Modul selbst

Wir werden nicht wirklich eine Methode namens Auditable#share erstellen. Warum nicht? Weil wir wollen, dass dies ein flexibles Dienstprogramm ist. Wenn wir die Methode Auditable#share fest kodieren, können wir sie nur für Methoden verwenden, die die Methode #share implementieren. Oder noch schlimmer, wir müssten dasselbe Auditor-Muster für jede Methode, die wir jemals prüfen wollen, neu implementieren. Nein danke.

Also werden wir stattdessen unsere Methode dynamisch definieren und die audit_method-Klassenmethode, um sie auszulösen:

Wenn audit_method in einer implementierenden Klasse aufgerufen wird, wird eine Methode im Auditable-Modul mit demselben Namen wie die zu prüfende Methode erstellt. In unserem Fall wird eine Methode namens Auditable#share erstellt. Wie bereits erwähnt, findet Ruby diese Methode, bevor es die ursprüngliche Methode auf Messenger findet, weil wir das Auditable-Modul in der implementierenden Klasse vorangestellt haben.

Das bedeutet, dass wir einen Superaufruf verwenden können, um die Vorgängerkette zu erreichen und Messenger#send auszuführen. Wenn wir das tun, geben wir die Argumente, die wir gesammelt haben (*arguments), auch die Kette hinauf.

Wenn wir die ursprüngliche Methode aufgerufen haben, drucken wir unsere Exit-Meldung und machen Schluss. Gute Arbeit, Leute!

Alles zusammenbringen

Jetzt müssen wir nur noch prependdas Modul in unsere MessengerKlasse einbinden und schon sollte es losgehen:

Und siehe da, es funktioniert:

Die Implikationen für die Prüfung sind enorm, aber dieser Trick hat noch mehr zu bieten. Sie können das Verhalten von Objekten durch Voranstellen ändern, ohne die Objekte selbst zu ändern. Dies ist ein anpassungsfähiger und klarer Weg, um Komponenten höherer Ordnung in Ruby zu erstellen. Leistungstests, Fehlerbehandlung. Man kann hier eine Menge tun.

Fazit

Ruby-Module sind komplizierter als die meisten Leute denken. Es ist nicht so einfach, Code von einem Ort zum anderen zu „dumpen“, und wenn man diese Unterschiede versteht, kann man einige wirklich nette Werkzeuge für seinen Utility-Gürtel freischalten.

Du hast vielleicht bemerkt, dass ich heute nur über Module#include und Module#prepend gesprochen habe, und dass ich Module#extend nicht erwähnt habe. Das liegt daran, dass sie ganz anders funktioniert als ihre Cousins. Ich werde demnächst eine ausführliche Erklärung zu Module#extend schreiben, um die Reihe zu vervollständigen.

Wenn du mehr erfahren willst, empfehle ich dir die Lektüre von Ruby Modules: Include vs Prepend vs Extend von Léonard Hetsch. Es war sehr hilfreich bei der Zusammenstellung des Ganzen.

Schreibe einen Kommentar Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Neueste Beiträge

  • Acela ist zurück: NYC oder Boston für 99 Dollar
  • OMIM Eintrag – # 608363 – CHROMOSOM 22q11.2 DUPLIKATIONSSYNDROM
  • Kate Albrechts Eltern – Erfahren Sie mehr über ihren Vater Chris Albrecht und ihre Mutter Annie Albrecht
  • Temple Fork Outfitters
  • Burr (Roman)

Archive

  • Februar 2022
  • Januar 2022
  • Dezember 2021
  • November 2021
  • Oktober 2021
  • September 2021
  • August 2021
  • Juli 2021
  • Juni 2021
  • Mai 2021
  • April 2021
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語
©2022 CDhistory | Powered by WordPress & Superb Themes