Конечный автомат на бытовом языке

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

Переходы случаются только по условию

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

Обработка ошибок вместо зависания

Открыли дверцу во время стирки — машина не продолжает как ни в чём не бывало. Она останавливает барабан и переводит себя в состояние паузы до момента, пока дверца снова не закроется. Если вода не набирается до нужного уровня дольше отведённого времени, машина не ждёт вечно: она переходит в состояние ошибки, зажигает индикатор и останавливает цикл. Перегруз барабана датчик тоже ловит и не даёт программе стартовать вовсе. Ровно ту же дисциплину ждут от кода. Любой внешний вызов — к базе данных, к другому сервису, к платёжному провайдеру — может отказать, и обработчик обязан перевести систему в явное состояние ошибки вместо того, чтобы сделать вид, будто всё прошло гладко.

Зачем нужен таймаут

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

Отложенный старт как очередь задач

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

Идемпотентность и повторное нажатие кнопки

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

Где ещё в софте встречается тот же принцип

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

Частые вопросы

Чем конечный автомат отличается от обычного набора условий if-else

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

Что если датчик в системе выдал неожиданное значение

Грамотно спроектированный автомат не пытается угадать, что имелось в виду, — он переводит систему в состояние ошибки при любом переходе, которого нет в разрешённом списке. Это правило одинаково работает и для сломанного датчика уровня воды, и для неожиданного ответа внешнего API.

Почему нельзя просто ждать ответа бесконечно вместо таймаута

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

Пригодится ли эта логика на фронтенде, если я не пишу бэкенд

Да, интерфейс живёт по тем же правилам. Экран приложения обычно проходит состояния загрузки, готового результата, ошибки и пустого списка, и без явного их описания легко получить экран, застрявший в состоянии «загрузка» навсегда после сбоя сети.