Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Технический долг: управление и рефакторинг

Технический долг: как им управлять, а не оправдывать

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

Прямой ответ

Технический долг — не «старый код» и не синоним плохого: это осознанно или случайно взятые обязательства, за которые команда платит процентами (замедление изменений, баги, сложность онбординга); «старый, но понятный и работающий код» — не долг, пока его не нужно менять. Управление долгом — процесс из четырёх шагов. Инвентаризация: реестр известных долгов с описанием «что болит и где» (модуль/симптом/оценка процентов — без реестра долг мифологизируется в обе стороны: «всё ужасно» и «долга нет»). Приоритизация по процентам, не по эстетике: в топ — долги на горячих путях изменений (модуль, который правят каждый спринт, и где каждая правка — боль — дороже «страшного, но не трогаемого» легаси), и долги-риски (отсутствие тестов у критичной логики, незакрытые CVE — это тоже долг). Оплата малыми шагами в потоке: рефакторинг как часть задач, а не мифический «спринт на рефакторинг, который никогда не дадут» (правило бойскаута + 10–20% времени задач на расчистку вокруг — устойчивее квартальных героических переписываний). Профилактика: ревью (чек-лист), тесты на новое, ADR на решения (документирование) — долг дешевле не брать. И разговор с бизнесом: не «нам нужен рефакторинг» (звучит как каприз), а «эта область замедляет фичи X и Y в 2 раза и дала 3 бага за квартал — расчистка на N дней окупится к такому-то сроку» — язык стоимости и рисков, не вкуса.

Что считать долгом: квадрат и границы

Полезная классификация (квадрант Фаулера): осознанный-преднамеренный («знаем, что костыль — дедлайн, вернёмся» — нормальный кредит с записью в реестр); осознанный-непреднамеренный («теперь понимаем, как надо было» — опыт пришёл после кода — рефакторинг по мере касания); неосознанный обоих видов — самые дорогие (команда не знает, что платит — отсюда ценность внешнего взгляда/ревью). Границы честности: не всякий старый код — долг (стабильный модуль без изменений — актив, не пассив — переписывание рабочего кода «потому что устарел» — новый долг с рисками регрессий); не всякий быстрый код — долг (решение под дедлайн с тестами и явной записью — кредит с планом, не позор); и «долг» не отменяет контекст решения (код, казавшийся правильным при данных 2023 года, мог быть правильным — переоценка — не повод для blame). Практический тест «долг ли это»: замедляет ли изменения сегодня (метрика: время правок в модуле против средних, частота багов — данные из трекера и git-истории), несёт ли риск (нет тестов на критичном пути, зависимость без обновлений), мешает ли людям (онбординг в модуль — недели, страх править). Три «нет» — не долг, как бы код ни выглядел.

Инвентаризация и приоритизация

Реестр долга — живой документ/раздел трекера: запись = «область + симптом + проценты + оценка работы + связанные риски»; источники записей: ретроспективы («правил модуль X три дня вместо трёх часов»), баг-репорты (кластеризация по модулям — где баги липнут), code review-паттерны (одни и те же замечания в одном месте — сигнал), метрики (сложность/покрытие тестами/частота изменений — hotspot-анализ git-истории: частые изменения × высокая сложность = топ-приоритет — объективнее мнений). Приоритизация — произведение «проценты × вероятность касания»: модуль, который правят каждый спринт и который больно править — топ; мёртвый ужасный код — очередь «когда-нибудь, если оживёт»; риски безопасности/данных — вне очереди (это не «долг на потом», а незакрытая задача — OWASP-рамка помогает аргументировать). Обновление реестра — часть рутины (закрытый долг вычёркивается с фиксацией выигрыша — «модуль Y: время правки с 3 дней до 4 часов» — история побед нужна для будущих разговоров с бизнесом); реестр без приоритов и обновлений — кладбище записей, демотивирующее хуже отсутствия.

Оплата: рефакторинг в потоке работы

