open atlas
↑ К треку
Паттерны и качество кода CP · 13 · 02

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

Квадрант долга Фаулера делит срезанные углы по намерению: намеренный против ненамеренного, разумный против безрассудного. Не всякий долг плох — разумный намеренный долг это инструмент финансирования. Опасны безрассудный и неучтённый долг, что копится незаметно.

CP Senior ◷ 19 min
Уровень
ОсновыJuniorMiddleSenior

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

Слово «долг» точнее, чем подразумевает извинение. Настоящий долг — это финансовое решение: ты занимаешь скорость сейчас и платишь процент потом в виде более медленных и рискованных изменений. Часть этих займов проницательна; часть безрассудна; часть ты даже не понял, что подписал. Работа senior-инженера — отличать их друг от друга и делать займ видимым, чтобы его можно было оценить и погасить, а не копить в темноте.

Цель

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

1

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

// «Долг»: намеренно узкий срезанный угол, взятый ради релиза MVP.
// Мы делаем частный случай для единственного существующего сегодня плана,
// вместо общего биллингового движка, который пока не оправдан.
function priceFor(plan: Plan): number {
  if (plan === "pro") return 20_00; // центы; единственный платный план на старте
  return 0; // бесплатный
}

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

2

У квадранта Фаулера две оси, и они отвечают на разные вопросы. Первая ось — намеренный против ненамеренного — спрашивает «знали ли мы, что берём долг?» Вторая — разумный против безрассудного — спрашивает «было ли это хорошим решением при том, что мы знали?» Их пересечение даёт четыре очень разные ситуации, которые единственное слово «долг» прячет:

  • Намеренный + разумный: «Сделать как надо нет времени, релизим сейчас, рефакторим после запуска — вот план.» Осознанный, оценённый компромисс.
  • Намеренный + безрассудный: «Сделать как надо нет времени.» Точка. Ты выбрал беспорядок, без плана и без расчёта процента.
  • Ненамеренный + разумный: «Теперь мы знаем, как надо было это спроектировать.» Честное обучение — ты не мог узнать лучшую форму, пока не построил первую.
  • Ненамеренный + безрассудный: «А что такое слои?» Ты взял долг, потому что не знал, что дизайн существует — и не знал, что не знаешь.

Сигнал зрелости — в каком квадранте оказываются твои срезанные углы. Разумно-намеренный долг — это инструмент. Остальные три — предупреждения разного рода: о нехватке дисциплины, о нехватке знаний или о том и другом.

3

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

// Сделай долг ВИДИМЫМ: не голый TODO, а запись о долге с
// (1) что заняли, (2) почему, (3) процент, (4) триггер погашения.
// DEBT(billing): единственная жёстко зашитая цена плана — релиз MVP 01.06.
//   Почему: один платный план на старте; биллинговый движок пока не оправдан.
//   Процент: каждый новый план трогает эту fn + всех вызывающих с одной ценой.
//   Гасить когда: в дорожную карту попадает ВТОРОЙ платный план. Владелец: @billing.
if (plan === "pro") return 20_00;

Голый // TODO: fix later — это не оно. Он не фиксирует ни обоснования, ни цены, ни триггера — поэтому никогда не приоритизируется и становится археологическим шумом, через который переступает следующий читатель. Дисциплина не в том, чтобы «оставлять TODO»; она в том, что «каждый намеренный срезанный угол оставляет запись о долге, с которой может работать планировщик».

4

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

// Сортировка, не вина. Вопрос в проценте, а не в несовершенстве.
type DebtCall = "take-it" | "pay-now" | "leave-it";

function triage(d: { boundedInterest: boolean; hasTrigger: boolean; blocksShip: boolean; risk: "low" | "high" }): DebtCall {
  if (d.risk === "high" && d.blocksShip) return "pay-now";          // занимать здесь безрассудно
  if (d.boundedInterest && d.hasTrigger && d.blocksShip) return "take-it"; // разумный намеренный
  return "leave-it"; // не на пути изменений → не плати процент, гоняясь за ним
}

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

Разбор примера

Тот же срезанный угол, превращённый в разумный долг вместо безрассудного. Команде нужно выкатить оформление заказа к пятнице. Правильный дизайн имеет интерфейс PaymentGateway, чтобы провайдеров можно было добавлять позже, но сегодня существует только Stripe. Безрассудная версия просто впаивает Stripe напрямую и ничего не говорит:

// checkout.ts — безрассудный намеренный долг: выбрал срез, не записал ничего
async function charge(amount: number) {
  return stripe.charges.create({ amount, currency: "usd" }); // прямая зависимость
}

Это работает и уезжает к пятнице. Но долг невидим: полгода спустя кому-то нужен PayPal, он грепает по stripe, находит его продёрнутым через девять файлов, и «быстрая» связанность теперь — многонедельное извлечение, которое никто не бюджетировал. Процент копился сложно в темноте.

Теперь разумно-намеренная версия — тот же срезанный угол, сделанный видимым и ограниченным:

// checkout.ts — разумный намеренный долг: занято, с записью и триггером.
// DEBT(payments): нет абстракции PaymentGateway — вызываем Stripe напрямую,
//   чтобы успеть к пятничному запуску. Процент: второй провайдер потребует
//   извлечь интерфейс по всем местам вызова. Гасить когда: зафиксирован
//   второй провайдер. Владелец: @payments.
async function charge(amount: number) {
  return stripe.charges.create({ amount, currency: "usd" });
}

Идентичное поведение в рантайме, идентичная дата запуска. Единственное отличие в том, что вторая версия оценила займ: она называет, что занято, какой процент и какой триггер заставляет погасить. Когда появится PayPal, запись превратит панические раскопки в запланированную задачу — а если PayPal так и не появится, ты правильно так и не заплатил процент за построение абстракции, которая была не нужна. Тот же код; противоположный квадрант; разница в записи, а не в хитрости.

Почему это работает

Почему намеренный долг часто безопаснее ненамеренного, хотя намеренный звучит хуже? Потому что намеренный долг — это, по определению, долг, который ты видишь в момент его взятия — а значит, можешь записать его, ограничить и привязать триггер. Ненамеренный долг — это дефект дизайна, который ты не знал, что он дефект; он копит процент ещё до того, как ты вообще заметишь баланс. Настоящий урок квадранта в том, что рычаг — это видимость, а не намерение: намеренный срез с записью о долге — это управляемое обязательство, тогда как ненамеренный — невыставленный счёт, который ты обнаруживаешь, только когда в него упирается изменение. Бо́льшая часть senior-управления долгом — это как можно раньше превращать невидимый вид в видимый.

Частая ошибка

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

Проверь себя
Викторина

Под дедлайном команда впаивает Stripe прямо в оформление заказа вместо построения абстракции платёжного шлюза. Какой фактор сильнее всего определяет, разумный это долг или опасность?

Итог

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

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.