Проектирование функциональных UI
Модель и масштаб — Основы БД, опорные темы, проектирование БД, пакетная работа. Карта — о разделе.
Проектирование функциональных UI
Эта статья показывает, как проектировать интерфейс, который корректно работает при таймаутах, повторных запросах и конкурентных изменениях данных, а не только в идеальном "демо-сценарии".
1. Функциональный UI и визуальный дизайн
Прежде чем перейти к проектированию, необходимо чётко развести две задачи:
- Визуальный дизайн — отвечает на вопрос "как это выглядит?" — композиция, цвет, типографика, анимации, эмоциональное восприятие. Его цель — сделать интерфейс привлекательным, интуитивным и доступным.
- Функциональное проектирование UI — отвечает на вопрос "как это работает?" — какие действия доступны пользователю, в какой последовательности, при каких условиях, как состояние системы отражается в интерфейсе, как обрабатываются ошибки. Его цель — сделать взаимодействие корректным, предсказуемым и безопасным.
Разработчик, проектирующий функциональный UI, работает с моделями состояний, контрактами API, ограничениями домена и сценариями использования. Художник создаёт макет кнопки "Оплатить"; инженер определяет, когда она должна быть активна, что происходит при нажатии, как обрабатывается таймаут, как отображается прогресс, как восстанавливается состояние после обновления страницы.
Если визуальный дизайн нарушается — страдает удобство.
Если функциональное проектирование нарушается — страдает корректность.
Например: кнопка "Оплатить" серая и некрасивая — плохо. Кнопка "Оплатить" активна при нулевой сумме — критическая ошибка.
2. UI как отражение бизнес-состояния
Фундаментальный принцип: пользовательский интерфейс не управляет системой — он отражает её состояние и предоставляет канал для воздействия.
Это означает:
- Любое действие пользователя — это запрос на изменение состояния, а не прямая команда.
- Любое отображаемое значение — это проекция текущего состояния, а не кэшированная копия.
- Любое ограничение интерфейса (недоступная кнопка, скрытое поле) — это выражение бизнес-правила, а не произвольное решение дизайнера.
Пример:
В системе управления заказами статус "Оплачен" означает:
- Нельзя изменить состав заказа,
- Можно инициировать отгрузку,
- Нельзя отменить без возврата средств.
Функциональный UI должен:
- Отключать элементы редактирования при статусе "Оплачен",
- Делать доступной кнопку "Отгрузить",
- Скрывать или переключать кнопку "Отменить" на "Запросить возврат".
Если эти правила реализованы только на фронтенде, возможна рассогласованность: пользователь может отправить запрос на изменение состава через Postman, и бэкенд его примет (если там нет проверки).
Если реализованы только на бэкенде — UI будет выглядеть "мертвым": кнопки активны, но при нажатии возвращается ошибка 403.
Правильный подход:
- Бизнес-правила декларируются в домене (например,
Order.CanModify()→bool). - Бэкенд предоставляет метаданные о доступных действиях (например,
GET /orders/123возвращает данные иavailableActions: ["ship", "requestRefund"]). - Фронтенд строит UI на основе этих метаданных, а не на основе локальной логики.
Такой подход называется HATEOAS (Hypermedia as the Engine of Application State) — один из принципов REST, редко реализуемый полностью, но крайне полезный как идеал.
3. Алгоритм проектирования функционального UI
Процесс начинается с анализа сценариев взаимодействия.
Шаг 1. Выявление акторов и целей
Кто использует интерфейс и зачем?
- Администратор хочет массово изменить статусы заказов.
- Покупатель хочет проследить этапы доставки.
- Оператор колл-центра хочет быстро найти заказ по телефону и продиктовать статус.
У каждого — разные цели, следовательно, разные приоритеты в интерфейсе:
- Для администратора — таблица с чекбоксами и bulk-действиями,
- Для покупателя — линейный прогресс-бар с пояснениями,
- Для оператора — поле поиска с автодополнением и крупным отображением статуса.
Проектирование начинается с пользовательских историй, но как источника для выявления ограничений и инвариантов.
Шаг 2. Моделирование состояний и переходов
Каждый UI-экран — это проекция состояния агрегата или процесса. Необходимо явно описать:
- Какие состояния возможны (например,
Draft → Confirmed → Paid → Shipped → Delivered), - Какие переходы разрешены,
- Какие условия необходимы для перехода (например, "только после оплаты можно перейти в Shipped"),
- Какие побочные эффекты сопровождают переход (email, webhook, изменение складских остатков).
Это часто оформляется как диаграмма состояний (state machine) для согласования между разработчиками, тестировщиками и аналитиками.
Если переход не моделируется явно, он реализуется императивно в коде (if (order.Status == "Paid") ...), что ведёт к:
- Рассогласованности между UI и бэкендом,
- Ошибкам при параллельных изменениях,
- Сложности добавления новых состояний.
Шаг 3. Определение контракта данных
UI требует структурированный контекст:
- Текущее состояние объекта,
- Возможные действия,
- Ограничения ввода (валидация в реальном времени),
- Справочники и enum’ы (со значениями и локализацией),
- История изменений (если нужна аудиторская трассировка).
Контракт должен быть самодостаточным. Пример плохого API:
{ "status": "paid", "canShip": true }
— откуда UI знает, что canShip означает? Что будет, если завтра добавится canCancel?
Хороший API:
{
"status": "paid",
"transitions": [
{ "action": "ship", "label": "Отгрузить", "requires": ["warehouseNote"] },
{ "action": "requestRefund", "label": "Запросить возврат" }
],
"validationRules": {
"warehouseNote": { "required": true, "maxLength": 255 }
}
}
Такой подход:
- Снижает связность UI и бэкенда,
- Позволяет динамически менять логику без деплоя фронтенда,
- Упрощает локализацию и A/B-тестирование интерфейсов.
Шаг 4. Управление асинхронностью и частичными состояниями
В распределённых системах UI часто работает с неполным или устаревшим состоянием. Проектирование должно учитывать:
- Что показывать во время выполнения операции? (индикатор, блокировка кнопки),
- Что делать при таймауте? (повтор, отмена, сохранение черновика),
- Как синхронизировать состояние после переподключения? (оптимистичные обновления, операционные трансформации),
- Как обрабатывать конфликты? ("другой пользователь изменил этот заказ").
Пример: редактирование заказа.
- Пользователь меняет адрес,
- В это время оператор отменяет заказ,
- Пользователь нажимает "Сохранить".
Если UI не проверяет условие If-Match (ETag) или не получает события OrderCancelled, сохранение пройдёт — и отменённый заказ снова станет активным.
Решения:
- Использовать оптимистичную блокировку (ETag в заголовках),
- Подписываться на события домена через SSE или WebSocket,
- Вводить локальные сайд-эффекты (например, отмена сохранения при получении события
OrderCancelled).
Шаг 5. Валидация
Валидация в UI — ускорение проверок на бэкенде.
| Уровень | Цель | Пример |
|---|---|---|
| UI-валидация (мгновенная) | Улучшить UX, снизить нагрузку | Проверка формата email при потере фокуса (/^[^@]+@[^@]+\.[^@]+$/) |
| Фронтенд-валидация (перед отправкой) | Предотвратить заведомо невалидные запросы | Проверка общей суммы ≥ минимальной корзины |
| Бэкенд-валидация (на входе) | Гарантия целостности | Повторная проверка формата, бизнес-правил, доступа |
| Доменная валидация (в сущностях) | Защита инвариантов | Order.AddLine() проверяет, что товар активен и в наличии |
Важно: никакая валидация на фронтенде не отменяет проверки на бэкенде. UI можно обойти; бэкенд — нет.
Шаг 6. Отображение ошибок
Ошибка — часть взаимодействия. UX ошибки должен быть:
- Конкретным: "Срок действия карты истёк" вместо "Ошибка оплаты",
- Действенным: предложить решение — "Введите новую карту" или "Свяжитесь с банком",
- Локализованным: код ошибки (
CARD_EXPIRED) + сообщение на языке пользователя, - Логируемым: уникальный correlation ID для диагностики.
Идеальный контракт ошибки:
Код ITЗагрузка примера кода…
4. Разделение ответственности между UI и бизнес-логикой
Последствия дублирования логики:
- UI содержит
if (total < 1000) shipping = 300 else shipping = 0, - Бэкенд содержит ту же логику,
- Правило меняется: доставка бесплатна от 800 ₽,
- Забыли обновить фронтенд — пользователь видит 300 ₽, но платит 0,
- Пользователь путается, поддержка получает жалобы.
Как избежать:
- Вынос правил в shared-библиотеку (если стек позволяет — например, TypeScript + .NET через генерацию DTO и валидаторов),
- Предоставление правил через API (например,
GET /pricing/rulesвозвращает{ "freeShippingThreshold": 800 }), - Использование декларативных форм (JSON Schema, form.io), где схема генерируется на бэкенде и интерпретируется на фронтенде.
Последний подход особенно эффективен для внутренних систем: схема формы — это проекция бизнес-модели, а не отдельный артефакт.
5. Доступность и инклюзивность как требования проектирования
Функциональное проектирование UI включает обеспечение доступности (accessibility) — как функциональное требование.
Примеры:
- Кнопка без
aria-label— недоступна для 1% пользователей, что нарушает функциональность. - Цветовая индикация статуса (красный = ошибка) без текста — некорректное отображение состояния.
- Отсутствие
tabindex— блокировка работы через клавиатуру.
Стандарты (WCAG 2.1, Section 508) содержат конкретные критерии, которые можно тестировать автоматически (axe, Lighthouse) и вручную. Их соблюдение — часть контракта UI.
6. Практический сценарий "Оплата заказа"
Шаблон поведения для критической кнопки:
- Пользователь нажимает "Оплатить".
- UI переводит кнопку в состояние
loadingи блокирует повторное нажатие. - Запрос отправляется с
Idempotency-Key. - При сетевой ошибке пользователь видит промежуточный статус "Платёж обрабатывается", а не мгновенный повтор без контекста.
- Итоговый статус подтверждается повторным чтением состояния заказа из API.
Так интерфейс остаётся предсказуемым, даже когда сеть и интеграции работают нестабильно.
7. Частые ошибки в функциональном UI
- UI дублирует бизнес-логику скидок, а бэкенд живет по другим правилам.
- Кнопка активна во время выполнения операции и запускает дубли действий.
- Ошибки показываются без кода и без следующего шага для пользователя.
- Локальное состояние считается "истиной" дольше, чем допустимо для домена.
Эти ошибки снижают доверие пользователя и увеличивают нагрузку на поддержку.
8. Мини-чек-лист перед релизом
- Для каждого экрана зафиксированы состояния
idle/loading/success/error. - Для каждой команды определен механизм защиты от повторов.
- Ошибки маппятся на понятные сообщения и action-подсказки.
- Доступность проверена клавиатурной навигацией и скринридером.
- UI отражает правила домена через контракт API, а не через "магические if".
Смежные материалы
- Проектирование API и интеграций
- Методы и ключ идемпотентности
- Проектирование сервисов и методов
- Документация как инструмент проектирования