1. Диагноз
Поднял прод полгода назад. Команда маленькая, инструменты быстрые, код генерируется промптами. Через полгода никто не может объяснить, почему валидация даты написана в трёх разных файлах тремя разными способами. Знакомо?
Технологический долг — метафора Уорда Каннингема: берёшь короткий путь сейчас, платишь процентами потом. Раньше проценты капали годами. Сейчас капают неделями, потому что скорость генерации кода выросла на порядок, а скорость его понимания — нет.
В этой статье — рабочий фреймворк для инженера и тимлида: как диагностировать долг, посчитать его в конкретных цифрах, разобрать по типам и погасить, не останавливая роадмап. Ничего эзотерического. Команды, таблицы, схема процесса, работающие инструменты.
Что получишь на выходе:
- Метод измерения долга через git churn и статический анализ — 30-40 минут на первый прогон
- Классификацию долга по типам (security, test, duplication, comprehension) с порядком погашения
- Готовый чек-лист для CI/CD, который не даёт долгу расти дальше
- Таблицу «тип проблемы → причина → решение → команда проверки»
Нужно: доступ к репозиторию с историей коммитов, Docker для локального прогона статического анализа, минут двадцать без дедлайна над головой.
Смотри на эту схему внимательно. Круг замыкается сам. Пока в нём нет узла с зелёной рамкой — точки, где кто-то реально останавливается и проверяет архитектуру — долг растёт по экспоненте, а не по прямой.
2. Причины
Семь причин, почему полгода на автогенерации бьют десять лет ручного legacy. Не пять — потому что в реальности их всегда больше, чем влезает в круглое число.
2.1. Дублирование логики вместо переиспользования
AI решает конкретный промпт. Он не ищет по кодовой базе готовую утилиту, которая делает то же самое — а если и находит, то не всегда использует. Итог: функция форматирования даты живёт в четырёх вариациях, один и тот же паттерн запроса к базе скопирован и слегка изменён в шести контроллерах. Анализ дублирования кода в проектах на генерации показывает рост в разы по сравнению с ручным кодом того же объёма.
2.2. Обработка только «счастливого пути»
Код обрабатывает тот случай, который ты описал в промпте. Случаи, которые не описал — недоступная на секунду база, внешний API с кодом 429, файл на килобайт больше лимита — остаются без обработки. Работает на тестах. Падает в проде в три ночи, когда ни одно из протестированных условий не выполняется.
2.3. «Работает, но никто не понимает почему»
Самый дорогой тип долга. Код даёт правильный результат через цепочку операций, которая была логична модели в момент генерации, но не имеет очевидной логики для человека, который читает её через месяц. Рефакторить страшно: не понятно, что сломается.
2.4. Тестовое покрытие проваливается по умолчанию
AI-инструменты пишут код, который проходит через промпт. Тесты они пишут, только если их явно попросили — и не всегда именно те тесты, что нужны. Практика показывает падение среднего покрытия в проектах на автогенерации до уровня в разы ниже отраслевой нормы.
2.5. Доля рефакторинга падает
Раньше разработчик, дописывая фичу, попутно причёсывал код вокруг. Когда код генерируется, а не пишется, привычка причёсывать теряется — генерировать новое проще, чем разбираться в старом. Доля рефакторинга в изменённых строках падает в разы год к году.
2.6. Организационное давление «быстрее»
Метрика «фичи в спринте» растёт. Метрика «архитектурных ревью на фичу» — нет. Никто не закладывает время на паузу, потому что паузу невозможно показать в отчёте о скорости.
2.7. Иллюзия чистоты
Код компилируется, проходит линтер, выглядит опрятно. Ревьюер видит аккуратный diff и меньше копает вглубь — визуально всё в порядке. Долг от этого не становится меньше. Он просто становится невидимым для человека и видимым только для профилировщика инцидентов в три часа ночи.
3. Рецепт: как измерить и приоритизировать долг
Подготовка
Три вещи нужны до старта:
- Git-репозиторий с полной историей коммитов, минимум за 3-6 месяцев
- Docker для локального прогона статического анализа — без облачной подписки
- Права на чтение метрик CI — если пайплайн есть, тесты и покрытие уже где-то считаются
Первый шаг — не инструмент, а вопрос: сколько кода в проекте написано человеком, а сколько сгенерировано и принято без изменений. Если ответа нет — это уже диагноз.
Шаг 1. Посчитай churn — как часто код переписывают через две недели после коммита
Churn-метрика показывает, сколько строк меняется или удаляется в первые две недели после появления. Высокий churn — явный маркер кода, который написали не подумав и сразу переписали.
# Список файлов, изменённых за последние 6 месяцев, с числом коммитов
git log --since="6 months ago" --name-only --pretty=format: | sort | uniq -c | sort -rn | head -30
Результат: список файлов-чемпионов по числу изменений. Если топ-10 файлов — это не конфиги и не миграции, а бизнес-логика — вот твой первый список кандидатов на разбор.
Шаг 2. Прогони статический анализ
Самый быстрый путь — локальный SonarQube в контейнере. Он считает технический долг в часах на устранение по методу SQALE — складывает время исправления по каждому найденному замечанию.
docker run -d --name sonarqube -p 9000:9000 sonarqube:community
# После старта (минута-две) - прогон анализа проекта
sonar-scanner \
-Dsonar.projectKey=my-project \
-Dsonar.sources=. \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=<твой_токен>
Результат: дашборд с числом code smells, дублей, покрытием тестами и итоговой оценкой долга в человеко-часах.
Шаг 3. Раздели найденное на четыре типа
Не весь долг одинаково опасен. Практический способ разбить — по типу риска, который он несёт:
| Тип долга | Что это | Почему опасен |
|---|---|---|
| Security debt | Уязвимости и небезопасные паттерны | Эксплуатируется извне, цена ошибки максимальная |
| Test debt | Отсутствие покрытия критичных путей | Ошибки находят пользователи, а не CI |
| Duplication debt | Одна логика в нескольких местах | Правишь баг в одном месте — он остаётся в трёх других |
| Comprehension debt | Код работает, но логика непрозрачна | Рефакторинг откладывается бесконечно из страха что-то сломать |
Классификация перекликается с квадрантом технического долга Мартина Фаулера — он делит долг на осознанный и неосознанный, безрассудный и разумный. Для кода на автогенерации почти весь долг попадает в квадрант «неосознанный, но не всегда безрассудный» — решение казалось разумным в моменте, просто никто не проверял последствия.
Шаг 4. Погашай в порядке риска, не в порядке раздражения
Соблазн — чинить то, что бесит больше всего. Правильный порядок другой: security debt первым, дальше test debt, потом duplication, comprehension — в последнюю очередь. Он самый неприятный, но реже всего убивает прод напрямую.
# Простой скрипт: помечает файлы с высоким churn и низким покрытием
# как кандидатов первой очереди на ревью
import subprocess
import json
def get_churn(path="."):
result = subprocess.run(
["git", "log", "--since=6 months ago", "--name-only", "--pretty=format:"],
cwd=path, capture_output=True, text=True
)
files = [f for f in result.stdout.splitlines() if f.strip()]
counts = {}
for f in files:
counts[f] = counts.get(f, 0) + 1
return counts
def flag_candidates(churn, coverage_map, churn_threshold=10, coverage_threshold=40):
candidates = []
for f, c in churn.items():
cov = coverage_map.get(f, 0)
if c >= churn_threshold and cov < coverage_threshold:
candidates.append({"file": f, "churn": c, "coverage": cov})
return sorted(candidates, key=lambda x: x["churn"], reverse=True)
if __name__ == "__main__":
churn = get_churn()
# coverage_map подставляется из отчёта SonarQube или coverage.py
coverage_map = {}
print(json.dumps(flag_candidates(churn, coverage_map), indent=2))
Результат: конкретный список файлов, где сходятся высокая частота правок и низкое покрытие тестами — именно там прячется самый дорогой долг.
4. Проверка
Долг снижается не потому, что «стало спокойнее», а потому что три числа идут в правильную сторону. Проверяй их еженедельно, не раз в квартал.
# Повторный прогон анализа для сравнения с прошлым разом
sonar-scanner -Dsonar.projectKey=my-project -Dsonar.host.url=http://localhost:9000
# Динамика churn за последние 4 недели против предыдущих 4
git log --since="4 weeks ago" --name-only --pretty=format: | sort | uniq -c | wc -l
git log --since="8 weeks ago" --until="4 weeks ago" --name-only --pretty=format: | sort | uniq -c | wc -l
| Метрика | Что означает рост | Что означает падение |
|---|---|---|
| Долг в часах (SonarQube) | Копится быстрее, чем гасится | Погашение опережает накопление |
| Churn топ-файлов | Нестабильная логика, переписывают на ощупь | Логика устоялась |
| Покрытие тестами | Тесты пишутся хуже кода | Тесты успевают за кодом |
| Доля дублей | Копипаста вместо переиспользования | Общие модули реально переиспользуются |
Если три из четырёх метрик двигаются не туда два месяца подряд — паника преждевременна, но повод собрать отдельный спринт на погашение уже есть.
5. Осложнения
Формат один: тип проблемы, почему она возникает, что делать, чем проверить результат.
| Проблема | Причина | Решение | Проверка |
|---|---|---|---|
| Один и тот же баг чинится, но вылезает в другом месте | Дублированная логика в нескольких файлах | Вынести общую функцию, найти все копии через поиск по сигнатуре | grep -rn "функция_валидации" --include=*.py . |
| Инцидент по редкому кейсу, который «никто не тестировал» | AI сгенерировал только happy path | Добавить тесты на граничные условия вручную, не полагаясь на автогенерацию тестов | Прогон покрытия по конкретному модулю |
| Рефакторинг откладывается месяцами | Никто не понимает логику модуля целиком | Парная читка кода перед изменением, документирование инвариантов прямо в коде | Ревью с обязательным комментарием «почему это так работает» |
| Security-скан находит уязвимости в свежем коде | AI-генерация без контекста политик безопасности проекта | Обязательный security-gate в CI до мерджа, не после | Прогон SAST-сканера в пайплайне на каждый PR |
6. Альтернативы
Есть минимум четыре стратегии погашения долга. Какая подходит — зависит от размера команды и того, сколько времени осталось до следующего релиза.
- Big-bang переписывание. Останавливаешь фичи, переписываешь модуль целиком. Быстро на бумаге, почти всегда дороже и дольше в реальности — оценки переписывания хронически занижают.
- Strangler pattern — постепенное вытеснение. Новый код пишется рядом со старым, старый вытесняется по частям. Медленнее, но прод не останавливается ни на день.
- Freeze-спринт на погашение долга. Фичи ставятся на паузу на одну итерацию, вся команда чинит долг по списку из раздела 3. Работает в командах до 10-15 человек, в крупных — организационно почти нереализуемо.
- Governance-слой поверх процесса. Постоянные quality gates в CI, обязательное архитектурное ревью для сгенерированного кода выше определённого объёма. Не разовое действие, а правило игры навсегда.
Для команд, где код массово генерируется AI-инструментами, рабочим по умолчанию оказывается сочетание strangler pattern для существующего долга и governance-слоя для нового кода. Полная остановка ради переписывания обычно проигрывает конкуренции — пока ты переписываешь, рынок не ждёт.
7. Профилактика
Три вещи держат долг под контролем без героизма по пятницам.
Мониторинг долга как метрики, а не как ощущения
Churn, покрытие и число code smells должны быть числами в дашборде, которые смотрят на регулярной встрече — так же, как смотрят на аптайм. Долг, который не измеряется, не управляется.
Quality gate в CI/CD — блокировка, а не рекомендация
# Пример порога для quality gate в CI (псевдоконфиг)
new_code_coverage: minimum 70%
new_duplicated_lines: maximum 3%
new_security_hotspots: 0 unresolved
Мердж не проходит, если новый код не проходит порог. Не «предупреждение в чате», а красный статус в пайплайне.
Сохранение экспертизы в команде
Часть индустрии сокращает найм джунов, полагаясь на AI-генерацию. Это создаёт разрыв: гасить долг руками некому именно тогда, когда его больше всего. Ревьюер, который прошёл путь снизу, видит подозрительный паттерн быстрее любого линтера.
8. FAQ
Почему технологический долг растёт даже после code review?
Потому что ревью смотрит на diff, а не на архитектуру целиком. Аккуратно оформленный код проходит ревью быстрее, даже если внутри дублирует логику из другого модуля. Ревью ловит стиль, а не системные последствия.
Как проверить, что рефакторинг реально снизил долг?
Сравни три числа до и после: часы долга по SonarQube, churn затронутых файлов за следующий месяц, покрытие тестами изменённых модулей. Субъективное «стало чище» не считается.
Что делать, если весь список долга не помещается в спринт?
Не пытайся закрыть всё разом. Бери верхние 20% из раздела «осложнения» по критерию риска — обычно они дают основную часть эффекта. Остальное распредели по следующим итерациям с фиксированной квотой времени на спринт.
Чем vibe coding отличается от обычного технического долга?
Скоростью и формой. Обычный долг растёт линейно — как проценты по кредиту. Долг от массовой AI-генерации растёт кусками: правки внутри одного модуля кажутся локально корректными, а вместе образуют несогласованную систему. Разница не в природе долга, а в том, что счётчик тикает в разы быстрее.
Нужно ли отказываться от AI-генерации кода, чтобы избежать долга?
Нет — и это было бы так же нелепо, как отказаться от компилятора. Нужен governance-слой: обязательное ревью, тесты и security-gate именно для сгенерированного кода, потому что у него другой профиль риска.
9. Прогноз
Долг никуда не денется — он есть в любой живой системе, старой или молодой. Разница между командой, у которой всё под контролем, и командой, которая тушит пожар каждую пятницу, не в том, кто пишет код быстрее. Она в том, кто первым завёл дашборд с churn и покрытием и не позволяет security-долгу дожить до следующего релиза.
Всё не так плохо, как кажется после прочтения раздела про причины. Всё намного проще: три метрики, один governance-слой в CI, приоритет по риску — и долг перестаёт расти быстрее, чем команда успевает его гасить. Если внедрил и не взлетело — пиши в комментарии, разберём конкретный случай.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



