Оптимистичный интерфейс (Optimistic UI)

Optimistic UI (оптимистичный интерфейс) — это способ ускорить “отзывчивость” (responsiveness) интерфейсов. Его суть в том, что мы показываем результат действия до того, как получили ответ от сервера. Мы оптимистично предполагаем, что ответ будет положительным и что он в принципе будет.

Например, когда пользователь нажимает лайк, интерфейс сразу показывает сердечко и увеличивает количество лайков под постом. Параллельно данные отправляются на сервер в надежде, что они будут успешно доставлены, обработаны и состояние на сервере совпадёт с состоянием в интерфейсе.

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

Но что если будет ошибка? У пользователя может не быть интернета, сервер может лежать или запрос может быть заблокирован надзорными органами где-то в середине пути.

Важно заранее продумать, как система будет реагировать в случае ошибки.

Есть разные подходы:

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

  2. Откат с уведомлением — возврат UI в исходное состояние с сообщением вроде “не удалось поставить лайк". Подходит, если действие важно для пользователя и не должно пропадать незаметно. Например, отправка сообщения в мессенджере. Обычно в таких случаях есть возможность повторить операцию кнопкой “try again”.

  3. Фоновый повтор — система сама пытается повторить операцию, без участия пользователя. Это снижает трение (friction), но в конечном счёте, если после нескольких попыток действие совершить не удалось, придётся всё же откатиться – либо тихо (1), либо с уведомлением (2).

Оптимистичный UI
Оптимистичный UI

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

В идеале, если всё делать “правильно” с точки зрения синхронизации состояния интерфейса и сервера, то интерфейс должен отправить лайк и отобразить его только тогда, когда данные обновятся на сервере.

Но даже в очень быстрых системах, путь данных “туда-обратно” (roundtrip) займёт какое-то время. Задержка более 100-200 миллисекунд ощущается пользователем как “чуть дольше, чем мгновенно”, задержка в 300-500 мс ощущается как “задумался”, а полная секунда уже вызывает раздражение.

После пары секунд задержки пользователь решит, что что-то сломано и начнёт снова нажимать кнопку. Это может ухудшить положение, если кнопка не заблокирована и отправляет действие повторно, тем самым, поочерёдно включая и отключая лайк пользователя под постом. Учитывая задержки, пользователь полностью теряет контроль над собой ситуацией.

Таким образом, чтобы обеспечить мгновенный отклик там, где это важно, мы сразу рисуем результат, скрещиваем пальцы и надеемся на лучшее. Чтобы надежда была подкреплена чем-то существенным, добавляем фоновые повторы (background retries).