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

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

Презентация #

Презентацию по теме лекции можно открыть по ссылке.

Видео-запись: Rutube

Документация #

Задания #

Задания выполняются на языке JavaScript в стиле лекции. Для каждого решения приведите код и краткое обоснование выбранной техники: копирование при записи или защитное копирование. Справочник по используемым методам: structuredClone, Object, Array.

  1. Реализация глубокого копирования. Расширьте учебную реализацию deepCopy из лекции так, чтобы она корректно копировала объекты Date, а также массивы и объекты произвольной глубины вложения:

    var order = {
      id: 42,
      created: new Date(2026, 0, 15),
      items: [{name: "shoes", tags: ["sale", "new"]}]
    };
    
    var copy = customDeepCopy(order);
    copy.items[0].tags.push("gift");
    
    console.log(order.items[0].tags.length); // ожидается 2
    console.log(copy.items[0].tags.length); // ожидается 3
    console.log(copy.created instanceof Date); // ожидается true
    

    Отдельно объясните письменно, почему функции при таком копировании остаются общими для оригинала и копии и к каким последствиям это приводит.

  2. Универсальная обёртка защитного копирования. Реализуйте функцию высшего порядка protect(fn), которая возвращает безопасную версию ненадёжной функции:

    var payrollCalcSafe = protect(payrollCalc);
    var checks = payrollCalcSafe(employees);
    
    var blackFridaySafe = protect(black_friday_promotion);
    shopping_cart = blackFridaySafe(shopping_cart);

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

  3. Защита от ненадёжной библиотеки событий. Унаследованный модуль котировок передаёт один и тот же объект всем подписчикам:

    ticker.onUpdate(function (quote) {
      render_quote(quote);
      history.push(quote);
    });

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

  4. Аудит унаследованного кода. Для каждой функции определите, требуется ли защитное копирование, и если да — при передаче, при получении или в обоих местах. Объясните, что произойдёт без копирования.

    // A
    function normalize_names(users) {
      var copy = [];
      for (var i = 0; i < users.length; i++) {
        copy.push({name: users[i].name.trim()});
      }
      return copy;
    }
    
    // B
    function fill_defaults(user) {
      if (!user.role)
        user.role = "guest";
      return user;
    }
    
    // C
    var roles = ["guest", "admin"];
    
    function get_roles() {
      return roles;
    }
  5. Поиск ошибок защитного копирования. Для каждого фрагмента определите, сохраняет ли он неизменяемость данных на границе с ненадёжным кодом. Ошибочные варианты исправьте и объясните причину ошибки.

    // A
    function call_legacy(cart) {
      var copy = cart.slice();
      legacy_promo(copy);
      return copy;
    }
    
    // B
    function call_legacy(cart) {
      var copy = customDeepCopy(cart);
      return legacy_promo(copy);
    }
    
    // C
    function call_legacy(cart) {
      return customDeepCopy(legacy_promo(cart));
    }
    
    // D
    function call_legacy(cart) {
      var copy = customDeepCopy(cart);
      legacy_promo(copy);
      return customDeepCopy(copy);
    }
  6. Выбор техники для проекта. Для каждой ситуации укажите подходящую технику и обоснуйте решение, включая стоимость применения:

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

© A. M. Васильев, 2026, CC BY-SA 4.0, andrey@crafted.su