Думайте состояниями Никлас Нильссон

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

«У нас абсолютно, ну совершенно абсолютно закончились запасы молока».

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

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

Рассмотрим простой интернет-магазин, который принимает к оплате только кредитные карты и не выписывает клиентам счета. При этом класс Order (заказ) содержит такой метод:

public boolean isComplete() {

 return isPaid() && hasShipped();

}

Разумно, так? Так вот, хоть это выражение и выделено в аккуратный метод, а не разбросано по всему коду путем копирования и вставки, его вообще не должно существовать. А то, что оно существует, указывает на проблему. Какую? Нельзя доставить заказ, прежде чем он оплачен. Поэтому hasShipped не может вернуть true, пока isPaid не вернет true, а в этом случае часть выражения избыточна. Возможно, вам все же нужен метод isComplete для ясности кода, но тогда он должен выглядеть как-то так:

public boolean isComplete() {

 return hasShipped();

}

В своей работе я постоянно сталкиваюсь и с недостающими, и с избыточными проверками. Я привел крошечный пример, но если добавить сюда отмену заказа и возврат денег, дело усложняется, и потребность в правильной обработке состояний возрастает. В данном случае заказ может находиться только в одном из трех разных состояний:

В процессе: можно добавлять и удалять товары. Нельзя осуществлять доставку.

Оплачен: нельзя добавлять и удалять товары. Можно доставлять.

Доставлен: конец. Больше никаких изменений.

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

Но как начать думать состояниями? Выделить выражения в осмысленные методы — очень хорошее начало, но это лишь начало. Главное — понимать конечные автоматы. Знаю, у вас сохранились неприятные воспоминания о них из курса информатики, но оставьте это в прошлом. Конечные автоматы — это не особенно сложно. Рисуйте их, тогда их легче понять и обсуждать. Запускайте свой код в тестовом режиме, чтобы разделить допустимые и недопустимые состояния, а также переходы между ними, и поддерживать их корректность. Изучите шаблон State. Освоившись с ним, почитайте о контрактном программировании (design by contract). Оно помогает обеспечить допустимость состояния путем проверки данных и самого объекта на входе и выходе из каждого открытого метода.

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

Загрузка...