В каждом бэклоге живёт папка тикетов с пометкой про долг: сотни серых карточек, созданных за три года, и закрытых за то же время штуки четыре. Каждый квартал команда собирается и соглашается: долг душит поставку, пора выделять время. Решение вносится в протокол и растворяется до следующего квартала, а карточки тихо стареют. Соседняя команда поступила иначе: она зарезервировала пятницу второй недели спринта под погашение и за год закрыла две трети своего реестра, не отменив ни одной фичи. Разница не в талантах и не в свободных руках, а в устройстве: у первой команды долг лежит бумагой, у второй работает стратегией. Устройство видно на одной планёрке: у первой разговор о долге случается по календарю, у второй по факту, и это меняет всё.

Метафора долга от Уорда Каннингема и её процент

Метафору придумал Уорд Каннингем в 1992 году, сравнив первый выпуск кода с займом: небольшой долг ускоряет разработку, если возврат следует быстро и по частям, а просроченный займ наращивает процент. Позже он отдельно подчёркивал: речь шла не о грязном коде как таковом, а о коде, написанном быстрее полного понимания задачи. Первый заход работает как черновик, погашение вторым заходом происходит, когда картина уже ясна, и в этом случае метафора честная, потому что возврат планируется с самого начала. В исходной формулировке было и предупреждение: невозвращённый долг делает дальнейшие изменения рискованными, потому что код перестаёт соответствовать пониманию команды, а скорость следующих правок падает.

Мартин Фаулер разложил метафору на арифметику, понятную даже финансисту. Хаотичная структура модулей растягивает новую фичу с четырёх дней до шести, и разница в два дня это процент по долгу. Полное погашение стоит пяти дней чистки, и для одной задачи оно убыточно: девять дней против шести. Но если впереди ещё две похожие фичи, чистка окупается с запасом. Отсюда рабочее правило: гасить постепенно, по кусочку за раз, и гасить именно там, где код трогают чаще всего, потому что постепенность сама наводит усилия на самые доходные участки.

Второе отличие от финансов хитрее. Кодовый процент начисляется не временем, а прикосновением: у финансового займа процент капает сам, у кода он списывается только в момент правки. Ужасный модуль, который никто не открывает годами, ничего не стоит команде, а перегруженный участок в горячем пути списывает по два часа с каждой фичи. Отсюда же диагноз типовым бэклогам, где сотня карточек "улучшить код" выглядят одинаково важными: без понятия процента у них нет и приоритета.

Сила метафоры не в красоте, а в переводе. Сказать "код неопрятный" значит завязать спор о вкусе, где каждая сторона по-своему права. Сказать "на этом участке каждая фича переплачивает два дня" значит перевести разговор на язык бюджета, на котором инженер и менеджер наконец говорят одинаково. Метафора различает и заём с разорением: взять под понятный процент это решение, а не платить вовсе это банкротство в рассрочку.

Не весь долг стоит гасить и границы для чистки

Фаулер же предложил квадрат из двух осей: осознанность долга и аккуратность его причин. Осознанный осторожный долг это честное решение выпустить черновик сейчас и вернуть деньги позже. Осознанный безрассудный это вечное "нет времени на проектирование". Неосознанный безрассудный хуже всех: команда не знает про слоистость и нарезает логику куда попало. Самый честный случай неосознанный осторожный: узнали позже, что так нельзя, записали и вернулись. Опаснее всего именно невидимый долг, поэтому первая задача стратегии не гасить, а увидеть.

Из различия процента следует и граница чистки. Стабильный, но кривой модуль за пределами рабочих путей можно не трогать годами: его процент равен нулю, а чистка съест недели без всякой отдачи. Горячие модули требуют обратной политики, нулевой терпимости к новому хаосу, потому что каждый коммит там платит. Простой индикатор горячести лежит в журнале Git:

git log --since="1 year ago" --name-only --pretty=format: -- billing/ | sort | uniq -c | sort -rn | head

Список самых правимых файлов за год часто противоречит интуиции: самым дорогим участком оказывается не тот, о котором шумят, а тихий обработчик платежей, тронутый сорок раз.

Практичный пример честного долга выглядит так. Команда строит интеграцию по непонятному протоколу и пишет первый вариант в лоб, без слоёв, зато за три дня, помечая место маркером и записью в реестре. Через месяц протокол освоен, и тот же модуль переписывается начисто за два дня по готовой карте. Суммарно это дешевле идеального первого варианта, потому что идеальный вариант на непонятом протоколе всё равно оказался бы черновиком, только долгим и без пометок. Граница термина тоже требует аккуратности: устаревшая версия библиотеки это обслуживание, а не долг в этом смысле. Долгом здесь называется только внутреннее устройство кода, замедляющее правки, и смешение этих вещей размывает разговор быстрее всего.

Живой реестр долга против кладбища тикетов

Формальный подход проваливается по понятной причине: тикет в общем бэклоге соревнуется за внимание с фичами, и фича всегда победит, потому что у неё есть спонсор и дата. Пустая карточка "почистить модуль" без процента и владельца обречена жить до конца времён, и через год её существование скорее вредит: менеджер честно отвечает, что долг в бэклоге есть, значит и проблема учтена. Живой реестр отличается ровно тремя свойствами: у каждой записи есть процент, владелец и маршрут погашения. Вот запись из реестра, которая работает:

Долг: обработчик подписок дублирует расчёт скидок из корзины
Место: billing/subscription.py рядом с billing/cart.py
Процент: любая правка скидок требует правки двух модулей и двух тестов
Погашение: вынести расчёт скидок в общий модуль, оценка три дня
Владелец: команда биллинга

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

