Привет, Хабр! Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги.
Любой практикующий архитектор знает: техдолг неизбежен. Он бывает двух видов — глупый (когда срезали угол просто потому, что лень) и осознанный (когда срезали угол ради продвижения выживания продукта на рынке). Вторая категория — это тот самый «долг со знаком плюс». Он работает на вас ровно до тех пор, пока процентная ставка не превышает экономическую выгоду.
Проблема в том, что в большинстве компаний долг измеряют качественно («кажется, стало больнее») или вообще игнорируют до пожара. А нужно — количественно, с автоматическими порогами и архитектурными тестами, которые не дадут выродиться системе в «большой комок грязи» (Big Ball of Mud).
В этой статье мы поговорим о том, как отличить полезный компромисс от смертельной петли, измерить трение в архитектуре и автоматически управлять техдолгом через фитнесс‑функции.
Диагностика: почему больно и где именно
Классические метрики кода — покрытие тестами, цикломатическая сложность, дублирование — описывают локальный долг. Но архитектурный долг гораздо глобальнее. Его главный симптом: стоимость добавления новой фичи перестала быть линейной.
Вы рисуете график (мысленно или в Prometheus). По оси X — спринты или недели. По оси Y — человеко-часы на реализацию типовой пользовательской истории (например, «добавить новое поле в отчет» или «создать новый тип платежа»).
В здоровой системе график идёт горизонтально или чуть вверх. В системе с критическим архитектурным долгом он сначала пологий, а потом взлетает по экспоненте. Перегиб — момент, когда проценты по долгу съедают весь запас по времени выхода на рынок.

Две метрики из мира DORA (DevOps Research and Assessment — это набор показателей для оценки эффективности доставки программного обеспечения и зрелости DevOps‑процессов), которые работают лучше любого code review. Посчитать их можно следующим образом.
Возьмите два числа за последние 30 дней:
Lead Time for Change (LTFC) — время от пулл‑реквеста до продакшена. Измеряется в часах/днях.
Change Failure Rate (CFR) — процент релизов, вызвавших инцидент, откат или хотфикс.

Здоровый диапазон для высокоэффективной команды: LTFC < 1 часа, CFR < 15%. Архитектурный долг себя выдаёт так:
LTFC растёт, CFR остаётся низким → система стала слишком хрупкой, каждый PR требует ручного тестирования.
LTFC растёт, CFR тоже растёт → классическая спираль смерти. Разработчики боятся менять код, делают костыли, костыли ломают ещё что‑то.
LTFC низкий, CFR высокий → вы быстро катите, но всё ломается. Долг в тестах или в отсутствии изоляции компонентов.
Самый опасный сценарий — когда оба показателя высоки. Это значит, что вы не можете выпускать фичи без боли, и даже когда выпускаете — они падают. Организация вошла в «архитектурную кому».
Как найти конкретные точки трения, а не просто констатировать факт
Метрики уровня команды нужны для триажа. Триаж в IT — это процесс первичной оценки, сортировки и приоритизации уязвимостей, инцидентов или алертов безопасности. Его цель — быстро определить критичность угроз и сосредоточить ресурсы на наиболее опасных из них, особенно когда поток данных или ресурсов ограничен.
Возьмём типовой симптом: для добавления одного поля в объект Order вам пришлось править 12 файлов в 5 разных модулях. Почему? Потому что модель данных не локализована, а размазана по слоям через самодельную ORM или общий DTO‑контракт.
Проведите простой эксперимент (ручной, но эффективный):
Выберите одну бизнес‑сущность (User, Payment, Shipment).
Найдите все файлы в репозитории, где она упоминается (grep + сортировка по пути).
Посчитайте количество уникальных директорий/модулей, затронутых этой сущностью.
Если число больше трёх‑четырёх — перед вами архитектурный долг со знаком минус. Потому что сущность стала глобальной, а должна быть скрыта за доменным интерфейсом.
Диагностический чек‑лист для архитектора
Прежде чем начинать лечить непонятное, поймите тип долга. Вот три основных клинических случая.
Случай А: Долг связности (Coupling debt). Изменение в модуле А гарантированно ломает модули Б, В и Г. Причина: общие таблицы в БД, общие DTO через границы сервисов, синхронные цепочки вызовов. Диагностика: собрать трассировку одного бизнес‑сценария в Jaeger. Если глубина вызовов > 5, а больше половины из них идут в одну базу — диагноз подтверждён.
Случай Б: Долг отложенного дизайна (Design debt). Архитектура есть, но она не соответствует реальным паттернам использования. Например, вы спроектировали event‑driven систему, но 90% трафика — это синхронные запросы, которые превращают Kafka в дорогую очередь. Диагностика: соотношение асинхронных сообщений к синхронным вызовам по одному потоку данных. Перекос > 10:1 в пользу синхронизации означает, что вы платите за инфраструктуру, которая вам не нужна.

