Development

Мыслите состояниями

(В оригинале – Thinking in States)

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

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

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

Представьте простой интернет-магазин, принимающий только кредитные карты, с классом Order , содержащим следующий метод:

public boolean isComplete() {
    return isPaid() && hasShipped();
}

Выглядит логично, не так ли? Однако если даже выражение красиво завернуто в метод вмето того, чтобы быть множество раз скопипасченым по всему коду, этого выражения не должно было вообще быть. То, что оно существует, показывает наличие проблемы. Почему? Потому что заказ не может быть доставлен, пока он не будет оплачен. Поэтому hasShipped не может стать true до того, как isPaid станет true, что делает выражение избыточным.

Возможно, вы все равно захотите оставить метод isComplete для ясности кода, но он должен при этом выглядеть вот так:

public boolean isComplete() { 
     return hasShipped();
}

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

  1. Формируется: можно добавлять или удалять покупки, нельзя доставлять;
  2. Оплачен: нельзя добавлять или удалять покупки, можно доставлять;
  3. Доставлено: заключительное состояние, никаких изменений.

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

Но как начать думать состояниями? Оборачивание выражений в осмысленные методы — хорошее начало, но это лишь начало. Основа — это понимание машин состояний (конечных автоматов, state machines). Возможно (и скорее всего), вы уже успели забыть теорию цифровых автоматов из ваших институтских лекций, но в принципе, это и необязательно. Машины состояний — достаточно просты. Визуализируйте их чтобы упростить понимание.

Испытайте ваш код на обнаружение разрешенных и запрещенных состояний и переходов. Изучите паттерн State. Когда почувствуете, что с паттерном разобрались, почитайте про контрактное программирование (Design by Contract). Это поможет вам гарантировать разрешенное состояние путем проверки входных данных и самого объекта на входе и выходе каждого метода.

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

Автор оригинала – Niclas Nilsson