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');
}),
);

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

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

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

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

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

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