Стратифицированный дизайн

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

2021

Содержание

Шаблоны стратифицированного дизайна

Напомним, что мы разбираем стратифицированный дизайн через призму из четырёх шаблонов. Первый мы разобрали на прошлом занятии

Шаблон № 1: Несложная реализация

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

Шаблон № 2: Барьеры из абстракций

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

Шаблон № 3: Минимальный интерфейс

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

Шаблон № 4: Удобные слои

Шаблоны и практики стратифицированного дизайна должны служить нашим требованиям как программистам, кто в свою очередь служит интересам бизнеса. Мы должны вкладывать усилия в слои, которые будут помогать доставлять ПО быстрее и качественнее. Реализация слоёв не является самоцелью. Исходный код и уровни абстракции, которые он формирует должен быть удобным для работы

Шаблон № 2: Барьеры из абстракций

Барьеры из абстракций позволяют решать ряд задач. Одна из них: чётко обозначать границы ответственности между командами

До применения барьеров

После применения барьеров

Барьеры из абстракций скрывают детали реализации

Барьером из абстракции называется слой функций, который скрывает детали реализации так, что для решения задачи достаточно использовать только функции из данного слоя

Программисты на ФЯП стратегически применяют барьеры из абстракций, так как они позволяют решать проблемы в более абстрактных терминах

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

Польза от игнорирования деталей

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

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

Замена структур данных корзины

Корзина реализована на основе массива, но товары индексируются по названию

Вопрос: Какие функции необходимо изменить, чтобы реализовать изменение структуры данных для корзины?

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

Реализация корзины на объектах

Ассоциативный массив позволяет эффективнее решать задачи поиска, добавления и удаления элементов из корзины

// Оринигальная реализация
function add_item(cart, item) {
  return add_element_last(cart, item);
}

// Новая реализация
function add_item(cart, item) {
  return objectSet(cart, item.name, item);
}

// Оригинальная реализация
function calc_total(cart) {
  var total = 0;
  for(var i = 0; i < cart.length; i++) {
    var item = cart[i];
    total += item.price;
  }
  return total;
}

// Новая реализация
function calc_total(cart) {
  var total = 0;
  var values = Object.values(cart);
  for(var i = 0; i < values.length; i++) {
    var item = values[i];
    total += item.price;
  }
  return total;
}

// Оригинальная реализация
function setPriceByName(cart, name, price) {
  var cartCopy = cart.slice();
  for(i = 0; i < cartCopy.length; i++) {
    if(cartCopy[i].name === name)
      cartCopy[i] = setPrice(cartCopy[i], price);
  }
  return cartCopy;
}

// Новая реализация
function setPriceByName(cart, name, price) {
  if(isInCart(cart, name)) {
    var item = cart[name];
    var copy = setPrice(item, price);
    return objectSet(cart, name, copy);
  } else {
    var item = make_item(name, price);
    return objectSet(cart, name, item);
  }
}

// Оригинальная реализация
function remove_item_by_name(cart, name) {
  var index = indexOfItem(cart, name);
  if(index !== null)
    return splice(cart, index, 1);
  return cart
}

// Новая реализация
function remove_item_by_name(cart, name) {
  return objectDelete(cart, name);
}

// Оригинальная версия
function indexOfItem(cart, name) {
  for(var i = 0; i < cart.length; i++) {
    if(cart[i].name === name)
      return i;
  }
  return null;
}

// Новая реализация не нужна

// Оригинальная версия
function isInCart(cart, name) {
  return indexOfItem(cart, name) !== null;
}

// Новая реализация
function isInCart(cart, name) {
  return cart.hasOwnProperty(name);
}

Барьеры из абстракций позволяют игнорировать детали

Когда стоит (и не стоит) использовать барьеры из абстракций

№ 1: Для возможности замены реализации

№ 2: Чтобы сделать код легче для написания и чтения

№ 3: Для снижения нагрузки при координации команд

№ 4: Для фокусировке над задачей

Обзор шаблона № 2, барьеры из абстракций

Код стал более понятным

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

function add_item(cart, item) {
  return objectSet(cart, item.name, item);
}

functions gets_free_shipping(cart) {
  return calc_total(cart) >= 20;
}

