О чём этот модуль
Технический долг часто обсуждают языком эстетического отвращения:
"Код грязный".
"Архитектура ужасная".
"Legacy надо переписать".
"Нормальные инженеры так не делают".
Такие высказывания могут передавать реальное напряжение инженеров, но ещё не показывают, где находится долг, какую цену он создаёт и почему организация должна вложиться в его изменение.
Технический долг — не синоним плохого кода. Это метафора отложенной цены решения. В формулировке Мартина Фаулера дополнительное усилие, которое приходится тратить на последующие изменения из-за внутреннего устройства системы, похоже на выплату процентов по долгу. При этом ужасный участок, который больше никогда не меняется, может не создавать практически значимой процентной нагрузки. Долг проявляется, когда реальная траектория продукта снова проходит через ограничивающее решение. (Martin Fowler)
Плохой код — оценка состояния артефакта.
Технический долг — модель связи между:
- прошлым решением;
- будущим изменением;
- дополнительной ценой этого изменения;
- риском для результата.
Поэтому лид не собирает бесконечный каталог всего несовершенного. Он восстанавливает причинность:
Какое текущее устройство системы создаёт ограничение?
При каком событии мы заплатим цену?
Как именно эта цена проявится?
Насколько вероятен этот сценарий?
Какой результат находится под угрозой?
Сколько стоит изменить ситуацию?
Как мы поймём, что долг действительно уменьшился?
Управление долгом не означает, что организация всегда выбирает его погашение. Осознанное решение оставить долг может быть рациональным, если процент несущественен, триггер маловероятен, система скоро выводится из эксплуатации или цена погашения выше ожидаемого ущерба.
Управлять долгом ≠ всегда платить долг.
Управлять долгом =
видеть обязательство,
понимать его экспозицию,
выбирать действие
и не выдавать неизвестность за отсутствие цены.
Результаты обучения
После модуля участник должен уметь:
- объяснять технический долг как экономическую и инженерную причинность, а не как категорию «грязного кода»;
- различать debt, defect, risk, toil, obsolescence, missing feature и эстетическое предпочтение;
- различать intentional, inadvertent, prudent и reckless debt;
- фиксировать осознанно принятый долг вместе с выгодой, ограничениями, владельцем и условием пересмотра;
- описывать legacy через реальные ограничения изменения, а не через возраст технологии;
- находить процент по долгу в delivery, production, cognitive load и стоимости координации;
- составлять debt item с наблюдаемыми evidence, trigger, impact и возможными treatment options;
- вести debt register, который помогает выбирать решения, а не хранит жалобы команды;
- приоритизировать долг по ожидаемой цене, вероятности активации, росту процента и стоимости вмешательства;
- выбирать между accept, contain, service, repay, replace и retire;
- строить refactoring strategy без скрытого бесконечного проекта;
- отличать refactoring от rewrite, redesign, migration и обычной доработки;
- использовать Strangler Fig, Branch by Abstraction и Parallel Change для постепенной замены;
- строить migration plan с совместимостью, наблюдаемостью, сверкой данных, rollback и decommission;
- переводить технический долг на язык delivery risk и продуктового результата;
- проверять эффект погашения по изменению реальной способности системы, а не по закрытому тикету.
1. Что именно обозначает метафора долга
Финансовая метафора полезна, если не обращаться с ней как с буквальной бухгалтерией.
Principal
Principal — условная стоимость изменения текущего устройства системы до состояния, в котором конкретное ограничение снято или существенно уменьшено.
Например:
Текущая ситуация:
правила расчёта тарифа размазаны по трём сервисам.
Principal:
выделить единую модель pricing,
создать контракт,
перевести потребителей,
удалить старые реализации.
Principal нельзя надёжно свести к одной цифре до исследования. В нём могут находиться:
- изменение кода;
- тестирование существующего поведения;
- миграция данных;
- совместимость со старыми потребителями;
- изменение deployment;
- обучение команды;
- временная поддержка двух путей;
- вывод старого решения из эксплуатации.
Interest
Interest — дополнительная цена, которую организация платит, пока ограничение остаётся.
Она может проявляться как:
- лишние дни на каждое изменение;
- повторяющиеся ручные операции;
- расширенная regression-проверка;
- частые production incidents;
- необходимость координировать несколько команд;
- невозможность безопасно делегировать работу;
- инфраструктурные или лицензионные расходы;
- потерянная продуктовая возможность;
- задержка обязательного security update;
- зависимость от одного человека.
Interest rate
Interest rate — то, как быстро растёт дополнительная цена.
Например, одна ручная операция на десять клиентов может быть терпимой. Та же операция на десять тысяч клиентов создаёт растущую нагрузку. Жёсткая связанность трёх сервисов может создавать умеренную цену сегодня и резко увеличивать её с каждым новым потребителем.
Trigger
Технический долг часто не берёт процент просто из-за хода времени. Его активирует событие:
- новая feature проходит через проблемный модуль;
- меняется тариф;
- появляется новый рынок;
- растёт нагрузка;
- требуется обновить библиотеку;
- уходит носитель знания;
- меняется regulation;
- происходит incident;
- внешний провайдер прекращает поддержку API.
Без trigger разговор о цене легко становится абстрактным.
Exposure
Exposure — сколько важных сценариев, данных, команд или обязательств затрагивает долг.
Одинаковый code smell имеет разное значение:
Сложный parser для отчёта, который запускают раз в год,
и сложный pricing engine, через который проходит каждый заказ,
не являются одинаковыми debt items.
Метафора ломается
Технический долг не является финансовым долгом буквально:
- principal известен лишь приблизительно;
- interest часто платится только при изменении или эксплуатации;
- будущая траектория продукта неизвестна;
- обучение может внезапно открыть ранее невидимый долг;
- удаление функции может обнулить долг без рефакторинга;
- «погашение» иногда создаёт новый долг;
- кредитора с формальным договором может не существовать.
Поэтому нельзя написать псевдоточную таблицу, объявить сумму долга в 2 450 story points и принять её за реальность.
Метафора помогает принять решение.
Метафора не превращает неизвестность в бухгалтерский факт.
2. Технический долг — не всё несовершенное
Если команда называет долгом любую неприятность, debt register становится контейнером неразличённого недовольства.
| Явление | Что это | Пример | Возможная связь с долгом |
|---|---|---|---|
| Technical debt | Текущее устройство увеличивает цену будущего изменения или эксплуатации | Каждая смена тарифа требует синхронной правки трёх сервисов | Непосредственный предмет управления |
| Defect | Система уже нарушает ожидаемое поведение | Неверно округляется сумма заказа | Может быть следствием долга, но сам баг не обязательно долг |
| Risk | Возможное нежелательное событие и его последствия | При падении primary database восстановление может занять сутки | Долг может повышать вероятность или impact риска |
| Toil | Ручная, повторяемая, автоматизируемая операционная работа без устойчивой ценности | Инженер ежедневно вручную перезапускает job | Может быть процентом по долгу; не всякий toil вызван долгом |
| Obsolescence | Технология, контракт или знание теряет поддержку или совместимость | Runtime выходит из security support | Может создать debt item, если замена требует будущей дорогой работы |
| Missing feature | Нужной способности ещё нет | Нет bulk export | Это продуктовая работа, пока отсутствие не вызвано ограничивающим устройством |
| Quality attribute gap | Система не достигает требуемого уровня надёжности, скорости или безопасности | p95 выше продуктового SLO | Может быть дефектом архитектуры, capacity problem или долгом — требуется анализ |
| Preference | Один вариант кажется инженеру красивее другого | Предпочтение другого naming или framework | Не долг без доказуемой связи с изменением или риском |
Один объект может принадлежать нескольким категориям одновременно, но категории не следует склеивать.
Наблюдение:
релиз требует вручную изменить восемь конфигураций.
Toil:
операция повторяется и требует рук.
Risk:
одну конфигурацию можно пропустить.
Debt:
раздельная модель конфигурации делает каждый следующий релиз
дороже и опаснее, чем он был бы при едином управляемом contract.
Google SRE определяет toil не как любую неприятную работу, а через свойства: ручная, повторяющаяся, автоматизируемая, тактическая, не создающая долговременной ценности и растущая вместе с сервисом. Это полезное различение: раздражение инженера само по себе не делает работу ни toil, ни debt. (Google SRE)
3. Intentional и inadvertent debt
Не весь долг появляется одинаково.
Intentional debt
Команда видит компромисс и сознательно принимает его ради конкретной выгоды.
Чтобы проверить спрос до конференции,
мы поддержим только один currency и сохраним currency code рядом с заказом.
Выгода:
выход на рынок на три недели раньше.
Ограничение:
нельзя подключать второй рынок до выделения money model.
Триггер пересмотра:
утверждение запуска во второй стране.
Осознанность требует не фразы «потом переделаем», а явного решения.
Inadvertent debt
Долг становится видимым после обучения, изменения контекста или накопления локальных решений.
Когда система создавалась, команда считала тариф атрибутом продукта.
Через год стало ясно, что тариф зависит от договора, региона и времени.
Исходное решение могло быть разумным для прежнего знания.
Теперь модель создаёт повторяющуюся цену.
Наличие inadvertent debt не доказывает некомпетентность. Хорошая команда тоже узнаёт домен через реализацию. Фаулер различает deliberate/inadvertent и prudent/reckless debt: долг может возникнуть даже в качественно спроектированной системе, когда команда позже понимает, каким дизайн должен был быть. (Technical Debt Quadrant)
Intentional не означает prudent
"Мы сознательно не пишем тесты, потому что у нас сроки"
— это намеренное решение, но намеренность не делает его рациональным. Нужно показать:
- конкретную выгоду;
- ограниченную область;
- допустимый downside;
- срок или trigger пересмотра;
- возможность безопасного возврата;
- отсутствие более дешёвой альтернативы.
4. Quadrant долга: две независимые оси
| Deliberate | Inadvertent | |
|---|---|---|
| Prudent | «Сейчас выпускаем ограниченный вариант, фиксируем последствия и условие погашения» | «После использования поняли домен и теперь видим лучшую модель» |
| Reckless | «На дизайн времени нет; потом как-нибудь» | «Мы не различали проблему и создали связанность, которую даже не видели» |
Quadrant нужен не для моральной оценки команды, а для выбора реакции.
Prudent + deliberate
Действие:
- зафиксировать полученную выгоду;
- ограничить blast radius;
- назвать trigger;
- назначить owner;
- определить stop condition;
- пересмотреть решение при изменении контекста.
Prudent + inadvertent
Действие:
- сохранить новое знание;
- найти будущие изменения, которые активируют процент;
- встроить улучшение в ближайшую релевантную работу;
- не превращать обучение в обвинение прошлой команды.
Reckless + deliberate
Действие:
- проверить, существовала ли реальная необходимость компромисса;
- сделать цену видимой decision owner;
- прекратить повторение решения как нормы;
- поставить явную границу там, где затронуты security, compliance или irrecoverable data.
Reckless + inadvertent
Действие:
- закрыть capability gap;
- изменить review и design process;
- определить локальные технические guardrails;
- проверить уже созданный blast radius;
- не ограничиваться исправлением одного проявления.
5. Как принимать intentional debt
Осознанный долг должен выглядеть как инженерное решение, а не обещание памяти.
# Intentional Debt Decision
## Context
Какое ограничение или возможность существует сейчас?
## Decision
Какой компромисс принимаем?
## Immediate benefit
Какой измеримый результат получаем?
## Known consequences
Что станет дороже, опаснее или недоступнее?
## Scope and guardrails
Где решение допустимо, а где его нельзя копировать?
## Trigger
При каком событии решение обязательно пересматривается?
## Stop condition
Какой уровень последствий больше не принимается?
## Owner
Кто отвечает за повторную оценку?
## Review date
Когда проверяем решение, если trigger раньше не сработал?
Пример:
Decision:
до пилота храним документы в существующем object storage без региональной репликации.
Benefit:
не откладываем пилот на четыре недели.
Known consequence:
RPO не соответствует требованиям полноценного запуска.
Guardrail:
только тестовые клиенты; не более 500 документов; нет regulated data.
Trigger:
подписание первого production contract.
Stop condition:
появление данных, восстановление которых невозможно запросить повторно.
Owner:
Engineering Lead Documents.
Если нет owner и trigger, «осознанный долг» часто означает только то, что команда осознанно забудет о последствиях.
6. Откуда появляется accidental debt
Accidental debt не обязательно является одной ошибкой. Он может быть эмерджентным результатом множества локально разумных действий.
Обучение домену
Первая модель отражает первое понимание. Реальные сценарии показывают дополнительные различия.
Изменение требований
Архитектура была построена для одного региона, канала продаж или объёма. Новая реальность меняет cost profile.
Локальная оптимизация
Каждая команда ускоряет свой участок и создаёт общий contract, за который никто не отвечает.
Архитектурный drift
Исключение добавляется один раз, затем копируется и становится неявным новым устройством системы.
Давление delivery
Команда многократно выбирает ближайшую поставку, но не пересматривает накопленный эффект.
Отсутствие ownership
У shared component много потребителей, но нет владельца, принимающего решения о его эволюции.
Утрата знания
Код не изменился, но ушли люди, знавшие инварианты, runbook и причины решений. Цена безопасного изменения выросла.
Рост системы
Ручная операция, shared database или synchronous chain, допустимые при малом масштабе, превращаются в ограничение.
Изменение внешней среды
Runtime, vendor API, regulation или threat model изменились. Ранее нейтральное решение стало обязательством.
AI-assisted accumulation
AI может ускорять создание локально работающего кода без общей модели системы. Если команда принимает результат по признаку «тесты зелёные», количество неразличённых contracts, дублированных правил и скрытых предположений может расти быстрее.
Проблема здесь не в AI как сущности. Причина — режим интеграции, в котором скорость генерации выше скорости проверки системной причинности.
7. Legacy — не возраст и не оскорбление
Legacy часто используется как слово, которым команда лишает систему достоинства:
"Это старьё".
"Там всё неправильно".
"Никто не понимает, как оно работает".
Но возраст технологии не показывает ценность системы и цену изменения.
Старый стабильный компонент,
который выполняет функцию, имеет тесты и редко меняется,
может не быть значимой проблемой.
Новый сервис,
созданный три месяца назад без границ и ownership,
может уже производить высокий процент по долгу.
Для работы лида полезно операционное описание:
Legacy-система — это действующая система,
которая несёт накопленную бизнес-ценность и ограничения,
а её безопасное изменение затруднено
техническими, информационными или организационными зависимостями.
Карта legacy-ограничений
| Контур | Вопросы |
|---|---|
| Поведение | Какие пользовательские и бизнес-сценарии реально выполняет система? |
| Инварианты | Какие правила нельзя нарушить? Где они зафиксированы? |
| Данные | Где source of truth? Каковы объём, качество, retention и связи? |
| Interfaces | Кто вызывает систему и кого вызывает она? Есть ли неизвестные consumers? |
| Deployability | Можно ли собрать, протестировать, выпустить и откатить изменение? |
| Observability | Видно ли, что система выполняет функцию? |
| Knowledge | Кто понимает ключевые участки? Есть ли проверяемая документация? |
| Technology | Поддерживаются ли runtime, libraries, OS и vendor contracts? |
| Operations | Какие ручные действия поддерживают жизнь системы? |
| Business criticality | Что произойдёт с пользователем и бизнесом при отказе? |
Цель анализа — не доказать, что legacy плох. Цель — найти реальные seams, contracts и риски изменения.
8. Где технический долг берёт проценты
В delivery
- одна feature требует изменений в нескольких несвязанных местах;
- refinement занимает больше времени из-за неизвестных зависимостей;
- изменения долго ждут единственного эксперта;
- regression scope непропорционален размеру feature;
- release требует общей синхронизации команд;
- маленькое изменение нельзя поставить отдельным slice.
В production
- повторяются incidents одного класса;
- rollback небезопасен;
- невозможно локализовать отказ;
- алерты требуют ручного исследования каждого раза;
- восстановление зависит от ручной последовательности;
- плохая изоляция увеличивает blast radius.
В cognitive load
- для изменения одного правила нужно удерживать несколько подсистем;
- названия и модели не соответствуют домену;
- инварианты существуют только как устная память;
- многочисленные исключения невозможно вывести из общей модели;
- review доступен только узкому кругу людей.
В координации
- изменения требуют approval людей, не владеющих результатом;
- shared database превращает локальную задачу в межкомандный проект;
- consumers не могут обновляться независимо;
- организационная граница не совпадает с технической.
В потерянных возможностях
- продукт отказывается от нужной функции как от «слишком дорогой»;
- business experiment нельзя провести достаточно быстро;
- нельзя выйти на новый рынок;
- обязательное изменение вытесняет весь roadmap;
- организация продолжает платить vendor cost из-за невозможности миграции.
В риске
- неподдерживаемая зависимость блокирует security patch;
- ручная миграция может повредить данные;
- отсутствие тестов делает неизвестным реальное поведение;
- tightly coupled deployment увеличивает вероятность совместного отказа.
Долг становится управляемым, когда абстрактная неудовлетворённость связывается с одним или несколькими такими наблюдаемыми эффектами.
9. Как находить долг: идти от трения, а не от вкуса
Code smell может быть полезным сигналом, но приоритизация начинается не с каталога запахов.
Delivery flow
Ищите:
- где задачи регулярно задерживаются;
- какие этапы требуют повторной работы;
- какие компоненты делают оценки особенно неопределёнными;
- где небольшая feature превращается в длинную dependency chain;
- какие изменения нельзя выпускать независимо.
Change hotspots
Полезно совместить:
- частоту изменений;
- количество дефектов и rollback;
- сложность зависимостей;
- число команд и reviewers;
- бизнес-критичность.
Сложный, но неизменяемый участок может быть ниже по приоритету, чем умеренно сложный hotspot, через который еженедельно проходят изменения.
Incidents и postmortems
Ищите не только root cause последнего сбоя, но и повторяемые enabling conditions:
- отсутствие isolation;
- невозможность безопасного rollback;
- неявные contracts;
- ручная конфигурация;
- отсутствующая observability;
- неподдерживаемый компонент.
Toil map
Повторяющиеся ручные действия показывают, где система каждый день взымает процент человеческим временем.
Dependency map
Особенно важны:
- shared databases;
- cyclic dependencies;
- общие библиотеки с lockstep release;
- внешние contracts без versioning;
- скрытые consumers;
- single points of organizational knowledge.
Upcoming roadmap
Приоритет долга зависит от будущей траектории:
Если в следующие два квартала планируется шесть изменений pricing,
долг pricing активируется многократно.
Если старый reporting module выводится из эксплуатации,
его полное внутреннее очищение может не иметь смысла.
Инженерное наблюдение
Субъективное ощущение команды не надо отбрасывать. Его надо развернуть в проверяемую гипотезу.
Сигнал:
"В billing страшно что-либо менять".
Исследование:
- какие изменения были последними;
- где возникали дефекты;
- что невозможно протестировать;
- какие инварианты неизвестны;
- кто нужен для review;
- сколько времени занимает безопасный release;
- какой будущий roadmap проходит через billing.
10. Anatomy of a debt item
Фраза «плохая архитектура» не является debt item.
Полный item содержит причинную цепочку.
Current condition
↓
Trigger / future change
↓
Additional cost or risk
↓
Affected outcome
↓
Treatment option
↓
Evidence of improvement
10.1. Current condition
Конкретное устройство, а не оценочное слово.
Правило применения скидки независимо реализовано
в checkout, invoice и refund services.
10.2. Evidence
Что подтверждает существование ограничения.
За последние четыре изменения:
- каждый раз изменялись три repositories;
- два раза реализации расходились;
- regression занимал 2–3 дня;
- один incident был вызван разным rounding.
10.3. Trigger
При каком событии цена возникает снова.
Новая discount campaign,
изменение rounding,
запуск возвратов в новой currency.
10.4. Impact
Какой результат страдает.
Замедление time-to-market кампаний,
риск неконсистентной суммы,
расширенный regression,
финансовая корректировка после релиза.
10.5. Treatment options
Не одна заранее любимая реализация, а варианты.
A. Оставить как есть и добавить contract tests.
B. Выделить общую library с versioning.
C. Выделить pricing module внутри checkout.
D. Создать pricing service.
E. Удалить часть вариантов скидки из продукта.
10.6. Success evidence
Как изменится способность системы.
Изменение discount rule выполняется в одном месте;
потребители получают одинаковый результат на contract suite;
campaign change поставляется без lockstep release трёх команд;
regression scope уменьшается до pricing contract и затронутого scenario.
11. Debt register: не кладбище жалоб
Debt register нужен для принятия решений. Если item невозможно связать с будущим воздействием, он не готов к приоритизации.
Минимальные поля
| Поле | Содержание |
|---|---|
| ID / title | Короткое различимое имя |
| Scope | Система, компонент, contract или процесс |
| Current condition | Что именно устроено так сейчас |
| Evidence | Наблюдаемые случаи, данные, incidents, delays |
| Origin | Deliberate / inadvertent / unknown |
| Trigger | Какое событие активирует цену |
| Interest | Какая повторяющаяся цена возникает |
| Exposure | Какие сценарии, данные, команды и обязательства затронуты |
| Growth | Почему цена стабильна, растёт или уменьшается |
| Horizon | Когда вероятен следующий существенный trigger |
| Treatment options | Accept / contain / service / repay / replace / retire |
| Estimate | Диапазон, uncertainty и discovery need |
| Owner | Кто отвечает за решение, а не обязательно за всю реализацию |
| Review condition | Дата или событие пересмотра |
| Status | Observed / assessed / accepted / planned / treating / verified / retired |
Что не надо хранить
"Переписать backend".
"Убрать legacy".
"Повысить code quality".
"Разобраться с архитектурой".
"Почистить TODO".
Это направления желания, а не управляемые обязательства.
Register не является backlog
Не каждый зарегистрированный item должен немедленно стать задачей.
Register отвечает:
что мы знаем о долге и какое решение приняли.
Backlog отвечает:
какую работу мы собираемся выполнить.
Если скопировать весь register в backlog, команда создаст иллюзию обязательства выполнить всё и потеряет реальную приоритизацию.
Исследования SEI рассматривают technical debt шире качества кода: как последствия краткосрочных решений, влияющие на стоимость будущих архитектурных изменений, и отдельно подчёркивают необходимость inventory, оценки impact и планирования treatment. (CMU SEI)
12. Приоритизация: какой долг платить первым
Самый некрасивый код не обязательно создаёт самый дорогой долг.
Основные измерения
Frequency
Как часто возникает trigger?
Cost per trigger
Какую дополнительную цену создаёт один случай?
Impact
Что находится под угрозой: revenue, user journey, compliance, reliability, delivery или capacity?
Growth
Цена стабильна или увеличивается с каждым consumer, клиентом, исключением и release?
Irreversibility
Можно ли восстановиться после реализации риска?
Horizon
Trigger ожидается на следующей неделе, в квартале или гипотетически когда-нибудь?
Treatment cost
Сколько стоит каждый вариант изменения, включая transition и decommission?
Confidence
Насколько подтверждены diagnosis, impact и стоимость?
Приближённая модель
Expected pressure ≈
trigger frequency
× additional cost per trigger
× impact weight
× growth factor
× confidence
Это не формула бухгалтерской истины. Она заставляет назвать компоненты решения.
Opportunity alignment
Лучший момент для погашения часто совпадает с реальным изменением в той же области.
Debt item:
pricing rules размазаны по трём сервисам.
Roadmap:
через месяц начинается запуск subscription plans.
Вывод:
погашение части долга до или внутри первой subscription feature
может быть дешевле отдельного проекта после шести новых правил.
Простая матрица
| Процент / риск | Trigger близко | Trigger далеко или неизвестен |
|---|---|---|
| Высокий | Планировать treatment сейчас | Contain, исследовать trajectory, задать stop condition |
| Низкий | Встроить локальное улучшение в change | Accept и пересматривать по событию |
Псевдоточность
Score 83.7 не делает решение объективным. Если две позиции близки, полезнее обсудить различия:
- у какой item подтверждён будущий trigger;
- где цена уже наблюдалась;
- где intervention создаёт option для нескольких roadmap items;
- где delay делает миграцию необратимо дороже;
- где сначала нужен discovery.
13. Стратегии обращения с долгом
Accept
Оставить состояние как есть и зафиксировать основание.
Подходит, если:
- trigger маловероятен;
- impact ограничен;
- система скоро выводится;
- treatment дороже ожидаемой цены;
- существует дешёвый fallback.
Accept — это решение с условием пересмотра, а не отсутствие решения.
Contain
Не погашать principal, но ограничить распространение и blast radius.
Примеры:
- запретить новым consumers прямой доступ к legacy database;
- поставить adapter перед unstable API;
- запретить копирование старой pricing logic;
- добавить contract tests вокруг текущего поведения;
- выделить owner для критического участка.
Service
Регулярно уменьшать процент, не устраняя весь principal.
Примеры:
- автоматизировать ручной release;
- добавить observability;
- ускорить test suite;
- документировать восстановление;
- изолировать наиболее частый change path.
Repay
Изменить внутреннее устройство так, чтобы конкретный повторяющийся cost исчез или существенно уменьшился.
Replace
Заменить компонент, platform, dependency или implementation, сохраняя требуемую способность.
Retire
Удалить функцию или систему вместе с долгом.
Иногда лучший рефакторинг — прекращение ненужной продуктовой способности.
Transfer
Передать ответственность vendor или managed service. Это не уничтожает обязательство автоматически: технический principal может превратиться в vendor dependency, contract risk и switching cost.
14. Когда долг не надо платить
Существование долга не создаёт автоматической обязанности его погасить.
Не платить сейчас может быть разумно, если:
- затронутая область стабильна и не входит в roadmap;
- система имеет подтверждённый срок retirement;
- у проблемы низкий impact и дешёвое восстановление;
- treatment потребует опасной миграции без соответствующей выгоды;
- diagnosis ещё не подтверждён;
- principal больше оставшегося lifetime value;
- продуктовый сценарий можно удалить дешевле;
- другое вмешательство резко снижает процент без полной замены.
Пример
Наблюдение:
report generator написан на старом framework.
Контекст:
- запускается раз в квартал;
- интерфейс стабилен пять лет;
- framework работает на поддерживаемом runtime;
- через девять месяцев отчёт заменит внешний BI;
Решение:
не переписывать;
добавить smoke test и runbook;
запретить новые функции;
пересмотреть только при сдвиге BI migration.
Это не пренебрежение качеством. Это управление траекторией.
15. Как выделять capacity на долг
Универсального процента 20% на техдолг не существует. Способ финансирования зависит от природы item.
Opportunistic repayment
Локальное улучшение выполняется внутри feature, которая уже касается проблемного участка.
Подходит для:
- preparatory refactoring;
- удаления дублирования по затронутому пути;
- улучшения contract tests;
- небольшой изоляции dependency.
Fixed capacity
Команда заранее резервирует часть capacity для устойчивого потока небольших items.
Это может помочь, если долг разнообразен и постоянно активируется. Но процент не заменяет приоритизацию: команда всё равно должна выбирать, какой эффект покупает.
Dedicated initiative
Отдельная программа оправдана, когда:
- изменение пересекает несколько команд;
- нужна platform migration;
- есть обязательный внешний deadline;
- transition architecture требует управления;
- локальные изменения не могут создать работоспособный slice.
Stop-the-line
Немедленное вмешательство нужно, когда продолжение создаёт неприемлемый или необратимый риск:
- повреждение данных;
- критическая security exposure;
- нарушение обязательного regulation;
- отсутствие возможности восстановить ключевой сервис;
- рост зависимости делает последующую миграцию существенно опаснее.
После incident
Не всякое action item postmortem является debt repayment. Но repeated incident class может показать debt item, а remedial work — стать treatment.
16. Refactoring strategy
Refactoring — дисциплинированное изменение внутренней структуры без изменения наблюдаемого поведения. Его сила — в малых проверяемых шагах, после каждого из которых система остаётся работоспособной. (Refactoring.com)
Это важное различение:
Refactoring ≠ любая работа над старым кодом.
Refactoring ≠ rewrite.
Refactoring ≠ добавление новой feature.
Refactoring ≠ архитектурная презентация.
Preparatory refactoring
Сначала придать коду форму, в которой нужное изменение станет локальным, затем выполнить feature.
До feature:
выделить calculation policy из HTTP handler.
Feature:
добавить новый тип тарифа через policy contract.
Comprehension refactoring
Во время исследования сделать структуру соответствующей обнаруженному пониманию, не меняя behavior.
Planned structural refactoring
Последовательность изменений имеет отдельную цель и несколько releases, но каждый этап сохраняет поставляемость.
Refactoring under tests
Тесты нужны не как ритуал coverage, а как наблюдатель сохраняемого поведения.
Если поведение неизвестно:
- определить critical scenarios;
- создать characterization tests;
- зафиксировать внешние contracts;
- добавить observability там, где тест не покрывает production distinction;
- менять маленькими шагами;
- сравнивать результаты.
Ограниченный refactoring objective
Плохо:
"Привести billing в порядок".
Нормально:
Сделать calculation rule единым для invoice и refund,
чтобы следующее изменение rounding выполнялось в одном module
и проверялось общей contract suite.
Exit criteria
Refactoring заканчивается не когда код стал идеальным, а когда достигнута выбранная способность:
- change path локализован;
- старое дублирование удалено;
- consumers переведены;
- regression scope уменьшен;
- новый contract наблюдаем;
- старый путь больше не используется.
17. Почему «давайте всё перепишем» почти ничего не объясняет
Rewrite может быть правильным решением. Но сама фраза не содержит causal case.
Что rewrite обещает психологически
- избавиться от ограничений прошлого;
- применить современный stack;
- начать с чистой модели;
- вернуть ощущение контроля;
- не разбираться в неприятной системе.
Последний пункт особенно опасен: rewrite часто требует понять старую систему глубже, чем локальный refactoring, потому что новая реализация должна воспроизвести все значимые, в том числе неявные, функции.
Скрытая спецификация
Legacy code может содержать:
- исключения для конкретных клиентов;
- historical data semantics;
- незафиксированные compliance rules;
- поведение, на которое полагаются внешние consumers;
- operational workarounds;
- порядок событий, ставший частью contract.
Новый код не отменяет эту реальность.
Две движущиеся цели
Во время rewrite старый продукт продолжает изменяться. Возникают:
- feature parity race;
- двойная реализация;
- долгий период без пользовательской ценности;
- сложный cutover;
- конфликт приоритетов между поддержкой old и созданием new;
- риск, что target architecture устареет раньше запуска.
Когда replacement может быть оправдан
- current platform объективно не может выполнить обязательное требование;
- component boundary и contract достаточно ясны;
- поведение можно проверить;
- есть путь постепенного cutover;
- old system можно реально decommission;
- lifetime новой способности покрывает стоимость transition;
- есть owner, capacity и business participation.
Главный вопрос
Не "можем ли мы написать лучше?"
А:
"можем ли мы безопасно перенести необходимую способность,
показать ценность по частям
и прекратить платить за старый путь?"
18. Strangler Fig: замена по частям
Strangler Fig — стратегия постепенного создания новой системы вокруг старой с последовательным переводом функций или трафика. Её ключевая ценность по сравнению с cut-over rewrite — уменьшение риска, частые поставки и возможность получать результат до завершения всей замены. (Original Strangler Fig)
Базовая последовательность
- Найти границу или создать seam.
- Перехватить вызов, событие или данные.
- Реализовать один ограниченный capability slice новым путём.
- Перевести выбранный segment.
- Сравнить поведение и production effect.
- Расширять долю нового пути.
- Остановить запись или вызовы старого пути.
- Удалить старую реализацию и transition layer, если он больше не нужен.
Варианты сегментации
- по user cohort;
- по tenant;
- по product type;
- по географии;
- по endpoint;
- по business capability;
- по типу события;
- read before write;
- new entities before existing entities.
Strangler Fig не равен «рядом пишем новую систему»
Без управляемого routing, сравнения, migration order и decommission возникают две постоянные системы вместо перехода.
Риск transition
Временная архитектура сложнее исходной:
- два пути;
- adapters;
- routing;
- data synchronization;
- feature flags;
- reconciliation;
- дополнительные failure modes.
Поэтому transition должен иметь owner, observability и срок жизни.
Patterns of Legacy Displacement показывают migration через найденный technical seam, event interception, параллельную работу old/new и постепенное увеличение доли объектов на новом пути с последующим decommission старого middleware. Это хороший пример того, что миграция является последовательностью работающих состояний, а не прыжком между двумя схемами. (Patterns of Legacy Displacement)
19. Branch by Abstraction
Branch by Abstraction позволяет постепенно заменить module, library, framework или supplier через стабильный внутренний contract.
Последовательность
- Выделить операции, реально нужные consumers.
- Создать abstraction над текущим supplier.
- Перевести consumers на abstraction.
- Проверить behavior через этот seam.
- Реализовать новый supplier за тем же contract.
- Переключать consumers или traffic по частям.
- Удалить старый supplier.
- Решить, остаётся ли abstraction полезной после migration.
Фаулер описывает эту технику как способ выполнять крупное изменение постепенно, продолжая регулярно выпускать работающую систему. (Branch by Abstraction)
Пример
Current:
40 мест напрямую вызывают legacy PDF library.
Step 1:
создать DocumentRenderer с минимальным contract.
Step 2:
перевести вызовы на DocumentRenderer.
Step 3:
добавить NewPdfRenderer.
Step 4:
сравнивать документы для выбранных templates.
Step 5:
переключать template groups через configuration.
Step 6:
удалить legacy dependency и compatibility code.
Ошибка abstraction
Нельзя просто завернуть весь старый API один к одному. Тогда abstraction сохраняет структуру supplier и не создаёт полезного seam.
Contract должен выражать потребность текущей системы, а не копировать каждую деталь заменяемой библиотеки.
20. Parallel Change: expand, migrate, contract
Parallel Change позволяет провести несовместимое изменение через период совместимости.
Expand
Supplier начинает поддерживать старую и новую форму.
Примеры:
- добавить новое поле, сохранив старое;
- создать новый endpoint;
- разрешить чтение двух schema versions;
- добавить новую колонку без удаления старой;
- публиковать новый event рядом со старым.
Migrate
Consumers постепенно переходят на новую форму.
Нужны:
- список consumers;
- progress visibility;
- compatibility tests;
- срок перехода;
- owner каждой стороны;
- способ обнаружить оставшееся использование.
Contract
Старая форма удаляется.
Именно этот этап чаще всего не выполняется.
Expand без Contract =
не migration, а накопление ещё одной версии.
Фаулер отдельно предупреждает: во время migrate supplier поддерживает две версии, а если contract не завершён, система может оказаться сложнее исходной. (Parallel Change)
21. Migration plan: управлять переходом, а не target diagram
Target architecture показывает желаемое конечное состояние. Migration plan показывает, как система останется работоспособной между настоящим и будущим.
21.1. Outcome
Не «перейти на новый stack», а способность и снятое ограничение.
Поставлять изменение pricing независимо от checkout release
и единообразно рассчитывать invoice, refund и preview.
21.2. Current state
- реальные flows;
- contracts;
- data ownership;
- consumers;
- operational dependencies;
- known unknowns;
- production volumes;
- critical invariants.
21.3. Target state
Только те свойства, которые нужны для outcome:
- ownership;
- boundaries;
- interfaces;
- data model;
- deployment and operations;
- reliability requirements.
21.4. Seam
Где old и new могут сосуществовать:
- API gateway;
- event router;
- adapter;
- repository abstraction;
- database view;
- feature flag;
- batch export/import;
- UI route;
- tenant routing.
21.5. Slices
Каждый slice должен создавать проверяемое работающее состояние.
Плохо:
1. Сделать новую архитектуру.
2. Перенести данные.
3. Переключить пользователей.
Нормально:
1. Ввести единый pricing contract и оставить old implementation.
2. Перевести price preview на contract.
3. Реализовать new calculator для одного plan type.
4. Включить shadow comparison.
5. Перевести internal users.
6. Перевести 5% production traffic.
7. Расширить plan types и traffic.
8. Остановить old writes.
9. Проверить отсутствие consumers.
10. Удалить old implementation.
21.6. Compatibility
- какие версии поддерживаются;
- кто обновляется первым;
- сколько длится coexistence;
- что считается совместимым;
- как обнаруживается нарушение contract.
21.7. Verification
- contract tests;
- shadow traffic;
- result comparison;
- reconciliation;
- canary;
- user journey metrics;
- business counters;
- error and latency signals.
21.8. Fallback
Rollback и fallback не всегда одно и то же.
Если новая система уже записала данные в новой форме, откат бинарника может не вернуть прежнее состояние. Нужны явные ответы:
- можно ли вернуть traffic;
- какая система остаётся source of truth;
- совместимы ли данные назад;
- что делать с уже обработанными событиями;
- сколько времени доступно безопасное возвращение;
- кто принимает решение.
21.9. Decommission
Migration не закончена, пока старая цена продолжает существовать.
Decommission включает:
- остановку traffic и writes;
- подтверждение отсутствия consumers;
- archival или удаление данных по policy;
- удаление code, flags, routes и jobs;
- прекращение licenses и infrastructure;
- обновление runbooks и diagrams;
- закрытие on-call responsibility;
- фиксацию достигнутого результата.
22. Data migration: отдельный контур риска
Перенос кода и перенос данных — разные задачи.
Source of truth
На каждом этапе должно быть известно, какая система авторитетна для каждого типа данных и операции.
Historical data
Не все данные надо переносить одинаково:
- active entities;
- immutable history;
- audit data;
- derived data;
- expired data;
- data with legal retention;
- corrupted or incomplete records.
Backfill
Нужно определить:
- порядок;
- batch size;
- throttling;
- idempotency;
- retry;
- checkpoint;
- expected duration;
- влияние на production;
- обработку новых writes во время backfill.
Dual write
Dual write создаёт временное средство миграции и новый класс failure modes:
Запись в old прошла, в new упала.
Запись в new прошла, old timeout не дал понять результат.
Повтор создал duplicate.
Порядок событий разошёлся.
Нужно явно определить consistency model, retry, idempotency и reconciliation. Нельзя писать dual write как одну строку migration plan.
Dual read и shadow read
dual readвлияет на пользовательский результат и требует правила выбора;shadow readсравнивает новый путь, но не отдаёт его результат пользователю;- sampling должен представлять критичные типы данных;
- различие результатов требует классификации, а не только счётчика mismatch.
Reconciliation
Что сравниваем?
С какой допустимой задержкой?
Какие расхождения допустимы?
Как классифицируем различие?
Кто исправляет данные?
Как предотвращается повтор?
Schema evolution
Изменения database полезно хранить как version-controlled first-class artifacts, которые проходят тот же управляемый процесс, что и application code. Evolutionary Database Design включает schema, reference data, transactional data и production data fixes в единый migration discipline. (Evolutionary Database Design)
23. Transitional architecture тоже создаёт долг
Во время миграции временно появляются:
- adapters;
- duplicate schemas;
- old/new APIs;
- event translators;
- routing rules;
- feature flags;
- reconciliation jobs;
- compatibility branches;
- dual ownership.
Они могут быть рациональным intentional debt: организация покупает безопасный переход ценой временной сложности.
Но временная архитектура имеет свой principal и interest.
Для каждого transition component нужны
- причина существования;
- owner;
- consumers;
- observability;
- условие удаления;
- крайний срок или migration milestone;
- действие, если migration остановлена.
Transition residue
Feature flag включён для 100% пользователей 14 месяцев.
Старый endpoint продолжает поддерживаться для неизвестного consumer.
Adapter больше не переключает реализации, но остаётся обязательным слоем.
Dual write работает, хотя old database никто не читает.
Это уже новый долг, созданный незавершённым переходом.
24. Как связать долг с delivery risk
Бизнесу не обязательно понимать внутреннюю красоту кода. Но бизнес уже платит цену через скорость, предсказуемость, надёжность и упущенные варианты.
Причинная цепочка
Technical condition
→ delivery mechanism
→ observable effect
→ business outcome
→ proposed intervention
→ expected evidence
Пример 1. Pricing
Technical condition:
правила тарифа находятся в трёх сервисах.
Delivery mechanism:
каждое изменение требует трёх releases и общей regression.
Observable effect:
последние четыре tariff changes занимали 4–6 дней,
дважды логика расходилась.
Business outcome:
кампании запускаются позже,
возникает риск разной суммы в checkout и invoice.
Intervention:
выделить pricing module и переводить consumers по одному.
Evidence:
следующее tariff change реализуется в одном contract,
checkout и invoice проходят общие consistency tests,
release не требует синхронизации трёх команд.
Пример 2. Deployment
Technical condition:
service configuration изменяется вручную в восьми environments.
Delivery mechanism:
release требует последовательности из 23 шагов.
Observable effect:
медиана release — 90 минут;
за квартал три configuration incidents.
Business outcome:
команда избегает небольших releases,
recovery и urgent fixes становятся медленнее.
Intervention:
versioned configuration и automated validation.
Evidence:
один repeatable pipeline,
configuration diff проверяется до production,
release не требует ручного изменения среды.
Пример 3. Vendor dependency
Technical condition:
core authentication зависит от неподдерживаемой версии SDK.
Trigger:
обязательное обновление provider API через четыре месяца.
Delivery risk:
обновление SDK требует изменения custom extension без тестового seam.
Business outcome:
риск потери login для всех клиентов после provider cutoff.
Intervention:
изолировать provider contract,
создать compatibility tests,
перейти на поддерживаемую SDK по cohorts.
25. Как объяснять долг бизнесу
Не продавать качество как нравственное превосходство
Плохо:
"Мы должны делать правильно".
"Такой код стыдно поддерживать".
"Нам нужно время на инженерное качество".
В этих фразах нет выбора, цены и результата.
Не скрывать неопределённость
Плохо:
"Если дадите два спринта, дальше всё будет в пять раз быстрее".
Если такого evidence нет, это продажа фантазии.
Lead-level формулировка
Сейчас [техническое условие].
Поэтому при [trigger]
мы каждый раз платим [наблюдаемая цена]
и рискуем [значимый outcome].
В ближайшем горизонте ожидаются [roadmap / external event],
поэтому цена, вероятно, возникнет [частота / масштаб].
Есть варианты:
A — принять цену;
B — ограничить риск;
C — изменить устройство.
Рекомендую [вариант], потому что [причинность].
Работа займёт [range / stages / uncertainty].
Первый проверяемый эффект появится [milestone].
Успех определим по [evidence].
Говорить о выборе
"Если не делаем migration сейчас,
feature всё равно возможна.
Но она потребует двух дополнительных integrations,
которые затем тоже придётся переносить.
Цена delay — не абстрактная деградация,
а расширение migration scope и ещё один lockstep consumer".
Это не запугивание. Это явное описание consequence.
26. Как не ломать команду долгом
Технический долг может использоваться как объяснение постоянного давления:
"Раньше сделали плохо, теперь героически чините".
"Надо и roadmap выполнить, и платформу заменить без изменения capacity".
"Рефакторинг делайте между задачами".
Лид должен сделать объём работы и transition cost видимыми.
Нельзя считать migration бесплатным фоном
Во время перехода команда поддерживает old, строит new, проверяет совместимость и отвечает за production. Это реальная capacity, а не «внутренняя техническая деталь».
Нельзя превращать долг в вину
Долг мог возникнуть из прежнего знания, constraints и решений, которых текущая команда не принимала. Обвинение не меняет architecture.
Нельзя обещать идеальное будущее
Погашение одного debt item не создаёт систему без долга. Цель — восстановить конкретную способность изменения и снизить выбранный риск.
Нужна защита focus
Если migration признана приоритетом, новые competing priorities должны менять forecast, а не молча добавляться поверх.
27. Как проверять эффект погашения
Закрытый epic не доказывает уменьшение долга.
До intervention
Зафиксируйте baseline, насколько это возможно:
- время типового change;
- количество затронутых components;
- regression scope;
- число ручных шагов;
- incident frequency;
- recovery time;
- количество синхронизируемых команд;
- долю ошибок определённого класса;
- infrastructure или license cost;
- dependency on expert;
- failed or delayed product opportunities.
После intervention
Проверьте не только архитектуру, но и реальное прохождение следующего изменения.
Мы выделили pricing module.
Но:
- все consumers по-прежнему копируют результат;
- release остаётся общим;
- old rules не удалены;
- change всё ещё требует трёх команд.
Значит, structural work выполнена,
но заявленный delivery effect ещё не достигнут.
Leading evidence
- consumers переходят;
- traffic увеличивается на новом пути;
- mismatch снижается;
- old calls исчезают;
- ручные шаги автоматизированы;
- contract coverage растёт.
Outcome evidence
- типовое изменение стало локальнее;
- release стал независимее;
- повторяющаяся ошибка исчезла;
- toil уменьшился;
- recovery упростился;
- новая capability стала возможной.
DORA-метрики могут показывать изменение общей способности delivery через throughput и instability, но не доказывают причинность отдельного debt item автоматически. Их надо связывать с конкретным scope и change path. (DORA)
28. Governance без долговой бюрократии
Один owner решения
Owner не обязан лично реализовывать treatment. Он отвечает за:
- качество описания;
- связь с roadmap и risk;
- пересмотр при trigger;
- актуальный status;
- закрытие или acceptance.
Review cadence
Не надо еженедельно пересматривать весь register.
Полезные события:
- quarterly planning;
- новый roadmap;
- крупный incident;
- platform end-of-support;
- изменение regulation;
- начало работы в затронутой области;
- уход ключевого владельца;
- остановка migration.
Debt review должен принимать решения
Для каждого обсуждаемого item:
Что изменилось?
Активировался ли trigger?
Какой evidence появился?
Как изменилась exposure?
Принимаем, ограничиваем, исследуем или платим?
Кто и когда пересматривает решение?
Архивировать, а не хранить вечность
Item закрывается, если:
- debt verified as repaid;
- затронутая способность retired;
- diagnosis оказался неверным;
- risk permanently accepted на оставшийся lifetime;
- item разделён на более точные обязательства.
29. Как не создавать следующий долг тем же способом
Управление долгом включает не только treatment, но и изменение механизма возникновения.
Decision memory
ADR или Intentional Debt Decision сохраняет:
- context;
- alternatives;
- consequences;
- assumptions;
- trigger пересмотра.
Guardrails
- dependency rules;
- API versioning;
- schema migration discipline;
- ownership boundaries;
- security and data constraints;
- test strategy для critical contracts;
- supported runtime policy.
Definition of Done
Не универсальный список формальностей, а требования к полноте изменения:
- observability;
- rollback или fallback;
- migration cleanup;
- documentation of new contract;
- removal of dead path;
- owner and runbook.
Architectural fitness
Часть важных свойств можно проверять автоматически:
- forbidden dependencies;
- cyclic modules;
- contract compatibility;
- schema backward compatibility;
- supported library versions;
- latency and reliability thresholds.
Review причинности
В code review нужно спрашивать не только «работает ли этот PR», но и:
Какой новый contract появляется?
Кто станет его consumer?
Где теперь находится правило?
Создаём ли мы второй source of truth?
Как будет удалён temporary path?
Что произойдёт при повторной доставке?
Можно ли изменить это независимо?
30. Полный кейс: pricing module
Исходное состояние
Компания продаёт подписки. Цена заказа вычисляется в:
checkout-serviceдля preview;billing-serviceдля списания;invoice-serviceдля документа;support-toolдля ручного перерасчёта.
Часть правил находится в shared library, часть скопирована, часть зависит от локальных данных. За последние полгода было пять изменений тарифов.
Наблюдения:
- каждое изменение занимало 4–7 дней;
- release требовал синхронизации трёх команд;
- дважды invoice отличался от checkout на rounding;
- один enterprise discount существует только в billing;
- support-tool использует версию library двухлетней давности;
- через два месяца запланированы usage-based plans.
Плохое описание
"Pricing — legacy. Надо вынести в микросервис".
Здесь заранее выбрана реализация, но не определён долг.
Debt item
Current condition:
pricing decisions не имеют единого owner и executable contract;
четыре consumers используют расходящиеся реализации.
Evidence:
5 изменений × 4–7 дней;
2 consistency defects;
3 команды на release;
1 скрытое правило;
1 устаревший consumer.
Trigger:
каждое изменение тарифа;
usage-based plans;
новый invoice requirement.
Interest:
повторная реализация,
межкомандная синхронизация,
расширенная regression,
риск финансового расхождения.
Exposure:
все платные заказы и финансовые документы.
Варианты
A. Оставить и усилить tests
Плюсы:
- небольшой initial cost;
- можно быстро обнаружить расхождение.
Минусы:
- четыре реализации остаются;
- delivery coordination не уменьшается;
- новые plan types увеличивают duplication.
B. Общая versioned library
Плюсы:
- единая calculation implementation;
- ниже operational complexity, чем у service.
Минусы:
- разные release cycles;
- consumers всё равно владеют частью данных;
- version drift требует управления.
C. Pricing module внутри billing boundary
Плюсы:
- можно быстро создать один domain model;
- нет network call;
- подготовка contract без преждевременной распределённости.
Минусы:
- остальным consumers нужен adapter или published calculation interface.
D. Pricing service
Плюсы:
- единый runtime decision point;
- независимое ownership и deployment.
Минусы:
- latency, availability и consistency становятся distributed-system concerns;
- требует data contract;
- migration дороже.
Решение
Команда выбирает C как первый этап, не объявляя service конечной религией.
Outcome:
одна pricing model и одна contract suite.
Stage 1:
characterization tests на текущих сценариях.
Stage 2:
PricingCalculator contract внутри billing.
Stage 3:
перевести billing на новый calculator.
Stage 4:
дать checkout synchronous adapter для preview
и включить shadow comparison.
Stage 5:
перевести invoice на сохранённый PricingDecision,
а не пересчитывать цену заново.
Stage 6:
перевести support-tool.
Stage 7:
удалить shared library и локальные rules.
Важное архитектурное различение
Invoice не обязательно должен снова вызывать calculator. Для финансовой воспроизводимости может быть важнее сохранить принятое PricingDecision вместе с версиями rules и входными данными.
То есть долг был не только в дублированном коде. Он находился в неразличении:
рассчитать текущую цену
≠
воспроизвести основание уже принятой цены.
Success evidence
- usage-based plan реализуется в одной модели;
- preview, charge и invoice используют один PricingDecision contract;
- нет локальных pricing rules в consumers;
- release не требует общей поставки трёх repositories;
- consistency suite не показывает расхождений;
- old library удалена.
31. Полный кейс: замена неподдерживаемого authentication SDK
Ситуация
Identity provider через пять месяцев прекращает поддержку старой версии API. Authentication gateway использует custom extension, тесно связанную со старой SDK.
Наблюдения:
- extension не имеет отдельного contract;
- интеграционные тесты работают только против production-like shared environment;
- все login flows проходят через один deployment;
- provider sandbox нестабилен;
- команда не знает, используют ли два internal tools старый callback;
Ошибочная постановка
"Обновить dependency с v2 на v4 — 3 points".
Версия package — только видимая поверхность. Реальный debt item включает отсутствие seam, неизвестных consumers и невозможность безопасного сравнения.
Risk horizon
Deadline внешнего provider делает trigger определённым. Delay уменьшает окно для controlled rollout.
План
- Инвентаризировать login flows и callbacks по access logs.
- Зафиксировать current behavior contract tests.
- Создать
IdentityProviderClientнад v2. - Перевести gateway на abstraction без изменения поведения.
- Реализовать v4 client.
- Создать deterministic provider simulator для contract-level tests.
- Включить shadow validation там, где нет side effects.
- Переводить internal users, затем employee cohort, затем customer cohorts.
- Наблюдать login success, error classes, latency и callback delivery.
- Остановить v2 traffic.
- Проверить отсутствие old callback calls.
- Удалить v2 client, custom compatibility и credentials.
Почему это debt reduction
Результат не только в новой версии SDK. Команда уменьшает цену следующей смены provider contract, потому что создаёт локальную границу, проверяемый contract и механизм controlled rollout.
32. Полный кейс: refactoring, который не окупается
Ситуация
Команда хочет переписать внутренний admin report module:
- 18 тысяч строк;
- устаревший UI framework;
- мало unit tests;
- код неприятно читать.
Исследование показывает:
- отчётом пользуются четыре сотрудника;
- изменения не делались 14 месяцев;
- через полгода функция перейдёт в purchased analytics platform;
- текущий module стабильно работает;
- единственный существенный риск — неизвестная процедура восстановления после deployment.
Решение
Не переписывать module.
Treatment:
- добавить smoke test critical report;
- зафиксировать build и deployment;
- создать restore runbook;
- запретить новые product features;
- поставить retirement owner;
- проверить migration на analytics platform через три месяца.
Различение
Код действительно трудно менять.
Но ожидаемых изменений почти нет.
Значит, principal существует,
а ожидаемый interest на оставшемся lifetime низок.
Лид защищает не только качество от бизнеса. Он защищает бизнес и команду от технической работы, которая не создаёт достаточного результата.
33. Язык лида
Когда долг ещё не подтверждён
"Команда считает этот участок дорогим для изменения.
Пока у нас есть два наблюдения, но нет baseline.
Перед приоритизацией восстановим последние пять изменений,
dependency surface и ближайший roadmap".
Когда принимаем intentional debt
"Мы сознательно ограничиваем первую версию одним типом договора,
чтобы проверить сценарий до конца месяца.
Решение нельзя расширять на enterprise contracts.
Пересмотр обязателен при подписании первого enterprise клиента".
Когда не платим долг
"Ограничение существует, но следующий trigger не ожидается,
а module выводится после BI migration.
Мы не переписываем его сейчас;
снижаем operational risk через smoke test и runbook".
Когда нужна миграция
"Прямой rewrite потребует год поддерживать две движущиеся реализации.
Предлагаю сначала создать routing seam,
перенести один product type
и проверить production behavior до расширения scope".
Когда migration остановилась
"Мы шесть месяцев поддерживаем old и new API,
но доля new consumers остаётся 40%.
Transition теперь увеличивает долг.
Нужно либо профинансировать завершение contract phase,
либо официально остановить migration и удалить второй путь".
Когда business просит точную окупаемость
"Точную экономию обещать нельзя: будущие изменения неизвестны.
Подтверждено, что четыре последних changes тратили по два дополнительных дня
и что в квартальном плане ещё три изменения этой области.
Первый этап стоит 5–7 дней и должен убрать двойную реализацию для этих changes.
После первого изменения сравним фактический эффект".
Когда команда хочет идеальную архитектуру
"Какую конкретную способность изменения мы восстанавливаем?
Если не можем назвать trigger, impact и success evidence,
это пока design preference, а не приоритетный debt case".
34. Практика модуля
Упражнение 1. Debt или не debt
Классифицируйте ситуации:
- В production неверно считается налог для одного региона.
- Команда предпочитает Kotlin вместо Java.
- Каждый новый налоговый регион требует правки семи
ifв четырёх сервисах. - Инженер вручную запускает ежедневную reconciliation.
- Runtime выходит из security support через четыре месяца.
- В системе нет bulk import, который хочет Product.
- Для изменения email template нужен общий release monolith.
Для каждой ситуации ответьте:
- какая категория наблюдается непосредственно;
- есть ли causal relation с debt;
- какой evidence нужен;
- какой trigger активирует цену.
Упражнение 2. Восстановить interest
Возьмите один участок, который команда называет legacy.
Опишите:
- три последних изменения;
- дополнительную цену каждого;
- повторяющиеся defects или toil;
- будущие roadmap triggers;
- affected outcomes;
- что останется, если ничего не делать.
Упражнение 3. Intentional Debt Decision
Дано:
Команде нужно за две недели запустить пилот.
Полная multi-region data replication займёт ещё месяц.
Составьте decision:
- immediate benefit;
- known consequences;
- scope;
- guardrails;
- trigger;
- stop condition;
- owner;
- review date.
Упражнение 4. Debt register
Создайте пять items своей команды. Затем удалите или перепишите всё, где нет:
- current condition;
- evidence;
- trigger;
- impact;
- treatment choice;
- review condition.
Упражнение 5. Приоритизация
Сравните:
A. Очень сложный reporting module,
который меняется раз в два года.
B. Умеренно сложный checkout module,
который меняется еженедельно и вызывает 30% regression defects.
C. Неподдерживаемая library в критическом сервисе,
vendor cutoff через шесть месяцев.
Не ранжируйте только по «плохости кода». Покажите frequency, impact, growth, horizon, treatment cost и confidence.
Упражнение 6. Refactoring boundary
Преобразуйте цель:
"Почистить order service".
в ограниченную refactoring strategy:
- change path;
- сохраняемое behavior;
- preparatory steps;
- tests;
- exit criteria;
- effect to verify.
Упражнение 7. Strangler Fig plan
Для legacy monolith выберите один capability slice и опишите:
- seam;
- routing;
- old/new coexistence;
- data ownership;
- verification;
- fallback;
- traffic expansion;
- decommission.
Упражнение 8. Разговор с Product
Подготовьте двухминутное объяснение debt item без слов:
грязный код
правильная архитектура
best practice
стыдно
невозможно поддерживать
Используйте condition, trigger, observed cost, options, recommendation и success evidence.
Упражнение 9. Незавершённая миграция
Система 18 месяцев поддерживает v1 и v2 API. На v1 остаются 12% traffic и три неизвестных consumer identities.
Составьте решение:
- как найти consumers;
- нужен ли enforced deadline;
- кто владеет migration;
- что делать с непереводимым consumer;
- когда удалить v1;
- какой evidence подтверждает завершение.
35. Итоговый артефакт: Technical Debt Operating System
35.1. Debt Item Card
# Debt Item: [название]
## Scope
[Система / component / contract / process]
## Current condition
[Наблюдаемое устройство без оценочного ярлыка]
## Evidence
- [change / delay / incident / toil / dependency]
## Origin
[Deliberate / inadvertent / unknown]
## Trigger
[Событие, при котором возникает цена]
## Interest
[Дополнительное время, risk, toil, coordination, cost]
## Exposure
[Сценарии, данные, users, teams, obligations]
## Growth
[Stable / increasing / decreasing + why]
## Horizon
[Когда ожидается trigger]
## Treatment options
1. Accept — [...]
2. Contain / Service — [...]
3. Repay / Replace / Retire — [...]
## Recommendation
[Выбор и причинность]
## Estimate and uncertainty
[Range, assumptions, discovery]
## Success evidence
[Как изменится способность системы]
## Owner
[Decision owner]
## Review condition
[Дата или событие]
35.2. Debt Register
| ID | Item | Scope | Trigger | Interest / impact | Horizon | Growth | Treatment | Owner | Review | Status |
|---|---|---|---|---|---|---|---|---|---|---|
| TD-001 | Pricing rules duplicated | Checkout / Billing | Tariff change | 3 releases, consistency risk | Q3 | Increasing | Repay in subscription work | Lead Billing | Subscription kickoff | Planned |
35.3. Intentional Debt Decision
# Intentional Debt Decision: [название]
## Context
[Почему решение принимается сейчас]
## Compromise
[Что делаем ограниченно или откладываем]
## Immediate benefit
[Что покупаем этим решением]
## Known consequences
[Что станет дороже, опаснее или недоступнее]
## Scope
[Где действует решение]
## Guardrails
[Где его нельзя повторять]
## Trigger
[Событие обязательного пересмотра]
## Stop condition
[Когда продолжать уже нельзя]
## Owner
[Кто пересматривает]
## Review date
[Дата]
35.4. Migration Plan
# Migration Plan: [capability]
## Outcome
[Какая способность появится / какое ограничение исчезнет]
## Current state
- Flows:
- Contracts:
- Consumers:
- Data:
- Operations:
- Unknowns:
## Target properties
- Ownership:
- Boundary:
- Contract:
- Data:
- Reliability:
- Deployability:
## Invariants
[Что нельзя нарушить]
## Seam
[Где old/new сосуществуют]
## Migration slices
1. [...]
2. [...]
## Compatibility
- Supported versions:
- Consumer migration order:
- Deprecation rule:
## Data plan
- Source of truth by stage:
- Backfill:
- New writes:
- Idempotency:
- Reconciliation:
## Verification
- Contract tests:
- Shadow / canary:
- Business counters:
- SLO / alerts:
## Fallback
- Traffic fallback:
- Data consequence:
- Decision owner:
- Safe window:
## Transitional components
| Component | Purpose | Owner | Removal condition |
|---|---|---|---|
## Decommission
- Stop old writes:
- Confirm no consumers:
- Remove code and routes:
- Archive / delete data:
- End infrastructure / licenses:
- Update docs and on-call:
## Milestones
| Milestone | Working state | Evidence | Decision |
|---|---|---|---|
35.5. Business Case
# Technical Investment Case
## Current condition
[Как устроено сейчас]
## Observed cost
[Что уже происходило]
## Upcoming triggers
[Roadmap / growth / external deadline]
## Options
| Option | Immediate cost | Ongoing cost | Risk | Reversibility |
|---|---:|---:|---|---|
## Recommendation
[Какой вариант и почему]
## First value milestone
[Когда появится первый проверяемый эффект]
## Success evidence
[Какие реальные изменения подтвердят результат]
## Unknowns
[Что пока нельзя обещать]
35.6. Debt Review Agenda
# Debt Review
## Context changes
- Roadmap:
- Incidents:
- External deadlines:
- Team / ownership:
## Items activated by new triggers
- [...]
## Decisions
| Item | Decision | Reason | Owner | Review condition |
|---|---|---|---|---|
## Migrations in progress
| Migration | Current slice | Evidence | Blocker | Decommission risk |
|---|---|---|---|---|
## Accepted debt
[Что оставляем и почему]
## Retired items
[Что погашено, удалено или признано нерелевантным]
36. Чек-лист лида
Перед тем как назвать что-то техническим долгом:
- [ ] Я описал текущее устройство, а не только оценил его?
- [ ] Я отличил debt от defect, risk, toil и preference?
- [ ] Есть наблюдаемое evidence?
- [ ] Назван trigger будущей цены?
- [ ] Понятно, какой outcome затронут?
- [ ] Известна exposure?
- [ ] Понятно, растёт ли цена?
Перед приоритизацией:
- [ ] Debt связан с roadmap или реальным risk horizon?
- [ ] Рассмотрены accept, contain, service, repay, replace и retire?
- [ ] Estimate содержит диапазон и assumptions?
- [ ] Discovery отделён от implementation?
- [ ] Псевдоточный score не заменяет judgment?
- [ ] Понятна цена delay?
- [ ] Назван decision owner?
Перед refactoring или migration:
- [ ] Определён outcome, а не только target technology?
- [ ] Известно сохраняемое поведение?
- [ ] Найден seam?
- [ ] Работа разрезана на working states?
- [ ] Есть compatibility strategy?
- [ ] Source of truth определён для каждого этапа?
- [ ] Есть verification на уровне system и business behavior?
- [ ] Fallback учитывает последствия для данных?
- [ ] Transitional components имеют owners и removal conditions?
- [ ] Decommission входит в scope?
После treatment:
- [ ] Старый путь действительно перестал использоваться?
- [ ] Temporary code и flags удалены?
- [ ] Проверен следующий реальный change?
- [ ] Delivery или operational effect наблюдается?
- [ ] Debt item закрыт по evidence, а не по завершению epic?
- [ ] Решение и новое устройство доступны команде?
37. Главные антипаттерны
Долг = плохой код
Исчезают trigger, impact и decision economics.
Legacy = старое
Возраст подменяет анализ changeability, value и risk.
TODO = debt register
Локальное напоминание принимается за управляемое обязательство.
Всё надо переписать
Желаемая чистота заменяет migration path и перенос реального behavior.
Новый stack погасит долг
Technology replacement не устраняет неясный domain, ownership и contracts автоматически.
20% решит проблему
Фиксированный capacity заменяет выбор debt items и проверку эффекта.
Сначала feature, потом качество
«Потом» не имеет trigger, owner и capacity.
Рефакторинг между задачами
Реальная работа скрывается и выполняется за счёт перегруза или качества.
Большой cleanup sprint
Команда улучшает всё понемногу, но не меняет ни одного целевого change path.
Code coverage как сумма долга
Один proxy принимается за экономическую цену системы.
Sonar score как business case
Tool signal заменяет связь с delivery и risk.
Самый громкий инженер задаёт приоритет
Личная боль не проверяется через frequency, impact и roadmap.
Intentional значит безопасный
Факт сознательного решения выдаётся за разумность компромисса.
Migration без contract phase
Old и new остаются навсегда, а transition удваивает сложность.
Dual write одной строкой
Consistency, retry, idempotency и reconciliation остаются неизвестными.
Rewrite без decommission
Новая система добавляется, старая продолжает жить и требовать денег.
Закрытый epic = погашенный долг
Structural activity принимается за изменение реальной способности.
Бизнес ничего не понимает
Инженерная команда не переводит condition в consequence и choice.
Техдолг виноват во всём
Метафора становится универсальным объяснением без диагностической ценности.
38. Основные формулы модуля
технический долг ≠ плохой код
legacy ≠ возраст
defect ≠ debt
risk ≠ debt
toil ≠ debt
preference ≠ debt
debt item =
current condition
+ trigger
+ additional cost / risk
+ affected outcome
principal ≈ цена изменения ограничивающего устройства
interest ≈ повторяющаяся дополнительная цена
exposure ≈ масштаб затронутых сценариев и обязательств
intentional ≠ prudent
inadvertent ≠ incompetence
acceptance ≠ ignorance
repayment ≠ perfection
refactoring ≠ rewrite
target architecture ≠ migration plan
new system ≠ migrated capability
expand + migrate без contract ≠ завершённый переход
закрытый тикет ≠ уменьшившийся долг
activity ≠ treatment effect
современный stack ≠ changeability
code metric ≠ business priority
Главная формула:
Приоритет долга определяется не тем,
насколько инженеру неприятно смотреть на код,
а тем,
какую цену текущее устройство создаёт
на вероятной траектории продукта и системы.
И ещё одна:
Погашение долга завершено,
когда изменилась способность системы:
изменение стало локальнее,
поставка — безопаснее,
восстановление — проще,
координация — меньше,
а старый путь — действительно удалён.
39. Финальное различение
Слабая работа с техническим долгом движется между двумя примитивными режимами.
Первый:
"Никакого техдолга не существует.
Есть только бизнес-задачи.
Если код работает — не трогайте".
Второй:
"Всё старое надо переписать.
Качество важнее сроков.
Дайте инженерам несколько месяцев,
и потом мы будем двигаться быстро".
Первый режим не видит отложенную цену до момента, когда она блокирует delivery или взрывается в production.
Второй не различает инженерную ценность и стремление к чистоте, обещает неизвестную окупаемость и способен превратить refactoring в отдельную реальность без пользователя.
Lead-level работа выглядит иначе:
Вот текущее устройство.
Вот evidence.
Вот событие, при котором оно создаёт цену.
Вот величина и форма этой цены.
Вот затронутый результат.
Вот ближайшая траектория продукта.
Вот варианты: принять, ограничить, обслуживать, погасить, заменить или удалить.
Вот рекомендуемый вариант и его trade-offs.
Вот первый работающий slice.
Вот неизвестность.
Вот условие остановки или пересмотра.
Вот evidence, по которому мы признаем результат.
Лид не защищает абстрактную красоту кода от бизнеса и не защищает roadmap от технической реальности.
Он удерживает связь между прошлым решением, будущей траекторией, ценой изменения и доступным выбором.
Плохая формулировка:
"Код плохой, надо переписать".
Лидерская формулировка:
"Из-за текущей структуры изменение тарифов занимает 5 дней вместо 1 дня,
потому что бизнес-правила размазаны по трём сервисам.
В следующие два квартала ожидаются четыре изменения тарифов.
Предлагаю сначала выделить pricing contract,
перевести billing и checkout поэтапно,
а эффект проверить на следующем тарифном изменении:
одно место изменения, единая consistency suite
и независимый release без синхронизации трёх команд".
Вот это язык, который понимает инженер, Product и человек, отвечающий за деньги и риск.