Comments 44
Хм. Вообще идеальная функция — это чистая функция. А чистая функция без аргументов — это константа, и смысла в ней не слишком много. Исключения конечно бывают вполне полезными (текущее время, или генератор случайных чисел), но считать что это идеально всегда, было бы перебором.
Избегайте необдуманного следования правилам моды. Сегодня они помогут, а завтра подведут.
Поругался в предыдущей теме, поругаюсь и здесь: чем простой цикл плох? Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде
.reduce((acc, linesOfCode) => acc + linesOfCode, 0);
когда достаточно было бы написать .sum();
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
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
Так объявите функцию sum
— сэкономится куча места и существенно вырастет читаемость.
Вообще, обратил внимание, что в обычных языках я никогда не использовал reduce в общем виде (он же Aggregate в C#, например), всегда хватало методов типа Sum, Average, Count и т.д.
Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде
Для того, чтобы восхитить других хипстеров столь модным кодом. Других причин нету.
Сегодня вот наткнулся на Rethinking JavaScript: Death of the For Loop. Смерть циклам. Заголовки js-статей всё пафоснее и пафоснее. Вскоре появятся слова из фентези, видать. А потом и до таких слов как "хтонический ужас" доберутся.
В статье стандартный набор штампов про, о боже, циклы мутируют переменные, о боже, в циклах сайд эффекты. О боже, такой код жутко не предсказуем, там же сплошные баги. Ааа, мы все умрём.
В случае записи в виде функций это получается более ясно (хотя непривычному глазу — возможно и не сразу).
Первое это перфоманс, у меня на последнем хроме он опережает foreach в разы.
Замерить можно тут: for vs forEach
Второе, это память, каждая такая супер функция создает новый массив выборки.
Ну и наконец читабельность. С моей точки зрения, код теряет наглядность, особенно если функции проверки вынести далеко от кода вызова или в другой файл.
Некоторые советы несколько спорны. Не в том плане, что они не верны, но уже хотя бы в том, что они не так просты как расписаны в статье. Для примера… Статья пестрит различными доводами в пользу выноса чего бы то ни было в отдельные методы. В частности условий из if
. В ряде случаев, в особенности где не тривиальная логика, это действительно бывает правильным и удобным. Но если вы внезапно начнёте дотошно применять все эти рекомендации одновременно в вашей кодовой базе, то у вас будут чуть ли не сотни методов, с длинными названиями, с реализацией вырванной из места своего логического контекста. И поди потом разбери этот клубок сложного кода. И внеси потом все необходимые правки. Обычно в кодовой базе по ходу дела приходится переключаться между режимами: "всё стало сложно, разносим по частям, делаем рефакторинг" и "всё стало просто, упрощаем кодовую базу". Нет какого однозначного верного подхода.
Или к примеру довод не писать switch
-и в угоду полиморфизму. Оно то конечно бывает удобно. Но автор не уточнил, что так стоит делать только и только тогда, когда оно того стоит. Т.е. когда подобных мест у вас несколько и вынос всех этих сущностей в отдельную иерархию с наследованием окупает затраты и делает код понятнее. А вовсе не переусложняет простую кодовую базу. Т.е. далеко не каждый switch нужно вот так вот превращать в груды классов.
А ещё, конечно забавно, как одни советы из одних статьей коррелируют с другими советами из других статей. Скажем статьи про "быстрый" код в JS, часто противоречат статьям про "правильный чистый код". Советы из статей с императивным подходом плохо сочетаются с функциональным.
И ещё. По поводу совета предпочитать функциональный подход. Тут одна беда пришла незаметно. У нас появились долгожданные генераторы и async-и. И вот ну очень уж плохо они дружат с функциональным подходом. Начинается монстро-код с Promise.all и прочими шаманствами.
Небольшая поправка. Использование Promise.all — это самостоятельный трюк, который не зависит от избранного подхода.
Скорее надо вспомнить про шаманство с reduce, в которые превращается простой императивный цикл. И при для нахождения суммы еще понятно что reduce делает — то при использовании then уже мало кто способен сходу понять что вообще в коде происходит.
Ещё вспомнилось про параметры и объект-параметр. Тут палка о двух концах. Если параметры передаются явно и отдельно, то мы получаем более предсказуемый и однозначный код, нежели когда передаётся объект-параметр. Однако когда число параметров вырастает, скажем, до 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')
. И что можно сказать на нее глядя?
Устанавливайте объекты по умолчанию с помощию 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' ]();
Если избегать, то уж лучше как-то так:Я надеюсь, вы несерьезно.
Да, сорри, упустил контекст совета в статье. Нужно было подзаголовок иначе сформулировать… Хотя плодить классы на каждый вариант свойств данных тоже совсем "не айс", ИМХО классы должны оставаться на уровне абстракций.
Если уж весь код все равно написан по месту, а не вынесен в отдельные классы — в чем вообще смысл заменять нормальный оператор выбора на подобное выражение? Это ж не дает ни-че-го.
Этот код будет работать во много раз быстрее при любых обстоятельствах:
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 на которые может тратится и совсем мало времени, но когда это происходит много раз в секунду — это может сказаться на производительности.
Чистый javascript.Функции