function cartTax(cart) {
  return calc_tax(calc_total(cart));
}

function remove_item_by_name(cart, name) {
  return objectDelete(cart, name);
}

function isInCart(cart, name) {
  return cart.hasOwnProperty(name);
}

// Пока ещё сложные в реализации функции
function calc_total(cart) {
  var total = 0;
  var values = Object.values(cart);
  for(var i = 0; i < values.length; i++) {
    var item = values[i];
    total += item.price;
  }
  return total;
}

function setPriceByName(cart, name, price) {

if(isInCart(cart, name)) {
    var copy = objectSet(cart[name], 'price', price);
    return objectSet(cart, name, copy);
  } else {
    var item = make_item(name, price);
    return objectSet(cart, name, item);
  }
}

Шаблон № 3: Минимальный интерфейс

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

Новая маркетинговая компания

Предлагается скидка в 10% тем, у кого в товаров в корзине на сумму больше 100 долларов, и если в корзине есть часы

Выбор места реализации

Мы не можем реализовать метод под барьером из абстракций, так как команда маркетинга не имеет к нему доступа

// На уровне барьера
function getsWatchDiscount(cart) {
  var total = 0;
  var names = Object.keys(cart);
  for(var i = 0; i < names.length; i++) {
    var item = cart[names[i]];
    total += item.price;
  }
  return total > 100 && cart.hasOwnProperty("watch");
}

// Над барьером
function getsWatchDiscount(cart) {
  var total = calcTotal(cart);
  var hasWatch = isInCart("watch");
  return total > 100 && hasWatch;
}

Какая из реализаций лучше? Почему?

Реализация над барьером лучше

Ведение журнала по добавлению товаров в корзину

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

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

logAddToCart(user_id, item)

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

function add_item(cart, item) {
  logAddToCart(global_user_id, item);
  return objectSet(cart, item.name, item);
}

Влияние расположения кода на архитектуру приложения

function update_shipping_icons(cart) {
  var buttons = get_buy_buttons_dom();
  for(var i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var item = button.item;
    var new_cart = add_item(cart, item);
    if(gets_free_shipping(new_cart))
      button.show_free_shipping_icon();
    else
      button.hide_free_shipping_icon();
  }
}

Лучшее место для вызова метода

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);
  logAddToCart(global_user_id, item);
}

Обзор шаблона № 3 минимальный интерфейс

Мы хотим, чтобы интерфейс был минимальным:

Шаблон № 4: Удобные слои

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

При формировании слоистой структуры часто хочется выделить множество слоёв

Абстракции могут обеспечить реализацию очень сложных идей. Примером такой абстракции может служить язык JavaScript по сравнению с машинным кодом. На создание такой абстракции ушло много человеко-лет

Удобные слои

Удобные слои предлагают нам тест, который позволяет понять: следует ли нам преследовать идеалы других шаблонов или стоит остановиться

Проверка заключается в ответе на вопрос: «удобно ли работать с текущими слоями?»

Нет исходного кода, который можно было бы считать идеальным: при работе над проектом всегда существует напряжение между работой над архитектурой приложения и его функциями

Шаблоны стратифицированного дизайна

Шаблон № 1: несложные реализации

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

Шаблон № 2: барьеры из абстракций

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

Шаблон № 3: Минимальный интерфейс

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

Шаблон № 4: Комфортные слои

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

Если с текущими слоями комфортно работать, то не стоит инвестировать в их дизайн только ради самого дизайна

Анализ графа вызова исходного кода

В рамках прошлой лекции мы рассмотрели граф вызовов, и как его анализ может сделать наш код проще

Посмотрим какие факты можно получить из анализа структуры самого графа

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

Структура графа может рассказать о

Код вверху графа легче изменять

Тестирование кода внизу графа важнее

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

Если написать их «вверху», то мы сможем протестировать больше функций

Если написать их «внизу», то мы сделаем надёжнее функции, которые опираются на работу этих функций

Код внизу графа легче повторно использовать

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

Резюме анализа структуры графа

В результате анализа структуры графа мы можем оценить

Удобство сопровождения

Пригодность к тестированию

Повторное применение

Заключение

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