Product Management UX, CJM, требования и user stories
0%

UX, CJM, требования и user stories

UX, CJM, требования и user stories

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

Эта статья — про инженерию этих двух переходов. Не «как рисовать красивые макеты» (это работа дизайнера) и не «как правильно оформить тикет» (это бюрократия), а про то, какая именно информация должна дойти до команды, в какой форме она не искажается, и почему интерфейс, собранный без модели пользователя, ломается предсказуемым образом.


1. Задача с первых принципов

1.1. Передача намерения по шумному каналу

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

У канала есть три свойства, которые определяют всё остальное:

  1. Он лоссовый. Естественный язык неоднозначен. «Пользователь должен видеть актуальные данные» — это раз в секунду или раз в час? «Быстро» — это 200 мс или 2 с?
  2. Он дорогой. Каждая страница спеки — время на написание, чтение, согласование и поддержание в актуальном состоянии. Спека на 60 страниц устаревает быстрее, чем её дочитают.
  3. Он двусторонний, но об этом забывают. Разработчик, увидевший требование, знает про систему то, чего не знает продакт: что дёшево, что дорого, где уже есть похожий механизм. Односторонняя передача («вот спека, реализуй») выбрасывает эту информацию.

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

1.2. Цена поздней ошибки в требованиях

Второй фундамент — экономический. Ошибка в требованиях, найденная на этапе формулировки, стоит одного разговора. Та же ошибка, найденная после релиза, стоит переделки кода, данных, документации, обучения поддержки и репутации. Классические оценки Боэма («Software Engineering Economics», 1981) давали разброс на два порядка; современные данные мягче, но порядок сохраняется — исправление после релиза заметно дороже, чем на этапе анализа (Boehm, «Software Defect Reduction Top 10 List»).

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

1.3. Что такое UX и чем он не является

UX — не «интерфейс» и не «красиво». UX — это весь опыт человека при достижении своей цели с помощью продукта, включая то, что происходит до входа в продукт и после выхода из него. Ожидание ответа поддержки, письмо о списании, статья в базе знаний — часть UX. Кнопка — маленькая часть последнего слоя.

Полезная декомпозиция — пять уровней Джесси Джеймса Гарретта («The Elements of User Experience»), от абстрактного к конкретному:

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


2. Модель пользователя: почему интерфейсы непонятны

2.1. Три модели и два разрыва

Дон Норман в «The Design of Everyday Things» описывает ситуацию через три модели одной системы:

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

Пользователь общается не с системой, а со своей моделью системы. Между его намерением и системой — два разрыва:

  • Разрыв исполнения (gulf of execution): «я знаю, чего хочу, но не понимаю, что нажать».
  • Разрыв оценки (gulf of evaluation): «я нажал, но не понимаю, что произошло и получилось ли».

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

2.2. Шесть принципов, закрывающих разрывы

Принцип Что делает Провал в интерфейсе
Аффорданс элемент подсказывает возможное действие текст, который на самом деле кликабелен
Сигнификатор явный признак этой возможности иконка без подписи в незнакомой роли
Соответствие (mapping) связь управления и результата естественна ползунки в произвольном порядке
Обратная связь система сообщает, что произошло кнопка «Отправить» без реакции 3 секунды
Ограничения невозможное запрещено физически поле даты, куда можно ввести «вчера»
Концептуальная модель целостная непротиворечивая картина «проект», «пространство» и «папка» без границ

Сильнейший из шести — ограничения. Валидировать ошибку после ввода дороже и хуже, чем сделать её невозможной: календарь вместо текстового поля, выпадающий список вместо строки. Это ровно тот же принцип, что «делай недопустимые состояния непредставимыми» в типизации — см. https://courses.digitable.life/post/typescript/00-overview/ и разбор доменных инвариантов в https://courses.digitable.life/post/ddd/00-overview/.

2.3. Эвристики Нильсена как чек-лист ревью

Десять эвристик Якоба Нильсена (NN/g, 1994, обновлены 2024) — не теория, а рабочий чек-лист. Продакту достаточно прогнать по ним макет за 15 минут:

  1. Видимость состояния системы — человек всегда знает, что происходит.
  2. Соответствие системы реальному миру — язык пользователя, а не язык базы данных.
  3. Свобода и контроль — отмена, выход, «назад» без потерь.
  4. Согласованность и стандарты — одно и то же называется одинаково везде.
  5. Предотвращение ошибок — см. ограничения выше.
  6. Узнавание вместо припоминания — не заставляйте держать данные в голове между экранами.
  7. Гибкость и эффективность — ускорители для опытных, не мешающие новичкам.
  8. Эстетичный минималистичный дизайн — каждый лишний элемент конкурирует с нужным.
  9. Помощь в распознавании и исправлении ошибок — что случилось, почему, как починить.
  10. Справка и документация — доступная в контексте, а не отдельным сайтом.

Типичная находка в B2B-продуктах — нарушения №2 и №9: «Ошибка валидации: constraint fk_account_owner violated» вместо «Нельзя удалить пользователя, пока на нём висят активные счета. Сначала передайте их другому».


