Monkey Patching w dynamicznych językach programowania: przykład JavaScript

Javascript
programowanie dynamiczne
Monkey Patching w dynamicznych językach programowania cover image

Wstęp

W tym artykule omówimy koncepcje języków programowania dynamicznego i statycznego, główne różnice między nimi oraz zalety i pułapki każdego paradygmatu. Ta eksploracja będzie dalej skupiać się na dynamicznych językach programowania, w szczególności na jednym z podstawowych wzorców, które umożliwia: Monkey Patch, ten wzorzec zostanie zaprezentowany na przykładzie w JavaScript.

Dynamiczne i statyczne języki programowania

Terminologia

Aby zrozumieć, co stanowi język dynamiczny czy statyczny, musimy zrozumieć kilka kluczowych terminów powszechnie używanych w tym kontekście: Czas kompilacji, Środowisko wykonawcze i *Sprawdzanie typów *.

Kompilacja i czas wykonania to dwa terminy odpowiadające różnym etapom cyklu życia programu komputerowego, zaczynając od czasu kompilacji.

Czas kompilacji

Czas kompilacji jest pierwszym krokiem w cyklu życia programu. Programista pisze kod w danym języku programowania. Najczęściej maszyna nie jest w stanie zrozumieć kodu napisanego w języku wysokiego poziomu, dlatego używany jest dedykowany kompilator do przetłumaczenia go na format pośredni niższego poziomu, który staje się gotowy do wykonania.

Czas działania

Środowisko wykonawcze zwykle obejmuje dwa etapy: załadowanie programu do pamięci poprzez przydzielenie zasobów potrzebnych do jego wykonania wraz z instrukcjami, a następnie wykonanie programu zgodnie z kolejnością tych instrukcji.

Poniższy diagram ilustruje ten proces:

Sprawdzanie typu

Sprawdzanie typu jest wbudowaną funkcją prawie wszystkich języków programowania. Jest to możliwość sprawdzenia, czy wartość przypisana do danej zmiennej odpowiada właściwemu typowi tej zmiennej. Każdy język programowania ma inny sposób reprezentowania wartości danego typu w pamięci. Te różne reprezentacje umożliwiają sprawdzenie zgodności pomiędzy typem wartości a typem zmiennej, do której próbujesz przypisać tę wartość.

Teraz, gdy mamy już ogólną wiedzę na temat cyklu życia programu i sprawdzania typów, możemy przystąpić do eksploracji statycznych języków programowania.

Statyczne języki programowania

Statyczne języki programowania, zwane także językami o typie statycznym, to języki, które stosują sprawdzanie typu, o którym wspominaliśmy na etapie kompilacji. Oznacza to w praktyce, że zmienna zachowuje swój typ przed deklaracją i nie można do niej przypisać żadnej wartości poza wartościami z jej typu deklaracji. Statyczne języki programowania zapewniają dodatkowe bezpieczeństwo w przypadku typów, ale w niektórych przypadkach mogą spowolnić proces programowania, gdy staje się to poważnym ograniczeniem.

Dynamiczne języki programowania

Z drugiej strony, dynamiczne języki programowania stosują sprawdzanie typu w czasie wykonywania. Oznacza to, że każda zmienna może przechowywać dowolną wartość w dowolnym momencie programu. Może to być korzystne, ponieważ zapewnia programiście poziom elastyczności, którego nie ma w językach statycznych. Języki dynamiczne są zwykle wolniejsze w wykonywaniu niż ich statyczne odpowiedniki, ponieważ obejmują dodatkowy etap dynamicznego ustalania typu każdej zmiennej.

Łatka Małpy

Typowanie statyczne i dynamiczne to podstawowa cecha języka programowania, przejście od jednego paradygmatu do drugiego może umożliwić zastosowanie wielu różnych wzorców i praktyk, które mogą znacznie poprawić jakość i szybkość rozwoju. Może również otworzyć drzwi dla wielu ograniczeń i antywzorców, jeśli przy podejmowaniu decyzji projektowych nie zostaną dokładnie rozważone.

