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

Термин «технический долг» придумал Уорд Каннингем ещё в 1992 году как метафору из финансов: можно быстро выпустить код сейчас и заплатить за это позже процентами в виде замедления разработки, или сразу написать правильно и заплатить временем вперёд. Метафора прижилась, но её чаще используют как ругательство, чем как рабочий инструмент — «у нас много технического долга» говорят про любой код, который не нравится, без единого числа за этой фразой.
Разница между «плохим кодом» и «техническим долгом» в том, что долг — это осознанный компромисс с известной ценой, а не просто ошибка. Если команда понимает, что обходит архитектурное решение ради срока, и фиксирует это, перед ней долг. Если код написан небрежно без всякого понимания последствий, перед ней просто плохой код, который неверно называть красивым финансовым термином. Разбираться, где долг действительно есть и сколько он стоит, помогает команда YUSMP Group, которая делает независимый аудит кода и инфраструктуры, когда своей оценке в команде не доверяют или на неё не хватает ресурсов.
Что такое технический долг и чем он отличается от «плохого кода»
Технический долг — это разрыв между текущим состоянием кодовой базы и тем состоянием, в котором она отражала бы всё, что команда уже знает о продукте на сегодня. Он не возникает из злого умысла: требования меняются, сроки давят, а архитектурные решения, разумные полгода назад, перестают быть разумными сейчас.
Ключевое отличие от простой ошибки — осознанность. Решение сделать прямой SQL-запрос вместо абстракции над хранилищем, чтобы успеть к релизу, это долг: команда знает цену и временные рамки. Забытый `TODO` без контекста или скопированный без понимания код — это не долг в строгом смысле, а просто техническая небрежность, у которой нет плана выплаты.
Какие виды технического долга бывают
Мартин Фаулер предложил делить долг по двум осям: осознанный он или нет, и обоснованный или безрассудный. Решение «мы знаем, что упрощаем, и знаем цену» — осознанный обоснованный долг. Решение, принятое без понимания последствий, — безрассудный долг, и именно он обычно самый дорогой в выплате, потому что никто не фиксировал, где он возник.
| Вид долга | Где возникает | Типичный пример |
|---|---|---|
| Долг кода | Логика и стиль реализации | Дублирование, длинные методы, отсутствие тестов |
| Архитектурный долг | Структура системы | Модуль, который должен был стать отдельным сервисом, но остался частью монолита |
| Долг тестов | Покрытие и качество проверок | Тесты есть, но проверяют только happy path |
| Долг документации | Знание о системе вне кода | Нет описания, почему модуль устроен именно так |
Эта таблица полезна не как академическая классификация, а как чек-лист при аудите: если команда ищет долг только в коде, она гарантированно пропустит архитектурный и документационный, которые часто стоят дороже.
Как понять, что долг уже критический
Долг становится проблемой не в момент возникновения, а в момент, когда начинает заметно замедлять команду. Распознать этот момент можно по нескольким признакам, которые видны без всяких инструментов.
- Время на типовую фичу растёт от релиза к релизу, хотя сложность задач в среднем та же
- Баги регулярно возвращаются в одни и те же 2-3 модуля, несмотря на фиксы
- Разработчики откровенно боятся трогать определённые файлы и обходят их стороной
- В код постоянно копируют фрагменты вместо того, чтобы переиспользовать существующую абстракцию
- Оценка задач систематически оказывается заниженной именно в одних и тех же частях системы
Если присутствуют хотя бы два признака из списка одновременно, долг в этой части системы уже перешёл из фоновой неприятности в фактор, который стоит измерить отдельно.
Как измерить технический долг — 4 рабочих метрики
Измерять долг в абсолютных числах почти бессмысленно: «цикломатическая сложность 18» ничего не говорит сама по себе. Полезна динамика — растёт показатель от релиза к релизу или падает. Вот четыре метрики, которые вместе дают достаточно полную картину без экзотических инструментов.
- Цикломатическая сложность функций. Считает количество независимых путей выполнения внутри функции. Статические анализаторы (PHPStan, PHP_CodeSniffer с правилами сложности, SonarQube) считают её автоматически; рост среднего значения по модулю от спринта к спринту — тревожный сигнал даже без привязки к конкретному порогу.
- Процент дублирования кода. Доля строк, которые почти дословно повторяются в разных местах проекта. Высокий процент означает, что любое изменение правила нужно внести в несколько мест одновременно, а значит рано или поздно забудут об одном из них.
- Динамика тестового покрытия. Не само значение покрытия, а его направление: падает оно при добавлении новых фич или растёт вместе с кодовой базой. Падающее покрытие — прямой индикатор того, что долг копится быстрее, чем команда успевает его осознавать.
- Счётчик TODO/FIXME и время прохождения изменений. Количество пометок в коде — грубый, но честный индикатор известного долга. Вместе с ним стоит смотреть на MTTR, среднее время от коммита до продакшена: если оно растёт вместе со счётчиком, долг уже влияет на скорость доставки, а не остаётся теоретической проблемой.
Важно именно сочетание метрик: сложность без дублирования может означать просто сложную предметную область, а не долг. Падающее покрытие вместе с ростом количества TODO — куда более надёжный сигнал, чем любая метрика сама по себе.
Сколько стоит технический долг в деньгах
Перевести долг в деньги проще, чем кажется, если не гнаться за точностью до копейки. Базовая формула: время команды, которое уходит на баги и доработки именно в проблемных модулях, умножить на стоимость часа разработчика.
Пример: если за квартал команда потратила 120 часов на баги в одном модуле оплаты при средней стоимости часа 3000 ₽, прямые издержки долга в этом модуле составили 360 000 ₽ за квартал — без учёта упущенной скорости разработки новых фич, которую посчитать сложнее, но которая обычно выше прямых издержек в полтора-два раза.
Такой расчёт не нужно делать идеально точным. Его задача — перевести спор «код плохой» в формат, понятный бизнесу: конкретная сумма в квартал убеждает выделить время на рефакторинг куда быстрее, чем жалоба разработчика на неудобный код.
Как расставить приоритеты — что гасить первым
Когда долгов несколько, приоритизация сводится к простой матрице: насколько сильно конкретный участок замедляет команду сейчас и сколько усилий займёт его исправление. Модули с высоким влиянием и низкими усилиями на исправление гасятся первыми — это самая дешёвая победа. Высокое влияние при высоких усилиях требует отдельного планирования, а не попытки закрыть всё одним спринтом.
Если нужен более формальный метод распределения ограниченного времени между задачами на долг, подойдёт любая стандартная матрица приоритизации — например RICE или ICE, адаптированная под метрики из предыдущего раздела вместо продуктовых метрик. Разворачивать сразу несколько методов одновременно не нужно: для долга достаточно одной простой матрицы влияние/усилия.
Как работать с долгом на постоянной основе
Долг, который гасят разовым авралом раз в квартал, копится снова почти сразу после такого аврала. Устойчивее работает постоянная практика, встроенная в обычный процесс разработки.
- Квота времени в каждом спринте. Фиксированная доля ёмкости команды (обычно 10-20%), которая закладывается в план заранее, а не остаётся «если время будет»
- Правило бойскаута. Любой участок кода, к которому прикасаешься по другой задаче, оставляют чуть чище, чем он был, не пытаясь переписать всё сразу
- Назначенный ответственный за долг. Человек, который ведёт список известного долга, приоритизирует его вместе с продуктом и следит, чтобы квота не обнулялась под давлением сроков
Эти три практики работают вместе: квота даёт время, правило бойскаута превращает любую доработку в маленький вклад в здоровье кода, а ответственный не даёт квоте молча исчезнуть в следующем спринте.
Когда нужен внешний аудит
Иногда команда гасит долг «на ощупь»: чувствует, что код тяжёлый, но не может назвать цифры, на которые можно опереться в разговоре с бизнесом или инвестором. В такой ситуации помогает взгляд со стороны — независимая проверка, которая считает те же метрики, что описаны выше, но без внутренних договорённостей «мы тут привыкли так писать».
Внешний аудит особенно уместен перед крупными решениями: привлечением инвестиций, продажей продукта, сменой подрядчика или масштабным наймом в команду, которой предстоит разбираться с чужим кодом. В этих случаях объективная оценка стоит значительно дешевле, чем решение, принятое вслепую.
Частые вопросы
Технический долг — это всегда плохо?
Нет. Осознанный долг — рабочий инструмент: команда сознательно упрощает решение ради срока и планирует выплату позже. Плохо не само его наличие, а долг, который никто не измеряет и не планирует гасить.
Как часто нужно измерять технический долг?
Метрики вроде цикломатической сложности и дублирования кода имеет смысл снимать на каждый релиз или хотя бы раз в спринт — важна динамика, а не разовое абсолютное значение.
Можно ли измерить технический долг без специальных инструментов?
Частично да: счётчик TODO/FIXME в коде и время прохождения типовой доработки можно отследить вручную. Для цикломатической сложности и процента дублирования практичнее статический анализатор, но и без него видно направление.
Что делать, если долга накопилось слишком много?
Не пытаться закрыть всё одним большим рефакторингом. Выбрать 2-3 самых дорогих по времени команды модуля и гасить их по очереди, не останавливая разработку новых фич.
Чем технический долг отличается от обычного рефакторинга?
Рефакторинг — это действие, долг — это состояние, которое рефакторинг устраняет. Можно рефакторить код, в котором нет накопленного долга, просто для удобства; а можно копить долг, вообще не рефакторя ничего.
Кто в команде должен отвечать за технический долг?
Обычно это тимлид или назначенный технический владелец модуля: он ведёт список известного долга, приоритизирует его вместе с продуктом и следит, чтобы квота на выплату не обнулялась в первом же спринте под давлением сроков.
Сколько времени в спринте закладывать на выплату долга?
Частый ориентир — 10-20% ёмкости спринта постоянной квотой, а не разовый аврал раз в квартал. Конкретная цифра зависит от возраста кодовой базы и темпа появления новых багов.



