Pull to refresh

Comments 44

>Идеальная ситуация — отсутствие аргументов.

Хм. Вообще идеальная функция — это чистая функция. А чистая функция без аргументов — это константа, и смысла в ней не слишком много. Исключения конечно бывают вполне полезными (текущее время, или генератор случайных чисел), но считать что это идеально всегда, было бы перебором.

Текущее время или ГСЧ — это не чистые функции.

Я не говорил, что она чистая. Я имел в виду, что она — достаточно редкий пример практически полезной функции без параметров.

Избегайте необдуманного следования правилам моды. Сегодня они помогут, а завтра подведут.

Вот бы ещё сглаживание тексту на иллюстрации к статье…
Вот ещё бы без ненужных иллюстраций к статье.

Поругался в предыдущей теме, поругаюсь и здесь: чем простой цикл плох? Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде


.reduce((acc, linesOfCode) => acc + linesOfCode, 0);

когда достаточно было бы написать .sum();

UFO just landed and posted this here

Let и const (const в конструкции for of) тоже не засоряют скоуп. Меньше места занимает только в коде, в памяти заметно больше, да и то есть ситуации, когда через for код получается компактнее. Это я уже молчу про то, что конструкция reduce в примерах посложнее суммы обычно настолько нечитаемая, что даже вы не то, что через пол года, через пол часа уже не поймете что он делает. Особенно если захотелось написать лаконично.


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


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

const в конструкции for of

К сожалению, Firefox упорно падал на любой попытке затолкать const в for. А let переменные там же, не обновлялись при каждой итерации. Это доставило мне много "славных" моментов debug-а, когда я использовал одну из таких переменных внутри новых Promise-ов. Бррр. Возможно такое поведение уже исправили.


for(const a of [1, 2, 3]){ }
// SyntaxError: missing = in const declaration
for(let a of [1, 2, 3]){ setTimeout(() => alert(a)) }
// 3 3 3

Хм. Нет. 50.1.0

let и const в 51 всё таки починили

for(const i of [1, 2, 3]) console.log(i)
1
2
3

for(let a of [1, 2, 3]) { setTimeout(() => console.log(a)) }
1
2
3

Подстава конечно с let от лисы, хоть об этом и говорится в https://kangax.github.io/compat-table/es6/, но это только если раскрыть ветвь
let/const for/for-in loop iteration scope no

Ух, спасибо за новость. Как бальзам на душу.

UFO just landed and posted this here

Так объявите функцию sum — сэкономится куча места и существенно вырастет читаемость.