3. Customer Journey Map

3.1. Зачем нужна карта, если есть воронка

Воронка (см. https://courses.digitable.life/post/product-management/02-metrics/) отвечает на вопрос «где падает конверсия». CJM отвечает на вопрос «почему» и, главное, показывает переходы между каналами, которые ни одна воронка внутри продукта не видит: человек написал в поддержку, три дня ждал, пошёл к конкуренту. В продуктовой аналитике этого нет вообще — он просто «перестал заходить».

CJM — это способ разложить опыт по этапам и связать в одной картине три обычно разъединённых источника: качественные данные (интервью), количественные (аналитика) и операционные (тикеты поддержки, звонки продаж).

Анатомия Customer Journey Map: слои строк и кривая эмоции

3.2. Анатомия: строки, которые обязаны быть

  • Персона и сценарий. Одна карта — один тип пользователя и одна цель. Карта «для всех пользователей» бесполезна: у бухгалтера и у аналитика разные пути через один продукт.
  • Этапы — крупные фазы (осознание → выбор → онбординг → первая ценность → привычка). Их 4–7; больше — распадается на детали, меньше — теряет смысл.
  • Действия — что человек делает, глаголами. Не «взаимодействует с системой», а «выгружает выписку из банк-клиента».
  • Точки контакта — канал и конкретный экран/письмо/человек.
  • Мысли — прямые цитаты из интервью. Кавычки обязательны: они не дают выдумывать.
  • Эмоция — кривая. Нужна не для красоты: пики и провалы указывают, где вложение усилий даёт максимальный сдвиг.
  • Боли — что именно ломается, с частотой и масштабом.
  • Возможности — куда можно вмешаться. Это вход в приоритизацию.
  • Метрика этапа — количественное подтверждение боли. Без этой строки карта остаётся красивым плакатом.
  • Владелец этапа — кто в компании отвечает. Часто самое ценное открытие: у провала между маркетингом и онбордингом нет владельца.

Компактный вариант той же структуры в mermaid — удобно держать прямо в репозитории рядом с кодом:

Цифра 1–5 — уровень удовлетворённости, справа — участники шага. Провал на импорте виден мгновенно, и видно, что он вовлекает поддержку, то есть стоит компании денег.

3.3. Как строить CJM, чтобы она не была фантазией

Порядок работ, отличающий карту-инструмент от карты-плаката:

  1. Определить вопрос. «Почему 40% зарегистрировавшихся не доходят до первого отчёта?» Карта без вопроса не сходится.
  2. Собрать имеющиеся данные до всяких воркшопов: воронка по событиям, топ причин обращений в поддержку, записи сессий, результаты интервью (методики — в https://courses.digitable.life/post/product-management/01-discovery-and-research/).
  3. Провести воркшоп с командой — построить черновик «как мы думаем». Ценность — в обнаружении разногласий: маркетинг и разработка обычно рисуют разные карты.
  4. Проверить черновик на пользователях. 5–8 интервью, где человек рассказывает свой реальный последний путь, а не оценивает вашу схему.
  5. Разметить каждую ячейку источником. Правило: цветной маркер на ячейках без данных. Обычно после этой процедуры цветным оказывается больше половины карты — и это главный результат упражнения.
  6. Вывести возможности и передать в приоритизацию (https://courses.digitable.life/post/product-management/03-prioritization/).

3.4. Родственные артефакты: не путать

Артефакт Ось Что показывает Когда применять
User flow шаги в UI конкретные экраны и ветвления проектирование одной фичи
CJM этапы жизни клиента опыт по всем каналам, эмоция поиск системных провалов
Service blueprint этапы + слои системы что происходит «за кулисами» опыт зависит от людей и процессов
Experience map этапы поведения путь вне привязки к вашему продукту вход на новый рынок

3.5. Service blueprint: то, что за кулисами

Как только опыт зависит не только от кода (доставка, модерация, поддержка, ручная проверка документов), CJM недостаточно: провал случается в невидимой для пользователя части. Service blueprint добавляет слои, разделённые «линиями видимости» и «внутреннего взаимодействия». Последовательная природа этого артефакта отлично ложится на sequenceDiagram:

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


4. Требования: уровни и формы

4.1. Пять уровней, которые нельзя смешивать

Классификация из BABOK и книги Вигерса («Software Requirements»):

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

4.2. Нефункциональные требования: главный источник переделок

НФТ (производительность, доступность, безопасность, приватность, локализация, наблюдаемость) имеют неприятное свойство: их невозможно добавить потом дёшево, потому что они — свойства архитектуры, а не фич. «Добавим мультиарендность в следующем квартале» — почти всегда переписывание слоя данных.

Правило формулировки: НФТ должно быть измеримым и с указанием условий измерения.

# Плохо — непроверяемо
- "Система должна работать быстро"
- "Интерфейс должен быть удобным"

# Хорошо — превращается в тест и в дашборд
performance:
  metric: "время от нажатия «Построить» до первого отрисованного байта отчёта"
  target: "p95 ≤ 2.0 s, p99 ≤ 5.0 s"
  conditions: "датасет ≤ 1 млн строк, 50 одновременных построений, регион eu-central-1"
  measured_by: "RUM-метрика report_render_ttfb, окно 7 дней"
availability:
  target: "99.9% успешных запросов к /api/reports за календарный месяц"
  error_budget: "43 минуты недоступности в месяц"
accessibility:
  target: "WCAG 2.2 уровень AA для критических сценариев: вход, импорт, экспорт"
  measured_by: "axe-core в CI + ручная проверка клавиатурной навигации перед релизом"
privacy:
  constraint: "персональные данные клиентов ЕС не реплицируются вне eu-*"

Такой формат делает НФТ частью инженерной практики: цифры попадают в SLO и алерты, а не в PDF-документ. Подробнее про бюджеты ошибок и SLO — в треке https://courses.digitable.life/post/architecture-patterns/00-overview/.

4.3. PRD, который читают

Живой PRD — 1–3 страницы, отвечающие на конкретные вопросы:

  1. Проблема и для кого — с доказательством: числами и цитатами.
  2. Почему сейчас — что изменилось; отсутствие ответа обычно означает отсутствие приоритета.
  3. Целевой результат — метрика и ожидаемый сдвиг, а не «улучшить опыт».
  4. Сценарии — 2–5 пользовательских историй верхнего уровня.
  5. Что вне охвата — самый полезный раздел документа.
  6. Ограничения и НФТ.
  7. Открытые вопросы — с именем ответственного и датой.
  8. Как поймём, что сработало — метрика, срок, критерий отката.

Всё остальное (макеты, схемы данных, API) живёт по ссылкам в источниках истины: Figma, схема, код. Дублирование содержимого в PRD гарантирует расхождение через две недели.


5. User stories

5.1. Что это на самом деле

История — не формат тикета, а обещание разговора. Рон Джеффрис описывает её тремя «C» (Ron Jeffries, «Essential XP: Card, Conversation, Confirmation»):

  • Card — короткая запись-напоминание, помещающаяся на карточку;
  • Conversation — разговор команды, где вскрывается контекст;
  • Confirmation — критерии приёмки, фиксирующие итог разговора.

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

Канонический шаблон:

Как <роль>, я хочу <действие>, чтобы <ценность>.

Роль отвечает «для кого», действие — «что», ценность — «зачем». Ценность — самая важная и чаще всего профанируемая часть: «чтобы я мог нажать кнопку экспорта» — это не ценность, а тавтология. «Чтобы отправить бухгалтеру данные в формате, который принимает 1С» — ценность, и из неё сразу следуют требования к формату.

5.2. Job story: когда роль мешает

Альтернатива от Intercom (Alan Klement, «Replacing the User Story with the Job Story») убирает персону и добавляет ситуацию — это часто сильнее, потому что поведение определяется контекстом, а не демографией:

Когда <ситуация>, я хочу <мотивация>, чтобы <ожидаемый результат>.

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

Практическое правило: user story — когда важна роль и права доступа; job story — когда важен контекст срабатывания. Связь с JTBD разобрана в https://courses.digitable.life/post/product-management/01-discovery-and-research/.

5.3. INVEST: критерии годной истории

Билл Уэйк (INVEST in Good Stories):

Буква Свойство Проверка
Independent независима можно ли выпустить её, не выпуская соседнюю?
Negotiable обсуждаема оставлено ли пространство для решения инженерами?
Valuable ценна смогу ли объяснить пользователю выгоду одной фразой?
Estimable оценима команда понимает объём хотя бы с точностью до порядка?
Small мала помещается в спринт с запасом (обычно ≤ 3–5 дней)?
Testable проверяема можно ли написать критерий «сделано»?

Estimable и Small связаны: неоценимая история почти всегда велика. Если команда не может оценить — это сигнал не «дожать оценку», а провести спайк: ограниченное по времени исследование («2 дня, ответ на вопрос: выдержит ли наш парсер XLSX с формулами»).

5.4. Критерии приёмки: два формата

Формат 1 — список правил. Хорош для правил вычислений и валидации:

История: Импорт банковской выписки

Критерии приёмки:
1. Поддерживаются форматы CSV (UTF-8, Windows-1251) и OFX.
2. Файл больше 20 МБ отклоняется с сообщением о лимите и подсказкой разбить по месяцам.
3. Строки с некорректной датой не блокируют импорт: они попадают в отчёт об ошибках,
   остальные импортируются.
4. Повторный импорт того же файла не создаёт дублей: ключ дедупликации —
   (номер счёта, дата, сумма, назначение).
5. Отчёт об ошибках доступен для скачивания в течение 7 дней.

Формат 2 — сценарии (Gherkin). Хорош, когда поведение зависит от состояния и когда критерии должны стать автотестами:

# language: ru
Функция: Дедупликация при импорте выписки

  Предыстория:
    Допустим пользователь "buh@example.com" авторизован
    И в счёт "40702810" уже импортирована выписка за март

  Сценарий: Повторная загрузка того же файла не создаёт дублей
    Когда пользователь загружает файл "march.csv" в счёт "40702810"
    Тогда количество транзакций за март не изменяется
    И показано сообщение "Все 412 операций уже были загружены ранее"

  Сценарий: Частичное пересечение периодов
    Допустим файл "feb-mar.csv" содержит 200 февральских и 412 мартовских операций
    Когда пользователь загружает файл "feb-mar.csv" в счёт "40702810"
    Тогда импортировано 200 новых операций
    И пропущено 412 операций как дубликаты
    И отчёт об импорте содержит строку "пропущено дубликатов: 412"

  Структура сценария: Некорректные строки не блокируют импорт
    Когда пользователь загружает файл с <плохих> некорректными строками из <всего>
    Тогда импортировано <ожидается> операций
    И отчёт об ошибках содержит <плохих> строк

    Примеры:
      | всего | плохих | ожидается |
      |   100 |      0 |       100 |
      |   100 |      5 |        95 |
      |   100 |    100 |         0 |

Тот же сценарий, исполняемый как тест (Python, behave/pytest-bdd):

# steps/import_steps.py — шаги, связывающие текст критериев с системой
from behave import given, when, then


@given('в счёт "{account}" уже импортирована выписка за март')
def step_preloaded(context, account: str) -> None:
    context.result = context.api.import_statement(account, fixture("march.csv"))
    assert context.result.imported == 412


@when('пользователь загружает файл "{filename}" в счёт "{account}"')
def step_upload(context, filename: str, account: str) -> None:
    context.result = context.api.import_statement(account, fixture(filename))


@then("импортировано {expected:d} новых операций")
def step_check_imported(context, expected: int) -> None:
    assert context.result.imported == expected, (
        f"ожидали {expected}, получили {context.result.imported}"
    )

Trade-off. Gherkin даёт живую документацию и защиту от регрессий, но стоит дорого: слой шагов нужно поддерживать, а плохо написанные сценарии («когда я нажимаю кнопку с id=submit») превращаются в хрупкие UI-тесты. Разумный компромисс, который держится в проде: Gherkin — только для бизнес-правил с высокой ценой ошибки (деньги, права доступа, расчёты), списки правил — для всего остального.

Про дедупликацию, кстати, стоит помнить о вычислительной стороне: наивная проверка «каждая новая строка против всех существующих» — это O(n·m) сравнений; правильная реализация строит хеш-множество ключей уже загруженных операций за период — O(n + m) времени и O(m) памяти. Такие вещи полезно фиксировать в критериях приёмки через НФТ: «импорт 100 000 строк ≤ 30 с».


6. Нарезка историй

6.1. Вертикально, а не горизонтально

Главное правило: история — сквозной срез через все слои системы, дающий пользователю работающий сценарий, пусть и узкий. «Сделать схему БД» — не история, потому что пользователь не может ничего с ней сделать и мы не узнаем ничего нового.

Горизонтальная и вертикальная нарезка пользовательских историй

Экономическая суть вертикальной нарезки — опционы. Каждый выпущенный срез даёт информацию и право (но не обязанность) продолжать. Горизонтальная нарезка лишает вас этого права: пока не сделаны все слои, ценность равна нулю, и вся стоимость уже потрачена.

6.2. SPIDR: пять надёжных способов разрезать

Майк Кон (Mike Cohn, SPIDR):

  • S — Spike. Не режется, потому что непонятна → выделить исследование, затем резать.
  • P — Path. Разные пути через сценарий: оплата картой / по счёту / из баланса.
  • I — Interface. Разные интерфейсы или платформы: сначала веб, потом мобильный; сначала одна валидация, потом продвинутая форма; сначала CSV, потом визуальный маппинг.
  • D — Data. Подмножество данных: сначала одна валюта, один банк, одна схема файла.
  • R — Rules. Ослабленные правила: сначала без учёта частичных возвратов и мультивалютности.

Дополнительные проверенные приёмы: разделить «happy path» и обработку ошибок (сначала первое), отделить ручное от автоматического (сначала оператор делает руками — это ещё и валидация спроса), отделить создание от редактирования и удаления.

6.3. Story mapping: от плоского бэклога к карте

Джефф Паттон («User Story Mapping») решает главную проблему бэклога: плоский список теряет последовательность и целостность. Карта историй возвращает двумерность.

Ключевое понятие — walking skeleton: тончайший сквозной срез через весь хребет, который уже позволяет человеку пройти сценарий от начала до конца. Он и есть кандидат в MVP (подробнее — https://courses.digitable.life/post/product-management/05-mvp-and-experiments/). Карта делает очевидным то, что плоский список скрывает: если из среза 1 выпал шаг «поделиться», сценарий не замыкается — пользователь построит отчёт и упрётся в тупик.

6.4. Жизненный цикл истории: DoR и DoD

Два состояния, которые обычно забывают:

  • Measured. Без него «Done» означает «код в проде», а не «проблема решена». Именно здесь замыкается петля с https://courses.digitable.life/post/product-management/08-analytics-and-decisions/.
  • Возврат из InProgress в Shaped. Разработчик, наткнувшийся на неучтённый случай, должен иметь право вернуть историю, а не выдумывать поведение самостоятельно. Отсутствие этого перехода — главная причина фраз «сделали не то, что просили».

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


7. Трассируемость: от боли до релиза

Когда продукт вырастает, возникает вопрос «почему мы это сделали?» — и на него нужно уметь ответить через год. Решение — модель трассируемости: явные связи между артефактами.

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


8. Проверка: прототипы и юзабилити-тесты

8.1. Выбор верности прототипа

Правило выбора: берите самый дешёвый инструмент, дающий сигнал нужного типа. Для проверки понятности хватает кликабельного макета. Для проверки спроса макет бесполезен — нужна фейковая дверь или concierge, потому что слова о намерении систематически расходятся с поведением (подробнее — https://courses.digitable.life/post/product-management/05-mvp-and-experiments/).

8.2. Сколько пользователей нужно

Знаменитое «пяти достаточно» (Nielsen, «Why You Only Need to Test with 5 Users») — не догма, а следствие простой вероятностной модели. Если каждая проблема независимо обнаруживается одним участником с вероятностью p, то доля найденных проблем при n участниках:

found(n) = 1 − (1 − p)^n
"""Планирование юзабилити-теста: сколько участников нужно.

Сложность: O(1) на расчёт — это замкнутая формула, а не симуляция.
"""
from math import ceil, log


def discovered_share(n: int, p: float = 0.31) -> float:
    """Доля проблем, найденных при n участниках.

    p = 0.31 — медианная оценка Нильсена и Ландауэра для типичных интерфейсов.
    Для узкоспециализированных продуктов p ниже (0.10–0.20): сценарии разнообразнее,
    и один участник задевает меньшую часть пространства проблем.
    """
    if not 0 < p < 1:
        raise ValueError("p должно быть в интервале (0, 1)")
    return 1 - (1 - p) ** n


def participants_needed(target: float, p: float = 0.31) -> int:
    """Минимальное n, при котором ожидаемая доля найденных проблем ≥ target."""
    if not 0 < target < 1:
        raise ValueError("target должно быть в интервале (0, 1)")
    return ceil(log(1 - target) / log(1 - p))


if __name__ == "__main__":
    for n in (1, 3, 5, 8, 12, 15):
        print(f"n={n:2d}: p=0.31 → {discovered_share(n):.0%}   "
              f"p=0.15 → {discovered_share(n, 0.15):.0%}")

    print("\nдля 85% проблем нужно:",
          participants_needed(0.85), "участников при p=0.31;",
          participants_needed(0.85, 0.15), "— при p=0.15")

Вывод:

n= 1: p=0.31 → 31%   p=0.15 → 15%
n= 3: p=0.31 → 67%   p=0.15 → 39%
n= 5: p=0.31 → 84%   p=0.15 → 56%
n= 8: p=0.31 → 95%   p=0.15 → 73%
n=12: p=0.31 → 99%   p=0.15 → 86%
n=15: p=0.31 → 100%  p=0.15 → 91%

для 85% проблем нужно: 6 участников при p=0.31; 12 — при p=0.15

Три оговорки, без которых правило вредно:

  1. Работает только для однородной аудитории и одного сценария. Три сегмента — умножайте на три (или тестируйте по 3–4 на сегмент).
  2. Работает для поиска проблем юзабилити, а не для измерения. Чтобы утверждать «конверсия выросла», нужны сотни и тысячи наблюдений и A/B-тест.
  3. Лучше 3 итерации по 5 человек, чем один тест на 15. Первые 5 находят самые грубые проблемы, которые маскируют остальные; после исправления вторые 5 видят следующий слой.

8.3. Что мерить в тесте

Мнения («вам нравится?») почти бесполезны: люди вежливы и склонны к постфактум-рационализации. Мерить нужно поведение при выполнении задач:

  • Task success rate — доля участников, дошедших до цели без помощи. Главная метрика.
  • Time on task — медиана и p90, а не среднее (распределение тяжелохвостое).
  • Число ошибок и точки, где человек застревал.
  • Уровень запрошенной помощи — 0 (сам), 1 (подсказка), 2 (показали).

Стандартизированные опросники применимы, но после задач, а не вместо них: SUS (10 вопросов, шкала 0–100) и более короткий UMUX-Lite.

"""Расчёт SUS (System Usability Scale) с доверительным интервалом.

Сложность: O(n·k) по времени (n участников × 10 вопросов), O(n) по памяти.
"""
from statistics import mean, stdev
from math import sqrt


def sus_score(answers: list[int]) -> float:
    """Балл SUS одного участника: 10 ответов по шкале 1..5 → значение 0..100.

    Нечётные вопросы сформулированы позитивно (вклад = ответ − 1),
    чётные — негативно (вклад = 5 − ответ). Сумма вкладов × 2.5.
    Чередование намеренное: оно ломает автоматическое проставление одной колонки.
    """
    if len(answers) != 10 or not all(1 <= a <= 5 for a in answers):
        raise ValueError("нужно ровно 10 ответов в диапазоне 1..5")
    positive = sum(answers[i] - 1 for i in range(0, 10, 2))
    negative = sum(5 - answers[i] for i in range(1, 10, 2))
    return (positive + negative) * 2.5


def grade(score: float) -> str:
    """Перцентиль по нормативной базе Sauro & Lewis (~5000 тестов).

    Важно: 68 — это средний по индустрии, а не «удовлетворительно» по школьной шкале.
    Балл 70 означает лишь «немного лучше среднего продукта», а не «хорошо».
    """
    if score >= 80.3:
        return "A — верхние ~10%, вероятны рекомендации"
    if score >= 68:
        return "B/C — около среднего по индустрии"
    return "D/F — заметно ниже среднего, есть системные проблемы"


def sus_summary(responses: list[list[int]], z: float = 1.96) -> dict:
    """Средний SUS по выборке и 95% доверительный интервал (нормальное приближение)."""
    scores = [sus_score(r) for r in responses]
    m = mean(scores)
    half = z * stdev(scores) / sqrt(len(scores)) if len(scores) > 1 else None
    return {
        "n": len(scores),
        "mean": m,
        "ci": (m - half, m + half) if half else None,
        "grade": grade(m),
    }


if __name__ == "__main__":
    sample = [
        [4, 2, 5, 2, 4, 1, 5, 2, 4, 2],
        [3, 3, 4, 2, 4, 2, 4, 3, 3, 2],
        [5, 1, 5, 1, 5, 1, 5, 1, 5, 1],
        [2, 4, 3, 4, 2, 4, 3, 3, 2, 4],
        [4, 2, 4, 2, 4, 2, 5, 2, 4, 3],
    ]
    print(sus_summary(sample))

На этой выборке результат — среднее 71.0 при 95% ДИ примерно (49, 93). Практический вывод: при n = 5 интервал занимает почти половину шкалы, то есть число «71» не говорит практически ничего — продукт с равным основанием может быть и заметно ниже среднего, и в верхних процентах. Используйте SUS для сравнения версий на выборках от 20 человек и для отслеживания динамики, а не для абсолютных заявлений на маленьком тесте.


9. Измерение UX в проде: HEART

Юзабилити-тест ловит проблемы на пяти людях в лаборатории. В проде нужен непрерывный сигнал. Фреймворк HEART от Google Research (Rodden, Hutchinson, Fu, «Measuring the User Experience on a Large Scale») даёт пять категорий и метод вывода метрик:

Категория Что означает Пример метрики
Happiness субъективная удовлетворённость CSAT после сценария, SUS, доля жалоб
Engagement глубина вовлечения отчётов на активного пользователя в неделю
Adoption освоение новой функции доля активных, впервые применивших фичу за 30 дней
Retention возвраты W4-retention когорты, отток по фиче
Task success успешность задачи доля успешных импортов, медианное время до отчёта

Механика вывода метрики — Goal → Signal → Metric: сначала цель («аналитик получает первый отчёт без обращения в поддержку»), затем наблюдаемый сигнал («сессия завершилась событием report_created без support_ticket_created»), только потом формула. Обратный порядок («у нас есть события, посчитаем что-нибудь») даёт метрики, которые невозможно интерпретировать.

Task success меряется из событийного лога:

-- Успешность сценария «первый отчёт» и время до него по когортам недели регистрации.
-- Сложность: O(n log n) — доминирует сортировка при оконных функциях и перцентилях.
WITH first_attempt AS (
    SELECT
        user_id,
        DATE_TRUNC('week', MIN(occurred_at)) AS signup_week,
        MIN(occurred_at)                     AS started_at
    FROM events
    WHERE event_name = 'import_started'
    GROUP BY user_id
),
first_success AS (
    SELECT user_id, MIN(occurred_at) AS succeeded_at
    FROM events
    WHERE event_name = 'report_created'
    GROUP BY user_id
),
asked_help AS (
    SELECT DISTINCT user_id
    FROM events
    WHERE event_name = 'support_ticket_created'
)
SELECT
    a.signup_week,
    COUNT(*)                                                   AS cohort_size,
    -- доля дошедших до отчёта в течение 7 дней
    AVG(CASE WHEN s.succeeded_at IS NOT NULL
              AND s.succeeded_at <= a.started_at + INTERVAL '7 days'
             THEN 1.0 ELSE 0.0 END)                            AS task_success_7d,
    -- «чистый» успех: без обращения в поддержку
    AVG(CASE WHEN s.succeeded_at IS NOT NULL AND h.user_id IS NULL
             THEN 1.0 ELSE 0.0 END)                            AS unassisted_success,
    -- время до ценности: медиана и хвост, среднее здесь бессмысленно
    PERCENTILE_CONT(0.5) WITHIN GROUP (
        ORDER BY EXTRACT(EPOCH FROM (s.succeeded_at - a.started_at)) / 3600
    )                                                          AS ttv_hours_p50,
    PERCENTILE_CONT(0.9) WITHIN GROUP (
        ORDER BY EXTRACT(EPOCH FROM (s.succeeded_at - a.started_at)) / 3600
    )                                                          AS ttv_hours_p90
FROM first_attempt a
LEFT JOIN first_success s ON s.user_id = a.user_id
LEFT JOIN asked_help    h ON h.user_id = a.user_id
GROUP BY a.signup_week
ORDER BY a.signup_week;

Две детали, отличающие рабочий запрос от учебного: unassisted_success (успех с помощью поддержки — это не успех продукта, а его субсидирование людьми) и перцентили вместо среднего (время до ценности всегда распределено с тяжёлым хвостом, среднее в нём смещено выбросами).


10. Доступность как требование, а не как благотворительность

Доступность (a11y) — это НФТ, у которого есть измеримый критерий, юридические последствия в ряде юрисдикций и заметный побочный эффект: интерфейс становится удобнее для всех. Базис — принципы POUR из WCAG 2.2: воспринимаемость, управляемость, понятность, надёжность.

Минимум, который продакт обязан внести в критерии приёмки:

  1. Клавиатура. Весь критический сценарий проходится без мыши, фокус виден.
  2. Контраст. ≥ 4.5:1 для обычного текста, ≥ 3:1 для крупного (≥ 18.66px bold / 24px).
  3. Альтернативный текст для несущих смысл изображений; декоративные — скрыты от скринридера.
  4. Формы. У каждого поля есть программно связанная подпись; ошибка сообщается текстом, а не только красной рамкой.
  5. Не только цвет. Статус передаётся ещё и формой/иконкой/текстом (8% мужчин имеют нарушение цветового зрения).

Контраст считается по формуле относительной яркости WCAG — это O(1) и легко ставится в CI:

"""Проверка контраста по WCAG 2.x. O(1) по времени и памяти."""


def _channel(c: int) -> float:
    """Линеаризация канала sRGB (обратная гамма-коррекция)."""
    s = c / 255
    return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4


def relative_luminance(rgb: tuple[int, int, int]) -> float:
    """Относительная яркость: веса отражают чувствительность глаза к каналам."""
    r, g, b = (_channel(c) for c in rgb)
    return 0.2126 * r + 0.7152 * g + 0.0722 * b


def contrast_ratio(fg: tuple[int, int, int], bg: tuple[int, int, int]) -> float:
    """Отношение контраста в диапазоне от 1:1 (одинаковые) до 21:1 (чёрный/белый)."""
    l1, l2 = relative_luminance(fg), relative_luminance(bg)
    lighter, darker = max(l1, l2), min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)


def wcag_verdict(fg, bg, *, large_text: bool = False) -> dict:
    ratio = contrast_ratio(fg, bg)
    aa = 3.0 if large_text else 4.5
    aaa = 4.5 if large_text else 7.0
    return {
        "ratio": round(ratio, 2),
        "AA": ratio >= aa,
        "AAA": ratio >= aaa,
        "hint": "ок" if ratio >= aa else f"нужно ≥ {aa}:1 — затемните текст или осветлите фон",
    }


if __name__ == "__main__":
    print(wcag_verdict((119, 119, 119), (255, 255, 255)))  # серый #777 на белом
    print(wcag_verdict((153, 153, 153), (255, 255, 255)))  # #999 на белом — провал AA
    print(wcag_verdict((255, 255, 255), (0, 122, 204)))    # белый на синем

Результат:

{'ratio': 4.48, 'AA': False, 'AAA': False, 'hint': 'нужно ≥ 4.5:1 — затемните текст или осветлите фон'}
{'ratio': 2.85, 'AA': False, 'AAA': False, 'hint': 'нужно ≥ 4.5:1 — затемните текст или осветлите фон'}
{'ratio': 4.51, 'AA': True, 'AAA': False, 'hint': 'ок'}

Показательно: #777 на белом даёт 4.48 — провал AA на 0.02, а белый на #007ACC даёт 4.51 — проход с запасом в 0.01. Оба значения на глаз неразличимы, и таких «на границе» в реальных палитрах десятки. Именно поэтому проверку надо автоматизировать (axe-core, pa11y в CI), а не полагаться на глаз: автотесты ловят порядка 30–40% нарушений WCAG, остальное требует ручной проверки, но эти 30–40% — самые массовые.


11. Типичные ошибки

  1. Спека вместо разговора. Документ на 40 страниц, который никто не дочитал, но все формально согласовали. Симптом: вопросы разработчиков, ответы на которые есть в тексте.
  2. Решение вместо проблемы в истории. «Как пользователь, я хочу выпадающий список» — в истории должна быть задача, а не элемент управления. Иначе команда лишена права найти решение дешевле и лучше.
  3. Ценность-тавтология. «…чтобы я мог экспортировать» в истории про экспорт. Правило: если «чтобы» повторяет «я хочу», значит, вы не знаете, зачем это нужно.
  4. CJM как плакат. Красивая карта на стене, построенная на воркшопе без единого пользователя. Лечится маркировкой ячеек источниками.
  5. Одна карта для всех сегментов. Усреднённый путь несуществующего человека.
  6. Горизонтальная нарезка. Спринты «сделали бэкенд», «сделали фронт»; ценность и обратная связь только в конце.
  7. Критерии приёмки только для happy path. Ошибки, пустые состояния, лимиты, права доступа, поведение при частичном отказе — не описаны, значит, будут реализованы наугад.
  8. Забытые пустые и предельные состояния. Экран, отлично выглядящий с 5 записями и разваливающийся на 0 и на 10 000. Правило: у каждого экрана есть четыре состояния — пустое, загрузка, ошибка, переполнение.
  9. НФТ без чисел. «Быстро», «надёжно», «безопасно» — нечего проверять и нечего мониторить.
  10. Аналитика после релиза. События размечают через месяц, когда нужно отчитаться; измерить эффект уже нельзя.
  11. Тестирование мнений вместо задач. «Вам нравится макет?» вместо «покажите, как вы сделали бы X».
  12. Дизайнер как рисовальщик. Дизайнера подключают после того, как решение уже выбрано, — теряется вся сила профессии, остаётся оформление.
  13. Доступность «во второй фазе». Второй фазы не бывает; переделка семантики и контрастов позже стоит в разы дороже.
  14. Смешение уровней требований. Бизнес-цель, сценарий и деталь реализации в одном списке — невозможно приоритизировать и невозможно сократить объём.

12. Как это устроено в проде

  • Trio. Продакт, дизайнер и тимлид формулируют требования вместе, а не последовательно (product trio у Терезы Торрес). Дизайнер участвует в discovery, разработчик — в определении охвата; это дешевле, чем передавать артефакты через стену.
  • Источник истины разнесён. Проблема и метрика — в PRD; макеты — в Figma; поведение API — в OpenAPI-схеме; правила — в критериях приёмки и тестах. PRD ссылается, а не копирует.
  • Story-mapping-сессия перед крупной инициативой (2–3 часа) заменяет недели споров про объём: карта делает видимым, что можно выбросить, не сломав сценарий.
  • Пример-воркшоп (Example Mapping, 25 минут). Команда раскладывает историю на правила, примеры и вопросы, используя карточки четырёх цветов (Matt Wynne, Cucumber). Обнаруженные вопросы обычно экономят несколько дней переделок.
  • Флаги функциональности по умолчанию. Каждая заметная история выкатывается за флагом — это даёт постепенный раскат, быстрый откат и площадку для A/B-теста (https://courses.digitable.life/post/product-management/05-mvp-and-experiments/).
  • Регулярный «разбор сессий». Час в неделю всей командой на просмотр записей реальных сессий и свежих тикетов поддержки. Самый дешёвый из известных способов поддерживать контакт с реальностью.
  • Дизайн-система как требование. Компоненты с уже решёнными состояниями, контрастами и клавиатурной навигацией снимают целый класс требований с каждой истории. Экономия особенно заметна на фронтенде — см. https://courses.digitable.life/post/typescript/00-overview/.
  • Ревью формулировок. Полезная практика: продакт пишет критерии приёмки, тестировщик их ревьюит до начала разработки. Тестировщик профессионально ищет незакрытые случаи, и делает это до того, как код написан, а не после.

13. Мини-итог

  • UX — это весь опыт достижения цели, а не интерфейс; решения верхних уровней (стратегия, охват) определяют нижние, и ошибку наверху нельзя исправить внизу.
  • Непонятный интерфейс — это разрыв исполнения или разрыв оценки; локализация проблемы по циклу действия Нормана превращает «неудобно» в чинибельную задачу.
  • Сильнейший инструмент дизайна — ограничения: делать ошибку невозможной дешевле, чем её обрабатывать.
  • CJM объединяет качественные, количественные и операционные данные в одну картину и показывает провалы между каналами, которых не видит воронка; ячейка без источника данных — гипотеза, и это надо помечать.
  • Service blueprint нужен, когда опыт зависит от людей и процессов за линией видимости.
  • Требования живут на пяти уровнях; смешение уровней делает список неприоритизируемым. НФТ должны быть числом с условиями измерения, иначе их не существует.
  • User story — обещание разговора, а не формат тикета: карточка без разговора и критериев приёмки хуже, чем обычная спецификация.
  • INVEST и SPIDR дают проверяемый способ резать истории вертикально; вертикальный срез — это опцион: право продолжить, а не обязанность.
  • Story map возвращает бэклогу измерение последовательности и делает видимым walking skeleton.
  • Пяти пользователей достаточно для поиска проблем юзабилити при однородной аудитории, но не для измерений; лучше три итерации по пять, чем один тест на пятнадцать.
  • В проде UX измеряется непрерывно: HEART + Goal-Signal-Metric, с успехом задачи как главной метрикой и обязательным отделением «успеха без помощи поддержки».
  • Доступность — обычное НФТ с численным критерием и автоматической проверкой в CI.

Источники


Что дальше

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

Ценообразование, монетизация и рост

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов