Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on december 12, 2021 by admin

I går var jeg til samtale om en stilling som Ruby-udvikler hos en virksomhed her i Denver, da min interviewer stillede mig over for en god metaprogrammeringsudfordring: han bad mig skrive en automatiseret metodeauditor.

Han beskrev en klassemetode, der ville tage navnet på en instansmetode og generere et automatisk, logget papirspor, hver gang denne målmetode blev kaldt. Noget i stil med dette:

Det ville give et output, der så således ud:

Jeg kom med en løsning, der fungerede, men som involverede nogle potentielt farlige hacks til Object#send. Inden jeg præsenterede min løsning, sagde jeg “det er nok en dårlig idé, men…”, og jeg havde ret. Gode ideer starter sjældent på den måde.

Løsningen, som han foreslog, involverede brug af den ofte oversete Module#prepend, og jeg syntes, at den var fed nok til at berettige en skrivning.

Strategien var at bruge adfærden i Module#prepend til at skabe en dynamisk genereret wrappermetode til Messenger#share.

I denne wrapper vil vi have en “Performing”-meddelelse, der affyres, før metodens krop udføres, selve udførelsen af metoden og derefter en endelig “Exiting”-meddelelse, når metodens udførelse er afsluttet med succes.

Men for at forstå, hvordan vi kan bruge Module#prepend til at gøre dette, skal vi først have en god forståelse af, hvordan metodearvning fungerer i Ruby.

  • Ancestor Chains
  • Modulet selv
  • Samler det hele sammen
  • Slutning

Ancestor Chains

Hvert Ruby-objekt sidder for enden af det, der kaldes en Ancestor Chain. Det er en liste over de forfædreobjekter, som objektet arver fra. Ruby bruger denne Ancestor Chain til at bestemme, hvilken version af en metode (hvis der overhovedet er nogen) der udføres, når objektet modtager en meddelelse.

Du kan faktisk se ancestor-træet for et hvilket som helst objekt ved at kalde Module#ancestors. Her er for eksempel ancestor-kæden for vores Messenger-klasse:

Når vi #include eller #prepend et modul ind i en klasse, foretager Ruby ændringer i denne klasses ancestor-kæde og justerer, hvilke metoder der findes og i hvilken rækkefølge. Den store forskel mellem #include og #prepend er, hvor denne ændring foretages. Lad os oprette et modul kaldet Auditable, som (i sidste ende) skal indeholde den kode, der udfører vores revisionsmagi:

Hvis vi bruger Module#include til at importere de (i øjeblikket ikke-eksisterende) metoder fra Auditable, vil Ruby klemme dette modul ind som en forfader til vores Messenger-klasse. Se selv her:

Når vi kalder en metode på Messenger, vil Ruby se på, hvad der er defineret i Messenger-klassen, og – hvis den ikke kan finde en metode, der passer til kaldet – kravle op i ancestor-kæden til Auditable for at kigge igen. Hvis den ikke finder det, den søger på Auditable, går den videre til Object og så videre.

Det er #include. Hvis vi i stedet bruger Module#prepend til at importere indholdet af modulet, får vi en helt anden effekt:

#prepend gør Messenger til en forfader til Auditable. Vent. Hvad?

Det kunne se ud som om, at Messenger nu er en overklasse til Auditable, men det er ikke helt det, der sker her. Instanser af klassen Messenger er stadig instanser af klassen Messenger, men Ruby vil nu søge efter metoder til brug for Messenger i modulet Auditable, før den leder efter dem på selve klassen Messenger.

Og det, venner, er det, vi skal udnytte til at bygge denne auditor: Hvis vi opretter en metode kaldet Auditable#share, vil Ruby finde den, før den finder Messenger#share. Vi kan derefter bruge et super-opkald i Auditable#share til at få adgang til (og udføre!) den oprindelige metode, der er defineret på Messenger.

Modulet selv

Vi vil faktisk ikke oprette en metode kaldet Auditable#share. Hvorfor ikke? Fordi vi ønsker, at dette skal være et fleksibelt hjælpeprogram. Hvis vi hardcode metoden Auditable#share, vil vi kun kunne bruge den på metoder, der implementerer #share-metoden. Eller endnu værre, vi ville være nødt til at genimplementere det samme auditormønster for hver eneste metode, vi nogensinde vil auditere. Nej tak.

Så i stedet vil vi definere vores metode dynamisk og audit_method-klassens metode til at affyre den:

Når audit_method kaldes i en implementerende klasse, oprettes der en metode i Auditable-modulet med samme navn som den metode, der skal auditeres. I vores tilfælde oprettes der en metode, der hedder Auditable#share. Som nævnt vil Ruby finde denne metode, før den finder den oprindelige metode på Messenger, fordi vi sætter Auditable-modulet i den implementerende klasse i forvejen.

Det betyder, at vi kan bruge et superkald til at nå op i ancestor-kæden og udføre Messenger#send. Når vi gør det, sender vi også de argumenter, vi har indsamlet (*arguments), opad i kæden.

Når vi har kaldt den oprindelige metode, udskriver vi vores exit-meddelelse, og så er det slut. Godt arbejde, bande!

Samler det hele sammen

Nu er det bare et spørgsmål om at prependføje dette modul til vores Messenger klasse, og så skulle vi være klar til at gå i gang:

Og god jøsses, det virker:

Det har store konsekvenser for auditering, men der er mere i dette trick. Du kan bruge prepending til at ændre objekters adfærd uden at ændre selve objekterne. Dette er en tilpasningsdygtig og overskuelig måde at skabe komponenter af højere orden i Ruby. Test af ydeevne, fejlhåndtering. Du kan gøre en masse her.

Slutning

Ruby-moduler er mere komplicerede, end de fleste tror. Det er ikke så simpelt som at “dumpe” kode fra et sted til et andet, og hvis du forstår disse forskelle, kan du låse op for nogle virkelig smarte værktøjer til dit værktøjsbælte.

Du har måske bemærket, at jeg kun har talt om Module#include og Module#prepend i dag, og at jeg ikke har berørt Module#extend. Det skyldes, at den fungerer meget anderledes end sine fætre og kusiner. Jeg vil snart skrive en uddybende forklaring på Module#extend for at fuldende sættet.

For nu vil jeg anbefale dig at læse Ruby-moduler, hvis du vil lære mere: Include vs Prepend vs Extend af Léonard Hetsch. Den var virkelig nyttig for at sætte alt dette sammen.

Skriv et svar Annuller svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *

Seneste indlæg

  • Acela er tilbage:
  • OMIM Entry – # 608363 – CHROMOSOM 22q11.2 DUPLIKATIONSSYNDROM
  • Kate Albrechts forældre – Få mere at vide om hendes far Chris Albrecht og mor Annie Albrecht
  • Temple Fork Outfitters
  • Burr (roman)

Arkiver

  • februar 2022
  • januar 2022
  • december 2021
  • november 2021
  • oktober 2021
  • september 2021
  • august 2021
  • juli 2021
  • juni 2021
  • maj 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