В Яндексе я руковожу службой общих интерфейсов. О них и поговорим. О том, как трудно (но приходится) делать что-то для всех. Позволю себе аналогию: сидишь, пишешь код и захотел пить. Налил себе сразу три стакана из одной бутылки, даже от клавиатуры не отрываясь. А если, к примеру, бутылка оказалась пустой, можно не пить. И по стаканам не разливать. Но если кто-то другой попросит, то придётся идти, ставить чайник, спрашивать: чай или кофе, чёрный или с молоком, нужен ли сахар, — если нужен, то сколько, резать бутерброды… Это намного сложнее. А когда задача — разработать общие компоненты, всё становится совсем сложно. Но мы попробуем разобраться.
Яндекс — не просто большая компания. Это гигантская компания, состоящая из множества больших компаний. Когда мы хотим переиспользовать компоненты, важно понимать, сколько у нас сервисов, насколько они разные и сложные внутри.

Консистентность: плюсы и минусы
+ Пользовательский опыт
Пользователям удобно, когда одинаковые по семантике компоненты интерфейса выглядят и ведут себя одинаково на разных сервисах — не нужно переучиваться.
+ Экономия при разработке
Повторное использование — это экономия на дизайне, реализации и дальнейшей поддержке. Но, к сожалению, у него есть дополнительная цена.
- Время поставки до пользователей
В борьбе за консистентность на масштабе сотен сервисов один только этап сбора требований может затянуться так, что не все проекты доживут до его завершения.
- Гибкость
Приходится разбираться, как компоненты используются в других проектах, чтобы ничего не сломать и не добавить проблем коллегам. Это замедляет и осложняет работу.
- Продуктовые метрики
Бывает, что на части сервисов компонент работает, но если внедрить его в остальные, возникают проблемы. В отдельных случаях компонент может навредить продуктовым метрикам.
Получается, что единственный способ эффективно использовать консистентность в интерфейсах в большой компании — соблюдать баланс.
Часть проблем консистентности — это баги. Их находят, исправляют и делают так, чтобы одинаковые сущности вели себя одинаково.
Есть и обратная сторона, когда неконсистентность оставляют осознанно. Например, если требуется сделать что-то быстро или что-то уникальное. Могут быть и другие причины, но важно понимать — здесь отклонение от общего работает лучше, и мы готовы с этим жить.
Загадочная метрика консистентности
Ключевые сервисы Яндекса обвешаны метриками сверху донизу, но нам пока не удалось найти способ точно посчитать, в каких случаях стоит прикладывать усилия ради единообразия.
Тем не менее мы исправляем баги консистентности между сервисами. Каждый раз это интуитивное решение на основе опыта без математического подтверждения. Если у вас есть идеи, как это подтверждение получить — добро пожаловать в комментарии, обсудим.
С повторным использованием кода всё будто бы проще. Есть гигантское количество сервисов, бери и внедряй уже написанный код. Но на практике это не так просто.
Повторное использование кода
Проблемы с переиспользованием кода плюс-минус те же, что и с консистентностью. Разработка замедляется и становится дороже из-за долгого сбора требований.
Код из единой библиотеки иногда порождает недостаточно гибкие решения для пользователей, а попытка сбалансировать это, добавляя новые и новые фичи, приведёт вас в мир неэффективных и медленных приложений.
Возникают сложности с внедрением. Одно дело, когда команда пишет локальный код и сразу отправляет в продакшн. Совсем другое, когда есть общая библиотека: приходится писать универсальный код по особым правилам, выпускать версии, доставлять до проектов и прикручивать к бизнес-логике.
Когда потребуется обновление, могут появиться проблемы с обратной совместимостью. Поддержка должна репортить баги в общую библиотеку и следить, чтобы исправления ничего не ломали соседям. Может, писать каждый раз заново получится быстрее и выгоднее?
Метрика повторного использования
Давайте попробуем найти метрику для оценки успешности библиотеки с универсальными компонентами. Мы в работе используем разные критерии:
Количество скачиваний (как в 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-версии общих библиотек с компонентами и сра��у проверять, что тесты сервисов не ломаются. Так, ещё на этапе пул-реквеста будет понятно, что изменения стабильны и ничего не затронули. Или наоборот.
Авторелизы
Доставлять изменения в сервисы нужно быстро и дёшево. Без автоматизации править версию во всех зависимостях было бы затруднительно.
Умные чейнджлоги
Пользователи могут сидеть на разных версиях, поэтому чейнджлоги выдают срез от текущей до той, что прилетела в пул-реквест. Это необходимо, чтобы тестировщики проверяли только то, что поменялось в сервисе.
А как же технологии?
Проблема более низкого уровня — сочетаемость технологий. Почему бы не ввести технофашизм? Выбрать определённые технологии, а остальные запретить. Этот подход кажется логичным, но приведёт к тому, что сервисы перестанут развиваться, не имея свободы пробовать новое.
Мы пришли к концепции школ разработки — островков с понятными разрешёнными технологиями в отдельных командах или объединениях команд, которые живут по прозрачным правилам. Переиспользование внутри одной школы становится простым и дешёвым.

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