В Яндексе я руковожу службой общих интерфейсов. О них и поговорим. О том, как трудно (но приходится) делать что-то для всех. Позволю себе аналогию: сидишь, пишешь код и захотел пить. Налил себе сразу три стакана из одной бутылки, даже от клавиатуры не отрываясь. А если, к примеру, бутылка оказалась пустой, можно не пить. И по стаканам не разливать. Но если кто-то другой попросит, то придётся идти, ставить чайник, спрашивать: чай или кофе, чёрный или с молоком, нужен ли сахар, — если нужен, то сколько, резать бутерброды… Это намного сложнее. А когда задача — разработать общие компоненты, всё становится совсем сложно. Но мы попробуем разобраться.
Яндекс — не просто большая компания. Это гигантская компания, состоящая из множества больших компаний. Когда мы хотим переиспользовать компоненты, важно понимать, сколько у нас сервисов, насколько они разные и сложные внутри.
Консистентность: плюсы и минусы
+ Пользовательский опыт
Пользователям удобно, когда одинаковые по семантике компоненты интерфейса выглядят и ведут себя одинаково на разных сервисах — не нужно переучиваться.
+ Экономия при разработке
Повторное использование — это экономия на дизайне, реализации и дальнейшей поддержке. Но, к сожалению, у него есть дополнительная цена.
- Время поставки до пользователей
В борьбе за консистентность на масштабе сотен сервисов один только этап сбора требований может затянуться так, что не все проекты доживут до его завершения.
- Гибкость
Приходится разбираться, как компоненты используются в других проектах, чтобы ничего не сломать и не добавить проблем коллегам. Это замедляет и осложняет работу.
- Продуктовые метрики
Бывает, что на части сервисов компонент работает, но если внедрить его в остальные, возникают проблемы. В отдельных случаях компонент может навредить продуктовым метрикам.
Получается, что единственный способ эффективно использовать консистентность в интерфейсах в большой компании — соблюдать баланс.
Часть проблем консистентности — это баги. Их находят, исправляют и делают так, чтобы одинаковые сущности вели себя одинаково.
Есть и обратная сторона, когда неконсистентность оставляют осознанно. Например, если требуется сделать что-то быстро или что-то уникальное. Могут быть и другие причины, но важно понимать — здесь отклонение от общего работает лучше, и мы готовы с этим жить.
Загадочная метрика консистентности
Ключевые сервисы Яндекса обвешаны метриками сверху донизу, но нам пока не удалось найти способ точно посчитать, в каких случаях стоит прикладывать усилия ради единообразия.
Тем не менее мы исправляем баги консистентности между сервисами. Каждый раз это интуитивное решение на основе опыта без математического подтверждения. Если у вас есть идеи, как это подтверждение получить — добро пожаловать в комментарии, обсудим.
С повторным использованием кода всё будто бы проще. Есть гигантское количество сервисов, бери и внедряй уже написанный код. Но на практике это не так просто.
Повторное использование кода
Проблемы с переиспользованием кода плюс-минус те же, что и с консистентностью. Разработка замедляется и становится дороже из-за долгого сбора требований.
Код из единой библиотеки иногда порождает недостаточно гибкие решения для пользователей, а попытка сбалансировать это, добавляя новые и новые фичи, приведёт вас в мир неэффективных и медленных приложений.
Возникают сложности с внедрением. Одно дело, когда команда пишет локальный код и сразу отправляет в продакшн. Совсем другое, когда есть общая библиотека: приходится писать универсальный код по особым правилам, выпускать версии, доставлять до проектов и прикручивать к бизнес-логике.
Когда потребуется обновление, могут появиться проблемы с обратной совместимостью. Поддержка должна репортить баги в общую библиотеку и следить, чтобы исправления ничего не ломали соседям. Может, писать каждый раз заново получится быстрее и выгоднее?
Метрика повторного использования
Давайте попробуем найти метрику для оценки успешности библиотеки с универсальными компонентами. Мы в работе используем разные критерии:
Количество скачиваний (как в npm).
Посещаемость сайта с документацией — чем выше, тем активнее пользуются библиотекой.
Процент пользователей общей библиотеки от количества фронтендеров в компании.
Процент пользователей на upstream. Кто-то обновляется регулярно, а кто-то однажды подключил библиотеку и с тех пор не получает никаких обновлений.
Количество используемых компонентов из библиотеки. Некоторые разработчики берут только логотип, а другие — полный набор.
Количество показов конечным сервисам. Одно дело, когда компонент используется парой человек во внутренней админке, и совсем другое — в результатах поисковой выдачи для десятков миллионов пользователей в день.
Ускорение процесса разработки.
Применимость инфраструктуры общей библиотеки для нужд других команд.
Но даже получив данные по этим критериям, мы не сможем с уверенностью сказать, пора ли создавать единую дизайн-систему в компании.
Универсальный компонент vs. локальные фичи
Давайте конкретизируем вопрос: сейчас лучше написать общий компонент или вложиться в несколько локальных фич? Чтобы ответить на него, хватит одной формулы, которая показывает полезность разработки в деньгах, позволяет ранжировать задачи и распределять ресурсы.
Разберём формулу: умножаем количество внедрений (подразумевается количество сервисов, где будет использован компонент) на среднюю стоимость разработки того же компонента на любом из сервисов. Делим произведение на количество внедрений (совпадает с тем, что в числителе), умноженное на стоимость внедрения. Так мы учитываем разницу между стоимостью программирования кнопки и внедрением из библиотеки. Добавляем стоимость унификации — усилия на то, чтобы вместо конкретной кнопки получить общую:
время на написание кода,
создание и публикация документации и примеров,
обучение пользователей.
Попробуем проверить. Например, есть три проекта, которым нужен одинаковый компонент. Стоимость разработки — пять дней. Пусть стоимость внедрения будет два дня. При этом стоимость унификации в три раза выше, чем у конкретного решения в сервисе. C такими вводными делать общий компонент невыгодно (тут мы не учитываем профит от унификации дизайна).
Возможна ситуация, когда внедрение универсального компонента окажется дороже, чем стоимость разработки очередного велосипеда. Например, если технологические стеки библиотеки и проекта разные.
Давайте перейдём от абстрактных примеров к реальным компонентам, которые мы разрабатываем в opensource-пакете @yandex/ui.
Яндекс у многих ассоциируется с таким списком ссылок. Разумеется, эти ссылки используются и в других сервисах компании. Рассчитаем рентабельность разработки общего компонента для ссылки. Сейчас он внедрён в 104 сервиса. Средняя стоимость разработки такого компонента на отдельном сервисе с запасом — один день, стоимость внедрения — 2 часа или 0.25 от рабочего дня. Время унификации с учётом написания документации — 3 дня.
Видно, что сделать ссылки универсальными в 3.5 раза выгоднее, чем каждый раз писать с нуля. Хотя, согласитесь, интуитивно казалось, что повторное использование готового компонента на 104 проектах даст больше экономии. Рассмотрим более сложный компонент — Select.
Он состоит из кнопки, иконки, попапа и меню, обладает относительно сложной логикой и требует учесть множество нюансов, чтобы оставаться доступным на всех платформах.
И хотя он работает всего на 61 сервисе, видно, что выгода от повторного использования в 11.3 раз выше, чем если бы команды писали компонент самостоятельно. Выгода продолжит расти по мере того, как всё больше сервисов будут внедрять Select. Похоже, мы нашли правильный баланс.
Как же достичь баланса?
Единственно верного ответа нет. Однако есть решения, которые помогают нам на практике. Мы выделили разработчиков общих компонентов в отдельную команду. Разработчик конкретного сервиса вряд ли поставит в приоритет проблемы других команд. У продуктов разные цели, разный релизный цикл, разная браузерная поддержка и много чего ещё разного даже внутри одной компании.
Важно настроить взаимодействие с дизайнерами, потому что писать универсальный код, когда дизайнеры между собой не договорились, бесполезно — сэкономить не получится.
И, конечно, нужно вкладываться в унификацию — дорогое внедрение съедает профит. В эту же категорию попадает обратная совместимость. Если каждый апдейт требует реальных действий со стороны пользователя — выгода нивелируется.
Дизайн-система дизайн-систем
Яндекс оставляет сервисам собственные дизайн-системы, чтобы использовать кастомные решения, когда они помогают продуктовым метрикам. Всё это объединено в систему систем со слоями: первый подходит всем сервисам, дальше мы поднимаемся на следующие уровни, и с каждым следующим шагом базовая система уточняется. У нас есть что-то для всего Яндекса, для группы сервисов, для конкретного сервиса и финальные штрихи, применимые к отдельной странице на конкретной платформе.
Изменяя компонент на базовом слое, мы автоматически поставляем его в остальные слои. Именно для создания таких систем разработана методология БЭМ. Для реализации визуальной части есть Whitepaper. А недавно у нас появился themekit, который реализует наследование по слоям для дизайн-токенов.
Когда у каждого сервиса есть своя дизайн-система, приходится консолидировать многообразие. Поэтому мы используем общий storybook со всеми компонентами: их можно найти, посмотреть, и увидеть, что где-то решение уже есть. Если оно понадобится на других проектах, мы сделаем его общим.
Монорепозиторий
Вносить изменения, которые затронут множество сервисов, проще, если код лежит в едином репозитории. Но даже если нельзя физически положить код в одно место, реально написать инструменты, которые сделают внедрение сквозных изменений удобнее.
Canary
У нас есть возможность выпускать canary-версии общих библиотек с компонентами и сразу проверять, что тесты сервисов не ломаются. Так, ещё на этапе пул-реквеста будет понятно, что изменения стабильны и ничего не затронули. Или наоборот.
Авторелизы
Доставлять изменения в сервисы нужно быстро и дёшево. Без автоматизации править версию во всех зависимостях было бы затруднительно.
Умные чейнджлоги
Пользователи могут сидеть на разных версиях, поэтому чейнджлоги выдают срез от текущей до той, что прилетела в пул-реквест. Это необходимо, чтобы тестировщики проверяли только то, что поменялось в сервисе.
А как же технологии?
Проблема более низкого уровня — сочетаемость технологий. Почему бы не ввести технофашизм? Выбрать определённые технологии, а остальные запретить. Этот подход кажется логичным, но приведёт к тому, что сервисы перестанут развиваться, не имея свободы пробовать новое.
Мы пришли к концепции школ разработки — островков с понятными разрешёнными технологиями в отдельных командах или объединениях команд, которые живут по прозрачным правилам. Переиспользование внутри одной школы становится простым и дешёвым.
Выводы и ссылки
Польза от консистентности определённо есть, но мы пока не придумали, как её измерить. Польза от переиспользования кода тоже есть, и она измерима. Однако за ней стоят сложные процессы, которые требуют соблюдения баланса.
О разных подходах к общим компонентам я рассказываю уже давно, и если вам интересна эта тема — вот ссылки на другие материалы:
alexbaum
Спасибо за статью! Хорошая методология для принятия решения — нужна ли отдельная команда UI-kit в компании.
Интересно будет продолжение — методики снижения стоимости внедрения и особенно унификации.
tadatuta Автор
Методика подсказывает, что при наличии достаточного количества внедрений, оптимизировать стоимость унификации особого смысла нет. А вот на оптимизации внедрений действительно важно сосредоточиться.