Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on 12 prosince, 2021 by admin

Včera jsem byl na pohovoru na pozici Ruby vývojáře v jedné firmě tady v Denveru, když mi tazatel položil dobrou metaprogramátorskou výzvu: požádal mě, abych napsal automatický auditor metod.

Popisoval metodu třídy, která by vzala jméno instanční metody a vygenerovala automatickou, zaznamenanou papírovou stopu při každém volání této cílové metody. Něco takového:

To by vytvořilo výstup, který by vypadal takto:

Navrhl jsem řešení, které fungovalo, ale zahrnovalo některé potenciálně nebezpečné zásahy do Object#send. Před prezentací svého řešení jsem si řekl: „Tohle je asi špatný nápad, ale…“ a měl jsem pravdu. Dobré nápady málokdy takhle začínají.

Řešení, které navrhl, zahrnovalo použití často přehlížené Module#prepend a já jsem si myslel, že je dostatečně cool na to, aby si zasloužilo zápis.

Strategie spočívala ve využití chování Module#prepend k vytvoření dynamicky generované obalové metody pro Messenger#share.

V tomto wrapperu necháme před provedením těla metody vystřelit jednu zprávu „Provádění“, samotné provedení metody a pak závěrečnou zprávu „Ukončení“, jakmile provedení metody úspěšně skončí.

Abychom však pochopili, jak k tomu použít Module#prepend, potřebujeme nejprve dobře porozumět tomu, jak v Ruby funguje dědičnost metod.

  • Řetězce předků
  • Samotný modul
  • Spojení všeho dohromady
  • Závěr

Řetězce předků

Každý objekt Ruby se nachází na konci takzvaného řetězce předků. Je to seznam objektů předků, od kterých objekt dědí. Ruby používá tento Řetězec předků k určení, která verze metody (pokud vůbec nějaká) se provede, když objekt obdrží zprávu.

Strom předků pro libovolný objekt můžete skutečně zobrazit voláním Module#ancestors. Například zde je řetězec předků pro naši třídu Messenger:

Když do třídy #include nebo #prepend vložíme modul, Ruby provede změny v řetězci předků této třídy a upraví, které metody a v jakém pořadí budou nalezeny. Velký rozdíl mezi #include a #prepend je v tom, kde se tato změna provede. Vytvořme modul Auditable, který bude (nakonec) obsahovat kód, který provádí naše auditovací kouzla:

Pokud použijeme Module#include pro import (v současnosti neexistujících) metod z Auditable, Ruby vmáčkne tento modul jako předka naší třídy Messenger. Zde se přesvědčte sami:

Když zavoláme metodu třídy Messenger, Ruby se podívá, co je definováno ve třídě Messenger, a – pokud nenajde metodu, která by odpovídala volání – vyšplhá se po řetězci předků až k Auditable, aby se podíval znovu. Pokud nenajde to, co hledá, na Auditable, přejde na Object a tak dále.

To je #include. Pokud místo toho použijeme Module#prepend pro import obsahu modulu, získáme zcela jiný efekt:

#prepend udělá z Messenger předka Auditable. Počkejte. Cože?“

Může to vypadat, že Messenger je nyní nadtřídou Auditable, ale to není přesně to, co se zde děje. Instance třídy Messenger jsou stále instancemi třídy Messenger, ale Ruby nyní bude hledat metody pro Messenger v modulu Auditable dříve, než je bude hledat v samotné třídě Messenger.

A právě toho, přátelé, využijeme při sestavování tohoto auditora: pokud vytvoříme metodu s názvem Auditable#share, Ruby ji najde dříve než Messenger#share. Můžeme pak použít volání super v Auditable#share k přístupu (a vykonání!) k původní metodě definované na Messenger.

Samotný modul

Vlastně nebudeme vytvářet metodu s názvem Auditable#share. Proč ne? Protože chceme, aby to byl flexibilní nástroj. Pokud natvrdo zakódujeme metodu Auditable#share, budeme ji moci použít pouze na metody, které implementují metodu #share. Nebo hůř, museli bychom znovu implementovat stejný vzor auditora pro každou metodu, kterou bychom kdy chtěli auditovat. Ne, díky.

Místo toho tedy budeme naši metodu definovat dynamicky a metodu třídy audit_method, která ji bude spouštět:

Při volání audit_method v implementační třídě se v modulu Auditable vytvoří metoda se stejným názvem jako metoda, kterou chceme auditovat. V našem případě se vytvoří metoda s názvem Auditable#share. Jak již bylo řečeno, Ruby najde tuto metodu dříve než původní metodu na Messenger, protože v implementační třídě předřazujeme modul Auditable.

To znamená, že můžeme použít supervolání, abychom se dostali nahoru v řetězci předků a provedli Messenger#send. Když to uděláme, předáme shromážděné argumenty (*arguments) také nahoru v řetězci.

Po zavolání původní metody vypíšeme naši výstupní zprávu a tím to hasne. Dobrá práce, bando!“

Spojení všeho dohromady

Teď už jen stačí prependpřidat tento modul do naší třídy Messenger a měli bychom být připraveni:

A světe div se, ono to funguje:

Důsledky pro auditování jsou zde obrovské, ale tento trik není jen tak. Pomocí prependingu můžete měnit chování objektů, aniž byste měnili objekty samotné. Jedná se o přizpůsobivý a přehledný způsob vytváření komponent vyššího řádu v jazyce Ruby. Testování výkonu, zpracování chyb. Můžete toho zde udělat hodně.

Závěr

Moduly v jazyce Ruby jsou složitější, než si většina lidí myslí. Není to tak jednoduché jako „přehazovat“ kód z jednoho místa na druhé a pochopení těchto rozdílů vám odemkne některé opravdu šikovné nástroje pro váš opasek s pomůckami.

Možná jste si všimli, že jsem dnes mluvil pouze o Module#include a Module#prepend a že jsem se nedotkl Module#extend. To proto, že funguje zcela jinak než jeho příbuzní. Brzy sepíšu podrobný výklad o Module#extend, aby byla sada kompletní.

Prozatím, pokud se chcete dozvědět více, doporučuji přečíst si Ruby modules: Doporučujeme: Include vs Prepend vs Extend od Léonarda Hetsche. Bylo to opravdu užitečné při skládání toho všeho dohromady.

Napsat komentář Zrušit odpověď na komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Nejnovější příspěvky

  • Acela je zpět:
  • OMIM záznam – # 608363 – CHROMOSOM 22q11.2 DUPLICATION SYNDROME
  • Rodiče Kate Albrechtové – více o jejím otci Chrisu Albrechtovi a matce Annie Albrechtové
  • Temple Fork Outfitters
  • Burr (román)

Archivy

  • Únor 2022
  • Leden 2022
  • Prosinec 2021
  • Listopad 2021
  • Říjen 2021
  • Září 2021
  • Srpen 2021
  • Červenec 2021
  • Červen 2021
  • Květen 2021
  • Duben 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