Посчитайте, во сколько техдолг обходится в неделю
Систематическое управление техническим долгом: сбор, приоритизация, планирование погашения. Используйте для аудита техдолга или планирования работы по его сокращению.
Как агент работает
Первое разделение меняет всю приоритизацию: технический долг — сознательное решение сделать быстрее и хуже ради выгоды сейчас, у него есть дата, полученная выгода и проценты, которые платятся до погашения. Плохой код написан плохо потому, что иначе не умели, и аргумента «мы ускорились тогда» у него нет. Третий класс — изменившиеся требования: код был правильным для трёх складов, складов стало сорок, углы никто не срезал, и это новая задача, конкурирующая с фичами. Долг гасится проектом, плохой код — процессом, линтером и ревью.
Инвентаризация идёт фактами. Маркеры в коде классифицируются по возрасту: моложе 30 дней — живая заметка, 30-180 дней — забытое намерение, 180-540 — маркер пережил автора, старше 540 — декорация; при этом blame даёт последнее касание строки, и массовое переформатирование обнуляет возраст всей базы, что лечится ключом -w и ignore-revs-file. Отключённые тесты выглядят как покрытие, но им не являются, и skip старше 90 дней — это удалённый тест, притворяющийся существующим; наборы, исключённые на уровне пайплайна, грепом не ищутся вовсе.
Хотспот — файл, который часто меняют и в котором часто чинят баги, — предсказывает будущие проблемы лучше любой метрики сложности: замороженный модуль не стоит ничего, каким бы страшным ни был. Ранг считается как квадрат числа фиксов, делённый на число правок, а что считать багфиксом, определяет конвенция самого репозитория. Красная зона — доля фиксов выше 0,40 при более чем 20 правках за год; кандидат номер один на рефакторинг — 0,25-0,40 при тех же правках; ниже 0,15 файл здоровый. Самый дорогой класс — долг в схеме БД: чинить его в 3-10 раз дороже кода той же сложности.
Проценты считаются в часах в неделю, а ставка спрашивается у пользователя: выдуманная обесценивает расчёт. Разобранный пример выгрузки остатков в 1С — ручной труд 1 час, инциденты 2 часа, замедление 1,5 часа, итого 4,5 часа в неделю, при ставке 2500 рублей это 11 250 рублей в неделю и около 585 000 в год против 60 часов на переписывание, то есть окупаемость 13 недель. Вне ранга гасятся достижимая уязвимость в прод-зависимости и утечка персональных или платёжных данных, а блокирующий долг всегда сверху.
Часть долга навык сознательно не гасит и пишет отказ строкой с причиной и датой пересмотра: код перед выводом из эксплуатации, неподтвердившаяся гипотеза, замороженная зона без коммитов за 18 месяцев, окупаемость длиннее остаточного срока жизни системы, осознанно взятый долг, чей срок ещё не наступил. Реестр держится в границах: элемент входит только с четырьмя заполненными полями, активных записей не больше 25, а дважды просроченная запись удаляется — за неё не взялись два цикла, и это и есть решение.
1. Долг, плохой код и изменившиеся требования
Технический долг — сознательное решение сделать быстрее и хуже ради выгоды сейчас: успеть к 11.11 на Wildberries, показать демо, закрыть требование банка к сроку. У него есть дата, полученная выгода и проценты — плата, пока он не погашен. Плохой код написан плохо, потому что иначе не умели: выгоды не было, «мы ускорились тогда» сказать нельзя. Разница меняет приоритизацию:
- У долга известна выгода. «Срезали три недели в марте, платим 6 часов в неделю с апреля» — разговор про сделку. У плохого кода такого аргумента нет, а выдуманный рушит доверие ко всему отчёту.
- Долг гасится проектом, плохой код — процессом. У долга есть конец, он локализован в одном-двух модулях; плохой код размазан ровным слоем и лечится линтером и ревью. Строка «весь легаси 1С-интеграции» не закроется никогда и утянет реестр в свалку.
Помечай элемент флагом осознанный: да/нет; нет — дефект качества, в денежный отчёт он не попадает. Третий класс, который путают с долгом, — изменившиеся требования: код был правильным для трёх складов, складов стало сорок. Углы никто не срезал — это новая задача, и конкурирует она с фичами.
2. Инвентаризация: факты, а не мнения
Клонируй через git_clone, собирай в sandbox_bash. Сначала масштаб (git ls-files | wc -l, git log --oneline | wc -l) — иначе доли не с чем сравнивать.
2.1 Маркеры в коде с классификацией по возрасту
Счётчик TODO бесполезен: он не отличает вчерашнюю заметку от костыля, пережившего два состава команды. Возраст маркера и есть его классификация.
| Возраст | Что это | Действие |
|---|---|---|
| < 30 дней | Живая заметка автора | В реестр не класть |
| 30–180 | Забытое намерение | Закрыть или оформить в реестр |
| 180–540 | Маркер пережил автора | Кандидат после проверки «проблема ещё есть?» |
| > 540 | Декорация | Снять, если проблемы нет; иначе элемент реестра |
Режим отказа. blame даёт последнее касание строки, а не дату написания маркера: массовое переформатирование обнуляет возраст всей базы. Признак — у 90% маркеров одна дата; лечение — -w и --ignore-revs-file. Генерируемые каталоги исключай.
2.2 Отключённые тесты
Самый дешёвый в поиске и дорогой по последствиям вид долга: выглядит как покрытие, но им не является.
Грепом не ищется главное — наборы, исключённые на уровне пайплайна. Сверь по .gitlab-ci.yml / .github/workflows/*.yml, какие пути реально попадают в прогон: типичный случай — набор, который гоняется только вручную и падает месяцами. Порог: skip старше 90 дней — это удалённый тест, притворяющийся существующим.
2.3 Зависимости с известными уязвимостями
В отчёт идут три числа: сколько critical/high достижимы из твоего кода (пакет импортируется, а не висит транзитивно в dev-ветке); сколько мажорных версий отставания у прямых зависимостей; сколько пакетов без релизов больше двух лет — такие не обновляют, а заменяют, и это проект. При сборке через зеркало пакетов аудит обязан смотреть на тот же индекс.
2.4 Мёртвый код и расходящиеся дубликаты
Анализаторы ложно срабатывают на всём, что вызывается по имени: точки входа CLI, обработчики фреймворка, ORM-модели, реестры плагинов. Невидимая им разновидность — мёртвые фича-флаги: флаг, включённый на 100% полгода, это два пути, из которых один не исполняется, но оба поддерживаются.
Дублирование само по себе не долг — долг там, где копии разошлись: одна логика в трёх местах, а правки за год приходили в две. Третья копия однажды даст баг «на Ozon скидка считается, на Wildberries нет».
2.5 Хотспоты: частота изменений × доля багфиксов
Файл, который часто меняют и в котором часто чинят баги, — лучший предиктор будущих проблем, чем любая метрика читаемости или сложности. Сложность вредит только там, где до неё дотрагиваются: замороженный модуль не стоит ничего, каким бы ужасным ни был.
Второй проход — только по коммитам-исправлениям. Что считается багфиксом, определи по конвенции этого репозитория: посмотри git log --pretty=format:'%s' | head -50, при conventional commits фильтруй ^fix, при русских сообщениях добавь исправ|починк|фикс|баг, при живом трекере — префикс bug-задач.
Ранг (фиксы² / правки) ставит выше файл с 40 правками и 20 фиксами, чем с 200 правками и теми же 20: во втором правки в основном плановые, в первом каждая вторая — тушение.
| fix_ratio | Правок за год | Диагноз |
|---|---|---|
| > 0,40 | > 20 | Красная зона: модуль не понимают даже те, кто его правит |
| 0,25–0,40 | > 20 | Кандидат №1 на рефакторинг: правки идут, качество не держится |
| > 0,40 | 5–20 | Хрупкий, но редко трогаемый — в реестр да, в квартал нет |
| < 0,15 | любое | Здоровый файл, даже если большой и страшный |
2.7 Долг в схеме БД и в данных — самый дорогой класс
Собирается запросами, не грепом. Ищи: колонки, которые код больше не пишет, а схема держит; text там, где значений конечное число (SELECT col, count(*) GROUP BY 1 даёт 5 значений на миллионе строк — инвариант есть, ограничения нет, и он уже где-то нарушен); отсутствующие внешние ключи (LEFT JOIN ... WHERE parent.id IS NULL); миграции, не применённые в базе.
Чинить схему в 3–10 раз дороже, чем код той же сложности, и разрыв растёт с числом строк: нужно окно обслуживания, откатить почти нельзя (вместе со схемой поехали данные), а ошибка не падает с трейсбеком, а тихо приезжает в отчёт клиенту.
3. Проценты: во что долг обходится в неделю
3.1 Что складывать
Бизнес отклоняет «модуль плохо написан» и обсуждает «стоит 68 000 ₽ в неделю, устранение 240 000 ₽, окупаемость 3,5 недели».
Упущенную функциональность оценивай только вместе с продуктом.
3.2 Формула и ставка
Ставку часа спроси у пользователя. Не знает — считай в часах и скажи это явно: выдуманная ставка обесценивает расчёт, а часы бизнес пересчитает сам.
3.3 Разобранный пример: выгрузка остатков в 1С
- Ручной труд 2 × 0,5 ч = 1 ч/нед; инциденты 4 ч на двоих раз в месяц = 2 ч/нед; замедление (правка требует ручного прогона, +3 ч на задачу раз в две недели) = 1,5 ч/нед.
- Итого 4,5 ч/нед; при ставке 2 500 ₽/ч — 11 250 ₽/нед, около 585 000 ₽ в год. Переписать на очередь с идемпотентностью — 60 ч, то есть 150 000 ₽: окупаемость 13 недель.
- Отдельной строкой без денег: отмены бьют по рейтингу продавца, рейтинг влияет на выдачу. В часах не считается, но это самый сильный аргумент в разговоре.
4. Приоритизация по четырём проверяемым осям
4.1 Оси
- Частота соприкосновения — из
/tmp/churn.txt. Долг, который не трогают, процентов не начисляет. - Стоимость ошибки. Верх шкалы: деньги клиента и отчётность в госорганы (расчёт цен, платежи, НДС, маркировка, ЕГАИС). Середина: работа сотрудников встаёт. Низ: косметика и внутренние отчёты.
- Блокирует ли новые задачи. Признак: в бэклоге есть задача, оценка которой содержит «сначала переделать X». Тогда это не долг, а предусловие, и оно поднимается выше всего.
- Растёт ли сам по себе — см. 4.2.
4.2 Растущий долг против стабильного
Стабильный стоит на месте: мёртвый код, старый стиль, дублирование, которое не трогают, — может ждать годами. Растущий дорожает каждую неделю без вашего участия; признаки: каждая новая фича добавляет ещё одну копию, потому что копировать проще, чем чинить; неверная модель данных продолжает накапливать неверные строки.
Правило: растущий долг с высокой частотой соприкосновения гасится в текущем квартале, даже если дорогой; стабильный с низкой частотой не гасится вовсе — записывается и живёт.
4.3 Ранг и то, что вне ранга
Вне ранга: достижимая уязвимость в прод-зависимости и утечка персональных или платёжных данных гасятся немедленно без расчёта окупаемости, блокирующий долг всегда сверху. Для персональных данных добавь строку про требования законодательства РФ об их обработке и локализации хранения: это регуляторный риск, и решение принимает не разработка.
5. Долг, который нельзя гасить постепенно
Часть долга не делится: половина миграции хуже её отсутствия. Признаки неделимости — смена формата хранения, замена библиотеки без прослойки, изменение контракта API с внешними потребителями (мобильное приложение, обмен с 1С), переезд на другую версию СУБД.
Планируется только отдельным проектом с четырьмя атрибутами: точка невозврата (отодвинуть как можно дальше); режим двойной записи или чтения (пока живут оба варианта, вы поддерживаете два кода — это и есть цена); критерий переключения (расхождение старого и нового пути ниже порога на реальном трафике неделю подряд); дата удаления старого пути. Нет хотя бы одного — «не начинать, пока не назначен критерий переключения».
Незавершённая миграция — самый дорогой вид долга: удваивает поддержку, и ни одна сторона не считается рабочей окончательно. Признак — два модуля одного назначения со словами new/v2/legacy в имени, живущие дольше полугода.
6. Когда долг правильно НЕ гасить
- Код перед выводом из эксплуатации. Модуль отключают в этом полугодии — вложения сгорят; дата вывода и есть причина отказа.
- Гипотеза не подтвердилась. Функциональностью пользуются десять человек в месяц: удалить её вместе с долгом, а не рефакторить. Решают цифры использования, не мнение.
- Зона заморожена.
git log --since='18 months ago' -- <путь> | wc -l= 0: проценты нулевые, погашение — чистый расход. - Окупаемость длиннее остаточного срока жизни системы: 14 месяцев у системы, которую меняют через год.
- Долг взят осознанно, срок не наступил. Обходной путь на сезон распродаж — нормальная сделка; ошибка не в том, что взяли, а в том, что не назначили дату возврата.
Отказ пишется строкой с причиной и датой пересмотра: без даты он через год превращается в «мы про это забыли».
7. Стратегии погашения и их честная цена
| Стратегия | Работает, когда | Ломается, когда |
|---|---|---|
| Правило бойскаута | Долг размазан по активным зонам | Долг там, куда не заходят. Крупных элементов не решает |
| Связанный рефакторинг | Есть хотспоты с высоким fix_ratio | Сроки фич жёсткие — режут первым |
| Квота времени | Команда от 4 человек, согласовано явно | Квоту съедает первый срочный релиз и она не возвращается |
| Техдолг-спринт | Долг неделимый (раздел 5) | Применяют к размазанному: база после спринта не лучше |
| Заморозка фич | Система физически не принимает изменения | Применяют рано — бизнес теряет доверие |
| Переписать заново | Требования известны точно, старая система — эталон сверки | Требования известны «в целом»: воспроизводите старый долг плюс новые баги. Нельзя описать старую систему тестами — переписывать нельзя |
Дефолт: связанный рефакторинг для хотспотов, отдельный проект для неделимого, явный список того, что не гасится.
8. Реестр, который не превращается в свалку
8.1 Порог входа и потолок
В реестр попадает элемент с четырьмя заполненными полями: путь, что сломается, сколько стоит в неделю (или честное «не считали»), растёт или стоит. Не заполняется — это ощущение, а ощущения не хранятся. Активных записей не больше 25: при переполнении поднимают порог входа, не потолок.
8.2 Правило устаревания записи
У каждой записи есть дата пересмотра: P0/P1 — 30 дней, P2 — 90, P3 — 180. Когда дата наступила, запись проходит один вопрос — «менялась ли зона с момента внесения?» (git log --since='<дата>' --oneline -- <путь> | wc -l). Ноль у P2/P3 — закрыть со статусом «не подтверждено». Ноль у P0/P1 — приоритет выставлен неверно, понизить. Правки были — обновить оценку и назначить новую дату. Просроченный дважды элемент удаляется: за него не взялись два цикла, это и есть решение «не делаем».
8.3 Где хранить
Реестр — через documents или файлом в репозитории, чтобы он ревьюился вместе с кодом. Решения «не гасим, потому что…» — в manage_memory: следующий аудит не должен переоткрывать закрытые вопросы. Согласованное погашение ставь через manage_task с путём и недельной ставкой.
Похожие навыки
Попробуйте этот навык
Зарегистрируйтесь и используйте навык «Управление техдолгом» бесплатно.