Перейти к основному содержимому

Цена технического долга: формула, в которую поверит ваш CFO

· 10 мин. чтения
Artur Pan
CTO & Co-Founder at PanDev

30-дневный срез Q1 2026 с команды из 14 инженеров: за год 187 тикетов прошли через легаси-компонент авторизации, средняя стоимость $1,820 за тикет при 18 часах. Greenfield-компонент онбординга у той же команды закрывал тикеты сопоставимого типа и severity по $640 за 6 часов. Разница — это и есть налог технического долга. После умножения один этот легаси-компонент течёт на $220K в год, и CFO подписывает эту сумму, не видя её отдельной строкой. Stripe в Developer Coefficient (обновление 2024) оценивает потери из-за «плохого кода» примерно в 17 часов в неделю на разработчика — около 42% задекларированной нагрузки. Это глобальное среднее. Цифра выше — то, как оно выглядит, когда вы наконец считаете локально.

Эта статья — для engineering manager, которому CEO сказал «принеси бизнес-обоснование для рефакторинга», а в табличку положить нечего. Формула — скучная. Настоящая работа — данные под ней.

Почему стандартный питч про техдолг не работает​

Большинство предложений по рефакторингу попадают в одну из двух ловушек. Первая — чистый нарратив. «Модуль авторизации сильно связан с биллингом, надо это починить». CFO спрашивает «сколько мы сэкономим», инженер отвечает «много», и предложение умирает в следующем бюджетном цикле. Коммуникационную сторону этого вопроса мы разобрали в technical-debt-cost — та статья о том, как переводить инженерный язык на CEO-язык. Эта — про математику снизу. Нужны обе. Коммуникация — пустая трата времени, если формула неверна, а формула — пустая аналитика, если CFO не получит фразу, на которую можно действовать.

Вторая ловушка — псевдоточная оценка. Инженер на глаз говорит, что легаси «делается вдвое дольше», и умножает дальше. CFO чувствует фантазию и обесценивает всё предложение. Цифра перестаёт убеждать в момент, когда становится очевидно, что её прикинули на салфетке.

DORA State of DevOps за 2022–2024 находит ту же корреляцию: команды с высоким техдолгом имеют в 2–3 раза выше change failure rate и в 2 раза длиннее lead time. Академическая литература туда же. Ramač и соавт. (IEEE Software, 2022) изучили 226 проектов и зафиксировали почти линейную связь плотности code smells с cycle time — каждое дополнительное стандартное отклонение плотности добавляло около 30% к медианному cycle time. Стоимость реальная и хорошо задокументированная. И при этом её невозможно перевести на язык CFO без долларовой цифры на тикет. Эту дыру и закрывает формула.

Формула​

tech_debt_cost_per_year =
extra_seconds_per_legacy_ticket
× tickets_per_year_in_legacy_component
× loaded_hourly_rate / 3600

Три члена, одно произведение. Два — простые. Первый — extra_seconds_per_legacy_ticket — место, где большинство анализов жульничает: чистого способа оценить его без per-ticket cost data не существует. Прикидка по памяти недосчитывает на 40–60% по нашему опыту; инженеры стабильно путают, сколько шёл тикет A против тикета B, потому что запоминаются катастрофы, а не медиана. Self-report ещё хуже: те же опросы Stack Overflow, давшие 17ч/неделю на работу с долгом, просят разработчика вспомнить разрыв, что смешивает раздражение с прошедшим временем.

Правильный способ извлечь extra_seconds_per_legacy_ticket — difference-in-means между двумя популяциями компонентов. Тегируем каждый тикет компонентом, в который он попадает (Jira label, ClickUp custom field, Linear scope), и сравниваем распределение стоимости легаси-компонента с распределением сопоставимого по форме greenfield-компонента. Разница, после контроля за типом и severity тикета, — это налог на тикет.

В PanDev Metrics это живёт колонкой в materialized view mv_activity_total_user_issue_daily. Каждый issue_key несёт лейблы, которые Jira отдала на синхронизации, поэтому cost-per-ticket уже группируется по компоненту без джойна с внешними данными. Тот же join, что мы разбирали в cost-per-task-issue-tracking, уровнем агрегации мельче:

SELECT
l.label AS component,
COUNT(DISTINCT m.issue_key) AS tickets,
ROUND(AVG(t.cost_usd), 0) AS avg_cost_per_ticket,
ROUND(AVG(t.hours), 1) AS avg_hours_per_ticket,
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY t.cost_usd), 0) AS median_cost
FROM mv_activity_total_user_issue_daily m
JOIN issue_labels l ON l.issue_key = m.issue_key
JOIN (
SELECT issue_key,
SUM(total_seconds / 3600.0 * r.loaded_hourly_rate) AS cost_usd,
SUM(total_seconds) / 3600.0 AS hours
FROM mv_activity_total_user_issue_daily mm
JOIN engineer_rates r ON r.user_id = mm.user_id
WHERE mm.day_date BETWEEN '2025-05-23' AND '2026-05-22'
GROUP BY issue_key
) t ON t.issue_key = m.issue_key
WHERE m.department_id = 17
GROUP BY l.label
HAVING COUNT(DISTINCT m.issue_key) >= 30
ORDER BY avg_cost_per_ticket DESC;