Случай В: Долг эволюции (Cruft). Система росла, требования менялись, но никто не пересматривал архитектурные решения (ADR). Появились «мёртвые зоны» — фичи, которые никто не использует, но которые требуют миграций данных и поддержки кода. Диагностика: профилирование вызовов в продакшене (например, через continuous profiling инструменты вроде Pyroscope). Функции или эндпоинты с нулевым трафиком за месяц, но с высоким временем сборки — главные кандидаты на удаление.
Решение: не переписывать, а выплачивать долг процентами
Самая частая ошибка — объявить «технический спринт» на полную перезапись системы. Это почти всегда заканчивается вторым, ещё более кривым, вариантом той же системы плюс потеря годового фичерного преимущества.
Правильная стратегия — лечить боль, а не симптомы. То есть, вы не обязаны починить всё, вы обязаны убрать бутылочное горлышко, которое блокирует развитие.
Пошаговый алгоритм для каждого типа долга
Для долга связности (случай А): внедряйте антикоррупционные слои (Anti‑Corruption Layer, ACL) не на границе монолита, а на границах доменов внутри монолита.
Пример на псевдокоде: до рефакторинга у вас везде напрямую используется OrderDbModel из общей БД. Везде, где нужен заказ, тянут Hibernate‑сущность.
После: создаёте модуль order.contract с интерфейсом OrderRepository и примитивной моделью Order. Внутри своего домена работаете только с ней. Адаптер к старой БД живёт в отдельной папке. Теперь, когда вы решите вынести заказы в микросервис, меняется только адаптер. Ни один файл за пределами order.* не узнает об этом.
Для долга отложенного дизайна (случай Б): откатывайте инфраструктуру там, где это возможно. Если синхронных вызовов в 20 раз больше, чем асинхронных, перестаньте гнать всё через брокер. Переключите прямые вызовы на HTTP с circuit breaker, а брокер оставьте только для фоновых задач, где задержка в 100 мс не критична. Это не регресс — это возврат к адекватной архитектуре.
Для долга эволюции (случай В): удаляйте код, а не комментируйте. Внедрите правило «Feature Toggle с датой удаления». Каждый тогл, который живёт дольше трёх месяцев, автоматически создаёт тикет на удаление кода. Метрика: если код не вызывается в проде за 14 дней и на него нет интеграционных тестов — он идёт в помойку. Страх «а вдруг пригодится» лечится через git history.

