Использование неизменяемых структур данных с ненадёжным кодом #

Васильев Андрей Михайлович, 2022

Версии презентации

Содержание #

  • Использование техники защитного копирования, чтобы защититься от унаследованного кода или недоверенного кода
  • Сравнение глубокого и поверхностного копирования
  • Правила применения техники защитного копирования и копировании при записи

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

Неизменяемость с унаследованным кодом #

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

Данное действие выполняется каждый месяц. Для решения данной задачи ранее был написан код и он успешно выполняет свою задачу

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

Для вызова данного кода необходимо вызвать функцию:

function black_friday_promotion(shopping_cart) {}

Для безопасного применения данного метода необходимо применить технику защитного копирования

Взаимодействие с ненадёжным кодом #

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

Код для продвижения товара не находится внутри зоны надёжного кода. Однако нам необходимо данный код вызвать, обмениваться данными

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

Техники КПЗ недостаточно, так как мы не знаем что и когда будет изменено кодом извне

Защитное копирование защищает неизменяемый оригинал #

Решением проблемы взаимодействия с ненадёжным кодом является создание копий. Двух копий на самом деле. Рассмотрим технику.

Получение данных из ненадёжной зоны #

Данные из ненадёжной зоны могут быть изменены в любой момент

  1. Сделать глубокую копию переданных данных
  2. Не использовать ссылку на оригинальные данные
  3. Использовать копию как неизменяемые данные

Передача данных в ненадёжную зону #

Код в ненадёжной зоне может изменить данные, которые он получает

  1. Перед передачей данных выполняется глубокое копирование
  2. Оригинальные данные не передаются
  3. Возвращается глубокая копия данных

Реализация защитного копирования #

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

Применим технику защитного копирования для вызова метода black_friday_promotion

// Оригинальный метод
function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  black_friday_promotion(shopping_cart);
}

// Копирование данных перед передачей
function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  var cart_copy = deepCopy(shopping_cart);
  black_friday_promotion(cart_copy);
}

// Копирование данных после выполнения
function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  var cart_copy = deepCopy(shopping_cart);
  black_friday_promotion(cart_copy);
  shopping_cart = deepCopy(cart_copy);
}

Правила защитного копирования #

Данная техника применяется для поддержания неизменяемости данных в случае работы с ненадёжным кодом

Правило № 1: копируйте данные при передаче #

Ненадёжный код может изменить данные, которые мы ему передаём

  1. Сделайте глубокую копию неизменяемых данных
  2. Передайте копию ненадёжному коду

Правило № 2: копируйте данные при получении #

Ненадёжный код может изменить данные, которые он нам передал

  1. Сразу сделать глубокую копию данных при их получении
  2. Использовать копию данных внутри кода

Общие положения #

  • Правила можно применять в любом порядке
  • Иногда не надо ничего копировать, если нет входов или выходов

Оборачивание ненадёжного кода #

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

// Оригинал
function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  var cart_copy = deepCopy(shopping_cart);
  black_friday_promotion(cart_copy);
  shopping_cart = deepCopy(cart_copy);
}

// Выделение метода-обёртки
function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  shopping_cart = black_friday_promotion_safe(
      shopping_cart);
}

// Интерфейс метода похож на КПЗ-методы
function black_friday_promotion_safe(cart) {
  var cart_copy = deepCopy(shopping_cart);
  black_friday_promotion(cart_copy);
  return deepCopy(cart_copy);
}

Практика № 1 #

Наше приложение использует внешнюю библиотеку для вычисления платёжных ведомостей. Функции payrollCalc() передаётся массив из записей работников и он возвращает массив с формированными платёжными ведомостями. Данный код ненадёжный

Необходимо реализовать защитную обёртку для использования данного кода. Сигнатура payrollCalc() выглядит следующим образом:

function payrollCalc(employees) {
  ...
  return payrollChecks;
}

Реализуйте безопасную обёртку:

function payrollCalcSafe(employees) {

}

Решение

function payrollCalcSafe(employees) {
  var employeesCopy = deepCopy(employees);
  var checks = payrollCalc(employeesCopy);
  return deepCopy(checks);
}

Практика № 2 #

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

Все обработчики данного сообщения получают ссылку на один и тот же объект оповещения и могут его изменить

userChanges.subscribe(function(user) {
  ...
  processUser(user);
})

Решение

Необходимо выполнять только копирование входящих данных

