Васильев Андрей Михайлович, 2022
Версии презентации
В рамках данной лекции мы рассмотрим каким образом можно выделить вычисления из данных
Одна из ключевых функций приложения — отображение общей стоимости товаров, которые были добавлены в корзину
Рассмотрим реализацию данной функции
var shopping_cart = []; // Глобальная переменная
var shopping_cart_total = 0; // Глобальная переменная
function add_item_to_cart(name, price) {
shopping_cart.push({
name: name,
price: price
});
cart_calc_total();
}
function cart_calc_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom(); // Обновление DOM
}
Магазин хочет предоставить бесплатную доставку, если общая стоимость заказа превышает 20 долларов
Наша задача — показать иконку рядом с товаром, если его добавление в корзину приведёт к увеличению стоимости товаров в корзине больше 20 долларов
Рассмотрим императивную реализацию:
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20)
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
Созданную функцию будем вызывать сразу после вычисления
function cart_calc_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom();
update_shopping_icons(); // Обновление иконок
}
Далее к нам пришло требование вычислять значение налогового сбора и обновлять его каждый раз при изменении общей суммы корзины
function update_tax_dom() {
set_tax_dom(shopping_cart_total * 0.10);
}
Добавим вызов этой функции в конец метода calc_cart_total
Это всё необходимо для тестирования логики вычисления величины налога
Бухгалтерия и отдел доставки хотят использовать код, но не могут:
Определим тип кода для каждого ключевого компонента
var shopping_cart = [];
var shopping_cart_total=[];
function add_item_to_cart(name, price) {
shopping_cart.push({
...
}
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
...
}
function update_tax_dom() {
set_tax_dom(shopping_cart_total * 0.10);
}
function calc_cart_total() {
shopping_cart_total = 0;
...
}
// Изменяемая глобальная переменная
var shopping_cart = [];
// Изменяемая глобальная переменная
var shopping_cart_total=[];
function add_item_to_cart(name, price) {
// Изменяем глобальную переменную
shopping_cart.push({
...
}
function update_shipping_icons() {
// Чтение данных из DOM, глобального состояния
var buy_buttons = get_buy_buttons_dom();
...
}
function update_tax_dom() {
// Запись данных в DOM, глобальное состояние
set_tax_dom(shopping_cart_total * 0.10);
}
function calc_cart_total() {
// Изменение глобальной переменной
shopping_cart_total = 0;
...
}
var total = 0;
function add_to_total(amount) { // Вход
console.log("Old total: " + total); // Вход
total += amount; // Выход
return total; // Выход
}
var total = 0;
function add_to_total(amount) { // Явный вход
console.log("Old total: " + total); // Неявный вход
total += amount; // Неявный выход
return total; // Явный выход
}
Неявные входы и выходы также можно назвать побочными эффектами
Тестирование 1: отделить бизнес-правила от изменения DOM
Взаимодействие с DOM является неявным взаимодействием. Его необходимо выполнить, но отдельно от вычислений
Тестирование 2: не использовать глобальные переменные
Глобальные переменные являются неявными входами для всего кода
Переиспользование 1: не использовать глобальные переменные
Переиспользование 2: не предполагать запись данных в DOM
Переиспользование 3: возвращайте результат вычисления из функций
// Оригинал
function calc_cart_total() {
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
// Выделение метода
function calc_cart_total() {
calc_total();
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
function calc_total() {
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
}
Займёмся преобразований неявных входов и выходов
// Оригинал
function calc_total() {
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
}
// Вызов метода
calc_total();
// Добавляем явный вывод
function calc_total() {
var total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
total += item.price;
}
return total;
}
// Вызов метода
shopping_cart_total = calc_total();
// Добавляем явный вывод
function calc_total() {
var total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
total += item.price;
}
return total;
}
// Вызов метода
shopping_cart_total = calc_total();
// Добавляем явный вход
function calc_total(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
// Вызов метода
shopping_cart_total = calc_total(shopping_cart);
Рассмотрим метод add_item_to_cart
// Оригинал
function add_item_to_cart(name, price) {
shopping_cart.push({
name: name,
price: price
});
}
// Выделение метода
function add_item_to_cart(name, price) {
add_item(name, price);
calc_cart_total();
}
function add_item(name, price) {
shopping_cart.push({
name: name,
price: price
});
}
// Избавляемся от неявных выходов
function add_item_to_cart(name, price) {
add_item(shopping_cart, name, price);
calc_cart_total();
}
function add_item(cart, name, price) {
cart.push({
name: name,
price: price
});
}
Текущая реализация модифицирует переданный аргумент, делая его неявным выводом
// Избавляемся от неявных выходов
function add_item_to_cart(name, price) {
add_item(shopping_cart, name, price);
calc_cart_total();
}
function add_item(cart, name, price) {
cart.push({
name: name,
price: price
});
}
// Избавляемся от неявного вывода
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total();
}
function add_item(cart, name, price) {
var new_cart = cart.slice(); // Создаём копию
new_cart.push({ // Модифицируем копию
name: name,
price: price
});
return new_cart; // Возвращаем новый список
}
Проблема увеличения объёма исходного кода
Какую ещё пользу кроме тестирования и повторного использования несут ФЯП?
Подходы ФЯП также полезны для организации архитектуры, многопоточного программирования и моделирования данных
Ряд разработанных методов можно применить за рамками целевой задачи, так и задумывалось?
Чем больше небольших функций, тем легче переиспользовать, тестировать и понимать
Функции-вычисления модифицируют локальные переменные, где же чистота функций и неизменность?
№1 Выделите код вычисления в отдельную функцию
№2 Определите неявные входы и выходы функции
№3 Преобразование неявных входов и выходов в явные
Задачи для самостоятельной проработки. Выделите вычисления из данных действий
function update_tax_dom() {
set_tax_dom(shipping_cart_total * 0.10);
}
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20)
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
var shopping_cart = []; // Действие
var shopping_cart_total = 0; // Действие
// Действие
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total();
}
// Действие
function calc_cart_total() {
shopping_cart_total = calc_total(shopping_cart);
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
// Действие
function update_shipping_icons() {
var buttons = get_buy_buttons_dom();
for(var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
if(gets_free_shipping(shipping_cart_total, item.price))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
// Действие
function update_tax_dom() {
set_tax_dom(calc_tax(shopping_cart_total));
}
// Вычисление
function add_item(cart, name, price) {
var new_cart = cart.slice();
new_cart.push({
name: name,
price: price
});
return new_cart;
}
// Вычисление
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 gets_free_shipping(total, item_price) {
return item_price + total >= 20;
}
// Вычисление
function calc_tax(amount) {
return amount * 0.10;
}