Васильев Андрей Михайлович
2021
Вспомним ключевые моменты предыдущего занятия
Если в рамках тела функции идёт обращение к некоторому значению, и это значение также присутствует в названии функции, то скорее всего у функции есть неявный аргумент
Цель данного рефакторинга — избавление от неявного аргумента. Это может помочь в выражении назначения кода и потенциально уменьшить дублирование
Данный рефакторинг позволяет выделить «тело функции», часть функции, которая различается между функциями. Она заменяется функцией, которая передаётся в качестве аргумента
При реализации шаблона копирование при записи мы часто дублировали код по созданию копий массивов и объектов. Шаблон копирования при записи:
К этому шаблону легко применить рефакторинг по замену тела функцией обратного вызова: пункт «модифцировать копию объекта» является телом
Шаг № 1: определение частей функции
function arraySet(array, index, value) {
var copy = array.slice();
copy[index] = value;
return copy;
}
function push(array, value) {
var copy = array.slice();
copy.push(value);
return copy;
}
function drop_last(array) {
var array_copy = array.slice();
array_copy.pop();
return array_copy;
}
function drop_first(array) {
var array_copy = array.slice();
array_copy.shift();
return array_copy;
}
// Общая часть
function action(...) {
var array_copy = array.slice(); // Общее начало
// Тело
return array_copy; // Общее окончание
}
Шаг № 2: выделение тела в функцию
// Оригинал
function arraySet(array, index, value) {
var copy = array.slice();
copy[index] = value;
return copy;
}
// Техническое выполнение шага
function arraySet(array, index, value) {
return withArrayCopy(array);
}
function withArrayCopy(array) {
var copy = array.slice();
copy[index] = value; // Не будет работать
return copy;
}
Шаг № 3: выделение функции для обратного вызова
// Результат шага № 2
function arraySet(array, index, value) {
return withArrayCopy(array);
}
function withArrayCopy(array) {
var copy = array.slice();
copy[index] = value;
return copy;
}
// Выделение функции для обратного вызова
function arraySet(array, index, value) {
return withArrayCopy(
array,
function(copy) {
copy[idx] = value;
});
}
function withArrayCopy(array, modify) {
var copy = array.slice();
modify(copy);
return copy;
}
Сравним оригинал функции и модификации
// Оригинал
function arraySet(array, index, value) {
var copy = array.slice();
copy[index] = value;
return copy;
}
// Модификация
function arraySet(array, index, value) {
return withArrayCopy(array, function(copy) {
copy[index] = value;
});
}
function withArrayCopy(array, modify) {
var copy = array.slice();
modify(copy);
return copy;
}
Реализация быстрой сортировки с помощью специальной версии
Можно выполнять несколько действий в рамках модификации
Реализуйте с помощью реализованного метода withArrayCopy
модифицируйте ранее созданные КПЗ-методы push
, drop_last
, drop_first
Реализуйте метод withObjectCopy(), который может быть использован для реализации КПЗ для объектов. В качестве основы для создания данного метода используйте следующие функции:
Вариант решения задачи
function withObjectCopy(object, modify) {
var copy = Object.assign({}, object);
modify(copy);
return copy;
};
function objectSet(object, key, value) {
return withObjectCopy(object, function(copy) {
copy[key] = value;
});
}
function objectDelete(object, key) {
return withObjectCopy(object, function(copy) {
delete copy[key];
});
}
При реализации функции withLogging()
мы смогли обобщить подход к выполнению журналирования произвольных ошибок во внешнюю систему
Постараемся сделать более общий метод, который позволит реализовывать разные стратегии по обработке исключительных ситуаций. Цель — написание кода
tryCatch(sendEmail, logToSnapErrors)
// вместо
try {
sendEmail();
} catch(error) {
logToSnapErrors(error);
}
Задача состоит в написании функции tryCatch()
Подсказка: данный метод должен принимать две функции в качестве аргументов
Вариант решения:
В качестве упражнения выполним оборачивание ещё одного элемента синтаксиса: условного оператора. Для упрощения задачи рассмотрим условный оператор без else
Пример кода, который необходимо модифицировать:
// Оригинал
if(array.length === 0) {
console.log("Array is empty");
}
// Замена
when(array.length === 0, function() {
console.log("Array is empty");
});
// Оригинал
if(hasItem(cart, "shoes")) {
return setPriceByName(cart, "shoes", 0);
}
// Замена
when(hasItem(cart, "shoes"), function() {
return setPriceByName(cart, "shoes", 0);
});
Вариант решения
После реализации функции when
люди стали её использовать и захотели полноправный аналог if
с else
-выражением. Ваша задача — реализация функции IF:
Вариант решения
При реализации метода withLogging()
мы смогли избавиться от большой части дублирования, однако для её применения всё-равно приходится изменять много кода
Мы экономим 2 строчки кода, однако всё ещё надо писать по 3 дополнительные строчки
Данная система формирует стандартный подход к решению проблемы, но он имеет следующие проблемы:
В примерах видно, что проблема дублирования существует
Было бы хорошо просто вызывать функции и автоматически получить желаемое поведение
Сделаем код более чётким, изменив название функций, чтобы они детальнее отражали отсутствие журналирования
Теперь мы чётко оделили функции с журналированием от оригинальных функций, но при вызове новых функций их точно не надо оборачивать
Применим первые шаги рефакторинга по замене тела с функцией обратного вызова
Определим общие и частные части
Вместо последнего шага рефакторинга, вызов функции, переданной в качестве аргумента, будем возвращать новую функцию-обёртку
Теперь можно легко создавать функции-обёртки:
Последнее стало возможно благодаря работе функции wrapLogging
:
saveUserData
— оригинальная функцияwrapLogging
— функция высшего порядка, которая создаёт функцию-обёртку и возвращает еёsaveUserDataWithLogging
— функция-обёртка, созданная функцией высшего порядкаwithLogging
сохраняется в переменную. Ранее в коде использовались функции, определённые с помощью слова function
Данный подход действительно поначалу может быть неудобен. Однако стандарты наименования функций и переменных в большинстве языков программирования совпадают. Ключевое отличие — использование глаголов для функций, существительных для данных
Надо быть готовым к расширенным подходам определения функций
withLogging
может делать обёртку только для функций с одним аргументом. А как её можно применить для функций с большим количеством аргументов? Как получить возвращаемое значение?Для возвращения значения — достаточно добавить в соответствующее место ключевое слово return
к вызову функции
Для работы с произвольным количеством аргументов мы можем обратиться к возможностям языка, описанным в стандарте ES6 (2015 год):
function wrapLogging(f) {
return function(...args) {
try {
return f(...args);
} catch (error) {
logToSnapErrors(error);
}
}
}
В других языках поддержка функций с произвольным количеством аргументов может быть реализована по-разному
Создайте функцию высшего порядка, которая будет перехватывать ошибки и игнорировать их (т.е. реализовывать стандартную стратегию обработки ошибок в приложении). Если происходит ошибка, то метод должен вернуть null
Подсказка. Для решения этой задачи без функций можно использовать код:
Реализуйте функцию высшего порядка makeAdder
которая создаёт функцию, которая добавляет число к переданному аргументу. Пример работы:
Если подойти технически к данному вопросу, то ответ скорее всего: да. Более правильный вопрос: стоит ли так поступать?
Подход с использованием функций высшего порядка более общий. Этот подход также радует программистов, так как позволяет им поиграться со сложными технологиями. Однако задача программистов — решать бизнес-задачи
Если видно, что после применения функций высшего порядка можно решить проблемы кодовой базы, то их стоит применять
При знакомстве с данным подходом следует попытаться применять его везде, чтобы понять сильные и слабые стороны данного подхода. Однако эксперименты должны проводиться вне продуктовой кодовой базы
Если в результате анализа применения данного подхода есть объективные положительные моменты, то стоит использовать данный подход
В рамках данной лекции мы углубили знание функций как объектов первого порядка, и функций высшего порядка. Потенциал этих идей будет рассмотрена на следующих занятиях