В этом уроке мы поговорим о CQS (Command-query Separation). Это принцип программирования, изобретенный Бертандом Майером, создателем языка Eiffel.
Этот принцип утверждает, что каждая функция считается:
- Либо командой, которая выполняет действие (action)
- Либо запросом, который извлекает данные (query)
При этом функция не может быть командой и запросом одновременно. Команда всегда связана с выполнением побочных эффектов, а чистые функции возможны только для запросов.
Как работает команда
Рассмотрим такой пример:
<?php
// Возвращает true или false как результат своего выполнения
save($user);
Согласно принципу CQS, функция save
считается командой. Ее единственная задача — это возвращать успешность своего выполнения как значение true
или false
. По той же логике может быть null
, как в случае с print_r
. Если мы попробуем вернуть какие-то осмысленные данные с помощью этой функции, это будет считаться нарушением CQS. Но в некоторых ситуациях этот принцип невозможно соблюсти. Например, открытие файла на запись возвращает файловый дескриптор — идентификатор, через который происходят манипуляции с файлом.
Отделение команд от запросов тесно связано с идеями, описанными в уроке о чистых функциях.
Команды по определению выполняют недетерминированный код с побочными эффектами, потому что повторный вызов команды приводит либо к ошибке, либо к повторному выполнению действия. Вообще команды можно сделать детерминированными, но это не лучшее решение — как правило, такой код скрывает логические ошибки.
Следовательно, чтобы отделить чистый код от кода с побочными эффектами, мы можем выделить запрос (возврат данных) из команды в отдельную функцию. Как мы увидим позже, запросы можно выполнять множество раз, не боясь что-то сломать:
<?php
$file = fopen('/etc/hosts', 'r');
Как работает запрос
Рассмотрим такой фрагмент кода:
<?php
// Возвращает true или false
isAdmin($user);
Функцию isAdmin
можно воспринимать как:
- Предикат
- Типичный запрос (query)
- Вопрос «Является ли пользователь администратором?»
С точки зрения CQS такая функция не может изменить состояние системы — например, поменять дату проверки на администратора внутри пользователя или сделать пользователя администратором.
Это противоречит не только CQS, но и здравому смыслу. В отличие от предыдущего примера, в случае предикатов true
и false
показывают не успешность выполнения функции, а ответ на этот запрос.
Взгляните на пример работы функции, которая меняет исходные данные:
<?php
$users = [
['name' => 'Stan', 'kids' => ['John', 'Mary']],
['name' => 'Donald', 'kids' => ['James']],
['name' => 'Lily', 'kids' => []],
['name' => 'Julian', 'kids' => []]
];
// Функция takeKids() возвращает массив детей всех пользователей
takeKids($users); // ['John', 'Mary', 'James']
// На самом деле внутри она меняет массив $users и возвращает его наружу
print_r($users); // => ['John', 'Mary', 'James']
Если сделать еще один вызов takeKids($users)
, то выполнение кода, скорее всего, завершится с ошибкой, потому что изменилась структура исходного массива. Такое поведение функции-запроса противоестественно. CQS имеет альтернативную формулировку, которая отлично характеризует код выше: «Задавая вопрос, не изменяй ответ».
К запросам относятся и любые вычисления:
<?php
$max = max([1, 30, 4]);
Этот код не создает никаких побочных эффектов и детерминирован. Его можно вызывать сколько угодно раз без риска получить ошибку или неверный результат.
Отсутствие изменений в запросах — это очень важный принцип, который нужно соблюдать всегда. Даже на интуитивном уровне ни один человек не ожидает, что проверка isAdmin
или вычисление максимального числа в массиве может выполнить какое-то деструктивное действие. С другой стороны, на практике такой код иногда попадается. Теперь вы знаете, как его исправить.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.