W szczególności wiadomo, że języki programowania z typem dynamicznym oferują wyższy poziom elastyczności, ponieważ nie ograniczają zmiennej do jednego typu. Ta elastyczność wiąże się z kosztami dodatkowej odpowiedzialności programisty podczas wdrażania i debugowania programów, aby upewnić się, że nie wystąpią żadne nieprzewidywalne zachowania. Wzór łaty małpy wywodzi się z tej filozofii.

Monkey Patch odnosi się do procesu rozszerzania/zmiany działania komponentu w czasie jego wykonywania. Komponentem, o którym mowa, może być biblioteka, klasa, metoda, a nawet moduł. Pomysł jest taki sam: fragment kodu tworzy się w celu wykonania określonego zadania, a celem małpiego łatania jest zmiana lub rozszerzenie zachowania tego fragmentu kodu, tak aby wykonywał on nowe zadanie, a wszystko to bez zmiany samego kodu .

Jest to możliwe w dynamicznym języku programowania, ponieważ niezależnie od tego, z jakim typem komponentu mamy do czynienia, nadal ma on tę samą strukturę obiektu z różnymi atrybutami, w atrybutach można przechowywać metody, które można ponownie przypisać, aby uzyskać nowe zachowanie obiektu bez wchodzenia w jego elementy wewnętrzne i szczegóły implementacji. Staje się to szczególnie przydatne w przypadku bibliotek i modułów innych firm, ponieważ są one zwykle trudniejsze do ulepszenia.

Poniższy przykład zaprezentuje typowy przypadek użycia, w którym można skorzystać z techniki małpiej łatki. Do implementacji użyto JavaScript, ale powinno to nadal mieć szerokie zastosowanie do każdego innego dynamicznego języka programowania.

Przykład

Zaimplementuj minimalną strukturę testową z natywnym modułem HTTP węzła

Testy jednostkowe i integracyjne mogą należeć do przypadków użycia łatania Monkey. Zwykle obejmują przypadki testowe obejmujące więcej niż jedną usługę w przypadku testów integracyjnych lub zależności API i/lub bazy danych w przypadku testów jednostkowych. W tych dwóch scenariuszach, aby przede wszystkim osiągnąć cele testowania, chcielibyśmy, aby nasze testy były niezależne od tych zasobów zewnętrznych. Sposobem na osiągnięcie tego jest kpina. Szydzenie polega na symulowaniu zachowania usług zewnętrznych, dzięki czemu test może skupić się na rzeczywistej logice kodu. Pomocne może być tutaj łatanie małp, ponieważ może ono modyfikować metody usług zewnętrznych, zastępując je metodami zastępczymi, które nazywamy „odgałęzieniem”. Metody te zwracają oczekiwany wynik w przypadkach testowych, dzięki czemu możemy uniknąć inicjowania żądań do usług produkcyjnych tylko na potrzeby testów.

Poniższy przykład to prosta implementacja łatania Monkey w natywnym module http NodeJ. Moduł http to interfejs implementujący metody protokołu http dla NodeJ. Służy głównie do tworzenia serwerów http typu barebone i komunikacji z usługami zewnętrznymi za pomocą protokołu http.

W poniższym przykładzie mamy prosty przypadek testowy, w którym wywołujemy usługę zewnętrzną w celu pobrania listy identyfikatorów użytkowników. Zamiast wywoływać właściwą usługę, poprawiamy metodę http get, tak aby zwracała oczekiwany wynik, którym jest tablica losowych identyfikatorów użytkowników. Może się to nie wydawać zbyt istotne, ponieważ tylko pobieramy dane, ale jeśli zaimplementujemy inny przypadek testowy, który wiąże się ze zmianą pewnego rodzaju danych, możemy przypadkowo zmienić dane produkcyjne podczas uruchamiania testów.