userChanges.subscribe(function(user) {
  var userCopy = deepCopy(user);
  processUser(userCopy);
}

Примеры применения защитного копирования #

Копирование в веб-API #

Большинство API в сети интернет использует технику защитного копирования

  • Запрос поступает на сервер в виде JSON-документа. Данный документ является глубокой копией данных, сериализованных на стороне клиента
  • Ответ поступает на клиент тоже в виде JSON-документа, который содержит глубокую копию данных с сервера

В результате защитного копирования веб-службы могут взаимодействовать без проблем

Копирование данных в Erlang и Elixir #

Языки Erlang и Elixir реализуют данную технику для реализации взаимодействия внутренних процессов друг с другом

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

Такой подход позволяет добиться высокой надёжности систем, реализованных на платформе Erlang

Обсуждение #

В последней практике мы сделали копию пользователя. Это нормально иметь несколько копий? Какая из них реально содержит информацию о пользователе? #

В императивных системах прикладываются специальные усилия, чтобы объекты были уникальными сущностями, а не данными

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

КПЗ и защитном копирование очень похожи. Неужели необходимо использовать обе техники? #

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

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

Сравнение КПЗ и защитного копирования #

КПЗ #

Когда применять #

Когда мы изменяем данные, которые контролируем

Где применять #

Используем в доверенной зоне, формирует доверенную зону

Тип копирования #

Поверхностное копирование

Правила применения #

  1. Сделать поверхностное копирование модифицируемого объекта
  2. Сделать изменение копии
  3. Вернуть копию

Защитное копирование #

Когда применять #

Когда происходит взаимодействие с ненадёжным кодом

Где применять #

Внутри доверенной зоны

Тип копирования #

Глубокое копирование данных

Правила применения #

  1. Сделать глубокую копию данных при входе в доверенную зону
  2. Сделать глубокую копию данных при выходе из доверенной зоны

Глубокое дороже поверхностного #

Ключевое отличие между техниками — глубокая копия не содержит общих структурных частей с копируемым объектом. Все вложенные объекты и массивы копируются

Реализация глубокого копирования #

В языке JavaScript нет встроенной поддержки глубокого копирования, а её надёжная реализация является достаточно сложной из-за особенностей языка. Для продуктового использования рекомендуется использовать метод _.cloneDeep() библиотеки Lodash

// Учебный пример реализации глубокого копирования
function deepCopy(thing) {
  if(Array.isArray(thing)) {
    var copy = [];
    for(var i = 0; i < thing.length; i++)
      copy.push(deepCopy(thing[i]));
    return copy;
  } else if(thing === null) {
    return null;
  } else if(typeof thing === "object") {
    var copy = {};
    var keys = Object.keys(thing);
    for(var i = 0; i < keys.length; i++) {
      var key = keys[i];
      copy[key] = deepCopy(thing[key]);
    }
    return copy;
  } else {
    // Числа, строки, логические значения неизменяемые
    return thing;
  }
}

Проверка знаний #

Отнесите утверждения, которые написаны ниже, либо к глубокому копированию, либо к поверхностному копированию, либо к обоим

  1. Происходит копирование каждого уровня вложенной структуры данных.
  2. Этот подход является более эффективным по сравнению с другим, так как оригинал и копия могут иметь общую структуру.
  3. Происходит копирование только тех частей, которые изменяются.
  4. Ввиду отсутствия общей структуры между оригиналом и копией данный подход лучше применим для работы с недоверенным кодом.
  5. Данный подход помогает реализовать архитектуру, в которой у компонентов нет общих элементов (shared nothing architecture).

Ответы #

Глубокое копирование: 1, 4, 5

Поверхностное копирование: 2, 3

Диалог между КПЗ и ЗК #

  • КПЗ: очевидно, что я более важная. Я помогаю поддерживать неизменяемость данных
  • ЗК: это не делает тебя более важной. Я тоже поддерживаю неизменяемость данных
  • КПЗ: мои поверхностные копии более эффективные по сравнению с твоими глубокими копиями
  • ЗК: тебе приходится думать об эффективности, т.к. копирование происходит при любом изменении данных. Мне же необходимо делать копию только после получения или перед отправкой данных
  • КПЗ: именно! Не существовало бы доверенного кода без меня
  • ЗК: да, это верно. Но доверенного кода тоже не было, если бы он не смог общаться с ненадёжным кодом: все библиотеки такие
  • КПЗ: меня тоже стоит применять в унаследованном коде и библиотеках. Они могли бы многому научиться у меня: преобразовать записать в чтение
  • ЗК: этого никогда не случится. Смирись. Слишком много кода уже написано и недостаточно программистов в мире, чтобы его переписать
  • КПЗ: ты права, я не смогу существовать без тебя
  • ЗК: я тоже

Проверка знаний #

Соотнесите утверждения с техниками достижения неизменяемости данных: КПЗ и ЗК

  1. Происходит глубокое копирование данных.
  2. Эта техника «дешевле» чем другая.
  3. Это важный подход для достижения неизменяемости данных.
  4. Происходит копирование данных перед их изменением.
  5. Применяется внутри доверенной зоны для достижения неизменяемости.
  6. Применяется для взаимодействия с ненадёжным кодом.
  7. Это самодостаточное решение для достижения неизменности, оно может быть использовано без другого.
  8. Происходит поверхностное копирование.
  9. Происходит копирование данных перед отправкой в ненадёжный код.
  10. Происходит копирование данных после получения от ненадёжного кода.

Решения #

КПЗ: 2, 3, 4, 5, 8

ЗК: 1, 3, 6, 7, 9, 10

Критерии применения техник #

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

  1. Применить ЗК при взаимодействии с унаследованным кодом.
  2. Применить технику КПЗ при взаимодействии с унаследованным кодом.
  3. Прочитать унаследованный код и проверить: изменяет ли он код и нет. Если не изменяет, то не надо применять никаких дисциплин.
  4. Переписать унаследованный код с помощью техники КПЗ и вызвать его без применения ЗК.
  5. Код принадлежит вашей команде, он уже часть доверенной зоны.

Ответы #

  1. Да. Техника ЗК защитит данные в доверенной зоне от непредвиденного копирования. Накладные расходы: повышенный расход памяти, реализация глубокого копирования.
  2. Нет. Техника КПЗ применима только внутри набора функций, которые все применяют данную технику. Унаследованный код скорее всего не реализует данную технику.
  3. Возможно. Если по результатам анализа вы поняли, что унаследованный код не изменяет переданные ему данные. Однако будьте осторожны: данные могут быть переданы в третью часть кода, которую вы не контролируете.
  4. Да. Если бюджет на разработку приложения позволяет.
  5. Нет. Если код просто находится в зоне ответственности вашей команды, то он не обязательно поддерживает неизменяемость данных.

Заключение #

Мы ознакомились с более мощной, но и более затратной техникой защитного копирования. Она более мощная, т.к. только её достаточно для достижения неизменяемости кода. Она более затратная, т.к. применяется подход с глубоким копированием. Хорошо дополняет технику КПЗ в плане взаимодействия с кодом, который изменяет данные.

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