1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>В этом уроке мы поговорим о<strong>CQS</strong>(<em>Command-query Separation</em>). Это принцип программирования, изобретенный Бертандом Майером, создателем языка Eiffel.</p>
1
<p>В этом уроке мы поговорим о<strong>CQS</strong>(<em>Command-query Separation</em>). Это принцип программирования, изобретенный Бертандом Майером, создателем языка Eiffel.</p>
2
<p>Этот принцип утверждает, что каждая функция считается:</p>
2
<p>Этот принцип утверждает, что каждая функция считается:</p>
3
<ul><li>Либо командой, которая выполняет действие (<em>action</em>)</li>
3
<ul><li>Либо командой, которая выполняет действие (<em>action</em>)</li>
4
<li>Либо запросом, который извлекает данные (<em>query</em>)</li>
4
<li>Либо запросом, который извлекает данные (<em>query</em>)</li>
5
</ul><p>При этом функция не может быть командой и запросом одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.</p>
5
</ul><p>При этом функция не может быть командой и запросом одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.</p>
6
<h2>Как работает команда</h2>
6
<h2>Как работает команда</h2>
7
<p>Рассмотрим такой пример:</p>
7
<p>Рассмотрим такой пример:</p>
8
<p>Согласно принципу CQS, функция save считается командой. Ее единственная задача - это возвращать успешность своего выполнения как значение true или false. По той же логике может быть null, как в случае с print_r. Если мы попробуем вернуть какие-то осмысленные данные с помощью этой функции, это будет считаться нарушением CQS. Но в некоторых ситуациях этот принцип невозможно соблюсти. Например, открытие файла на запись возвращает файловый дескриптор - идентификатор, через который происходят манипуляции с файлом.</p>
8
<p>Согласно принципу CQS, функция save считается командой. Ее единственная задача - это возвращать успешность своего выполнения как значение true или false. По той же логике может быть null, как в случае с print_r. Если мы попробуем вернуть какие-то осмысленные данные с помощью этой функции, это будет считаться нарушением CQS. Но в некоторых ситуациях этот принцип невозможно соблюсти. Например, открытие файла на запись возвращает файловый дескриптор - идентификатор, через который происходят манипуляции с файлом.</p>
9
<p>Отделение команд от запросов тесно связано с идеями, описанными в уроке о чистых функциях.</p>
9
<p>Отделение команд от запросов тесно связано с идеями, описанными в уроке о чистых функциях.</p>
10
<p>Команды по определению выполняют<strong>недетерминированный</strong>код с побочными эффектами, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия. Вообще команды можно сделать детерминированными, но это не лучшее решение - как правило, такой код скрывает логические ошибки.</p>
10
<p>Команды по определению выполняют<strong>недетерминированный</strong>код с побочными эффектами, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия. Вообще команды можно сделать детерминированными, но это не лучшее решение - как правило, такой код скрывает логические ошибки.</p>
11
-
<p>Следовательно, чтобы отделить чистый код от кода с побочными эффектами, мы можем выделить запрос (возврат данных) из команды в отдельную функцию. Как мы увидим позже, запросы можно выполнять множество раз, не боясь что-то сломать:</p>
11
+
<p>Следовательно, чтобы отделить чистый код от кода с побочными эффектами, мы можем выделить запрос (возврат данных) из ко��анды в отдельную функцию. Как мы увидим позже, запросы можно выполнять множество раз, не боясь что-то сломать:</p>
12
<h2>Как работает запрос</h2>
12
<h2>Как работает запрос</h2>
13
<p>Рассмотрим такой фрагмент кода:</p>
13
<p>Рассмотрим такой фрагмент кода:</p>
14
<p>Функцию isAdmin можно воспринимать как:</p>
14
<p>Функцию isAdmin можно воспринимать как:</p>
15
<ul><li>Предикат</li>
15
<ul><li>Предикат</li>
16
<li>Типичный запрос (<em>query</em>)</li>
16
<li>Типичный запрос (<em>query</em>)</li>
17
<li>Вопрос "Является ли пользователь администратором?"</li>
17
<li>Вопрос "Является ли пользователь администратором?"</li>
18
</ul><p>С точки зрения CQS такая функция не может изменить состояние системы - например, поменять дату проверки на администратора внутри пользователя или сделать пользователя администратором.</p>
18
</ul><p>С точки зрения CQS такая функция не может изменить состояние системы - например, поменять дату проверки на администратора внутри пользователя или сделать пользователя администратором.</p>
19
<p>Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, в случае предикатов true и false показывают не успешность выполнения функции, а ответ на этот запрос.</p>
19
<p>Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, в случае предикатов true и false показывают не успешность выполнения функции, а ответ на этот запрос.</p>
20
<p>Взгляните на пример работы функции, которая меняет исходные данные:</p>
20
<p>Взгляните на пример работы функции, которая меняет исходные данные:</p>
21
<p>Если сделать еще один вызов takeKids($users), то выполнение кода, скорее всего, завершится с ошибкой, потому что изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: "Задавая вопрос, не изменяй ответ".</p>
21
<p>Если сделать еще один вызов takeKids($users), то выполнение кода, скорее всего, завершится с ошибкой, потому что изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: "Задавая вопрос, не изменяй ответ".</p>
22
<p>К запросам относятся и любые вычисления:</p>
22
<p>К запросам относятся и любые вычисления:</p>
23
<p>Этот код не создает никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.</p>
23
<p>Этот код не создает никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.</p>
24
<p>Отсутствие изменений в запросах - это очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin или вычисление максимального числа в массиве может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается. Теперь вы знаете, как его исправить.</p>
24
<p>Отсутствие изменений в запросах - это очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin или вычисление максимального числа в массиве может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается. Теперь вы знаете, как его исправить.</p>