Васильев Андрей Михайлович, 2022
Версии презентации
Мы рассмотрели технику КПЗ для достижения неизменяемости данных. Однако нам необходимо иногда взаимодействовать с кодом, не применяющим данную технику, с кодом, изменяющим данные.
Продолжим рассмотрение приложения онлайн-магазина. Отдел маркетинга хочет, чтобы сайт предлагал пользователям старые модели, чтобы можно было очистить склад
Данное действие выполняется каждый месяц. Для решения данной задачи ранее был написан код и он успешно выполняет свою задачу
Нам необходимо интегрировать код в приложение, так как нет возможности его изменить с применением техник КПЗ. Ключевая проблема — код может изменять аргументы
Для вызова данного кода необходимо вызвать функцию:
function black_friday_promotion(shopping_cart) {}
Для безопасного применения данного метода необходимо применить технику защитного копирования
Мы можем доверять нашему коду, каждая функция будет поддерживать неизменяемость данных. Мы можем спокойно использовать все функции-вычисления, они не будут изменять данные
Код для продвижения товара не находится внутри зоны надёжного кода. Однако нам необходимо данный код вызвать, обмениваться данными
Любые данные, которые покидают зону безопасности, становятся потенциально изменяемыми. Аналогично любые данные, поступающие из ненадёжной зоны, могут быть изменены после передачи
Техники КПЗ недостаточно, так как мы не знаем что и когда будет изменено кодом извне
Решением проблемы взаимодействия с ненадёжным кодом является создание копий. Двух копий на самом деле. Рассмотрим технику.
Данные из ненадёжной зоны могут быть изменены в любой момент
Код в ненадёжной зоне может изменить данные, которые он получает
Предположим, что нам необходимо вызывать функцию, которая изменяет свои аргументы, но мы не хотим отказываться от неизменяемых данных
Применим технику защитного копирования для вызова метода 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);
}
Данная техника применяется для поддержания неизменяемости данных в случае работы с ненадёжным кодом
Ненадёжный код может изменить данные, которые мы ему передаём
Ненадёжный код может изменить данные, которые он нам передал
Мы успешно реализовали защитное копирование, но исходный код стал немного грязным от операций копирования данных. Также мы можем захотеть вызывать внешний метод в разных частях кода. Реализуем отдельный метод
// Оригинал
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);
}
Наше приложение использует внешнюю библиотеку для вычисления платёжных ведомостей. Функции payrollCalc()
передаётся массив из записей работников и он возвращает массив с формированными платёжными ведомостями. Данный код ненадёжный
Необходимо реализовать защитную обёртку для использования данного кода. Сигнатура payrollCalc()
выглядит следующим образом:
function payrollCalc(employees) {
...
return payrollChecks;
}
Реализуйте безопасную обёртку:
function payrollCalcSafe(employees) {
}
Решение
function payrollCalcSafe(employees) {
var employeesCopy = deepCopy(employees);
var checks = payrollCalc(employeesCopy);
return deepCopy(checks);
}
Приложение использует другую унаследованную систему, которая предоставляет данные о пользователях. Для получения информации о пользователях необходимо подписаться на оповещения.
Все обработчики данного сообщения получают ссылку на один и тот же объект оповещения и могут его изменить
userChanges.subscribe(function(user) {
...
processUser(user);
})
Решение
Необходимо выполнять только копирование входящих данных
userChanges.subscribe(function(user) {
var userCopy = deepCopy(user);
processUser(userCopy);
}
Большинство API в сети интернет использует технику защитного копирования
В результате защитного копирования веб-службы могут взаимодействовать без проблем
Языки Erlang и Elixir реализуют данную технику для реализации взаимодействия внутренних процессов друг с другом
Во время вызова процесса данные копируются в ящик входящих сообщений получателя. Т.е. данные копируются при получении и при отправлении из процесса.
Такой подход позволяет добиться высокой надёжности систем, реализованных на платформе Erlang
В императивных системах прикладываются специальные усилия, чтобы объекты были уникальными сущностями, а не данными
В ФЯП нет сущности, описывающей пользователя. Есть запись и обработка данных о пользователе. Факты можно копировать, они неизменны
Обе техники используются для достижения неизменяемости данных. Потенциально можно было бы использовать только защитное копирование даже в доверенной зоне
Ключевая особенность защитного копирования — выполнение глубокого копирования. Данная техника гораздо дороже нежели поверхностное копирование, применяемое в КПЗ. Поверхностного копирования достаточно для достижения неизменяемости в доверенной зоне
Когда мы изменяем данные, которые контролируем
Используем в доверенной зоне, формирует доверенную зону
Поверхностное копирование
Когда происходит взаимодействие с ненадёжным кодом
Внутри доверенной зоны
Глубокое копирование данных
Ключевое отличие между техниками — глубокая копия не содержит общих структурных частей с копируемым объектом. Все вложенные объекты и массивы копируются
В языке 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, 4, 5
Поверхностное копирование: 2, 3
Соотнесите утверждения с техниками достижения неизменяемости данных: КПЗ и ЗК
КПЗ: 2, 3, 4, 5, 8
ЗК: 1, 3, 6, 7, 9, 10
Предположим, что ваша команда применяет технику КПЗ для создания доверенной зоны. Новая задача требует взаимодействия с существующим кодом, который не использует технику КПЗ. Какие из следующих действий могут быть использованы? Почему?
Мы ознакомились с более мощной, но и более затратной техникой защитного копирования. Она более мощная, т.к. только её достаточно для достижения неизменяемости кода. Она более затратная, т.к. применяется подход с глубоким копированием. Хорошо дополняет технику КПЗ в плане взаимодействия с кодом, который изменяет данные.