Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on 12 grudnia, 2021 by admin

Wczoraj przeprowadzałem rozmowę kwalifikacyjną na stanowisko programisty Ruby w firmie tutaj w Denver, kiedy mój rozmówca postawił mi dobre wyzwanie metaprogramistyczne: poprosił mnie o napisanie automatycznego audytora metod.

Opisał metodę klasową, która pobierałaby nazwę metody instancji i generowała automatyczny, rejestrowany ślad za każdym razem, gdy wywoływana jest metoda docelowa. Coś w tym stylu:

To dałoby dane wyjściowe, które wyglądałyby tak:

Wymyśliłem rozwiązanie, które działało, ale wymagało kilku potencjalnie niebezpiecznych hacków do Object#send. Przed przedstawieniem mojego rozwiązania, powiedziałem „to prawdopodobnie zły pomysł, ale…” i miałem rację. Dobre pomysły rzadko zaczynają się w ten sposób.

Rozwiązanie, które zaproponował wiązało się z użyciem często pomijanego Module#prepend i pomyślałem, że jest na tyle fajne, że warto o nim napisać.

Strategia polegała na użyciu zachowania Module#prepend do stworzenia dynamicznie generowanej metody wrappera dla Messenger#share.

W tym wrapperze, będziemy mieli jeden komunikat „Performing” wystrzelony przed wykonaniem ciała metody, samo wykonanie metody, a następnie końcowy komunikat „Exiting” po pomyślnym zakończeniu wykonania metody.

Aby zrozumieć jak używać Module#prepend do tego celu, musimy najpierw dobrze zrozumieć jak działa dziedziczenie metod w Rubim.

  • Łańcuch przodków
  • Sam moduł
  • Bring it all Together
  • Wnioski

Łańcuch przodków

Każdy obiekt w Rubim znajduje się na końcu tak zwanego łańcucha przodków. Jest to lista obiektów przodków, po których dany obiekt dziedziczy. Ruby używa tego łańcucha przodków do określenia, która wersja metody (jeśli w ogóle) zostanie wykonana, gdy obiekt otrzyma wiadomość.

Możesz zobaczyć drzewo przodków dla dowolnego obiektu wywołując Module#ancestors. Na przykład, oto łańcuch przodków dla naszej klasy Messenger:

Gdy #include lub #prepend moduł do klasy, Ruby dokonuje zmian w łańcuchu przodków tej klasy, zmieniając, które metody są znajdowane i w jakiej kolejności. Dużą różnicą pomiędzy #include a #prepend jest to, gdzie ta zmiana jest dokonywana. Stwórzmy moduł o nazwie Auditable, który (w końcu) będzie zawierał kod, który robi naszą magię audytu:

Jeśli użyjemy Module#include do zaimportowania (obecnie nieistniejących) metod z Auditable, Ruby wciśnie ten moduł jako przodka naszej klasy Messenger. Zobacz sam:

Gdy wywołamy metodę w Messenger, Ruby spojrzy na to, co jest zdefiniowane w klasie Messenger i – jeśli nie znajdzie metody, która pasuje do wywołania – wspina się w górę łańcucha przodków do Auditable, aby poszukać ponownie. Jeśli nie znajdzie tego, czego szuka na Auditable, przechodzi do Object i tak dalej.

To jest #include. Jeśli zamiast tego użyjemy Module#prepend do zaimportowania zawartości modułu, uzyskamy zupełnie inny efekt:

#prepend czyni Messenger przodkiem Auditable. Zaraz. Co?

To może wyglądać tak, że Messenger jest teraz nadklasą Auditable, ale to nie jest dokładnie to, co się tutaj dzieje. Instancje klasy Messenger są nadal instancjami klasy Messenger, ale Ruby będzie teraz szukał metod do użycia dla Messenger w module Auditable przed szukaniem ich w samej klasie Messenger.

