Очерки

Выращивание или проектирование через тестирование

Проектирование — деятельность многоплановая. Чем-то похожа на распутывание множества шнурков, хитро свившихся друг с другом. Пошаговое принятие решений с ясной конечной целью роднит эти два занятия. Мне всегда хотелось выявить понятный любому алгоритм проектирования.
Лучшая метафора неопределённости — лабиринт. Работа с неопределенностью можно рассматривать как поиск выхода из лабиринта. Здесь и цикличность (итерации). Фотография — Universal Eye

В поисках алгоритма для проектирования

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

Приём «Штука»

Артём Горбунов как-то поделился со мной приёмом «Штука». В годы работы в Студии он наблюдал как работал арт-директор Рома Воронежский. Ромочка любил приговаривать в процессе создания дизайна: «нам нужна такая штука». Артём интуитивно вынес из этого приём. Как он рассказывает сам, ему было важно ощущение, что штука эта волшебная — то есть она сказочным образом решает все проблемы, и при этом может принять любую форму. Это похоже на оператор «идеального конечного результата» в ТРИЗ, даёт волю гиперболизировать требуемые функции в пределах подвижости нашего воображения.
Но мне здесь важно другое. «Штука» — это в начале абстрактная вещь, которой пока нет, её нужно создать. Мы не знаем как она называется, как будет выглядеть и из чего состоять, но точно можем сформулировать зачем она нужна и что должна делать. Ударживая эти установки в голове как целевые, циклично добавляем что-то для решения или убираем лишнее и постепенно приближаемся к законченному решению. Такой способ похож на выращивание объекта, решающего задачу.
В общем случае любая «интерфейсная штука» делает всего две вещи:
  • показывает информацию: какие-то данные, какие действия с ней можно произвести; намекает на то как её тронуть, чтобы произошли действия;
  • даёт манипулировать собой, а тем самым влиять на что-то более существенное в виртуальном или реальном мире.
Применим приём «Штука» для создания интерфейса настройки оплаты двумя способами: подписка на период и немедленная оплата. Для начала нужно сформулировать, что штуковина для оплаты должна показывать и давать делать. У меня вышел такой список:
  1. Какие тарифы есть вообще?
  2. Какой тариф выбран сейчас, как его сменить?
  3. Какой способ оплаты выбран: немедленная или оплата по подписке?
  4. Текущее состояние оплаты: оплатился ли последний месяц, когда будет следующий автоматический платёж, если включена подписка.
  5. Как внести немедленную оплату?
  6. Как отказаться от продления подписки?
Сформулировав этот набор, мы можем приняться за «выращивание». Займёмся этим на примере последнего, 6-го требования из списка. Скомпонуем первый вариант формы. Я буду делать это в текстовом формате.
Что здесь произошло. Я ввёл сущность «Подписка», описал её состояние и прикрепил к ней манипулятор — кнопка «Отписаться». Пришлось зайти дальше и удовлетворить 4-му требования — описать последнюю операцию и рассказать о том, что за режим оплаты выбран. Я решил, что вся история платежей будет полезна, но будет размещена на отдельной странице, сейчас туда ведёт ссылка «История платежей».
Попробуем добавить сюда 5-й пункт требований «какой тариф выбран сейчас, как его сменить». Для начала просто помещу информацию о тарифе и способе его сменить в наш текстовый макет.
Форма обогатилась, но мне теперь не нравится насколько она усложнилась: кнопка смены тарифа затесалась в длинную «колбасу» с заголовком. Кроме прочего, перед глазами нет структуры тарифов, и многоточие на кнопке «Сменить» говорит о том, что нужно проектировать какое-то другое место — всплывающее окно или экран, в котором будет происходить смена тарифа. Слишком сложно. Поэтому я вновь пробую пересобрать макет.
Что произошло. Разместил здесь же другие тарифы, теперь никуда не нужно ходить для их обозрения и смены. Выбор текущего пока условно отмечен галочкой. В реальном интерфейсе скорее всего нужно будет сделать что-то посерьезнее, чтобы было очевидно, что пункты можно нажимать и что один из них всегда выбран. Кнопка «Отписаться» теперь прильнула ко всей конструкции «Подписка по тарифу», что более логично. Стало чуть лучше с точки зрения структуры и видимости. Теперь хочется внести информацию о разном уровне экономии в плашку каждого из тарифов.
Мы видим итеративный процесс в действии. Далее предстоит проделать то же самое с остальными пунктами списка требований. Вновь получающиеся блоки потребуют пересмотра. Какие-то несколько блоков сольются в один, чтобы убрать дублирование. Что-то придётся, наоборот, разделить, чтобы новые требования выполнялись.

TDD или test-driven design