Архитектурная фитнес функция как автопилот долга
Разовые рефакторинги имеют один недостаток — через месяц после них долг нарастает снова, потому что не поменялись привычки команды. И для реального решения нам нужен автоматический страж.
Фитнес функции — это тест, который проверяет не корректность логики, а соблюдение архитектурных ограничений. Идея взята из книги «Building Evolutionary Architectures» (Ford, Parsons, Kua).
Для реализации создается отдельный модуль с тестами, которые запускаются в CI на каждый PR. Используйте готовые инструменты или пишите свои проверки.
Пример для Java с ArchUnit (можно адаптировать под любой язык с рефлексией):
@Test void domain_should_not_depend_on_infrastructure() { // Запрещаем доменному слою импортировать что-либо из БД, HTTP-клиентов или фреймворка noClasses() .that().resideInAPackage("..domain..") .should().dependOnClassesThat().resideInAnyPackage("..jpa..", "..httpclient..", "..spring..") .check(importedClasses); } @Test void order_module_should_expose_only_interfaces() { // Модуль заказов должен экспортировать наружу только интерфейсы и DTO, // но не реализации репозиториев и сервисов classes().that().resideInAPackage("..order..") .should().haveSimpleNameEndingWith("Impl") .andShould().haveModifier(Modifier.PUBLIC) .check(importedClasses); }
Пример для модульности на уровне сборки (Gradle или Bazel): запрет циклических зависимостей между модулями. Проверяется через команду gradle modules ‑scan или через jQAssistant.
Пороги, которые не дают умереть
Недостаточно просто написать фитнес функцию, нужно задать уровни.
Зелёный: всё хорошо. Новый код укладывается в правила.
Жёлтый: предупреждение. Например, цикломатическая сложность модуля выросла с 8 до 11 при лимите 10. CI пропускает, но генерирует отчёт. Команда должна разобрать это на ретроспективе.
Красный: блокировка слияния. Если нарушена архитектурная граница (например, UI‑слой полез в БД напрямую) или превышен порог технологического долга (количество публичных классов в модуле больше 50 — признак, что модуль надо делить).
Здесь есть еще один важный нюанс: механизм исключений. У фитнес функции должна быть кнопка «обойти на две недели» с обязательным созданием тикета и указанием имени архитектора, давшего разрешение. Без этого тесты станут формальностью, а долг вернётся через исключения в коде.
Как мы платили проценты по долгу и не обанкротились
Рассмотрим обобщённый случай из жизни. Платформа онлайн‑обучения, на которую потрачены три года разработки. Монолит на Django, который вырос до 150 тысяч строк. Проблема: добавление нового типа контента (например, интерактивный квиз вместо видео) требовало правки в 12 файлах: модели, схемах API, админке, вьюхах, шаблонах, signals, celery‑тасках.
LTFC вырос до 5 дней. CFR — 34%. Команда, естественно, устала.
Для диагностики проблемы построили граф импортов в модуле content. Оказалось, что quiz/models.py импортирует video/models.py, а тот — quiz/utils.py. Зацикливание.
Первая фитнес-функция содержит запрет циклических импортов на уровне модулей. Красная зона для PR, где новый импорт замыкает цикл.
Вторая: ограничение на количество файлов, которые трогает один PR по добавлению новой фичи (сбор через diff‑статистику в CI). Предел — 10 файлов. Если лимит превышен, значит, сущность слишком размазана.
Для решения выделили content.contract — модуль с интерфейсами ContentProcessor, ContentRenderer, ContentAdmin. Каждый тип контента (видео, квиз, статья) стал подключаться через DI, а не через прямые импорты. Старый код три месяца работал параллельно через адаптер‑прокси, который направлял вызовы либо к старой логике, либо к новой в зависимости от Feature Toggle.
Через полгода старые модели были удалены. Импорты остались только через contract.
Результат через три месяца: LTFC упал с 5 дней до 4 часов. CFR — с 34% до 12%. Количество файлов на типовой PR сократилось с 12 до 3–4.
Что делать в итоге
В заключении рассмотрим три шага, не требующих одобрения архитектурного комитета и бюджета на переписывание всего.
Шаг 1. Зафиксируйте боль на графике. Возьмите три фичи, которые команда сделала за последний месяц. Посчитайте человеко‑часы на каждую. Сравните с аналогичными фичами годичной давности. Если рост больше 50% — у вас есть подтверждённый архитектурный долг, а не просто ощущение.
Шаг 2. Внесите первую функцию в CI. Начните с малого: запретите циклические зависимости между пакетами (в Java это ArchUnit, в Python — pytest with import‑linter, в Go — go mod graph + grep). Это займёт два часа и остановит самый опасный вид долга — когда все зависит от всего.
Шаг 3. Выберите одну «горячую» сущность и изолируйте её за антикоррупционным слоем. Не чините всё. Возьмите ту фичу, которая чаще всего вызывает баги и долгие PR. Сделайте вокруг неё фасад. Переключите на него один, самый простой сценарий. Промерьте LTFC через неделю. Если стало легче — масштабируйте подход.
Умение замечать архитектурный долг начинается с понимания архитектурных принципов и компромиссов. Для самопроверки можно пройти вступительный тест курса «Архитектор программного обеспечения» и посмотреть, какие темы стоит изучить глубже. |
Важно понимать, что архитектурный долг со знаком плюс — это когда вы им управляете, а не он вами. Если вы знаете его точную величину и процентную ставку, вы можете решать: платить или брать новый транш. Без метрик и фитнес функций вы не инвестор, а заложник собственных старых решений.

Разобраться в архитектурных компромиссах и инженерных метриках можно на бесплатных уроках OTUS. На занятиях получится посмотреть на подходы практиков, задать вопросы и понять, как похожие решения применяются в реальной разработке.
17 июня, 20:00. «Архитектура информационных систем. Монолиты, SOA и микросервисы». Записаться.
Разберём сильные и слабые стороны разных архитектурных подходов и критерии выбора архитектуры под конкретные задачи.8 июля, 20:00. «Чистая архитектура на Go без “карго-культа”: слои, DTO и интерфейсы». Записаться
Разберём, как проектировать границы модулей и избегать избыточной связности в приложениях.
Полный список бесплатных уроков июня смотрите в дайджесте.