Вообще, обратил внимание, что в обычных языках я никогда не использовал reduce в общем виде (он же Aggregate в C#, например), всегда хватало методов типа Sum, Average, Count и т.д.

Часто видел Aggregate в чужом коде. Но неизменно оказывалось, что это результат автоматической замены ReSharper'ом цикла foreach.
Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде

Для того, чтобы восхитить других хипстеров столь модным кодом. Других причин нету.

Сегодня вот наткнулся на Rethinking JavaScript: Death of the For Loop. Смерть циклам. Заголовки js-статей всё пафоснее и пафоснее. Вскоре появятся слова из фентези, видать. А потом и до таких слов как "хтонический ужас" доберутся.


В статье стандартный набор штампов про, о боже, циклы мутируют переменные, о боже, в циклах сайд эффекты. О боже, такой код жутко не предсказуем, там же сплошные баги. Ааа, мы все умрём.

Статье не хватает следующей части под названием «1000 и 1 способ придумать название для расплодившихся на ровном месте функций-однострочников»

Даже cat.name не избежал участи превратиться в
const getName = cat => cat.name
Вообще, простой цикл хуже компонуется. И для него не очевидно его назначение — в виде похожих циклов могут быть записаны очень разные вещи (а зачастую — и много разных вещей в одном цикле).

В случае записи в виде функций это получается более ясно (хотя непривычному глазу — возможно и не сразу).
Простой цикл не просто неплох, он хорош.
Первое это перфоманс, у меня на последнем хроме он опережает foreach в разы.
Замерить можно тут: for vs forEach
Второе, это память, каждая такая супер функция создает новый массив выборки.
Ну и наконец читабельность. С моей точки зрения, код теряет наглядность, особенно если функции проверки вынести далеко от кода вызова или в другой файл.

Небольшая придирка. Если мне не изменяет память, то циклы в jsperf лучше не тестировать вовсе. Он там, на самом деле, корёжит ваш код в целях каких-то своих измерений и в итоге замеряет чёрт знает что.

Некоторые советы несколько спорны. Не в том плане, что они не верны, но уже хотя бы в том, что они не так просты как расписаны в статье. Для примера… Статья пестрит различными доводами в пользу выноса чего бы то ни было в отдельные методы. В частности условий из if. В ряде случаев, в особенности где не тривиальная логика, это действительно бывает правильным и удобным. Но если вы внезапно начнёте дотошно применять все эти рекомендации одновременно в вашей кодовой базе, то у вас будут чуть ли не сотни методов, с длинными названиями, с реализацией вырванной из места своего логического контекста. И поди потом разбери этот клубок сложного кода. И внеси потом все необходимые правки. Обычно в кодовой базе по ходу дела приходится переключаться между режимами: "всё стало сложно, разносим по частям, делаем рефакторинг" и "всё стало просто, упрощаем кодовую базу". Нет какого однозначного верного подхода.


Или к примеру довод не писать switch-и в угоду полиморфизму. Оно то конечно бывает удобно. Но автор не уточнил, что так стоит делать только и только тогда, когда оно того стоит. Т.е. когда подобных мест у вас несколько и вынос всех этих сущностей в отдельную иерархию с наследованием окупает затраты и делает код понятнее. А вовсе не переусложняет простую кодовую базу. Т.е. далеко не каждый switch нужно вот так вот превращать в груды классов.


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


И ещё. По поводу совета предпочитать функциональный подход. Тут одна беда пришла незаметно. У нас появились долгожданные генераторы и async-и. И вот ну очень уж плохо они дружат с функциональным подходом. Начинается монстро-код с Promise.all и прочими шаманствами.

Небольшая поправка. Использование Promise.all — это самостоятельный трюк, который не зависит от избранного подхода.


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

+1 по поводу switch и полиморфизма. Кроме того, наличие if и вообще ветвлений в коде никаким образом не показывает, что у этого кода два и более назначений. Вовсе нет.

Ещё вспомнилось про параметры и объект-параметр. Тут палка о двух концах. Если параметры передаются явно и отдельно, то мы получаем более предсказуемый и однозначный код, нежели когда передаётся объект-параметр. Однако когда число параметров вырастает, скажем, до 5-7, это всё выливается в такой геморрой (особенно если код очень полиморфичен)… Брр… Приходится балансировать, глядя на реальную задачу.


Функции с большим количеством параметров вызывают в моей голове воспоминания об Windows API. Такое ощущение, что тамошнее API писали большие фанаты аргументов.


Ещё касательно совета не использовать флаги. Ну с таким то примером и правда не сложно вынести обёртку в отдельный метод. А что если флаг не заменяется простой заменой аргументов функции?

Функции с большим количеством параметров вызывают в моей голове воспоминания об Windows API. Такое ощущение, что тамошнее API писали большие фанаты аргументов.
Аж до слез.

Можно же использовать объект-параметр вместе с деструктуризацией. Код будет столь же предсказуемым и однозначным.

Нууу, не совсем. Для этого придётся открыть метод и пробежаться по нему глазами. Если деструктуризация в сигнатуре — то пробежать глазами по ней. А в случае аргументов хватит и взгляда на строчку с вызовом метода. Правда от появления доп. аргументов вы, в любом случае, не застрахованы.


К тому же, ИМХО, деструктуризация в сигнатуре выглядит… ужасно. Честно говоря я не сторонник того, чтобы сигнатура метода содержала в себе столько логики, сколько её сейчас туда пихают. Ещё и дефолтные значения. А учитывая, что деструктуризация бывает многоуровневой...


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

предсказуемый и однозначный код
Ну кстати, предсказуемость и однозначность можно получить введением типизации для этих объектов-параметров через flow/ts. Тогда можно положиться на IDE и не держать постоянно в голове всю структуру.
type TFooConfig = {
  foo: string,
  bar?: number
}
export const foo = (config: TFooConfig) => {
}

Проще объявить конструктор для конфига. Типизация, да еще и с внешними зависимостями, тут может быть не нужна.

А в конструктор этот как значения передавать? Снова через аргументы или через еще один объект?

А в чем проблема? Берете и передаете так-же, как передавали бы в функцию. Конструктор для сериализации/формализации + подсветка в IDE. Если у вас объект с параметрами формируется где-то рядом в функции, где происходит и вызов метода с этими параметрами, то это все вообще делать не нужно, тем более тащить сюда flow.

Да ну? Вот есть строка с вызовом метода — foo(5, 7, 'bar'). И что можно сказать на нее глядя?

Какие-то магические константы? 5, 7, 'bar'? :) Вместо них, в реальном коде, будет что-нибудь типа widget.id, position, mode. И сказать можно будет гораздо большее.

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

