Стрелковый Ад, Или Новый Круг Старой Проблемы



Происхождение Когда-то JavaScript-разработчики очень любили использовать анонимные функции (на самом деле многие до сих пор любят их использовать), и все бы ничего, и код стал короче, и не надо было бы придумывать другое имя для функцию, но рано или поздно она превращается в «водопад функций»», который можно прочитать только в хорошей IDE и потом немного поломать голову.

Вот один пример: Пример 1:

  
  
  
  
  
  
   

$http.request('GET', '/api').

then(function (data) { return data.arr.map(function (item) { item.props.filter(function (prop) { return !!prop; }).

map(function (prop) { prop.a = prop.a * 2; return prop; }) }).

filter(function (item) { return item.props.some(function (prop) { return prop.a > 10; }); }); }, function (error) { throw new TypeError(error); });

Это очень простой пример, я в своей практике сталкивался с «монстрами» со 100 — 200, а то и 300+ строками кода, которые приходилось рефакторить, прежде чем понять, какую магию они творят. В свое время переход к «функциональному шаблону проектирования», в котором каждая функция объявляется отдельно и используются только ссылки на них, сделал код красивым, аккуратным и, самое главное, легко читаемым, что очень важно, когда вы не работают с этим в одиночку.

Например, этот пример имеет следующий вид: Пример 2:

$http.request('GET', '/api').

then(requestSuccessHandler, requestErrorHandler); function requestSuccessHandler(result) { return result.arr .

map(transformResultArr) .

filter(filterResultArr); function transformResultArr(item) { item.props .

filter(checkProperty) .

map(transformProperty) } function filterResultArr(item) { return item.props.some(filterProperty); } function checkProperty(prop) { return !!prop; } function transformProperty(prop) { prop.a = prop.a * 2; return prop; } function filterProperty(prop) { return prop.a > 10; } } function requestErrorHandler(error) { throw new TypeError(error); }

Хотя кода больше, с этим вариантом проще работать, его легче читать и отлаживать.

Но все хорошее длилось недолго.



Стрелочные функции

Прежде чем перейти к проблеме, давайте посмотрим, что такое стрелочные функции и что они нам дали своим появлением.

Стрелочные функции — это сокращение анонимных функций.

В принципе это все, но на практике это оказывается очень полезно.

Например, если вам нужно выбрать из массива все элементы, у которых есть поле id. Раньше это было бы написано так: Пример 3:

arr.filter(function(item) { return !!item.id; });

Или вот так: Пример 4:

arr.filter(filterID); function filterID(item) { return !!item.id; }

Но со стрелочными функциями эта запись станет неприлично короткой: Пример 5:

arr.filter(item => !!item.id);

Кажется, все отлично.

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

Проблема проявляется не сразу.



Стрелки во всем

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

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

И все бы ничего, если бы это не были водопады функций со 100+ строками и 4+ уровнями вложенности.

Для ясности вернемся к первому примеру; в этом проекте это будет выглядеть примерно так: Пример 5:

$http.request('GET', '/api').

then(data => { return data.arr.map(item => { item.props.filter(prop => !!prop) .

map(prop => { prop.a = prop.a * 2; return prop; }) }).

filter(item => item.props.some(prop => prop.a > 10)); }, error => { throw new TypeError(error) });

В общем, он стал короче (и я, кстати, сам люблю, когда код короткий).

В некоторых местах стало яснее.

Но это только если сравнивать с первым примером.

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

Но я не говорю, что использовать стрелки плохо, просто их нужно использовать в меру.



Как мы можем сделать все лучше?

На пути создания хороших практик с использованием ECMAScript 2015 будет много спотыканий и падений, но сначала давайте начнем комбинировать лучшие старые и новые практики.

Например, объединим примеры 2 и 5: Пример 6:

$http.request('GET', '/api').

then(requestSuccessHandler, requestErrorHandler); function requestSuccessHandler(result) { return result.arr .

map(transformResultArr) .

filter(filterResultArr); function transformResultArr(item) { item.props .

filter(prop => !!prop) .

map(transformProperty) } function filterResultArr(item) { return item.props.some(prop => prop.a > 10); } function transformProperty(prop) { prop.a = prop.a * 2; return prop; } } function requestErrorHandler(error) { throw new TypeError(error); }

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

Теги: #JavaScript #ecmascript 2015 #codestyle #функции стрелок #JavaScript

Вместе с данным постом часто просматривают: