Общие библиотеки: когда выносить код, а когда копировать
Общие библиотеки могут экономить время или тормозить команды. Узнайте, когда выносить код в библиотеку, когда лучше копировать, и кто должен отвечать за обновления.

Почему общий код превращается в трения между командами
Код может выглядеть как общий, прежде чем команды вокруг него начнут работать одинаково. Одна продуктовая команда хочет выпустить во вторник. Другая ждёт большого релиза. Третья больше заботится о стабильности. Код оказывается в центре — и небольшое изменение превращается в разговор, а не в коммит.
Тогда общие библиотеки начинают мешать. Небольшая правка кнопки, исправление логирования или новая конфигурация кажутся мелочью. На практике кто-то спрашивает, кто ещё использует этот код, кто будет тестировать, безопасно ли повышать версию и стоит ли ждать следующего релиза. Десять минут кодинга превращаются в неделю переписок.
Большая часть боли — во времени. Продуктовая работа следует дедлайнам клиентов, баг-репортам и договорённостям с продажами. Общий код следует правилам совместимости, повышений версий и окон для апгрейдов. Эти часы редко совпадают. Когда они расходятся, одна команда ждёт или выпустит обходное решение.
Контроль важен не меньше. Команды работают быстрее, когда могут открыть файл, исправить и задеплоить. Они тормозят, если код принадлежит другой группе с другими приоритетами. После пары раундов ожидания в чужом бэклоге люди перестают трогать общий код, если только у них нет выбора.
Шаблон легко распознать. Команды копируют старые версии в свои репозитории и патчат локально. Никто не хочет апгрейдиться, боясь сломать чужую работу. Небольшие различия превращаются в споры о том, чей кейс должен определять дизайн. Вскоре люди строят вокруг библиотеки, а не проходят через неё.
В этот момент код уже не полезно «общий». Это просто зависимость, которую команды терпят. Такое случается и в стартапах, и в больших компаниях. Проблема с кодом часто невелика — проблема владения делает её дорогой.
Что должно оставаться внутри одного продукта
Некоторый код кажется переиспользуемым с того момента, как его замечает вторая команда. Это часто слишком рано. Если код всё ещё меняется под рабочие процессы одного продукта, оставьте его внутри этого продукта.
UI-код — место, где команды чаще всего ошибаются. Форма оплаты, админская таблица или онбординг могут выглядеть похожими, но мелкие различия быстро складываются. Одна команда хочет три поля, другая — пять, и скоро каждая правка дизайна превращается в переговоры.
Правила, которые меняются каждую итерацию, тоже лучше держать локально. Логика ценообразования, шаги согласования, лимиты триалов и крайние случаи сдвигаются по мере того, как команды узнают потребности пользователей. Если вы перенесёте такие правила в общую библиотеку слишком рано, простые продуктовые изменения начнут зависеть от расписания релизов и приоритетов других команд.
Код стоит держать локально, когда только одна команда действительно его понимает. Это не критика — иногда одна группа писала фичу в режиме дедлайна, знает все странные случаи и может изменить её безопасно за час. Переносить такой код в общую упаковку до тех пор, пока другие команды не смогут его поддерживать, — значит распространять путаницу.
Ранние эксперименты тоже плохо подходят для переиспользования. Если команда может удалить фичу через месяц, не стоит сегодня вылизывать её в пакет. Скопировать 100 строк часто дешевле, чем делать пакет, писать документацию, добавлять тесты и поддерживать апгрейды для функции, которая может исчезнуть.
Копирование иногда чистее
Копирование звучит грязно, но иногда это честный выбор. Две команды могут начать с одной базы, пойти разными путями и понять, что действительно остаётся стабильным.
Это часто встречается в стартапах. Одна команда тестирует workflow для продаж, другая — self-serve версию. Экранов сначала много общего, но через несколько недель отличия важнее общего начала.
Простое правило: оставляйте код внутри продукта, если его форма зависит от ритма одной команды, языка или ежедневных решений. Выносите позже, когда шершавости перестанут двигаться. Так вы будете выпускать быстрее и не будете превращать мелкие правки в межкомандные процессы.
Что принадлежит общей библиотеке
Код заслуживает быть общим, когда более одной команды зависят от точного одинакового результата. Лучший кандидат — не код, который просто выглядит похожим, а код, который должен вести себя одинаково во всех приложениях.
Повторяющаяся ошибка — один из самых явных сигналов. Если несколько продуктов постоянно фиксируют ту же проблему с разбором дат, проверкой прав или подписыванием API-запросов, проблема уже не локальная. Отдельные копии означают, что каждая команда пропустит исправление в разное время.
Стабильные задачи тоже хорошо подходят для общих библиотек. Если правила меняются каждую итерацию, библиотека принесёт больше совещаний, чем пользы. Если же задача ясна и вряд ли изменится — например, валидация формата файла, применение единой политики безопасности или работа с одним внутренним сервисом — общий код может реально сэкономить усилия.
Настоящая проверка проста: нужны ли командам одинаковое поведение или сегодня у них просто похожий код? Похожий код часто расходится по уважительным причинам: одному продукту нужна более мягкая валидация, другой требует других сообщений об ошибках или политики повторных попыток. Если такие различия важны, держите код в каждом продукте.
Владение так же важно, как и сам код. Библиотеке нужна одна команда или один человек, который может тестировать, выпускать релизы, ревьюить изменения и отвечать на простой вопрос: «Можем ли мы поменять это, не сломав всех?» Без владельца общие библиотеки превращаются в груду полуподдерживаемых обещаний.
Хорошая библиотека обычно выполняет одну узкую задачу, исправляет одну и ту же проблему в нескольких приложениях, требует согласованного поведения и имеет чёткого владельца. Когда эти условия соблюдены, совместное использование кода снижает работу, а не распределяет её по нескольким командам.
Как решить за пять шагов
Короткий чеклист лучше длинных дебатов. Большинство плохих общих библиотек возникают, когда код выносят слишком рано, прежде чем команды договорятся, что именно одинаково и кто будет поддерживать библиотеку.
- Начните с того, где код живёт сейчас. Запишите репозиторий, пакет, сервис, тесты и продуктовые правила вокруг него. Если код уже зависит от продуктовых моделей, текстов UI или логики биллинга — считайте это предупреждением.
- Опишите точное поведение, которое нужно каждой команде. Будьте требовательны. «Почти одно и то же» обычно скрывает реальные различия в краевых случаях, сообщениях об ошибках, таймингах или правах доступа.
- Отделите стабильные части от подвижных. Разбор дат или узкий клиент API может быть стабильным месяцами. Правила ценообразования, шаги онбординга и sales-workflow — обычно нет.
- Выберите владельца до того, как что-то выносить. Одна команда или даже один человек должны утверждать изменения, ревьюить PR и решать, попадает ли запрос в библиотеку или остаётся локальным.
- Установите правила релизов и апгрейдов простым языком. Решите, как публиковать версии, что считать ломаюшим изменением, кто апгрейдит зависимые проекты и как долго старые версии остаются поддерживаемыми.
Небольшой пример проясняет выбор. Если два продукта отправляют почту, но один нужен только базовые шаблоны, а другому нужны региональные юридические тексты, кастомные правила повторных попыток и аудит-логи — пока копируйте код. Если же оба используют одного провайдера, одинаковую обработку ошибок и одинаковые тесты, имеет смысл вынести узкую часть.
Если вы застряли на шаге 2 или 4, не выносите код пока. Копируйте, двигайтесь дальше и пересмотрите через несколько недель реального использования.
Кто владеет библиотекой в повседневной жизни
У общей библиотеки должна быть одна команда-владелец. Не рабочая группа, не по очереди и не «все, кто её используют». Когда владение расплывчато, мелкие исправления остаются в чатах, пока кто-то не надоест и не сделает патч в обход.
Выберите команду, которая лучше всех знает код и кто страдает, когда он ломается. Эта команда принимает окончательные решения по дизайну, времени релизов и тому, что вообще должно быть в библиотеке. Другие команды могут давать обратную связь, но им не нужно согласовывать каждую строчку.
На практике команда-владелец ревьюит и утверждает изменения, выпускает релизы, отвечает на вопросы по использованию и планирует работу по поддержке в своём бэклоге. Последнее важнее, чем кажется: работа по поддержке — реальная работа. Если багфиксы, запросы на апгрейд и проверки совместимости живут вне бэклога владельца, никто не выделит на них время.
Сделайте путь запросов простым. Пользователи должны точно знать, куда идти за изменением. Единственная очередь задач, понятный шаблон и один контакт обычно достаточно. Избегайте приватных сообщений и договорённостей в обход — они кажутся быстрее для одной команды, но создают путаницу для всех остальных.
Утверждение должно оставаться за командой-владельцем, даже если код пишет другая команда. Это защищает библиотеку от разовых продуктовых нужд и поддерживает стабильность релизов, потому что одна команда видит полную картину: старых потребителей, стоимость апгрейда и долгосрочный формат API.
В малой компании настройка может быть простой: одна продуктовая команда владеет библиотекой, а CTO вмешивается только в случае конфликтов приоритетов. В повседневности именно команда-владелец двигает её вперёд.
Как справляться с апгрейдами, не блокируя работу
Продуктовые команды выпускают по своим дедлайнам. Команды библиотек — по другому ритму. Если расписания пересекаются, даже маленький апгрейд превращается в совещание, задержку или срочный патч.
Простые и предсказуемые правила релизов лучше хитрых схем. Чёткие номера версий решают большую часть проблем. Patch — фиксация, minor — безопасное добавление, major — ломающее и требует планирования. Люди должны понимать риск по номеру версии.
Случайные ломаюшие изменения создают основную фрикцию. Группируйте их в запланированные мажорные релизы несколько раз в год, вместо того чтобы вталкивать их в обычные обновления. Это даёт продуктовым командам время запланировать работу и протестировать всё вне недели запуска.
Старые версии нужно поддерживать в фиксированное окно. 6–12 месяцев работает для многих внутренних библиотек. В этот период владелец библиотеки фиксит серьёзные баги в предыдущей мажорной версии, а новые фичи идут в текущую.
Рутина релизов не требует церемоний. Используйте номера версий, которые отражают реальный риск. Публикуйте одну заметку к каждому релизу. Ставьте ломающее изменение вверху, кратко опишите шаги обновления и укажите дату окончания поддержки старой версии.
Заметка не обязана быть отшлифована: несколько простых предложений — что изменилось, кому это важно и что обновить. Если команда читает её за минуту, шансов, что она что-то сделает, больше.
Для напряжённых релизов нужен и план B. Если продуктовая команда подключает крупного клиента или выпускает большую фичу, разрешите им отложить апгрейд и догнать позже. Отложенный апгрейд обычно дешевле, чем форсить изменение в и без того напряжённый релиз.
Простой пример с двумя продуктовыми командами
Команда A делает форму регистрации для SaaS. Команда B — форму приёма клиентов для внутреннего инструмента. Обе формы запрашивают одно и то же: имя, email, телефон и данные компании. Сначала каждая команда пишет свою валидацию, потому что скопировать несколько проверок проще, чем настраивать общую библиотеку.
Этот выбор нормален на раннем этапе. Формы похожи, но выполняют разные задачи. Команда A хочет короткие дружелюбные ошибки для новых пользователей. Команда B нуждается в строгих правилах и дополнительных заметках для сотрудников. Даже порядок полей отличается из‑за потока продукта.
Проблемы начинаются, когда одна и та же ошибка появляется несколько раз. Одна команда поправляет валидацию email, чтобы убирать пробелы в конце. Другая забывает. Потом телефонные номера с кодами стран проходят в одном продукте и падают в другом. Теперь обе команды тратят время, сравнивая мелкие фиксы, вместо того чтобы двигаться дальше.
Не нужно выносить всю систему форм. Это заставит спорить о верстке, метках и каждом UX-решении. Вынесите только ту часть, которая действительно совпадает между продуктами: чистую логику валидации.
Общий код останется маленьким. Он будет убирать пробелы перед валидацией, проверять формат email, нормализовать телефоны, отвергать пустые обязательные поля и возвращать простые коды ошибок. Каждая команда по‑прежнему контролирует пользовательский слой. Команда A пишет текст ошибки «Пожалуйста, введите рабочий email». Команда B пишет «Email обязателен для создания аккаунта». Команда A сохраняет мобильную одну-колоночную верстку. Команда B — более плотную форму для внутренних сценариев.
Этот раскол работает, потому что владение остаётся ясным. Общий пакет владеет логикой, которая должна вести себя одинаково везде. Каждый продукт владеет видом формы и тем, как ошибки показаны пользователю.
Обычно это безопасная граница для общих библиотек: делитесь скучными правилами, которые не должны расходиться. Всё, что связано с поведением продукта, текстом или интерфейсом, держите в кодовой базе продукта.
Ошибки, которые усложняют повторное использование
Многое из неудач при повторном использовании происходит ещё до того, как библиотека начнёт приносить пользу. Команды часто выносят код, как только видят два похожих файла, но один кейс реальный, а другой — гипотеза. Получается пакет, сформированный предположениями, а не реальным использованием.
Похожий код не всегда идентичен. Два продукта могут отправлять счета, но одному нужны утверждения, налоговые правила или кастомные биллинг-циклы. Если вы принудительно объедините их рано, каждое изменение превратится в спор о флагах и исключениях.
Ещё одна ошибка — запихивать в один пакет несвязанные вещи. «Common» библиотека начинается с одного хелпера, а затем растёт до auth, UI, логирования и бизнес-правил. Сначала кажется аккуратно. Позже никто не уверен, что можно менять безопасно, и маленькое обновление рискует задеть команды, которые никогда не просили половину функционала.
Владение создаёт столько же проблем, сколько и дизайн. Когда у каждой команды есть право голоса по каждому изменению, прогресс замедляется до ползучего темпа. Небольшой фикс не должен требовать три совещания и обширного согласования. Одна команда должна управлять библиотекой в повседневности.
Апгрейды быстро подрывают доверие. Если команды обязаны переходить на самую новую версию сразу, они начнут избегать библиотеки, фиксировать старые версии или копировать код обратно в продукт. Активные проекты нуждаются в окне, чтобы закончить текущую работу, прежде чем брать на себя риск обновления.
Следите за признаками:
- код вынесли раньше, чем появилось два проверенных случая использования
- пакет смешивает несколько несвязанных задач
- каждое изменение нужно одобрять множеству людей
- давление релизов заставляет обновляться в худшие моменты
- код выглядит похожим, но каждый продукт всё ещё нуждается в своих правках
Хорошие общие библиотеки остаются узкими, скучными и имеют конкретного владельца. Если пакет требует постоянных переговоров, он, скорее всего, слишком широкий или создан слишком рано. Часто копировать 50 строк в два места дешевле, чем плохо шарить 500 строк.
Быстрая проверка перед тем, как делиться кодом
Вынести код в библиотеку — это красиво, но красиво не значит полезно. Пакет помогает, только если он убирает повторную работу без добавления совещаний, задержек релизов и драмы с апгрейдами.
Быстрый тест лучше длинного документа. Если ответ на любой из этих вопросов сомнителен, держите код внутри продукта:
- Решают ли две и более команды одну и ту же задачу почти одинаково, а не используют похожий код по совпадению?
- Может ли одна команда владеть релизами, багфиксами и поддержкой хотя бы ближайшие шесть месяцев?
- Если другая команда пропустит одну версию, сможет ли она всё ещё выпускать фичи без блокировки?
- Можно ли описать библиотеку одной простой фразой без длинных оговорок?
Последний пункт очень важен. Если описание превращается в «она обрабатывает auth, логирование, конфиг, кеширование и пару утилит», значит это, вероятно, не одна библиотека, а куча несвязанных вещей, которая запутает новые команды.
Владение — еще одна частая причина неудач. Общие библиотеки умирают, когда все могут менять их, а никто не обязан поддерживать. Одна команда должна решать релизы, ревьюить изменения и говорить «нет», если запрос помогает одному продукту, но ухудшает пакет для остальных.
Лёгко упустить свободу апгрейда. Библиотека не должна заставлять команды обновляться сразу, как только кому‑то нужен фикс. Если пропуск версии ломает нормальную работу, зависимость слишком жёсткая.
Небольшой пример: если два продукта нужны одинаковые хелперы форматирования дат, копирование может быть дешевле. Если же им нужны одинаковые правила биллинга, налоговые вычисления и аудит, тогда общая библиотека имеет смысл, потому что бизнес-правило действительно одно и то же.
Хорошие общие библиотеки имеют узкую задачу, ясного владельца и путь релизов, который не замедляет всех.
Что делать дальше
Начните с правила, которое кажется почти слишком простым: сначала копируйте, потом выносите. Зафиксируйте это в письменном виде. Когда две команды думают, что им нужен один и тот же код, пусть каждая выпускает с собственной копией, пока шаблон не станет стабильным. Похожего кода недостаточно: повторное использование имеет смысл, когда одинаковые правила, краевые случаи и ритм релизов повторяются в нескольких продуктах.
Затем пройдите по существующим общим библиотекам. У большинства команд уже есть пакеты, которые создают больше обсуждений, чем ускорения. Назначьте владельца для каждой библиотеки. Этот человек не обязан писать каждый патч, но должен решать по объёму, утверждать ломаюшие изменения и держать версионирование в порядке.
Короткая уборка часто помогает больше, чем ещё одна архитектурная дискуссия. Отметьте каждую библиотеку как «имеет владельца», «без владельца» или «готова к списанию». Удалите пакеты, которых команды избегают, форкают или о которых жалуются каждую итерацию. Разделите стабильный утилитарный код и продукт-специфичную логику. Напишите одно простое правило обновлений, которому команды смогут следовать без постоянного согласования.
Если никто не хочет владеть библиотекой, не делайте вид, что это общая инфраструктура — спишите её. Копия модуля внутри продукта чаще дешевле, чем общая зависимость, которой никто не доверяет и которую никто не обновляет.
Это решение тоже требует даты, а не только доброго намерения. Назначьте день в ближайшие две недели. Проведите 60 минут с техлидами: перечислите все общие пакеты, назначьте владельца и решите, какие оставить, какие разделить, а какие удалить. Эта одна встреча может убрать месяцы мелких трений.
Если нужен внешний обзор, Oleg Sotnikov на oleg.is делает такую работу Fractional CTO. Его подход практичен: решайте, кто за что отвечает, держите продуктовый код локальным, а общий код — там, где он действительно убирает повторную работу, а не добавляет координацию.
Часто задаваемые вопросы
Когда стоит копировать код вместо выноса в библиотеку?
Копируйте код, когда он всё ещё формируется под нужды одного продукта. Если логика меняется вместе с рабочими процессами, скоростью релизов или краевыми случаями одной команды — оставьте её локально на данный момент.
Небольшое копирование часто обходится дешевле, чем создание общей библиотеки, которая приносит задержки на ревью, работу с версиями и межкомандные споры.
Что является самым явным признаком того, что код должен быть в общей библиотеке?
Код стоит сделать общим, когда две или более команд требуют одинакового поведения, а не просто похожих файлов. Повторяющиеся исправления одной и той же ошибки, одинаковые правила валидации или единая логика работы с сервисом — хороший сигнал для общего решения.
Если же у каждой команды свои правила обработки ошибок или разные требования, держите код внутри продукта.
Сколько кейсов нужно, прежде чем выносить код?
Дождитесь как минимум двух проверенных случаев использования, которые оставались похожими в реальной работе. Один текущий кейс и один предполагаемый обычно приводят к библиотеке, пытающейся решить слишком много.
Дайте коду немного времени: если обе команды продолжают делать одни и те же правки, тогда вынос имеет смысл.
Какой код обычно должен оставаться в одном продукте?
Оставляйте в продукте UI-потоки, тексты для пользователей, правила ценообразования, шаги утверждения и быстро меняющуюся бизнес-логику. Эти части часто дрейфуют, потому что команды учатся у разных пользователей и работают по разным дедлайнам.
Локальный код позволяет команде быстро изменить и выпустить функциональность, не спрашивая разрешения у другой группы.
Кто должен владеть общей библиотекой?
Назначьте одну команду-владельца, а не комитет. Эта команда должна ревьюить изменения, выпускать релизы, отвечать на вопросы по использованию и решать, что действительно должно быть в библиотеке.
Выберите команду, которая лучше всех знает код и чьи пользователи страдают, когда что-то ломается — это ускоряет принятие решений и делает поддержку видимой.
Насколько маленькой должна быть общая библиотека?
Держите библиотеку узкой. Хорошая библиотека делает одну стабильную задачу, общую для разных продуктов — например, правила валидации, подпись API или небольшой клиент сервиса.
Как только пакет смешивает UI, логирование, auth и бизнес-логику, никто не уверен, что можно менять безопасно.
Как обрабатывать апгрейды, чтобы не блокировать продуктовую работу?
Используйте простые правила версий и придерживайтесь их. Patch — это исправление, minor — безопасное добавление, major — плановое ломающее изменение.
Поддерживайте старые мажорные версии в фиксированное окно (обычно 6–12 месяцев), чтобы продуктовые команды могли апгрейдить, когда у них есть окно в графике.
Стоит ли выносить UI-компоненты в общую библиотеку?
Большинству команд не стоит делиться UI-компонентами слишком рано. Похожие экраны часто требуют разных полей, верстки, текста и правил валидации уже через несколько недель.
Сначала поделитесь «скучной» логикой под UI — форматированием, сырой валидацией — а сам экран оставьте в кодовой базе продукта.
Что делать, если командам нужна одна и та же логика, но разный пользовательский опыт?
Разделяйте проблему правильно: поместите общую низкоуровневую логику в библиотеку, а текст, верстку и поток оставьте в каждом приложении.
Так команды получат одинаковые правила там, где это важно, но не будут вынуждены делать одинаковый UX.
Что делать с библиотекой, которую никто не хочет поддерживать?
Если никто не хочет поддерживать библиотеку — это тревожный знак. Ветшание или отказ от владения превращает пакет в источник задержек, локальных фиксов и устаревших версий.
В таком случае лучше вывести код обратно в продукты или официально списать библиотеку. Копия внутри продукта часто работает лучше, чем зависимость, которой никто не доверяет.