HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Command-query Separation (CQS) - принцип программирования, изобретённый Бертраном Мейером, создателем языка Eiffel. Согласно этому принципу, каждая функция является либо командой, которая выполняет действие (action), либо запросом (query), который извлекает данные, но не тем и другим одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.</p>
1 <p>Command-query Separation (CQS) - принцип программирования, изобретённый Бертраном Мейером, создателем языка Eiffel. Согласно этому принципу, каждая функция является либо командой, которая выполняет действие (action), либо запросом (query), который извлекает данные, но не тем и другим одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.</p>
2 <h2>Команда</h2>
2 <h2>Команда</h2>
3 <p>Согласно принципу CQS, функция save() является командой. Единственное, что она может - возвращать (опять же согласно принципу) успешность своего выполнения, то есть true или false (либо undefined, как в случае с console.log()). Возврат этой функцией любых осмысленных данных рассматривается как нарушение CQS. Однако, стоит сказать, что существуют ситуации, в которых невозможно соблюсти этот принцип. Например, открытие файла на запись возвращает файловый дескриптор - необходимый идентификатор, через который происходят манипуляции с файлом.</p>
3 <p>Согласно принципу CQS, функция save() является командой. Единственное, что она может - возвращать (опять же согласно принципу) успешность своего выполнения, то есть true или false (либо undefined, как в случае с console.log()). Возврат этой функцией любых осмысленных данных рассматривается как нарушение CQS. Однако, стоит сказать, что существуют ситуации, в которых невозможно соблюсти этот принцип. Например, открытие файла на запись возвращает файловый дескриптор - необходимый идентификатор, через который происходят манипуляции с файлом.</p>
4 <p>Отделение команд от запросов тесно связано с идеями, описанными в уроке про чистые функции. Команды по определению выполняют недетерминированный код с побочными эффектами. Недетерминированный, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия (хотя их и можно сделать детерминированными, но, как правило, такой код скрывает логические ошибки). Следовательно, выделение запроса (возврата данных) из команды в отдельную функцию, помогает изолировать чистый код от кода с эффектами.</p>
4 <p>Отделение команд от запросов тесно связано с идеями, описанными в уроке про чистые функции. Команды по определению выполняют недетерминированный код с побочными эффектами. Недетерминированный, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия (хотя их и можно сделать детерминированными, но, как правило, такой код скрывает логические ошибки). Следовательно, выделение запроса (возврата данных) из команды в отдельную функцию, помогает изолировать чистый код от кода с эффектами.</p>
5 <h2>Запрос</h2>
5 <h2>Запрос</h2>
6 <p>Функция isAdmin() - предикат, типичный запрос (query) или, можно даже сказать, вопрос, который звучит так "Пользователь - администратор?" Такая функция, с точки зрения CQS, не может изменять состояние системы: например, поменять дату проверки на администратора внутри пользователя или даже сделать пользователя администратором. Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, true и false в случае предикатов - это не успешность выполнения функции, а ответ на заданный вопрос.</p>
6 <p>Функция isAdmin() - предикат, типичный запрос (query) или, можно даже сказать, вопрос, который звучит так "Пользователь - администратор?" Такая функция, с точки зрения CQS, не может изменять состояние системы: например, поменять дату проверки на администратора внутри пользователя или даже сделать пользователя администратором. Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, true и false в случае предикатов - это не успешность выполнения функции, а ответ на заданный вопрос.</p>
7 <p>Взгляните на пример работы функции, которая меняет исходные данные:</p>
7 <p>Взгляните на пример работы функции, которая меняет исходные данные:</p>
8 <p>Если сделать еще один вызов takeChildren(users), то выполнение кода, скорее всего, завершится с ошибкой, так как изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: "Задавая вопрос, не изменяй ответ".</p>
8 <p>Если сделать еще один вызов takeChildren(users), то выполнение кода, скорее всего, завершится с ошибкой, так как изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: "Задавая вопрос, не изменяй ответ".</p>
9 <p>К запросам относятся и любые вычисления:</p>
9 <p>К запросам относятся и любые вычисления:</p>
10 <p>Этот код не создает никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.</p>
10 <p>Этот код не создает никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.</p>
11 <p>Отсутствие изменения в запросах - очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin() или вычисление максимального числа в массиве может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается, и теперь вы знаете, как правильно его исправить.</p>
11 <p>Отсутствие изменения в запросах - очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin() или вычисление максимального числа в массиве может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается, и теперь вы знаете, как правильно его исправить.</p>
12 <p>Как поступать в случаях, когда невозможно отделить команду от запроса? Представьте себе функцию, которая должна сформировать имя пользователя, если его не существует, и записать его в базу данных. Такая задача стоит в<a>4 проекте Хекслета</a>на фронтенде. В этом проекте разрабатывается аналог Slack. При входе на сайт пользователь сразу попадает в чат, и в этот момент ему генерируется случайное имя пользователя.</p>
12 <p>Как поступать в случаях, когда невозможно отделить команду от запроса? Представьте себе функцию, которая должна сформировать имя пользователя, если его не существует, и записать его в базу данных. Такая задача стоит в<a>4 проекте Хекслета</a>на фронтенде. В этом проекте разрабатывается аналог Slack. При входе на сайт пользователь сразу попадает в чат, и в этот момент ему генерируется случайное имя пользователя.</p>
13 <p>Логика этой функции выглядит так:</p>
13 <p>Логика этой функции выглядит так:</p>
14 <p>Если убрать эту логику внутрь функции, то как она должна называться? Посмотрите на такой вариант:</p>
14 <p>Если убрать эту логику внутрь функции, то как она должна называться? Посмотрите на такой вариант:</p>
15 <p>Такое имя функции вводит в заблуждение. Оно выглядит как запрос, но не является им. Не зная устройство этой функции, невозможно догадаться о происходящем внутри. Первая мысль всегда будет о том, что это просто чтение каких-то уже записанных данных.</p>
15 <p>Такое имя функции вводит в заблуждение. Оно выглядит как запрос, но не является им. Не зная устройство этой функции, невозможно догадаться о происходящем внутри. Первая мысль всегда будет о том, что это просто чтение каких-то уже записанных данных.</p>
16 <p>Правильный подход в подобных ситуациях - именовать функцию как команду. Тогда у нее не будет скрытых смыслов. Да, она все еще будет нарушать CQS, но здесь мы и не пытаемся от этого уйти. Главное - явно показанное намерение:</p>
16 <p>Правильный подход в подобных ситуациях - именовать функцию как команду. Тогда у нее не будет скрытых смыслов. Да, она все еще будет нарушать CQS, но здесь мы и не пытаемся от этого уйти. Главное - явно показанное намерение:</p>
17  
17