HAVING COUNT(DISTINCT m.issue_key) >= 30 делает реальную работу — фильтрует бутиковые компоненты, где среднее посчитано по двум тикетам и одной случайности. loaded_hourly_rate — на каждого инженера, считается OverheadCoefficientCronJob раз в месяц, чтобы коэффициент K оставался актуальным при изменениях оргструктуры (разбирали в loaded-hourly-rate-true-cost). Тот же SQL крутит endpoint POST /departments/{id}/finance/tasks, когда вы фильтруете по лейблу компонента в UI.

Как выглядит сравнение по компонентам​

Ниже — реальная таблица с команды из 14 инженеров, окно 12 месяцев, тип тикета ограничен bug + story (без рефакторов, спайков и инцидент-реакций — они искажают сравнение). Пять компонентов, отсортированы по стоимости тикета:

КомпонентГодТикетов/годЧасов среднеМедианаСреднее
A — auth (легаси)201818718.0$1,640$1,820
B — billing (легаси)201914216.2$1,490$1,640
C — payments (смешанный)20219811.0$1,020$1,100
D — onboarding (greenfield)2024767.4$680$720
E — notifications (greenfield)2025546.1$610$640

Bar chart: средняя стоимость тикета по пяти компонентам — auth-легаси $1,820 в начале, greenfield-notifications $640 в конце. Компонент A несёт примерно 3× стоимости тикета компонента E при сопоставимых типах. Колонка «Год» — год последнего значимого рефакторинга, и она ранжируется со стоимостью с неловкой точностью.

Несколько вещей, которые таблица делает, а нарратив — нет. Во-первых, она даёт CFO число на строку — она может спорить с методологией, но не может спорить, что «дорогой компонент» — это неопределённость. Во-вторых, она показывает градиент, а не бинарность. Компонент C частично отрефакторен и приходится примерно посередине по стоимости. Так и выглядят данные «полу-рефакторинга», и именно это убеждает CFO, что формулу не подогнали: промежуточный случай реально лежит посередине.

В-третьих, разрыв между медианой и средним — сигнал длинного хвоста. У компонента A среднее выше медианы на $180 — у легаси-компонента есть p95-выбросы, которых нет у greenfield. Медиана — это что закрывается чаще всего. Среднее — то, что бьёт по бюджету. CFO волнует второе.

Декомпозиция для одного компонента​

Когда разница на тикет получена, остальное — арифметика. Полная декомпозиция компонента A против baseline-greenfield (E):

ПеременнаяЗначениеИсточник
Среднее на тикет — компонент A$1,820mv view, 12 месяцев
Среднее на тикет — компонент E$640mv view, 12 месяцев
Разрыв (лишняя стоимость на легаси-тикет)$1,180A − E
Тикетов/год — компонент A187Jira, 12 месяцев
Средний loaded rate команды$98OverheadCoefficientCronJob, K = 0.42
Подразумеваемые лишние часы на тикет12.0$1,180 ÷ $98
Годовой налог — компонент A$220,660$1,180 × 187

Теперь переворачиваем вопрос на стоимость рефакторинга. Та же команда, план — 3 месяца, 4 инженера:

ПеременнаяЗначение
Инженеров4
Месяцев3
Loaded стоимость на инженера/месяц$7,500
Инвестиция в рефакторинг$90,000
Восстановленный годовой налог (80%)$176,500
Срок окупаемости5.1 месяца
3-летний NPV (10% дисконт, 80% возврата)+$439K

Окупаемость 5 месяцев — заголовочная цифра. $439K — якорь для торга: когда CFO спрашивает «а если отложим ещё на квартал», вот сколько стоит задержка. Тот же приём работает для любого решения по рефакторингу одной фичи; формула идентична той, что мы разбирали в cost-per-feature-sql-formula; меняется только измерение, по которому агрегируем.

Допущение в 80% делает много работы​

Честный момент: «80% налога восстанавливается после рефакторинга» — несущее допущение всего NPV. Откуда оно? Из двух мест.