Хранится реестр рядом с кодом или в отдельном проекте трекера с одним фильтром, форма вторична. Существенно другое: запись должна всплывать в момент планирования фичи того же модуля, иначе и реестр станет кладбищем, только аккуратным. После каждого погашения процент по участку пересчитывается, а раз в квартал реестр чистится: записи, не пережившие смену архитектуры, удаляются без сожалений, потому что мёртвые строки разъедают доверие ко всем остальным.

Выделенное время на погашение и его размер

Расхожие схемы выделения времени различаются жёсткостью. Первая: фиксированная доля ёмкости спринта, обычно от десятой части до четверти, зарезервированная под погашение и защищённая от перехвата. Вторая: выделенная неделя после крупного выпуска, когда код и так в головах. Третья: привязка погашения к фичам, когда команда, трогающая горячий модуль, обязана почистить кусочек по пути. У схем есть общий риск: выделенное время красиво выглядит в протоколе и первой режется при дедлайне, поэтому его защищают не обещаниями, а местом в самом определении готовности спринта.

Отдельно стоит предостеречь от долговых спринтов, целиком отданных под чистку. На бумаге это ударная работа, в жизни редкий бизнес выдержит месяц без новых функций, а команда после недели "просто чиним кривость" работает рассеянно и по инерции. Малые доли ёмкости живут годами, потому что не ломают ритм поставки. Правило скаута, оставить код чище, чем нашёл, работает только на горячих путях: на холодных участках оно превращается в чистку ради чистки и сжигает доверие к самой идее. Есть у выделенного времени и тихая выгода для новичков: задача по погашению с готовой записью даёт карту модуля, ограниченный тестами риск и честное знакомство с целым участком за одну неделю. Арифметика доли проста и убеждает лучше лозунгов. Пять инженеров и двухнедельный спринт дают пятьдесят человеко-дней, десятая часть это пять дней, четверть двенадцать. Пяти дней хватает на два-три погашения из реестра среднего размера, и за квартал такой политики самые дорогие участки уже дышат ровнее.

Измерения и инструменты, делающие долг видимым

Статический анализ даёт первую опору. Сервисы вроде SonarQube считают дублирование, сложность функций и оценку стоимости исправлений, а анализаторы в непрерывной интеграции не пускают новый хаос в горячие модули. Вторая опора самодельная и точнее любой метрики: дневник трения. Команда в течение квартала отмечает случаи, когда правка заняла дольше расчётного из-за структуры кода, и записывает участок. К концу квартала картина процента по модулям складывается сама, без единой спорной формулы.

Метрики требуют одной оговорки: показатель показывает, где искать, а решение принимает человек. Погоня за снижением метрик ради самих метрик рождает переписывание рабочего кода под красоту графика, и это чистый вред. Третья опора маркеры в коде со ссылкой на реестр, по которым считается текущий хвост:

grep -rn "TODO debt:" --include="*.py" src | wc -l

Число само по себе ничего не решает, но его динамика на планёрке честнее любых рапортов: растёт хвост или убывает. У статического анализа есть предел, о котором стоит помнить: он не знает, как часто участок трогают руками и сколько стоит каждая правка, поэтому его списки работают вместе с журналом Git и дневником трения, а не вместо них.

Кто отвечает за долг и разговор с бизнесом

Виноватых в долге искать бессмысленно: он рождается из нормальной неизвестности, из сжатых сроков и из кода, который писали уже ушедшие люди. Отвечать должны обе стороны: команда видит процент, бизнес владеет приоритетами. Зрелая форма соучастия выглядит скучно: раз в квартал команда приносит короткий отчёт, сколько часов ушло на переплату по трём самым горячим участкам и сколько вернулось после погашения. Цифры скромные, зато разговор перестаёт быть спором вкусов.

Помогает и переименование задач. Не "отрефакторить модуль рассылок", а "снизить цену следующей фичи рассылок с шести дней до четырёх": менеджер не обязан понимать архитектуру, но цену фичи понимает всегда. И последнее: просить время под долг выгоднее после собственной победы, когда команда только что отдала фичу раньше срока, а не в разгар горящих сроков. Доверие к стратегии зарабатывается поставкой, а не презентациями.

План внедрения для команды с забитым бэклогом

Команде, у которой долг копился годами, разумнее начинать не с анонса, а с инвентаризации. Порядок выглядит так:

  1. Собрать старые карточки про долг в одном списке и выбросить те, что не пережили смену архитектуры;
  2. Оценить для каждого участка процент, сколько лишних часов стоит типовая правка;
  3. Отсортировать записи по произведению процента и частоты прикосновений, а не по возрасту карточек;
  4. Закрепить фиксированную долю ёмкости спринта и включить её в определение готовности, чтобы её нельзя было снять одним решением;
  5. Привязать крупные погашения к ближайшим фичам тех же модулей и проходить их вместе;
  6. Вести реестр живым: закрыли запись, пересчитали процент, удалили мёртвые строки.

Через квартал такой работы у команды появляется то, чего не даёт ни один протокол: знание своих процентов. Разговор о долгах перестаёт быть формальностью, потому что каждая просьба о времени подкреплена ценой и датой возврата. Дальше стратегия работает на инерции: каждый погашенный участок снижает процент соседних записей, и реестр сжимается сам.

Технический долг сам по себе не порок, порок незнание его цены. Команда, которая знает проценты по своим модулям, берёт долг осознанно и возвращает его на горячих путях, пока он дешёвый. Команда, которая складывает карточки в бэклог без процента и владельца, имитирует управление: бумаги много, а процент капает. Разница между ними не в бюджете, а в трёх вещах: живой реестр, защищённая доля времени и привычка гасить там, где чаще трогаешь. Остальные ухищрения вторичны.