Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете
JS: Функции

Разделение команд и запросов

Command-query Separation (CQS) — принцип программирования, изобретённый Бертраном Мейером, создателем языка Eiffel. Согласно этому принципу, каждая функция является либо командой, которая выполняет действие (action), либо запросом (query), который извлекает данные, но не тем и другим одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.

Команда

// Возвращает true или false как результат своего выполнения
save(user);

Согласно принципу CQS, функция save() является командой. Единственное, что она может — возвращать (опять же согласно принципу) успешность своего выполнения, то есть true или false (либо undefined, как в случае с console.log()). Возврат этой функцией любых осмысленных данных рассматривается как нарушение CQS. Однако, стоит сказать, что существуют ситуации, в которых невозможно соблюсти этот принцип. Например, открытие файла на запись возвращает файловый дескриптор — необходимый идентификатор, через который происходят манипуляции с файлом.

const file = fopen('/etc/hosts', 'r');

Отделение команд от запросов тесно связано с идеями, описанными в уроке про чистые функции. Команды по определению выполняют недетерминированный код с побочными эффектами. Недетерминированный, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия (хотя их и можно сделать детерминированными, но, как правило, такой код скрывает логические ошибки). Следовательно, выделение запроса (возврата данных) из команды в отдельную функцию, помогает изолировать чистый код от кода с эффектами.

Запрос

// Возвращает true или false
isAdmin(user);

Функция isAdmin() — предикат, типичный запрос (query) или, можно даже сказать, вопрос, который звучит так "Пользователь — администратор?" Такая функция, с точки зрения CQS, не может изменять состояние системы: например, поменять дату проверки на администратора внутри пользователя или даже сделать пользователя администратором. Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, true и false в случае предикатов — это не успешность выполнения функции, а ответ на заданный вопрос.

Взгляните на пример работы функции, которая меняет исходные данные:

const users = [
  { 'name': 'Stan', 'children': ['John', 'Mary'] },
  { 'name': 'Donald', 'children': ['James'] },
  { 'name': 'Lily', 'children': [] },
];

// takeChildren() возвращает массив детей всех пользователей
takeChildren(users); // ['John', 'Mary', 'James']
// На самом деле внутри она меняет массив users и возвращает его наружу
console.log(users); // => ['John', 'Mary', 'James']

Если сделать еще один вызов takeChildren(users), то выполнение кода, скорее всего, завершится с ошибкой, так как изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: "Задавая вопрос, не изменяй ответ".

К запросам относятся и любые вычисления:

// Функция Math.max() возвращает максимальное число из переданных
const getMaxNumber = Math.max(1, 30, 4); // 30

Этот код не создаёт никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.

Отсутствие изменения в запросах — очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin() или вычисление максимального числа в массиве, может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается, и теперь вы знаете, как правильно его исправить.

Как поступать в случаях, когда невозможно отделить команду от запроса? Представьте себе функцию, которая должна сформировать имя пользователя, если его не существует, и записать его в базу данных. Такая задача стоит в 4 проекте Хекслета на фронтенде. В этом проекте разрабатывается аналог Slack. При заходе на сайт, пользователь сразу попадает в чат и в этот момент ему генерируется случайное имя пользователя.

Логика этой функции выглядит так:

// Проверяем есть ли
let username = getName();
if (!username) {
  // Если имени нет, то генерируем и запоминаем
  username = generateName();
  setName(username);
}

Если убрать эту логику внутрь функции, то как она должна называться? Посмотрите на такой вариант:

const username = getUserName();

Такое имя функции вводит в заблуждение. Оно выглядит как запрос, но не является им. Не зная устройство этой функции, невозможно догадаться о происходящем внутри. Первая мысль всегда будет о том, что это просто чтение каких-то уже записанных данных.

Правильный подход в подобных ситуациях – именовать функцию как команду. Тогда у нее не будет скрытых смыслов. Да она все еще будет нарушать CQS, но здесь мы и не пытаемся от этого уйти. Главное – явно показанное намерение:

const username = setUserName();

// Или даже так
const username = setUserNameIfEmpty();

Дополнительные материалы

  1. Command-query Separation
  2. Принцип наименьшего удивления

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

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

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».