Первое — наша клиентская база. У нас 11 деплоев, где команды инструментировали per-component cost, провели структурный рефакторинг и пересняли через 6 месяцев. Процент восстановления группируется в диапазоне 65–90%, медиана 78%. Округляем до 80%, потому что это примерно медиана популяции, а CFO не доверяет подозрительно точным некруглым числам. Если хочется консервативно — поставьте 65%; NPV проседает, но окупаемость растягивается всего до 7 месяцев, что всё равно проходит на ура.

Второе — Tom и соавт. (Journal of Systems and Software, 2013) сделали лонгитюдное исследование 16 рефакторингов и зафиксировали восстановление производительности в диапазоне 70–85% в большинстве случаев. Выбросы — либо крошечные рефакторинги, не двигающие цифру (восстановление около 30%), либо полные переписывания, превышавшие baseline (>100%). 70–85% посередине — это про обычный mid-scope рефакторинг, который в реальности и предлагает большинство engineering managers.

Контр-тезис: большинство предложений по рефакторингу не упираются в NPV-математику. Они упираются в дисциплину тегирования тикетов. Если команда не тегирует тикеты компонентами, у формулы нет левой части и упражнение умирает в SQL-редакторе. Видим это стабильно: команды с >90% compliance на component-теги получают чистые цифры; команды ниже 70% видят, как анализ схлопывается в «(без лейбла): 60% всех тикетов», и статистического обхода тут нет. 30 минут за спринт, которые ваш техлид тратит на нудёж за тегами, в большинстве оргов — единственная самая высокорычажная активность для разблокировки бюджета на рефакторинг. CFO не волнует ваш coupling. Её волнует, рассказывают ли данные внутренне непротиворечивую историю.

Где формула ломается​

Формула предполагает, что типы тикетов сопоставимы между легаси и greenfield. Часто — нет. Легаси-компонент решает более тяжёлые задачи в силу того, что является ядром системы — auth, billing, identity. Часть разрыва — настоящая сложность, а не долг. Если применить разрыв в лоб как «налог», вы переоцените возвратимую экономию, и CFO поймает это постфактум, что подожжёт следующее предложение.

Лечение — соединять cost gap с метриками качества кода. Конкретно с двумя:

  • Cyclomatic complexity на файл (SonarQube, CodeClimate, ваш линтер). Легаси-компонент с разрывом стоимости и высокой цикломатикой — это настоящий долг. Легаси-компонент с разрывом стоимости и низкой цикломатикой — скорее внутренне сложная задача, чем долг.
  • Change frequency vs. defect rate. Microsoft Research, Code Hotspots (Bird et al., 2011) показали, что файлы с одновременно высоким churn и высокой сложностью в 5–8 раз чаще содержат дефекты. Если ваш дорогой компонент пробивает оба показателя — налог реальный и возвратимый. Если только один — диагноз размыт.

Второе ограничение: формула — по компоненту. Она не ловит межкомпонентный долг — когда тесная связь auth с billing делает дорогими изменения в обоих сразу. Реальные деньги обычно прячутся именно там, и per-component анализ это пропускает. Мы вытаскиваем это отдельно матрицей co-change в PanDev Metrics, но формула выше его не увидит. Считайте формулу нижней границей стоимости техдолга, а не финальной цифрой.

Что сделать завтра утром​

Тактическая последовательность:

  1. Аудит compliance компонент-тегов в трекере за последние 90 дней. Если ниже 70% — сначала чините это. Бюджет на рефакторинг без чистых component-данных невыигрываем.
  2. Прогоните SQL выше (или эквивалент) за 12 месяцев. Возьмите топ-5 компонентов по cost-per-ticket и низ-5. Посмотрите на разброс глазами.
  3. Возьмите наибольший cost gap, где оба компонента закрывают сопоставимые типы тикетов. Это кандидат на формулу. Не сравнивайте auth с CRUD-админкой — сопоставимость severity важна.
  4. Проверьте разрыв метриками качества кода. Высокая цикломатика + высокий churn = настоящий долг. Если не оба — снижайте уверенность и не обещайте лишнего по recovery.
  5. Соберите одностраничную таблицу. Две колонки: легаси против greenfield. Пять строк: средняя стоимость тикета, тикетов в год, разрыв, годовой налог, окупаемость. CFO больше не прочитает.

Цифра, с которой вы зайдёте в её кабинет, — это extra_seconds × tickets × rate. Реальная работа была в том, чтобы 12 месяцев тикеты тегировались правильно, и умножение оказалось настоящим, а не догадкой. Предложения по рефакторингу проваливаются обычно не на формуле. Они проваливаются, потому что данные внизу никогда не собирали, и CFO это видит.

У техдолга есть цена. Цена — измерима. Большинство инженерных оргов выбирают, через бездействие в дисциплине тегирования, её не знать.

Готовы увидеть метрики своей команды?

30-минутная персональная демонстрация. Покажем как PanDev Metrics решает задачи именно вашей команды.

Забронировать демо