Устанавливайте объекты по умолчанию с помощию Object.assign

function createMenu(config) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable = config.cancellable === undefined ? config.cancellable : true;
}

Это не "плохо", это вообще неправильно. Пустая строка может являться корректным значением, но в приведенном коде она заменяется на дефолтное.


Использовать Object.assign тоже не очень правильно, есть риск унаследовать кучу мусора прилетевшую в config например из DOM. Нужно как-то так:


const menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
};
Object.keys(menuConfig).forEach((prop) => {
        if (config.hasOwnProperty(prop)) {
         menuConfig[prop] = config[prop];
        }
      });

Избегайте негативных условий

Это еще почему?


Избегайте условных конструкций

Если избегать, то уж лучше как-то так:


let doSomethingIf = {
value1: () => {doSomething(1);},
value2: () => {doSomething(2);},
value3: () => {doSomething(3);},
};
doSomethingIf[value || 'value1' ]();
Если избегать, то уж лучше как-то так:
Я надеюсь, вы несерьезно.

Да, сорри, упустил контекст совета в статье. Нужно было подзаголовок иначе сформулировать… Хотя плодить классы на каждый вариант свойств данных тоже совсем "не айс", ИМХО классы должны оставаться на уровне абстракций.

Если уж весь код все равно написан по месту, а не вынесен в отдельные классы — в чем вообще смысл заменять нормальный оператор выбора на подобное выражение? Это ж не дает ни-че-го.

Да и вообще, если использовать tagged unions и тип never с парой флагов из последнего TS, то это один в один ADT и pattern matching с полноценными exhaustive свитчами, так что все эти нагромождения абстракций ради нагромождения абстракций становятся ни к чему.
Это всё прекрасные советы и замечания, но только когда слово «производительность» стоит на последнем месте в проекте.

Этот код будет работать во много раз быстрее при любых обстоятельствах:
let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}


чем этот:

const totalOutput = programmerOutput
  .map((programmer) => programmer.linesOfCode)
  .reduce((acc, linesOfCode) => acc + linesOfCode, 0);


Это же касается совета отказаться практически от любых условных и множественных операций в угоду создания большего количества функций. Однако не стоит забывать что при создании и вызове функции производятся различные операции под капотом, например работа с внутренними объектами scope, proto на которые может тратится и совсем мало времени, но когда это происходит много раз в секунду — это может сказаться на производительности.
Sign up to leave a comment.

Articles