javascript

Переосмысление JavaScript: Смерть for

  • пятница, 19 мая 2017 г. в 03:14:30
https://habrahabr.ru/post/329004/
  • JavaScript


image

Цикл for хорошо послужил нам, но он устарел и должен уступить место более новым, функциональным техникам программирования.

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

Так в чем проблема цикла for в JavaScript?

Дизайн цикла for подталкивает к мутациям состояния (англ. mutation of state — изменение состояния — прим. переводчика) и применению сайд эффектов (англ. side effects — побочные эффекты — прим. переводчика), которые являются потенциальными источниками багов и непредсказуемого поведения кода.

Все мы слышали, что глобальное состояние — это плохо, и что мы должны избегать его. Однако, локальное состояние разделяет те же проблемы что и глобальное, мы просто не сталкиваемся с ними так часто, ведь они проявляются в меньших масштабах. Фактически, мы никогда не решали проблему, а просто сводили ее к минимуму.

Однажды, используя мутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика), значение случайной переменной изменится по неизвестной причине, и вы потратите часы на отладку и поиск причины изменения. У меня волосы на голове встают дыбом, только от одной мысли об этом.

Я бы хотел немного поговорить о сайд эффектах. Эти слова даже звучат ужасно, сайд эффекты. Дрянь. Вы хотите чтобы в ваших программах были сайд эффекты? Нет, я не хочу сайд эффектов в моих программах!

Но что такое сайд эффекты?

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

Сайд эффекты — действительно мощный инструмент, но с большой силой приходит большая ответственность.

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

Меньше слов, больше кода. Давайте посмотрим на типичный цикл for, который вы вероятно видели сотни раз.

const cats = [
 { name: 'Mojo',    months: 84 },
 { name: 'Mao-Mao', months: 34 },
 { name: 'Waffles', months: 4 },
 { name: 'Pickles', months: 6 }
]
var kittens = []
// Типичный, плохо написанный  цикл for
for (var i = 0; i < cats.length; i++) {
 if (cats[i].months < 7) {
   kittens.push(cats[i].name)
 }
}
console.log(kittens)

Я собираюсь отрефакторить этот код шаг за шагом, чтобы вы смогли пронаблюдать как легко превратить ваш собственный код в нечто более прекрасное.

Во-первых, я извлеку условное выражение в отдельную функцию:

const isKitten = cat => cat.months < 7
var kittens = []
for (var i = 0; i < cats.length; i++) {
 if (isKitten(cats[i])) {
   kittens.push(cats[i].name)
 }
}

Выносить условия — это в целом хорошая практика. Изменение фильтрации с “меньше чем 7 месяцев” на “является ли котенком” — большой шаг вперед. Теперь код передает наши намерения гораздо лучше. Почему мы берем котов до 7 месяцев? Не совсем понятно. Мы хотим найти котят, так пусть код говорит об этом!

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

Следующее изменение — извлечь преобразование (или отображение) кота его в имя. Это изменение будет понятнее позднее, а сейчас просто доверьтесь мне.

const isKitten = cat => cat.months < 7 
const getName = cat => cat.name
var kittens = []
for (var i = 0; i < cats.length; i++) {
 if (isKitten(cats[i])) {
   kittens.push(getName(cats[i]))
 }
}

Я собирался написать несколько абзацев для описания механики работы filter и map, но вместо этого, я покажу вам как легко читать и понимать этот код, даже увидев их (filter и map — прим. переводчика) впервые. Это лучшая демонстрация того, насколько читабельным может стать ваш код.

const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const kittens =
 cats.filter(isKitten)
     .map(getName)

Обратите внимание, мы избавились от kittens.push(...). Никаких больше мутаций состояния и никаких var.

Код отдающий предпочтение const перед var и let выглядит чертовски привлекательно


Конечно, мы могли использовать const с самого начала, так как он не делает сам объект иммутабельным (об этом больше в другой раз), но это придуманный пример, не наседайте!

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

И все вместе:

const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const getKittenNames = cats =>
 cats.filter(isKitten)
     .map(getName)
const cats = [
 { name: 'Mojo',    months: 84 },
 { name: 'Mao-Mao', months: 34 },
 { name: 'Waffles', months: 4 },
 { name: 'Pickles', months: 6 }
]
const kittens = getKittenNames(cats)
console.log(kittens)

Домашнее задание


Изучить извлечение методов filter и map из их объектов. Задание со звездочкой: исследовать композицию функций.


Что насчет break


Многие из вас спрашивали, “Что насчет break”, посмотрите часть вторую серии “Переосмысление JavaScript: break это GOTO циклов”.


Заключение


Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл for?

Для вас это мелочь, но меня очень радует когда кто-то подписывается на меня на медиуме или в твиттере (@joelnet), а если вы считаете мой рассказ бесполезным, то расскажите об этом в комментариях.

Спасибо!