W ten sposób możemy wdrażać nasze funkcjonalności i pisać testy dla każdej funkcjonalności, zapewniając jednocześnie bezpieczeństwo naszych usług produkcyjnych.

// import the http module
let http = require("http");

// patch the get method of the http module
http.get = async function(url) {
  return {
    data: ["1234", "1235", "1236", "1236"]
  };
}

// example test suite, call new patched get method for testing
test('get array of user ids from users api', async () => {
  const res = await http.get("https://users.api.com/ids");
  const userIds = res.data;
  expect(userIds).toBeDefined();
  expect(userIds.length).toBe(4);
  expect(userIds[0]).toBe("1234");
});

Powyższy kod jest prosty, importujemy moduł http, ponownie przypisujemy metodę http.get za pomocą nowej metody, która po prostu zwraca tablicę identyfikatorów. Teraz wywołujemy nową poprawioną metodę wewnątrz przypadku testowego i otrzymujemy nowy oczekiwany wynik.

~/SphericalTartWorker$ npm test

> nodejs@1.0.0 test
> jest

PASS  ./index.test.js
  ✓ get array of user ids from users api (25 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.977 s, estimated 2 s
Ran all test suites.

Typowe pułapki i ograniczenia

Nie powinno dziwić, że łatanie małp ma swoje wady i ograniczenia. W kontekście modułów w systemie modułów węzłów łatanie modułu globalnego, takiego jak http, jest uważane za operację powodującą skutki uboczne, ponieważ http jest dostępny z dowolnego miejsca w bazie kodu i każdy inny element może być od niego zależny. Podmioty te oczekują, że moduł http będzie działał w swoim zwykłym zachowaniu, zmieniając jedną z metod http, skutecznie łamiemy wszystkie inne zależności http wewnątrz bazy kodu.

Ponieważ działamy w języku dynamicznie typowanym, coś może nie od razu zawieść i raczej domyślnie przybierze nieprzewidywalne zachowanie, co sprawia, że ​​debugowanie jest niezwykle złożonym zadaniem. W innych przypadkach mogą istnieć dwie różne łatki tego samego komponentu z tym samym atrybutem, w takim przypadku nie możemy tak naprawdę przewidzieć, która łatka będzie miała pierwszeństwo przed drugą, co spowoduje jeszcze bardziej nieprzewidywalny kod.

Należy również wspomnieć, że łatanie małp może powodować niewielkie różnice w zachowaniu w różnych językach programowania. Wszystko zależy od projektu języka i wyborów dotyczących implementacji. Na przykład w Pythonie łatka nie będzie miała wpływu na wszystkie instancje korzystające z łatanej metody. Jeśli instancja jawnie wywoła łataną metodę, otrzyma nową, zaktualizowaną wersję, wręcz przeciwnie, inne instancje, które mogą mieć tylko atrybuty wskazujące na łataną metodę i nie wywołują jej jawnie, otrzymają oryginalną wersję, wynika to ze sposobu, w jaki python działa wiązanie w klasach.

Wniosek

W tym artykule zbadaliśmy ogólne rozróżnienia między statycznymi i dynamicznymi językami programowania i zobaczyliśmy, w jaki sposób dynamiczne języki programowania mogą skorzystać na nowych paradygmatach i wzorcach wykorzystujących wrodzoną elastyczność, jaką oferują te języki. Przykład, który zaprezentowaliśmy, dotyczył łatania Monkey, techniki używanej do rozszerzania zachowania kodu bez zmiany go ze źródła. Widzieliśmy przypadek, w którym zastosowanie tej techniki byłoby korzystne wraz z jej potencjalnymi wadami. Tworzenie oprogramowania wymaga kompromisów, a zastosowanie odpowiedniego rozwiązania problemu wymaga od programisty szczegółowych rozważań oraz dobrego zrozumienia zasad i podstaw architektury.


Career Services background pattern

Usługi związane z karierą

Contact Section background image

Pozostańmy w kontakcie

Code Labs Academy © 2024 Wszelkie prawa zastrzeżone.