Учитесь писать код без If
Зачем писать код с if, если можно этого не делать? Вот почему.
Перевод статьи автора курсов по программированию Самера Буны.
Когда я учу новичков программированию, одно из моих любимых заданий - попросить решить некоторую задачу без использования if (тернарного оператора, switch и т.д.).
Вы можете задаться вопросом: "а для чего это вообще может понадобиться?".
В первую очередь, я считаю, что это заставляет решить задачу нестандартно. Зачастую это может стать даже лучшим решением.
С if всё в порядке. Но избегание его иногда делает код более читаемым. Иногда. Это не общее правило и, разумеется, бывает наоборот. Вам решать.
Это — не единственная причина для избегания if — есть и другие теоретические обоснования. Как показывают примеры ниже, этот подход приближает нас к концепции "код как данные", открывающий такие возможности, как модификация кода во время исполнения, а также облегчение для программной оптимизации кода.
Во всяком случае, это увлекательный челлендж!
Представляю вам несколько задач с двумя решениями на JavaScript - использующих if и без них.
Задача №1: сосчитать число нечётных чисел в массиве
Допустим, дан массив целых чисел наподобие следующего. Как посчитать нечётные элементы?
const arrayOfIntegers = [1, 4, 5, 9, 0, -1, 5];
Решение с if:
let counter = 0; arrayOfIntegers.forEach((integer) => { const remainder = Math.abs(integer % 2); if (remainder === 1) { counter++; } }); console.log(counter);
Решение без if:
let counter = 0; arrayOfIntegers.forEach((integer) => { const remainder = Math.abs(integer % 2); counter += remainder; }); console.log(counter);
Внимание: примеры выше используют forEach и изменяют переменную counter. Это не очень хороший стиль, но и не является предметом этой статьи. Также, пример без if не будет работать произвольным типом чисел.
В решении без if мы используем то свойство операции % 2, что она возвращает 0 для чётных и 1 для нечётных. Этот результат — и есть данные, мы их и используем.
Совет: самостоятельно подумайте над решением аналогичной задачи для чётных чисел.
Задача №2: выходнойИлиБудни(...)
Необходимо написать функцию, которая принимает один аргумент — дату (как new Date()), а возвращает строку “weekend” или “weekday”.
Решение с if:
const weekendOrWeekday = (inputDate) => { const day = inputDate.getDay(); if (day === 0 || day === 6) { return 'weekend'; } return 'weekday'; // Or, for ternary fans: // return (day === 0 || day === 6) ? 'weekend' : 'weekday'; }; console.log(weekendOrWeekday(new Date()));
Решение без if:
const weekendOrWeekday = (inputDate) => { const day = inputDate.getDay(); return weekendOrWeekday.labels[day] || weekendOrWeekday.labels['default']; }; weekendOrWeekday.labels = { 0: 'weekend', 6: 'weekend', default: 'weekday' }; console.log(weekendOrWeekday(new Date()));
Обратите внимание, что в первом решении условный оператор несёт в себе данные — будний день или выходной. Во втором мы вынесли данные в объект — тем самым перенесли их на другой уровень.
Вы можете справедливо заметить, что оператор || по сути также является логическим. Но здесь он используется, чтобы не писать 1:'weekday' … 5:'weekday'.
Задача №3: удвоитель
Нужно написать функцию, удваивающую аргумент в зависимости от его типа:
- если он числового типа, то оно умножается на 2 (10 -> 20, -5.5 -> -11);
- если string, то каждая буква повторяется (‘proglib’ -> ‘pprroogglliibb’);
- если функция, то она вызывается дважды;
- если массив или объект, то функция вызывает себя для каждого элемента.
Решение со switch:
const doubler = (input) => { switch (typeof input) { case 'number': return input + input; case 'string': return input .split('') .map((letter) => letter + letter) .join(''); case 'object': Object.keys(input) .map((key) => (input[key] = doubler(input[key]))); return input; case 'function': input(); input(); } }; console.log(doubler(-10)); console.log(doubler('hey')); console.log(doubler([5, 'hello'])); console.log(doubler({ a: 5, b: 'hello' })); console.log( doubler(function() { console.log('hey yo proglib.io'); }), );
Решение без условных операторов:
const doubler = (input) => { return doubler.operationsByType[typeof input](input); }; doubler.operationsByType = { number: (input) => input + input, string: (input) => input .split('') .map((letter) => letter + letter) .join(''), function: (input) => { input(); input(); }, object: (input) => { Object.keys(input) .map((key) => (input[key] = doubler(input[key]))); return input; }, }; console.log(doubler(-10)); console.log(doubler('hey')); console.log(doubler([5, 'hello'])); console.log(doubler({ a: 5, b: 'hello' })); console.log( doubler(function() { console.log('hey yo proglib.io'); }), );
Заметили? Здесь мы снова выносим данные (какие операции нужно выполнять) из условного оператора в объект. И объект используем далее для получения результата.
Согласитесь, такое простое самоограничение даёт весьма интересные результаты? Берите на вооружение!
А как насчёт программирования без циклов? Это уже отдельная тема, за которой скрыто ещё больше полезных решений. Интересует?