Технический долг: как его считать, обсуждать с бизнесом и гасить

Менеджмент6 мин чтения
  • #technical-debt
  • #refactoring
  • #product-management
  • #engineering

Термин «технический долг» Уорд Каннингем ввёл именно как метафору с деньгами: вы берёте в долг (быстрое, неоптимальное решение), чтобы быстрее выпустить фичу, и платите проценты (замедление следующих релизов), пока не вернёте долг (рефакторинг). Как любая метафора, она ломается, если понять её слишком буквально. Разберём, какие бывают «долги», как считать «проценты» и почему переписать всё — почти всегда ошибка.

Три рода технического долга

Не любой плохой код — это «долг». Полезно различать три принципиально разных вида.

Революционный (осознанный) долг

«Мы знаем, что это костыль, но выпускаем к сроку; запишем в бэклог как долг и вернём позже». Это нормальный инженерный инструмент. Сроки и приоритеты реальны, и иногда быстрое решение с осознанным компромиссом — правильно. Каннингем имел в виду именно этот случай: долг взят сознательно, как инвестиция, и запланирован к возврату.

Ключевое слово — «запланирован к возврату». Если вы взяли долг и не занесли его в бэклог, не позвали его «погасить» в следующих итерациях — это уже не осознанный долг, а просто плохой код.

Эволюционный (неосознанный) долг

Код писался нормально, по当时的 меркам хорошо. Но требования менялись, бизнес развивался, и архитектура перестала соответствовать новой реальности. Это не вина автора: вчера это было правильным решением, сегодня оно устарело.

Пример: сервис начинался как монолит на одном сервере. Трафик вырос в 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% случаев заканчивается провалом, потому что идёт годами без новых фич и теряет неявные знания старого кода.

Что почитать