Продолжаем серию коротких постов о рефакторинге кода! В нем мы обсуждаем методы и инструменты, которые могут помочь вам улучшить ваш код и проекты.

Сегодня мы поговорим о декларативном стиле кода и о том, как он может помочь нам уменьшить количество случайных ошибок и ошибок в нашем коде.


Императивный и декларативный стили

Чтобы понять разницу между императивным и декларативным стилями, давайте рассмотрим пример:

// 1.

const evenNumbers = []

for (const x of array) {
  if (x % 2 === 0) {
    evenNumbers.push(x)
  }
}


// 2.

const isEven = x => x % 2 === 0
const evenNumbers = array.filter(isEven)
Войти в полноэкранный режим

Выйти из полноэкранного режима

Первый фрагмент кода является императивным — он описывает, как решить проблему в виде набора инструкций:

  • Создать пустой evenNumbers множество;
  • Итерация по заданному array переменная;
  • Проверьте каждое число, если оно четное;
  • Если да, добавьте его в evenNumbers.

С другой стороны, второй фрагмент описывает какие нужно сделать. Он фокусируется на критериях фильтрации, а не на Детали алгоритма фильтрации.

В этом разница между императивным и декларативным стилями. Декларативный код описывает какие делать, в то время как императивный код описывает как сделать это.


Смешанные опасения

В императивном коде мы должны думать о «цели» и «как ее достичь» одновременно. Из-за этого императивный код часто содержит больше строк и статистически с большей вероятностью содержит ошибку.

Давайте посмотрим на другой пример. selectOperation функция ниже выбирает математическую операцию по данному ключу:

function selectOperation(kind) {
  let operation = null;

  switch (kind) {
    case "log":
      operation = (x, base) => Math.log(x) / Math.log(base);
    case "root":
      operation = (x, root) => x ** -root;
    default:
      operation = (x) => x;
  }

  return operation;
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Функция выглядит нормально, но каждый блок case в ней пропускает break утверждение. В результате operation переменная всегда будет равна (x) => x.

Такую ошибку относительно легко обнаружить в небольшой функции, но если кода много, ее гораздо легче не заметить.

Поскольку в императивном коде количество строк и символов меньше, нам легче увидеть такие ошибки.

Для пропавших без вести break заявления, мы всегда можем настроить правила линтера и тесты для автоматического поиска таких ошибок.
Однако, по моему опыту, этого не всегда достаточно, потому что если в коде есть способ совершить ошибку, люди сделает ошибку.
Так что лучше писать код так, чтобы было меньше потенциальных «ям».


Улучшение кода

Мы можем избавиться от проблемы случайных ошибок, сделав выборку декларативной:

const log = (x, base) => Math.log(x) / Math.log(base);
const pow = (x, power) => x ** power;
const id = (x) => x;

function selectOperation(kind) {
  const operations = { root, pow, id };
  return operations[kind] ?? operations.id;
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

В приведенном выше коде мы теперь делегируем «выбор» язык сам. Нам все равно, как сделан этот выбор, нам важен только его результат.

В этом коде гораздо сложнее сделать случайную ошибку, потому что кода меньше, а его структура более надежна.

Мне также нравится последний фрагмент из эстетических соображений. Выбор из объекта по ключу выглядит более естественным решением этой проблемы, тогда как код с switch кажется шумным и многословным.


Подробнее о рефакторинге в моей книге

В этом посте мы обсудили только надежность декларативного стиля кода.

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

Если вы хотите узнать больше об этих аспектах и ​​о рефакторинге в целом, я рекомендую вам ознакомиться с моей онлайн-книгой:

Книга бесплатна и доступна на GitHub. В нем я объясняю тему более подробно и с большим количеством примеров.

Надеюсь, вы найдете это полезным! Наслаждайтесь книгой 🙌