Привет, Хабр! Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги.

Любой практикующий архитектор знает: техдолг неизбежен. Он бывает двух видов — глупый (когда срезали угол просто потому, что лень) и осознанный (когда срезали угол ради продвижения выживания продукта на рынке). Вторая категория — это тот самый «долг со знаком плюс». Он работает на вас ровно до тех пор, пока процентная ставка не превышает экономическую выгоду.

Проблема в том, что в большинстве компаний долг измеряют качественно («кажется, стало больнее») или вообще игнорируют до пожара. А нужно — количественно, с автоматическими порогами и архитектурными тестами, которые не дадут выродиться системе в «большой комок грязи» (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‑контракт.

Проведите простой эксперимент (ручной, но эффективный):

  1. Выберите одну бизнес‑сущность (User, Payment, Shipment).

  2. Найдите все файлы в репозитории, где она упоминается (grep + сортировка по пути).

  3. Посчитайте количество уникальных директорий/модулей, затронутых этой сущностью.

Если число больше трёх‑четырёх — перед вами архитектурный долг со знаком минус. Потому что сущность стала глобальной, а должна быть скрыта за доменным интерфейсом.

Диагностический чек‑лист для архитектора

Прежде чем начинать лечить непонятное, поймите тип долга. Вот три основных клинических случая.

Случай А: Долг связности (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.

Пороги, которые не дают умереть

Недостаточно просто написать фитнес функцию, нужно задать уровни.

  1. Зелёный: всё хорошо. Новый код укладывается в правила.

  2. Жёлтый: предупреждение. Например, цикломатическая сложность модуля выросла с 8 до 11 при лимите 10. CI пропускает, но генерирует отчёт. Команда должна разобрать это на ретроспективе.

  3. Красный: блокировка слияния. Если нарушена архитектурная граница (например, 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 и интерфейсы». Записаться
    Разберём, как проектировать границы модулей и избегать избыточной связности в приложениях.

Полный список бесплатных уроков июня смотрите в дайджесте.

Комментарии (2)


  1. rukhi7
    12.06.2026 05:38

    Правильная стратегия — лечить боль, а не симптомы.

    Вообще то, боль это как раз симптом, что характерно. Поэтому так все и делают.


  1. titan_pc
    12.06.2026 05:38

    И вот в 2026 году, когда ИИ делает ревью, кругом царят лозунги "Мы оплатили Вам курсор - вы должны в 10 раз быстрее пилить фичи". Выходит довольно неплохая статья на тему техдолга. Который никому не итересен из владельцев бизнеса. Они сейчас живут в версии что ещё год - два и смогут себе сбербанк навайбкодить с кайфом.

    Что 10 лет назад, что сейчас. В таких статьях не хватает как раз того, что лечение тех долга начинается с политики и выбивания времени на это лечение. А внедрить линтеры, чистую архитектуру и гору всего ещё полезного - ну это тривиально и легко сейчас.

    Вот советы о том, как бы так аккуратно взять и 2 месяца рефакторинга размазать по спринтам на пол года, чтоб никто не догадался, что кроме фич, там ещё какие то непонятные техдолги команда закрывает, которые как бывает говорят деньгодержаиели "сама себе придумала" - вот за такую статью я бы ящик прива проставил))