admin 22 сентября 2017

Учитесь писать код без If

if Code from a Facebook project

Зачем писать код с 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');
}),
);

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

Согласитесь, такое простое самоограничение даёт весьма интересные результаты? Берите на вооружение!

А как насчёт программирования без циклов? Это уже отдельная тема, за которой скрыто ещё больше полезных решений. Интересует?

Если вам понравилась эта статья, возможно вас заинтересует:

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
DevOps инженер
по итогам собеседования
QA (manual)
Омск, от 50000 RUB до 70000 RUB
Junior Java Developer
от 90000 RUB до 140000 RUB
Product Manager
от 200000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