Технический долг: как его считать, обсуждать с бизнесом и гасить
Термин «технический долг» Уорд Каннингем ввёл именно как метафору с деньгами: вы берёте в долг (быстрое, неоптимальное решение), чтобы быстрее выпустить фичу, и платите проценты (замедление следующих релизов), пока не вернёте долг (рефакторинг). Как любая метафора, она ломается, если понять её слишком буквально. Разберём, какие бывают «долги», как считать «проценты» и почему переписать всё — почти всегда ошибка.
Три рода технического долга
Не любой плохой код — это «долг». Полезно различать три принципиально разных вида.
Революционный (осознанный) долг
«Мы знаем, что это костыль, но выпускаем к сроку; запишем в бэклог как долг и вернём позже». Это нормальный инженерный инструмент. Сроки и приоритеты реальны, и иногда быстрое решение с осознанным компромиссом — правильно. Каннингем имел в виду именно этот случай: долг взят сознательно, как инвестиция, и запланирован к возврату.
Ключевое слово — «запланирован к возврату». Если вы взяли долг и не занесли его в бэклог, не позвали его «погасить» в следующих итерациях — это уже не осознанный долг, а просто плохой код.
Эволюционный (неосознанный) долг
Код писался нормально, по当时的 меркам хорошо. Но требования менялись, бизнес развивался, и архитектура перестала соответствовать новой реальности. Это не вина автора: вчера это было правильным решением, сегодня оно устарело.
Пример: сервис начинался как монолит на одном сервере. Трафик вырос в 100 раз, и теперь архитектура, оптимизированная под один процессор, узкое место. Это не «плохой код», а свойство развивающейся системы. Лечится не переписыванием «плохого», а эволюцией архитектуры.
Гнилостный долг
Код низкого качества: дублирование, запутанная логика, отсутствие тестов, copy-paste, который плодится. Это настоящий долг с высокими процентами — каждая новая фича в этой области стоит дороже и вносит больше багов.
Отличие от эволюционного: гнилостный долг возникает не от смены требований, а от низкой дисциплины написания. И именно он имеет свойство распространяться: разработчики копируют плохой код, потому что «так уже принято в этом модуле», и долг множится.
Как считать «проценты»
«Проценты» технического долга — это не строки кода и не абстрактные «technical debt units». Это замедление разработки. Конкретнее: сколько времени занимает добавление фичи в этой части кода сейчас, и насколько это дольше, чем могло бы быть в чистой архитектуре.
Если в модуле X добавление простой фичи занимает неделю (из-за запутанности, страха что-то сломать, ручного тестирования), а в чистой архитектуре заняло бы день — разница в 4 дня и есть «процент» по долгу этого модуля. За год накопится существенная сумма потерянного времени.
Полезная метрика — cycle time по областям кода. Если в одних модулях фичи делаются за 2 дня, а в других за 10 — это сигнал долга во вторых. Точные цифры разнятся, но тренд важнее абсолютов.
Как обсуждать с бизнесом
Главная ошибка инженеров — объяснять техдолг на языке инженеров: «нам надо отрефакторить модуль авторизации, там спагетти из колбэков». Бизнесу всё равно, какие там колбэки. Переводите на язык денег и сроков:
Сейчас добавление нового способа авторизации занимает 3 недели из-за запутанного модуля. После рефакторинга это будет занимать 3 дня. Рефакторинг стоит 2 недели. Окупится на третьем способе авторизации, а дальше — каждая фича в этой области в 5 раз дешевле.
Бизнес понимает «окупаемость инвестиций» и «срок окупаемости». Рефакторинг ради «чистого кода» — не понять. Рефакторинг ради «каждая следующая фича в 5 раз дешевле» — понять.
Второй аргумент, который работает: риск багов. Запутанный код с отсутствием тестов — источник регрессий. Переводите в деньги: «последние три бага в проде вышли из этого модуля и стоили N часов разбора + недовольство клиентов». Risk-based аргумент часто убедительнее, чем cost-based.
Главное правило погашения: никогда «не переписать всё»
Классическая ловушка: «этот код такой запутанный, давайте перепишем модуль (или весь сервис) с нуля на современном стеке». В 90% случаев это заканчивается катастрофой.
Почему: переписывание идёт месяцами или годами, всё это время продукт не получает новых фич (старую ветку не развивают, новую не выпускают). К моменту, когда «новое» готово, рынок ушёл вперёд, конкуренты выпустили десятки релизов. И «новое», написанное с нуля, повторяет часть ошибок старого, потому что за годы работы старый код накопил неявные знания о краевых случаях, которые в новой версии не учтены.
Знаменитые провалы: переписывание Netscape/Mozilla (Mozilla Foundation пришлось вернуться к старой кодовой базе, потому что «новая» задержалась на годы), переписывание Borland, история Joel Spolsky о переписывании архитектуры в Fog Creek.
Правильный подход — постепенная эволюция, без «big bang rewrite»:
- Boy scout rule — оставляй код чуть лучше, чем нашёл. В каждой затронутой фиче слегка улучшай модуль: вынеси функцию, добавь тест, переименуй переменную. За год модуль станет чище без отдельного «проекта рефакторинга».
- Strangler fig pattern — постепенная замена: новая фича пишется в новой архитектуре, старая остаётся; со временем старая вытесняется. Не «выключить старое и включить новое», а плавный переход.
- Тесты как фундамент — прежде чем рефакторить, покрой тестами текущее поведение. Иначе не отличишь «сломал» от «исправил».
Когда «переписать» всё-таки оправдано
Из каждого правила есть исключения. Переписать с нуля имеет смысл, когда:
- текущий стек технически мёртв (framework больше не поддерживается, нет специалистов);
- кодовая база настолько гнилая, что любая попытка эволюции дороже переписывания (но это редкость, обычно переоценивают);
- бизнес-модель продукта меняется радикально, и старая архитектура не годится для новой.
Но даже в этих случаях — не «переписать за полгода и выпустить разом», а strangler-подход: вынести часть в новую систему, переключить трафик, продолжать.
Короткое summary
Технический долг — не любой плохой код, а три разных явления. Революционный (осознанный) — нормальный инструмент, если запланирован к возврату. Эволюционный — устаревшая архитектура, не вина автора. Гнилостный — низкокачественный код, который плодится; самый дорогой. «Проценты» долга измеряются замедлением разработки, не строками. Обсуждать с бизнесом надо на языке денег и сроков окупаемости (в 5 раз дешевле каждая следующая фича), а не «чистого кода». Главное правило погашения — никогда «не переписать всё разом»: boy scout rule, strangler fig pattern и тесты как фундамент. «Big bang rewrite» в 90% случаев заканчивается провалом, потому что идёт годами без новых фич и теряет неявные знания старого кода.
Что почитать
- Ward Cunningham: «The WyCash Portfolio Management System» — оригинальное выступление, где введён термин «technical debt».
- Martin Fowler: «Technical Debt Quadrant» — разбор типов долга (reckless/prudent × deliberate/inadvertent).
- Joel Spolsky: «Things You Should Never Do» — классика про провалы переписывания с нуля.