Стратегии по убыванию устойчивости: правило бойскаута в задачах (правя фичу, расчистить вокруг: переименовать, разбить функцию, добавить тест на тронутое — 10–20% времени задачи, незаметно для плана); рефакторинг перед изменением (нужна фича в плохом модуле — сначала подготовить почву — выделение функции/интерфейса, потом фича в чистое место — «двойная работа» окупается следующей правкой); целевые проекты для структурного (миграция фреймворка, разделение модуля — план с этапами и промежуточными рабочими состояниями — expand/contract-мышление из миграций БД переносится на код); «большой переписывательный взрыв» — крайность (переписать всё и выкатить разом — месяцы без фич, риск «второй системы» и замороженного легаси с багфиксами в двух кодовых базах — почти всегда проигрывает пошаговой замене — strangler fig: новое рядом со старым, трафик переключается постепенно — деплой-стратегии в применении к архитектуре). Обязательное условие любого рефакторинга: тесты до/вокруг (рефакторинг без тестов — не рефакторинг, а азартная игра — characterization-тесты на текущее поведение первым шагом — тестовая фиксация по духу) и отдельные коммиты/PR «рефакторинг» и «изменение поведения» (ревью смешанного диффа — боль).

Практический сценарий: квартал с реестром долга

Команда 6 человек, ощущение «легаси душит, фичи еле ползут». Спринт 0 — инвентаризация: ретро-сессия + git-анализ (hotspots: модуль расчёта скидок — 40% правок проекта при 900 строках и нуле тестов; кластер багов — интеграция доставки). Реестр — 14 записей с оценками; приоритеты: скидки (проценты × касания — топ), доставка (баг-риск), «страшный модуль отчётов, который никто не трогает» — низкий приоритет вопреки эмоции (не касается — не платим). Процесс: 15% времени задач — расчистка вокруг (правило записано в DoD); перед каждой фичей в скидках — characterization-тесты текущего поведения (два спринта: 30 тестов зафиксировали поведение, включая найденный баг округления — бизнес подтвердил «это баг» — первый оплаченный долг с видимым эффектом). К концу квартала: модуль скидок разделён на расчёт/применение (правку фичи делает один человек за часы), баги скидок — ноль за месяц, история побед («3 дня → 4 часа», «−100% багов») ушла в квартальный отчёт — разговор о бюджете на миграцию доставки в следующем квартале прошёл с цифрами, не с эмоциями. Что не делали: «переписать всё» не планировали ни разу — поток малых оплат оказался быстрее героизма.

FAQ

Сколько времени закладывать на техдолг в спринт? Ориентир 10–20% ёмкости в потоке задач (не отдельная строка плана, которую режут первой, а встроенная часть оценок); квартальные «долговые спринты» — реже и хуже (потеря ритма фич, соблазн отменить в кризис); исключение — целевые структурные проекты с явным бюджетом и вехами.

Как убедить бизнес выделить время? Языком денег и рисков: замедление конкретных фич (время до/после — данные), баги и их цена (поддержка/репутация), риски (безопасность, уход разработчиков из неподъёмного кода — найм дороже рефакторинга); «нам надо переписать» без цифр проигрывает любой фиче — и справедливо, пока нет цифр.

Тесты на легаси без тестов — с чего начать? С путей, которые будут тронуты (characterization-тесты текущего поведения — фиксация «как есть», включая странности — перед правкой), затем горячие пути по баг-статистике; цель не «100% покрытия легаси» (часто невозможно/нецелесообразно), а страховка на изменяемом — покрытие растёт там, где код живёт.

Что делать с долгом в чужом коде, который «нельзя трогать»? Разделять «нельзя» по причинам: страх регрессий (лечится тестами — characterization), организационный запрет (владельцы/команда — согласование через риски и план), техническая хрупкость (нет тестов, побочные эффекты — начинать с изоляции/обёрток); абсолютное «нельзя» бывает редко — чаще не договорились об условиях; и да — часть легаси правда лучше не трогать (не касается — не платит проценты — оставить в покое осознанно).

Источники: Martin Fowler (Technical Debt, Quadrant), Refactoring Guru — ссылки в шапке.