Monkey Patching в динамических языках программирования: пример JavaScript

Javascript
динамическое программирование
Обезьянье исправление в динамических языках программирования cover image

Введение

В этой статье будут рассмотрены концепции динамических и статических языков программирования, основные различия между ними, а также преимущества и недостатки каждой парадигмы. В дальнейшем в этом исследовании основное внимание будет уделено динамическим языкам программирования, в частности одному из основных шаблонов, которые он обеспечивает: Monkey Patch. Этот шаблон будет продемонстрирован на примере JavaScript.

Динамические и статические языки программирования

Терминология

Чтобы понять, что представляет собой динамический язык или статический, нам необходимо разобраться с несколькими ключевыми терминами, обычно используемыми в этом контексте: Время компиляции, Время выполнения и *Проверка типов. *.

Компиляция и время выполнения — это два термина, которые соответствуют различным этапам жизненного цикла компьютерной программы, начиная со времени компиляции.

Время компиляции

Время компиляции — это первый шаг жизненного цикла программы. Разработчик пишет код на заданном языке программирования. Чаще всего машина не может понять код, написанный на языке высокого уровня, поэтому используется специальный компилятор для перевода его в промежуточный формат более низкого уровня, который становится готовым к выполнению.

Время выполнения

Среда выполнения обычно включает в себя два этапа: загрузку программы в память путем выделения ресурсов, необходимых для ее выполнения, вместе с ее инструкциями, а затем выполнение программы в соответствии с порядком этих инструкций.

Следующая диаграмма иллюстрирует этот процесс:

Проверка типа

Проверка типов — встроенная функция почти всех языков программирования. Это возможность проверить, соответствует ли значение, присвоенное данной переменной, правильному типу этой переменной. Каждый язык программирования имеет свой способ представления значения данного типа в памяти. Эти различные представления позволяют проверить соответствие между типом значения и типом переменной, которой вы пытаетесь присвоить это значение.

Теперь, когда у нас есть общее представление о жизненном цикле программы и проверке типов, мы можем перейти к изучению статических языков программирования.

Статические языки программирования

Статические языки программирования, также называемые статически типизированными языками, — это языки, которые применяют проверку типов, о которой мы упоминали на этапе компиляции. Фактически это означает, что переменная сохраняет свой тип из объявления, и ей не может быть присвоено никакое значение, кроме значений из ее типа объявления. Статические языки программирования обеспечивают дополнительную безопасность при работе с типами, но могут замедлить процесс разработки в определенных случаях использования, когда это становится жестким ограничением.

Языки динамического программирования

С другой стороны, языки динамического программирования применяют проверку типов во время выполнения. Это означает, что любая переменная может содержать любое значение в любой точке программы. Это может быть полезно, поскольку предлагает разработчику уровень гибкости, которого нет в статических языках. Динамические языки, как правило, выполняются медленнее, чем их статические аналоги, поскольку они включают дополнительный этап динамического определения типа каждой переменной.

Патч обезьяны

Статическая и динамическая типизация — это фундаментальная черта языка программирования. Выбор одной парадигмы над другой может позволить использовать множество различных шаблонов и практик, которые могут значительно улучшить качество и скорость разработки. Это также может открыть двери для многих ограничений и антишаблонов, если при принятии проектных решений не уделяться тщательного внимания.

В частности, известно, что динамически типизированные языки программирования предлагают более высокий уровень гибкости, поскольку они не ограничивают переменную одним типом. Эта гибкость сопряжена с дополнительной ответственностью разработчика при реализации и отладке программ, чтобы гарантировать отсутствие непредсказуемого поведения. Узор обезьяньего пятна возник из этой философии.

Monkey Patch — это процесс расширения/изменения работы компонента во время выполнения. Рассматриваемый компонент может быть библиотекой, классом, методом или даже модулем. Идея та же: фрагмент кода создается для выполнения определенной задачи, и цель обезьяньего исправления — изменить или расширить поведение этого фрагмента кода, чтобы он выполнял новую задачу, и все это без изменения самого кода. .

Это стало возможным в языке динамического программирования, поскольку независимо от того, с каким типом компонента мы имеем дело, он по-прежнему имеет одну и ту же структуру объекта с разными атрибутами, атрибуты могут содержать методы, которые можно переназначать для достижения нового поведения объекта. не вдаваясь в его внутренности и детали реализации. Это становится особенно полезным в случае сторонних библиотек и модулей, поскольку их сложнее настроить.