Подход «сначала тесты», когда тестирование идёт не в конце, а до написания программы, был впервые предложен в концепции экстремального программирования в конце девяностых. Суть его заключается в том, что мы сначала пишем приёмочный тест — например, что сложение двух конкретных чисел 2 и 3 даёт на выходе 5, затем пишем код реализующий это. Затем добавляем новый тест, например, с числами −2 и 3, чтобы наш метод умел работать с отрицательными числами, и модифицируем код так, чтобы он удовлетворял обоим тестам. И так далее. Внесение каких-то тестов сделает код нерабочим. Это штатная ситуация — значит пришло время модифицировать программу.
Тот же метод прекрасно ложится в область проектирования. Формулируем набор приёмочных критериев до того как начинаем конструировать решение, начинаем конструирование и прилаживаем конструкцию до тех пор, пока она не удовлетворит всем критериям.
Разберём на примере. Будем проектировать механизм, который позволит сотрудникам отдела обрабатывать стопку задач. Мы знаем, что человечество уже изобрело очередь, как приём, и даже электронную её версию, но здесь мы пока не знаем подробностей того, как именно она должна быть организована, это и должно быть спроектировано.
Ниже я записываю критерии готовности для будущего дизайна. Обратите внимание, что все они сформулированы так, что на каждый из них можно быстро ответить «да» или «нет» — удовлетворили мы текущим решением конкретному критерию или нет.
Критерии готовности механизма распределения задач по сотрудникам:
  • Весь объём работ распредёлен между сотрудниками на смене. Задачи не назначаются тем, кто сейчас не работает с системой.
  • Обеспечена одновременная работ с очередью старых и новых входящих заявок. Работа с очередью не блокирует другую работу с таблицей документов.
  • Виден объём отдельных очередей по типам задач и общей очереди.
  • Определён непротиворечивый принцип приоритезации задач.
  • Приоритетные задачи берутся вперёд неприоритетных.
  • Задача изымается из очереди, если сделаны ключевые изменения по ней.
  • Откладываемые задачи попадают тому же исполнителю через некоторое время.
  • У сотрудника есть возврат к отложенным им задачам.
  • Есть возможность отказаться от задачи по причине некомпентности.
  • Очередь прощает ошибки при ложном сообщении сотрудника о готовности по задаче.
Теперь можно начать циклично «выращивать» конструкцию интерфейса по аналогии с тем, как мы выше действовали в приёме «штука».

Инвентаризация агрегатов

Когда у нас зафиксирован набор критериев готовности или есть набор пользовательских историй, собранных методом User Story Mapping, полезным бывает предварительно провести инвентаризацию, то есть опись. Такой процесс помогает мне предварительно сообразить из чего будет состоять конструкция интерфейса, обежать её мысленным взором.
Для инвентаризации мы проглядываем список критериев или пользовательских историй и вычленяем оттуда будущие агрегаты интерфейса. Причём именно агрегаты, а не элементы. Примерами агрегатов будут — обработчики очереди, «шапка», «подвал», мультизагрузчик файлов, насыщенная фильтрами таблица — то есть крупные смысловые наборы узлов конструкции интерфейса, объединенные одной функцией. Агрегаты всегда крупнее отдельных элементов интерфейса таких как поле ввода, кнопка или подпись. Размышлять на уровне агрегатов на этапе предварительного конструирования удобнее, чем на уровне элементов-атомов.
Пробежимся по набору критериев механизма распределения задач выше и вычленим будущие агрегаты. Нам точно понадобится способ сообщить системе, что человек «вышел на линию», то есть начал работать или ушёл с неё. Понадобятся визуализация объёма в стопках задач и механизм их приоритезации. Понадобятся навигация по очереди, по отложенным для сотрудника задачам, способ сказать, что это не моя задача. Согласно последнему критерию не обойтись без проверки на то, действительно ли задача, отправленная сотрудником как готовая, может считаться таковой. И так далее.
Порой инвентаризация зачерпывает не только интерфейсные агрегаты, но и внутренние механики — логику, которая в итоге спрячется под капотом системы. Всё это хорошие претенденты для размещения в тексте проектной документации. Ниже приведён один пример инвентаризации USM, другие примеры доступны в статье о вспомогательных инструментах для USM.
Пример инвентаризации набора пользовательских историй. Красный текст — результат фазы инвентаризации

Плюсы и минусы проектирования через тестирование

Подход я бы классифицировал как «эволюционный» и «из нуля в единицу». Последним термином я отмечаю ситуацию изобретения, когда до начала работ не существовало известного решения, а после появилось. Он хорошо срабатывает, когда в начале проектирования никто не знает каким именно должен быть конечный результат. Когда известного паттерна законченой формы ранее не существовало, или он нам неизвестен. В этом сильная стороная метода.
К плюсам метода я бы отнёс наличие ясного чёткого текста, который можно свалидировать с заказчиком и командой до того как вы нырнёте в процесс проектирования. Удобно сформулировать критерии, утвердить их и удалиться на некоторое время на фазу генерации решений. Нам останется только каждый раз проводить мысленный эксперимент, проверяя соответствие решения набору критериев. Так у нас растут шансы не улететь в космос на фазе генерации даже без частых синхронизаций. Полезно, если вам крайне необходимо быть изолированным какое-то время.
Когда у вас на руках именно перечень критериев, то вы обладаете структурой решения, а значит вам проще выбирать от чего стоит отказаться, если времени на реализацию начинает не хватать. Та же структура помогает контролировать достижение решением полноты.
К минусам метода я бы отнёс отсутствие проверки важности каждого из критериев в решении. В такой список легко затащить всё подряд, даже откровенные безделушки и чьи-то хотелки. Спасением является проверка ценностью. В каждом из критерием можно пойти дальше и после вопроса «чтобы что» записать причинно-следственную цепочку, ведущую до первой адекватной ценности.
Плюсы
  • Помогает согласовать курс на решение с другими людьми
  • Не требует представления о форме решения
  • Подходит для работы в квадрантах Complex и Complicated фреймворка Cynefin
  • Проще флексить — находить более дешевые в плане времени или денег решения
  • Проще проверять решение на полноту
Минусы
  • Требует проверки на адекватность приёмочных критериев, не даёт своих практик для этого, требуется применять другие

Материалы

  • Кент Бек. Экстремальное программирование: разработка через тестирование — Test-driven Development. — Питер, 2003. — 224 с.
  • The Cynefin Framework, the author video summary
Проектирование Интерфейс