Проектирование систем для небольших команд: сначала границы, потом абстракции
Небольшие команды часто переусложняют код, добавляя абстракции слишком рано. В статье предлагается фреймворк на основе границ и зависимостей, чтобы решить, когда абстрагироваться, а когда оставить всё просто.
Проектирование систем для небольших команд: сначала границы, потом абстракции
На прошлой неделе друг рассказал о своём AI-инструменте для письма. Сначала они использовали одну LLM (GPT-4), потом захотели добавить Claude и локальную модель. Команда спроектировала «универсальный адаптер моделей» — фабрика, стратегия, интерфейсы, нормализация параметров… Код писали три недели. В итоге три модели отличались только в двух местах: формат запроса и парсинг ответа. Три недели на абстракцию, которая не понадобилась.
Это не единичный случай. Самая распространённая ошибка небольших команд — слишком ранняя абстракция. Мы боимся «а вдруг потом придётся расширять» и вводим паттерны, слои, промежуточные middleware. Код становится тяжелее, каждое изменение затрагивает много файлов, онбординг замедляется.
Почему маленькие команды склонны к переусложнению?
Три причины:
- Ловушка опыта — Кто-то из большой компании приносит сложные паттерны (микросервисы, событийно-ориентированное, DDD) и применяет их к маленькому проекту без контекста.
- Страх будущего — Босс говорит «мы будем поддерживать десять моделей», и вы проектируете под десять, хотя в реальности за год используете только две.
- Инженерная гордость — Код, который не «элегантен», кажется неправильным. Лучше потратить лишнее время, чтобы диаграмма классов выглядела идеально.
Цена реальна: увеличивается когнитивная нагрузка, удлиняется отладка, растёт стоимость изменений. В маленькой команде один слой абстракции может превратить задачу на полдня в два дня.
Мой фреймворк: сначала границы, потом отношения
После нескольких продуктов я выработал простой метод — «карта границ и отношений». Суть: сначала определить, что действительно должно меняться независимо, а потом решить, как они связаны.
Шаги:
1. Перечислите кандидатов на изменения
Запишите, что может измениться:
- Формат запроса для разных AI-моделей
- Разные базы данных (MySQL vs PostgreSQL)
- Разные платёжные каналы (WeChat, Alipay)
- Разные темы UI
Включайте только те изменения, которые вы реально пережили или точно ожидаете в ближайшее время. Не включайте фантазии «а вдруг когда-нибудь…».
2. Оцените каждый кандидат по двум шкалам:
- Вероятность: Насколько вероятно изменение в ближайшие 6 месяцев. 0-10.
- Влияние: Сколько файлов/модулей придётся изменить, если это произойдёт? 0-10.
Пример: формат запроса AI-модели → Вероятность 8 (уже планируется вторая модель), Влияние 5 (затрагивает отправку, парсинг, обработку ошибок). Смена БД → Вероятность 1 (не планируется), Влияние 8.
3. Решите, нужна ли абстракция:
- Если вероятность низкая (<4), пишите жёстко, рефакторите, когда изменение реально произойдёт.
- Если вероятность высокая и влияние большое (>5), стоит построить слой абстракции.
- Если вероятность высокая, но влияние маленькое (например, меняется только одна функция), используйте простую обёртку — не нужен паттерн «фабрика».
Вернёмся к примеру с AI-моделями: Вероятность 8, но Влияние 5. Достаточно простого конфигурационного файла и switch-case (или словаря функций). Никакого адаптера.
4. Нарисуйте границы: определите, какой код меняется вместе
Используйте концепцию ограниченных контекстов (без полного DDD). Отметьте области, где код склонен меняться синхронно:
- Слой вызова моделей: конструирование запроса, парсинг ответа, повторные попытки
- Слой бизнес-логики: сборка промптов, обработка результатов, взаимодействие с пользователем
- Слой данных: сохранение, кэширование
Принцип: высокая связность внутри, низкая связанность снаружи. Если изменения в слое вызова моделей не затрагивают бизнес-логику, граница верна.
5. Нарисуйте отношения: определите направление зависимостей между границами
Ещё одна распространённая ошибка — хаотичное направление зависимостей. Например, бизнес-логика напрямую зависит от конкретных классов вызова моделей, и при смене модели её приходится менять. Правильный подход: бизнес-логика зависит только от простого интерфейса слоя вызова моделей, а не от конкретных реализаций. Но этот интерфейс должен предоставлять только те методы, которые реально нужны бизнес-логике, а не все возможные возможности модели.
Пример:
# Бизнес-логике нужно:
def generate_text(prompt: str, model_config: dict) -> str:
pass
А не:
# Переусложнено
class ModelInterface:
def generate(self, prompt: str, temperature: float, max_tokens: int, stop_sequences: list, ...):
pass
def stream_generate(self, ...):
pass
def count_tokens(self, ...):
pass
Абстрагируйте только то, что реально нужно бизнесу. Игнорируйте то, что может понадобиться потом.
Практический чек-лист
Перед каждым решением о проектировании пройдитесь по пунктам:
- Произойдёт ли это изменение в ближайшие 6 месяцев? (Если нет — не абстрагируйте сейчас)
- Если не абстрагировать, будет ли стоимость будущего рефакторинга приемлемой? (Если да — не абстрагируйте)
- Уменьшает ли абстракция общее количество строк кода? (Если увеличивает — пересмотрите)
- Вводит ли абстракция новые зависимости или сторонние библиотеки? (Если да — взвесьте затраты)
- Сможет ли каждый член команды понять эту абстракцию? (Если только один — это риск)
- Упрощает ли абстракция тестирование? (Если усложняет — откажитесь)
Когда это может не сработать
Этот фреймворк не идеален. Иногда ранняя абстракция может быть полезна:
- Команда опытна и может точно предсказать будущие изменения, экономя время на рефакторинг.
- Проекту нужен быстрый демо-показ с поддержкой нескольких моделей, и ранняя абстракция уменьшает авральные правки.
- Проектирование абстракции само по себе является обучением, помогающим команде лучше понять проблемную область.
Но для большинства небольших команд, особенно когда направление рынка неопределённо, простота — самый безопасный выбор. Когда я делал AI-продукты для письма в Chengdu Past Time, мы сначала поддерживали только одну модель. Когда пользователи попросили другие, мы внесли изменения — и объём работы оказался гораздо меньше ожидаемого.
Итог
Проектирование систем — это не нагромождение паттернов. Это управление неопределённостью. У маленьких команд ограниченные ресурсы, и их стоит тратить на основную бизнес-логику, а не на прокладывание путей для изменений, которые могут никогда не произойти. Сначала нарисуйте границы, чтобы понять, что меняется вместе, затем определите чистые направления зависимостей, и наконец введите минимальную абстракцию для решения реальной проблемы.
В следующий раз, когда будете проектировать новую фичу, спросите себя: «Если я не сделаю абстракцию сейчас, а изменение придёт позже, смогу ли я позволить себе рефакторинг?» Если ответ «да» — не абстрагируйте пока.
PaxLee