I właśnie to, przyjaciele, wykorzystamy do zbudowania tego audytora: jeśli stworzymy metodę o nazwie Auditable#share, Ruby znajdzie ją zanim znajdzie Messenger#share. Możemy wtedy użyć wywołania super w Auditable#share, aby uzyskać dostęp (i wykonać!) do oryginalnej metody zdefiniowanej na Messenger.

Sam moduł

Właściwie nie będziemy tworzyć metody o nazwie Auditable#share. Dlaczego nie? Ponieważ chcemy, aby było to elastyczne narzędzie. Jeśli na sztywno zakodujemy metodę Auditable#share, będziemy mogli jej używać tylko w metodach, które implementują metodę #share. Albo co gorsza, będziemy musieli ponownie zaimplementować ten sam wzorzec audytora dla każdej metody, którą kiedykolwiek będziemy chcieli audytować. Nie dziękuję.

Więc zamiast tego, zdefiniujemy naszą metodę dynamicznie i metodę klasy audit_method do jej odpalenia:

Gdy audit_method jest wywoływane w klasie implementującej, w module Auditable tworzona jest metoda o takiej samej nazwie jak metoda do audytu. W naszym przypadku, zostanie utworzona metoda o nazwie Auditable#share. Jak wspomniano, Ruby znajdzie tę metodę zanim znajdzie oryginalną metodę w Messenger, ponieważ poprzedzamy moduł Auditable w klasie implementującej.

To oznacza, że możemy użyć superwywołania, aby dotrzeć do łańcucha przodków i wykonać Messenger#send. Kiedy to zrobimy, przekażemy argumenty, które zebraliśmy (*arguments) również w górę łańcucha.

Po wywołaniu oryginalnej metody, drukujemy nasz komunikat o wyjściu i nazywamy to dniem. Dobra robota, gang!

Bring it all Together

Teraz to tylko kwestia prepend dodania tego modułu do naszej Messenger klasy i powinniśmy być gotowi do pracy:

A good golly it works:

Wpływ na to jest ogromny dla audytu, ale jest więcej do tej sztuczki. Możesz użyć prependingu do zmiany zachowania obiektów bez zmiany samych obiektów. Jest to łatwy do przystosowania i przejrzysty sposób na tworzenie komponentów wyższego rzędu w Rubim. Testowanie wydajności, obsługa błędów. Możesz tutaj zrobić wiele.

Wnioski

Moduły Rubiego są bardziej skomplikowane niż większość ludzi myśli. Nie jest to tak proste jak „zrzucenie” kodu z jednego miejsca w drugie, a zrozumienie tych różnic odblokowuje kilka naprawdę zgrabnych narzędzi do twojego paska narzędzi.

Możesz zauważyć, że mówiłem dziś tylko o Module#include i Module#prepend, a nie poruszyłem tematu Module#extend. To dlatego, że działa on zupełnie inaczej niż jego kuzyni. Wkrótce napiszę dogłębne wyjaśnienie Module#extend, aby uzupełnić ten zestaw.

Jak na razie, jeśli chcesz dowiedzieć się więcej, polecam przeczytanie modułów Rubiego: Include vs Prepend vs Extend autorstwa Léonarda Hetscha. To było naprawdę pomocne w złożeniu tego wszystkiego razem.

Dodaj komentarz Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Ostatnie wpisy

  • Acela powraca: NYC lub Boston za 99 dolarów
  • OMIM Entry – # 608363 – CHROMOSOME 22q11.2 DUPLICATION SYNDROME
  • Rodzice Kate Albrecht – Dowiedz się więcej o jej ojcu Chrisie Albrechcie i matce Annie Albrecht
  • Temple Fork Outfitters
  • Burr (powieść)

Archiwa

  • luty 2022
  • styczeń 2022
  • grudzień 2021
  • listopad 2021
  • październik 2021
  • wrzesień 2021
  • sierpień 2021
  • lipiec 2021
  • czerwiec 2021
  • maj 2021
  • kwiecień 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