В следующем примере продемонстрирован распространенный вариант использования, который может выиграть от использования метода обезьяньего патча. Здесь для реализации использовался Javascript, но это по-прежнему широко применимо к любому другому динамическому языку программирования.

Пример

Реализация минимальной среды тестирования с помощью собственного HTTP-модуля Node

Модульное и интеграционное тестирование может подпадать под случаи использования патчей Monkey. Обычно они включают в себя тестовые сценарии, охватывающие несколько сервисов для интеграционного тестирования, или зависимости API и/или базы данных для модульного тестирования. В этих двух сценариях и для достижения целей тестирования в первую очередь нам бы хотелось, чтобы наши тесты были независимы от этих внешних ресурсов. Способ достижения этого — издевательство. Мокинг имитирует поведение внешних сервисов, поэтому тест может сосредоточиться на реальной логике кода. В этом случае может оказаться полезным исправление Monkey, поскольку оно может изменять методы внешних служб, заменяя их методами-заполнителями, которые мы называем «заглушкой». Эти методы возвращают ожидаемый результат в тестовых случаях, поэтому мы можем избежать инициации запросов к производственным сервисам только ради тестов.

Следующий пример представляет собой простую реализацию исправления Monkey в собственном http-модуле NodeJ. Модуль http — это интерфейс, который реализует методы протокола http для NodeJ. В основном он используется для создания базовых http-серверов и связи с внешними службами по протоколу http.

В приведенном ниже примере у нас есть простой пример тестирования, в котором мы вызываем внешнюю службу для получения списка идентификаторов пользователей. Вместо вызова реальной службы мы исправляем метод http get, чтобы он просто возвращал ожидаемый результат, который представляет собой массив случайных идентификаторов пользователей. Это может показаться не таким уж важным, поскольку мы просто извлекаем данные, но если мы реализуем другой тестовый пример, который включает в себя какое-либо изменение данных, мы можем случайно изменить данные в рабочей среде при запуске тестов.

Таким образом, мы можем реализовать наши функции и написать тесты для каждой функции, гарантируя при этом безопасность наших производственных сервисов.

// 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");
});

Приведенный выше код прост: мы импортируем модуль http, переназначаем метод http.get на новый метод, который просто возвращает массив идентификаторов. Теперь мы вызываем новый исправленный метод внутри тестового примера и получаем новый ожидаемый результат.

~/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.

Распространенные ловушки и ограничения

Неудивительно, что у обезьяньего исправления есть свои недостатки и ограничения. В контексте модулей в системе узловых модулей исправление глобального модуля, такого как http, считается операцией с побочными эффектами, поскольку http доступен из любой точки внутри кодовой базы, и любой другой объект может зависеть от него. Эти объекты ожидают, что модуль http будет работать в обычном режиме: изменяя один из методов http, мы эффективно разрушаем все остальные зависимости http внутри кодовой базы.

Поскольку мы работаем с динамически типизированным языком, все может не сразу выйти из строя, а по умолчанию будет действовать непредсказуемо, что делает отладку чрезвычайно сложной задачей. В других случаях использования могут существовать два разных патча одного и того же компонента для одного и того же атрибута, и в этом случае мы не можем точно предсказать, какой патч будет иметь приоритет над другим, что приведет к еще более непредсказуемому коду.

Также важно отметить, что исправления обезьян могут иметь небольшие различия в поведении между разными языками программирования. Все зависит от конструкции языка и выбора реализации. Например, в Python не все экземпляры, использующие исправленный метод, будут затронуты этим исправлением. Если экземпляр явно вызывает исправленный метод, то он получит новую обновленную версию, напротив, другие экземпляры, которые могут иметь только атрибуты, указывающие на исправленный метод и не вызывающие его явно, получат исходную версию, это связано с тем, как Python действует привязка в классах.

Заключение

В этой статье мы исследовали высокоуровневые различия между статическими и динамическими языками программирования и увидели, как динамические языки программирования могут извлечь выгоду из новых парадигм и шаблонов, используя присущую этим языкам гибкость. Пример, который мы продемонстрировали, был связан с исправлением Monkey — методом, используемым для расширения поведения кода без изменения его исходного кода. Мы видели случай, когда использование этого метода было бы полезно, несмотря на его потенциальные недостатки. Разработка программного обеспечения — это компромисс, и использование правильного решения проблемы требует от разработчика тщательного рассмотрения и хорошего понимания принципов и основ архитектуры.


Career Services background pattern

Карьерные услуги

Contact Section background image

Давай останемся на связи

Code Labs Academy © 2024 Все права защищены.