Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on Dicembre 12, 2021 by admin

Ieri stavo facendo un colloquio per una posizione di sviluppatore Ruby in un’azienda qui a Denver quando il mio intervistatore mi ha posto una bella sfida di metaprogrammazione: mi ha chiesto di scrivere un revisore automatico di metodi.

Ha descritto un metodo di classe che avrebbe preso il nome di un metodo di istanza e generato una traccia automatica e registrata ogni volta che quel metodo target veniva chiamato. Qualcosa come questo:

Che avrebbe prodotto un output simile a questo:

Ho trovato una soluzione che ha funzionato ma ha coinvolto alcuni hack potenzialmente pericolosi per Object#send. Prima di presentare la mia soluzione, ho detto “questa è probabilmente una cattiva idea, ma…” e avevo ragione. Le buone idee raramente iniziano in questo modo.

La soluzione che ha proposto ha coinvolto l’uso del spesso trascurato Module#prepend e ho pensato che fosse abbastanza figo da giustificare un articolo.

La strategia era di usare il comportamento di Module#prepend per creare un metodo wrapper generato dinamicamente per Messenger#share.

In questo wrapper, avremo un messaggio “Performing” prima che il corpo del metodo venga eseguito, l’esecuzione del metodo stesso, e poi un messaggio finale “Exiting” una volta che l’esecuzione del metodo è terminata con successo.

Ma per capire come usare Module#prepend per fare questo, abbiamo prima bisogno di capire bene come funziona l’ereditarietà dei metodi in Ruby.

  • Catene di antenati
  • Il modulo stesso
  • Collegando il tutto
  • Conclusione

Catene di antenati

Ogni oggetto Ruby si trova alla fine di quella che è chiamata una catena di antenati. È una lista di oggetti antenati da cui l’oggetto eredita. Ruby usa questa catena di antenati per determinare quale versione di un metodo (se esiste) viene eseguita quando l’oggetto riceve un messaggio.

Puoi effettivamente vedere l’albero degli antenati per qualsiasi oggetto chiamando Module#ancestors. Per esempio, ecco la catena degli antenati per la nostra classe Messenger:

Quando inseriamo #include o #prepend un modulo in una classe, Ruby apporta dei cambiamenti alla catena degli antenati di quella classe, modificando quali metodi vengono trovati e in quale ordine. La grande differenza tra #include e #prepend è dove viene fatto questo cambiamento. Creiamo un modulo chiamato Auditable che conterrà (eventualmente) il codice che fa la nostra magia di auditing:

Se usiamo Module#include per importare i metodi (attualmente inesistenti) da Auditable, Ruby infilerà quel modulo come antenato della nostra classe Messenger. Ecco, guardate voi stessi:

Quando chiamiamo un metodo su Messenger, Ruby guarderà cosa è definito nella classe Messenger e – se non trova un metodo che corrisponde alla chiamata – risalirà la catena degli antenati fino a Auditable per cercare di nuovo. Se non trova quello che sta cercando su Auditable si sposta su Object e così via.

Questo è #include. Se invece usiamo Module#prepend per importare il contenuto del modulo, otteniamo un effetto completamente diverso:

#prepend rende Messenger un antenato di Auditable. Aspetta. Cosa?

Questo potrebbe sembrare che Messenger sia ora una superclasse di Auditable, ma non è esattamente quello che sta succedendo qui. Le istanze della classe Messenger sono ancora istanze della classe Messenger ma Ruby ora cercherà i metodi da usare per Messenger nel modulo Auditable prima di cercarli nella classe Messenger stessa.

E questo, amici, è ciò di cui approfitteremo per costruire questo revisore: se creiamo un metodo chiamato Auditable#share, Ruby lo troverà prima di trovare Messenger#share. Possiamo quindi usare una chiamata super in Auditable#share per accedere (ed eseguire!) il metodo originale definito su Messenger.

Il modulo stesso

In realtà non creeremo un metodo chiamato Auditable#share. Perché no? Perché vogliamo che questa sia un’utilità flessibile. Se codifichiamo il metodo Auditable#share, saremo in grado di usarlo solo sui metodi che implementano il metodo #share. O peggio, dovremmo reimplementare questo stesso modello di auditor per ogni metodo che vogliamo controllare. No grazie.

Così, invece, definiremo il nostro metodo dinamicamente e il metodo di classe audit_method per attivarlo:

Quando audit_method viene chiamato in una classe implementante, viene creato un metodo nel modulo Auditable con lo stesso nome del metodo da controllare. Nel nostro caso, verrà creato un metodo chiamato Auditable#share. Come detto, Ruby troverà questo metodo prima di trovare il metodo originale su Messenger perché stiamo anteponendo il modulo Auditable nella classe implementatrice.

Questo significa che possiamo usare una super chiamata per risalire la catena degli antenati ed eseguire Messenger#send. Quando lo facciamo, passiamo anche gli argomenti che abbiamo raccolto (*arguments) su per la catena.

Una volta che abbiamo chiamato il metodo originale, stampiamo il nostro messaggio di uscita e la finiamo qui. Bel lavoro, ragazzi!

Collegando il tutto

Ora si tratta solo di prependaggiungere questo modulo alla nostra classe Messengere dovremmo essere pronti a partire:

E perbacco se funziona:

Le implicazioni qui sono enormi per l’auditing, ma c’è di più in questo trucco. Potete usare il prepending per cambiare il comportamento degli oggetti senza cambiare gli oggetti stessi. Questo è un modo adattabile e chiaro per creare componenti di ordine superiore in Ruby. Test di performance, gestione degli errori. Puoi fare molto qui.

Conclusione

I moduli Ruby sono più complicati di quanto si pensi. Non è semplice come “scaricare” il codice da un posto all’altro, e capire queste differenze sblocca alcuni strumenti davvero puliti per la tua cintura di utilità.

Potresti aver notato che ho parlato solo di Module#include e Module#prepend oggi, e che non ho toccato Module#extend. Questo perché funziona in modo molto diverso dai suoi cugini. Scriverò presto una spiegazione approfondita di Module#extend per completare il set.

Per ora, se volete saperne di più vi consiglio di leggere Ruby modules: Include vs Prepend vs Extend di Léonard Hetsch. È stato molto utile per mettere insieme tutto questo.

Lascia un commento Annulla risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Articoli recenti

  • Acela è tornato: NYC o Boston per $99
  • I genitori di Kate Albrecht – Per saperne di più sul padre Chris Albrecht e la madre Annie Albrecht
  • Temple Fork Outfitters
  • Burr (romanzo)
  • Trek Madone SLR 9 Disc

Archivi

  • Febbraio 2022
  • Gennaio 2022
  • Dicembre 2021
  • Novembre 2021
  • Ottobre 2021
  • Settembre 2021
  • Agosto 2021
  • Luglio 2021
  • Giugno 2021
  • Maggio 2021
  • Aprile 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