Это мини-серия статей по написанию поддерживаемого объектно-ориентированного кода без лишней нервотрепки.

Olga Sayfudinova
Dec 15, 2018 · 2 min read

У функций есть побочные эффекты. Иногда они изменяют состояние системы в самый неожиданный момент и рушат все, что только можно. Крайне трудно избавиться от побочных эффектов с помощью парадигмы объектно-ориентированного программирования. Поэтому нужно научиться управлять этими эффектами — тогда они не испортят систему при первой возможности.

Управление побочными эффектами

Отличным способом управления побочными эффектами является создание четкого разделения команд и запросов. В данном контексте команда изменяет систему и обладает побочным эффектом. Запрос возвращает вычисленное значение или наблюдаемое состояние системы.

Пример

Вы ждете, что при вызове функции getAmount()будет возвращаться некое значение без изменения системы. И было бы ужасно, если бы все происходило наоборот. То же и с вызовом setAmount(, у которой есть свои побочные эффекты. Вы знаете, что она должна изменять состояние системы. Но что, по вашему мнению, вернет setAmount()? Вероятно, ничего.

Формальное определение

Разделение команд и запросов дает формальное определение:

Функции, которые изменяют состояние, не должны возвращать значения, а функции, возвращающие значения, не должны изменять состояние.

Данный термин был придуман Бертраном Мейером в его книге «Объектно-ориентированное конструирование программных систем».

Плюсы

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

int m();  // query
void n(); // command

Исключения

Посмотрите на вызов ниже. Это команда или запрос? Очевидно, что такая строка изменяет состояние системы.

User u = UserService.login(username, password);

А что происходит с пользовательским объектом? Почему он возвращается? Как им можно управлять? Нужно ли в какой-то момент вызывать функцию выходаlogout? Разве это не должен быть простой геттер?

User u = UserService.getUser();

Обработка ошибок

Может, login()вернул User, чтобы потом вернуть nullпри ошибке? И велик соблазн вернуть код ошибок из команды, если она не может изменить состояние. Но лучше выбросить исключение и руководствоваться правилом о том, что команды возвращают void.

Подвохи

Несмотря на то, что разделение команд и запросов отлично работает в большинстве случаев, не обходится и без исключений. Удаление элемента из стека — это пример функции, которая изменяет состояние и возвращает результат в виде запроса.

Element e = Stack<Element>.pop(); // stateful query

Итог

  • Команды возвращают void, a запросы возвращают значения.
  • Пользуйтесь исключениями вместо возвращения и проверки на ошибки.

Как сказал Мартин Фаулер, было бы здорово, если бы язык сам поддерживал такое разделение. В таком случае, язык бы обнаруживал методы изменения состояния или, по крайней мере, помогал бы размечать их программистам.

Перевод статьи Arun Sasidharan: Object Oriented Tricks: #1 The Art of Command Query Separation

NOP::Nuances of programming

Перевод и адаптация статей в сфере IT

Olga Sayfudinova

Written by

NOP::Nuances of programming

Перевод и адаптация статей в сфере IT

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade