Перейти к содержимому

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

Константин Потапов
18 min

Технический долг — это не «плохой код». Это финансовый инструмент разработки. Разбираем метрики для измерения долга, framework для принятия решений о рефакторинге и реальные кейсы, когда НЕ рефакторить — правильное решение.

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

"Мы должны срочно порефакторить весь проект! Это же технический долг!"

"Забудь. Мы не трогаем код, который работает. Концентрируйся на фичах."

Два CTO. Два противоположных подхода. Оба правы. И оба ошибаются.

Первая команда потратила 3 месяца на рефакторинг модуля авторизации. Код стал красивым. Тесты зелёные. Архитектура — как из учебника. Бизнес потерял $400k упущенной выгоды, пока конкуренты запустили две новые фичи.

Вторая команда игнорировала "технический мусор" 2 года. Каждая новая фича занимала в 3 раза больше времени. Багов стало в 4 раза больше. Проект ушёл на полную переписывание, потеряв год разработки.

Вопрос не в том, рефакторить или нет. Вопрос — когда, что и за сколько.

Я 15 лет коллекционирую истории про технический долг. Был консультантом на 40+ проектах, где "долг" убивал продукт. Разберём, как измерять то, что кажется неизмеримым, и принимать решения, которые не приведут вас к банкротству (ни финансовому, ни моральному).

Что такое технический долг (и почему все понимают его неправильно)

Определение, которое реально работает

Технический долг — это разница между текущей архитектурой и идеальной архитектурой для решения текущих бизнес-задач.

Ключевое слово: текущих. Не будущих. Не гипотетических. Текущих.

Это как кредит в банке:

  • Берёшь взаймы — идёшь в production быстрее
  • Платишь проценты — каждая новая фича стоит дороже
  • Можешь объявить дефолт — переписывание с нуля
  • Можешь рефинансировать — рефакторинг

Метафора от Ward Cunningham (автор термина, 1992):

"Shipping first-time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite... The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt."

Виды технического долга (классификация)

Не весь долг одинаковый. Есть ипотека, а есть кредит на iPhone. Разберём.

1. Преднамеренный долг (Deliberate Debt)

Что: Сознательное решение написать "быстро и грязно" ради скорости.

Пример:

# TODO: Сейчас hardcode, потом вынести в конфиг
PAYMENT_API_URL = "https://api.stripe.com/v1"
 
def process_payment(amount):
    # Временный костыль для MVP
    # После MVP: добавить retry logic, fallback, мониторинг
    response = requests.post(PAYMENT_API_URL, data={"amount": amount})
    return response.json()

Когда оправдан:

  • MVP для валидации гипотезы
  • Deadline критичен (конференция, выставка, демо инвестору)
  • Feature нужен для замера метрик (A/B тест)

Риски:

  • TODO превращается в NEVER
  • "Временный" костыль живёт годами
  • Проценты копятся незаметно

2. Непреднамеренный долг (Inadvertent Debt)

Что: Команда не знала лучшего решения. Наивная реализация.

Пример:

# Junior думал, что это нормально
def get_all_users():
    users = []
    for user_id in range(1, 100000):  # О, боже...
        user = db.query(f"SELECT * FROM users WHERE id={user_id}")
        if user:
            users.append(user)
    return users
 
# Production через месяц: 503 Server Timeout

Как возникает:

  • Junior разработчики без менторства
  • Отсутствие code review
  • Команда не знает паттерны для данной задачи

Как избежать:

  • Code review обязателен
  • Парное программирование для сложных задач
  • Обучение команды best practices

3. Устаревший долг (Bit Rot)

Что: Код был хорош год назад. Но библиотеки обновились, паттерны изменились.

Пример:

# 2020 год — это было модно
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]
 
# 2025 год — теперь так никто не делает
# Есть async views, Pydantic валидация, rate limiting из коробки

Причины:

  • Зависимости устарели
  • Best practices эволюционировали
  • Новые фреймворки появились

Когда трогать:

  • Security уязвимости
  • Блокирует новые фичи
  • Мешает масштабированию

4. Архитектурный долг (Architectural Debt)

Что: Неправильная архитектура на уровне системы.

Пример:

# Первоначальная архитектура (MVP)
Monolith Django App
  ↓
PostgreSQL

# Реальность через 2 года (100k+ users)
Monolith (5M строк кода)
  ↓
PostgreSQL (1TB, тормозит)

# Нужная архитектура
API Gateway
  ↓
├─ User Service (microservice)
├─ Payment Service (microservice)
├─ Notification Service (microservice)
  ↓
PostgreSQL + Redis + Kafka

Когда критично:

  • Team вырос с 3 до 30 человек
  • Deploy занимает 2 часа
  • Нельзя масштабировать горизонтально

Метрики технического долга (как измерить неизмеримое)

Нельзя управлять тем, что не измеряешь. Вот метрики, которые я использую на реальных проектах.

1. Code Churn Rate (частота изменений кода)

Что измеряет: Как часто файл меняется. Высокий churn = высокий долг.

Формула:

Code Churn = Количество изменений файла за период / Всего файлов

Как измерить:

# Git: топ-10 самых изменяемых файлов за 6 месяцев
git log --since="6 months ago" --pretty=format: --name-only \
  | sort | uniq -c | sort -rg | head -10

Пример вывода:

245 src/models/user.py        # ← Тут явно проблемы
189 src/api/payments.py       # ← И тут тоже
 87 src/utils/validators.py
 56 src/views/dashboard.py

Интерпретация:

  • Churn > 100: Файл переписывается каждую неделю. Код нестабилен.
  • Churn 50-100: Высокая активность. Возможно, долг копится.
  • Churn < 50: Стабильный код.

Действия:

# Если user.py меняется 245 раз за 6 месяцев:
# 1. Code review: почему столько изменений?
# 2. Рефакторинг: возможно, класс слишком большой (God Object)
# 3. Разделить: User → UserProfile, UserAuth, UserSettings

2. Cyclomatic Complexity (цикломатическая сложность)

Что измеряет: Количество независимых путей выполнения в коде.

Формула:

Complexity = Edges - Nodes + 2 (для графа потока управления)

Простыми словами: Количество if, for, while, and, or в функции.

Инструмент:

# Python: radon
pip install radon
radon cc -a src/ --total-average
 
# Вывод:
# src/models/user.py
#   M 245:0 UserModel.validate - D (23)  # ← Сложность 23 — это плохо
#   M 112:0 UserModel.save - C (15)

IDE с встроенной поддержкой:

Современные IDE показывают Cyclomatic Complexity прямо в редакторе:

  • PyCharm — показывает complexity в виде подсветки (серый = норма, желтый = внимание, красный = проблема)
  • VS Code — через расширение "Python Complexity" или "SonarLint"
  • IntelliJ IDEA — встроенный анализ кода с метриками
  • Visual Studio — Code Metrics (Analyze → Calculate Code Metrics)

Пример в PyCharm:

def process_payment(...):  # ← PyCharm показывает: Complexity: 23 (серая подсказка)
    # Если наведёте курсор, увидите: "Cyclomatic complexity too high"

Преимущество IDE:

  • Видишь метрики во время написания кода (не пост-фактум)
  • Настраиваешь пороги (warning при > 10, error при > 20)
  • Не нужно запускать отдельные команды

Интерпретация (стандарт Software Engineering Institute):

  • 1-10: Простой код, легко тестировать
  • 11-20: Средняя сложность, риск багов растёт
  • 21-50: Высокий риск, код непонятен
  • 50+: Untestable code, рефакторинг обязателен

Пример плохого кода:

def process_payment(user, amount, currency, method, promo_code=None):
    if user.is_active:
        if user.balance >= amount:
            if currency in ["USD", "EUR", "RUB"]:
                if method == "card":
                    if user.card_verified:
                        if promo_code:
                            discount = get_discount(promo_code)
                            if discount:
                                amount = amount * (1 - discount)
                        # ... ещё 50 строк вложенных if
                        return True
    return False
 
# Complexity: 42 🤯

Рефакторинг:

def process_payment(user, amount, currency, method, promo_code=None):
    _validate_user(user)
    _validate_currency(currency)
    _validate_payment_method(user, method)
 
    final_amount = _apply_discount(amount, promo_code)
    return _charge_user(user, final_amount, method)
 
# Complexity каждой функции: 3-5 ✅

3. Test Coverage (покрытие тестами)

Что измеряет: % кода, покрытого тестами.

Формула:

Coverage = (Строки, выполненные в тестах / Всего строк кода) × 100%

Инструмент:

# Python: pytest-cov
pytest --cov=src --cov-report=html
 
# Вывод:
# Name                Stmts   Miss  Cover
# ---------------------------------------
# src/models/user.py    245     89    64%  # ← Низкое покрытие
# src/api/auth.py       156      5    97%  # ← Хорошо

Интерпретация:

  • 80-100%: Хорошее покрытие (не гарантия, но индикатор)
  • 50-80%: Средний долг, риск регрессий
  • < 50%: Высокий долг, каждое изменение — русская рулетка

Важно: 100% coverage ≠ 0% багов. Но 0% coverage = 100% боль при рефакторинге.

Пример из жизни:

# Код без тестов (coverage 0%)
def calculate_discount(user, cart_total):
    if user.is_vip and cart_total > 1000:
        return cart_total * 0.2
    elif user.orders_count > 10:
        return cart_total * 0.1
    return 0
 
# Junior поменял логику:
def calculate_discount(user, cart_total):
    if user.is_vip or cart_total > 1000:  # ← Ошибка: 'and' → 'or'
        return cart_total * 0.2
    # ...
 
# Результат: VIP клиент получил скидку 20% на корзину $50
# Бизнес потерял $10k за выходные

С тестами:

def test_vip_discount():
    user = User(is_vip=True)
    assert calculate_discount(user, 1500) == 300  # ❌ Тест упал
    # Junior увидел ошибку ДО production

4. Code Duplication (дублирование кода)

Что измеряет: % повторяющихся блоков кода.

Инструмент:

# jscpd (работает с Python, JS, etc.)
npm install -g jscpd
jscpd src/
 
# Вывод:
# Duplications: 23.4%  # ← 23% кода дублируется
# Files: 156
# Clones: 42

Интерпретация:

  • < 5%: Нормально (иногда дублирование оправдано)
  • 5-15%: Средний долг
  • > 15%: Высокий долг, DRY principle нарушен

Пример:

# ❌ Дублирование (3 раза один и тот же код)
def create_user(data):
    if not data.get("email"):
        return {"error": "Email is required"}, 400
    if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", data["email"]):
        return {"error": "Invalid email"}, 400
    # ...
 
def update_user(data):
    if not data.get("email"):
        return {"error": "Email is required"}, 400
    if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", data["email"]):
        return {"error": "Invalid email"}, 400
    # ...
 
def send_invite(data):
    if not data.get("email"):
        return {"error": "Email is required"}, 400
    if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", data["email"]):
        return {"error": "Invalid email"}, 400
    # ...

✅ Рефакторинг:

def validate_email(email):
    if not email:
        raise ValueError("Email is required")
    if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email):
        raise ValueError("Invalid email")
    return email.lower()
 
def create_user(data):
    email = validate_email(data.get("email"))
    # ...

5. Bus Factor (фактор автобуса)

Что измеряет: Сколько человек должно попасть под автобус, чтобы проект встал.

Формула:

Bus Factor = Количество ключевых разработчиков, которые знают критичный код

Инструмент:

# Git: кто автор каждой строки?
git ls-files | xargs -n1 git blame --line-porcelain | grep "^author " | sort | uniq -c | sort -rg
 
# Вывод:
# 12456 John Doe       # ← Если John уволится — проект умрёт
#  3421 Alice Smith
#   987 Bob Johnson

Интерпретация:

  • Bus Factor = 1: Критично. Один человек знает весь код.
  • Bus Factor = 2-3: Риск. Зависимость от нескольких людей.
  • Bus Factor ≥ 5: Здоровая команда.

Пример из жизни:

Senior ушёл в отпуск на 2 недели. Production упал. Никто не знал, как работает авторизация. Потеряли 3 дня на реверс-инжиниринг собственного кода.

Решение:

  • Code review всем кодом
  • Парное программирование
  • Документация архитектуры
  • Rotation (каждый должен поработать с каждым модулем)

6. Debt Ratio (коэффициент долга)

Что измеряет: Стоимость исправления долга / Стоимость написания кода с нуля.

Формула (SonarQube):

Debt Ratio = (Remediation Cost / Development Cost) × 100%

Пример:

Remediation Cost: 120 дней (время на рефакторинг)
Development Cost: 400 дней (время, потраченное на разработку)
Debt Ratio = (120 / 400) × 100% = 30%

Интерпретация:

  • < 5%: Отличный код
  • 5-10%: Хорошо
  • 10-20%: Средний долг
  • > 20%: Критично, рефакторинг или переписывание

Инструмент:

# SonarQube (бесплатная версия)
docker run -d --name sonarqube -p 9000:9000 sonarqube:latest
# Запустить анализ проекта

7. Lead Time (время добавления фичи)

Что измеряет: Сколько времени занимает добавление новой фичи.

Формула:

Lead Time = Время от коммита до production

Как измерить:

# JIRA/GitHub: среднее время задачи "In Progress" → "Done"
# Или через git:
git log --all --format="%h %ai" | head -100

Интерпретация:

  • Lead Time растёт со временем: Долг копится
  • Lead Time стабилен: Долг контролируется
  • Lead Time снижается: Вы молодцы (или фичи становятся проще)

Пример:

ПериодСредний Lead TimeВывод
Q1 20243 дняНормально
Q2 20245 днейДолг растёт
Q3 20248 днейКритично
Q4 202414 днейКод превратился в болото

Причины роста:

  • Код стал сложнее (Cyclomatic Complexity вырос)
  • Больше регрессий (Test Coverage упал)
  • Больше merge conflicts (Архитектура слабая)

Framework для принятия решений (рефакторить или забить?)

Теперь у вас есть метрики. Что с ними делать? Вот decision framework из 4 шагов.

Шаг 1: Оценить стоимость долга (Cost of Debt)

Формула:

Cost of Debt = Время на обходные решения × Стоимость часа разработки

Пример:

# У вас в коде:
def get_user_orders(user_id):
    # Костыль: нет индекса на user_id, запрос тормозит
    # Каждый раз добавляем LIMIT 100, чтобы не упасть
    return db.query("SELECT * FROM orders WHERE user_id = ? LIMIT 100", user_id)
 
# Стоимость долга:
# - Каждая новая фича с заказами требует костыль
# - Разработчики тратят 30 минут на каждый костыль
# - 10 фич в квартал = 5 часов
# - Стоимость часа разработчика: $50
# Cost of Debt = 5 часов × $50 = $250/квартал

Если стоимость > $1000/год — рассмотрите рефакторинг.


Шаг 2: Оценить стоимость рефакторинга (Cost of Refactoring)

Формула:

Cost of Refactoring = Время на рефакторинг × Стоимость часа + Риск регрессий

Пример:

# Рефакторинг: добавить индекс на orders.user_id
# Время: 2 часа (миграция + тесты)
# Риск: низкий (индекс не ломает логику)
# Cost of Refactoring = 2 часа × $50 + $0 (риск) = $100

Вывод: $100 вложений vs $250/квартал экономии = окупится за 1.5 месяца.


Шаг 3: Оценить opportunity cost (упущенная выгода)

Вопрос: Что вы НЕ сделаете, пока будете рефакторить?

Пример:

Вы планируете 2 недели на рефакторинг архитектуры платежей. За это время:

  • Конкурент запустит новую фичу
  • Вы НЕ выпустите запланированный feature
  • Клиенты будут ждать bug fix

Формула:

Opportunity Cost = Потенциальная выгода от новых фич × Вероятность успеха

Пример:

Новая фича: Subscription billing
Потенциальная выгода: $10k MRR
Вероятность успеха: 70%
Opportunity Cost = $10k × 0.7 = $7k

Рефакторинг платежей:
Экономия: $2k/год на поддержке
Opportunity Cost: $7k упущенной выгоды

Вывод: НЕ рефакторить сейчас. Сначала фича.

Шаг 4: Приоритизация по матрице Impact/Effort

Матрица:

High Impact │  REFACTOR NOW  │  SCHEDULE    │
            │  (quick wins)  │  (важно)     │
────────────┼────────────────┼──────────────┤
Low Impact  │  MAYBE         │  IGNORE      │
            │  (if free time)│  (tech vanity)│
────────────┴────────────────┴──────────────┘
            Low Effort       High Effort

Примеры:

ЗадачаImpactEffortРешение
Добавить индекс на user_idHighLow✅ REFACTOR NOW
Переписать монолит в микросервисыHighHigh📅 SCHEDULE
Переименовать переменную x → userLowLow🤷 MAYBE
Переписать на TypeScriptLowHigh❌ IGNORE

Полный алгоритм принятия решения

def should_refactor(debt_item):
    # Шаг 1: Стоимость долга
    cost_of_debt = estimate_debt_cost(debt_item)
 
    # Шаг 2: Стоимость рефакторинга
    cost_of_refactoring = estimate_refactoring_cost(debt_item)
 
    # Шаг 3: Упущенная выгода
    opportunity_cost = estimate_opportunity_cost(debt_item)
 
    # Шаг 4: ROI рефакторинга
    roi = (cost_of_debt - cost_of_refactoring - opportunity_cost) / cost_of_refactoring
 
    # Правила решения
    if roi > 2.0:
        return "REFACTOR NOW"  # Окупится в 2x
    elif roi > 1.0:
        return "SCHEDULE"       # Окупится, но не срочно
    elif roi > 0:
        return "MAYBE"          # Окупится, но есть лучшие варианты
    else:
        return "IGNORE"         # Не окупится

Пример использования:

debt_item = {
    "name": "Добавить индекс на orders.user_id",
    "cost_of_debt": 250,        # $/квартал
    "cost_of_refactoring": 100, # $
    "opportunity_cost": 0       # Не блокирует фичи
}
 
roi = (250 - 100 - 0) / 100 = 1.5
# Вывод: SCHEDULE (окупится за 1.5 месяца)

Реальные кейсы: когда рефакторить, а когда — забить

Кейс 1: "Как мы НЕ рефакторили payment gateway и потеряли $500k"

Ситуация:

E-commerce платформа. Payment gateway написан в 2018 году. Код работал, но:

  • Нет тестов (coverage 0%)
  • Hardcode на каждом шагу
  • 1 разработчик (Bus Factor = 1)

Debt Metrics:

  • Cyclomatic Complexity: 68 (critical)
  • Code Churn: 180 изменений за год
  • Lead Time для payment фич: 2 недели (вместо 2 дней)

Решение команды: "Не трогаем. Работает — не лезь."

Что случилось:

Black Friday 2023. Трафик вырос в 10 раз. Payment gateway упал. Разработчик в отпуске. Никто не знает, как чинить.

Результат:

  • 8 часов downtime
  • $500k упущенных продаж
  • 2 недели на emergency fix
  • 1 месяц на полную переписывание

Урок: Если Bus Factor = 1 на критичном модуле — рефакторинг ОБЯЗАТЕЛЕН.

Что надо было сделать:

  1. Добавить тесты (1 неделя)
  2. Упростить код (1 неделя)
  3. Документация (2 дня)

Итого: 2.5 недели vs 1 месяц emergency + $500k потерь.


Кейс 2: "Как мы зря порефакторили admin панель и потеряли 3 месяца"

Ситуация:

SaaS стартап. CTO решил "привести код в порядок". Начал с admin панели.

Debt Metrics:

  • Cyclomatic Complexity: 12 (нормально)
  • Test Coverage: 75% (хорошо)
  • Code Churn: 15 (низкий)

Решение: Переписать admin на React вместо Django admin.

Что случилось:

  • 3 месяца разработки
  • Конкуренты выпустили 2 новых фичи
  • Клиенты недовольны (нет обновлений)
  • Рефакторинг НЕ принёс бизнес-выгоды

Результат:

  • $400k упущенной выгоды
  • 2 ключевых клиента ушли к конкурентам
  • Team выгорел

Урок: Не рефакторите то, что работает и не блокирует бизнес.

Красные флаги:

  • "Давайте перепишем на модный фреймворк"
  • "Этот код некрасивый" (но работает)
  • "Мне не нравится архитектура" (без бизнес-обоснования)

Кейс 3: "Как 2 дня рефакторинга сэкономили $50k/год"

Ситуация:

API сервис. Каждый endpoint дублирует логику аутентификации.

Debt Metrics:

  • Code Duplication: 32% (критично)
  • 150 строк одинакового кода в 45 файлах

Проблема:

Bug в логике аутентификации → нужно фиксить в 45 местах → 2 дня работы → риск пропустить файл.

Решение:

2 дня на рефакторинг:

  1. Вынести аутентификацию в middleware
  2. Покрыть middleware тестами
  3. Удалить дублированный код

Результат:

  • Bug fix теперь: 15 минут (вместо 2 дней)
  • Экономия: 20 bug fixes/год × 2 дня × $300/день = $12k/год
    • снижение риска security ошибок

ROI:

Cost: $600 (2 дня × $300)
Savings: $12k/год
ROI: 20x в первый год

Урок: Code duplication на критичных участках — первый кандидат на рефакторинг.


Инструменты для мониторинга технического долга

1. SonarQube (лучший all-in-one)

Что умеет:

  • Cyclomatic Complexity
  • Code Duplication
  • Test Coverage
  • Security уязвимости
  • Debt Ratio

Установка:

docker run -d --name sonarqube -p 9000:9000 sonarqube:latest

Интеграция в CI/CD:

# .gitlab-ci.yml
sonarqube:
  script:
    - sonar-scanner -Dsonar.projectKey=my-project
  only:
    - main

2. CodeClimate (для GitHub)

Что умеет:

  • Автоматический анализ pull requests
  • Debt trends (динамика долга)
  • Интеграция с GitHub Actions

Цена: $0 (open source), $199/месяц (private repos)


3. Radon (Python, бесплатно)

Установка:

pip install radon

Команды:

# Cyclomatic Complexity
radon cc -a src/
 
# Maintainability Index
radon mi src/
 
# Raw metrics (LOC, LLOC, SLOC)
radon raw src/

4. Git analytics (бесплатно)

Code Churn:

git log --since="6 months ago" --pretty=format: --name-only \
  | sort | uniq -c | sort -rg | head -10

Bus Factor:

git ls-files | xargs -n1 git blame --line-porcelain \
  | grep "^author " | sort | uniq -c | sort -rg

5. Codecov (Test Coverage)

Интеграция:

# .gitlab-ci.yml
test:
  script:
    - pytest --cov=src --cov-report=xml
    - bash <(curl -s https://codecov.io/bash)

Фичи:

  • Coverage trends
  • Pull request комментарии
  • Badges для README

Чек-лист: Рефакторить или нет?

✅ РЕФАКТОРИТЬ СЕЙЧАС, если:

  • Bus Factor = 1 на критичном модуле
  • Cyclomatic Complexity > 20
  • Test Coverage < 50% на критичном коде
  • Security уязвимости (CVSS > 7.0)
  • Lead Time растёт каждый квартал
  • Code Duplication > 30%
  • Production incidents связаны с этим кодом

📅 ЗАПЛАНИРОВАТЬ РЕФАКТОРИНГ, если:

  • Debt Ratio > 10%
  • Code Churn > 100 за полгода
  • Блокирует новые фичи (но не критично)
  • Команда растёт (новички тонут в коде)
  • Onboarding занимает > 1 месяца

❌ НЕ РЕФАКТОРИТЬ, если:

  • Код работает и не блокирует бизнес
  • ROI < 1.0 (не окупится)
  • Opportunity cost высок (есть важные фичи)
  • "Просто некрасиво" (без бизнес-боли)
  • Модуль планируется удалить в ближайшие 6 месяцев

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

1. Debt Sprint (20% времени на рефакторинг)

Правило: Каждый 5-й спринт — debt sprint.

Что делаем:

  • Фиксим top-10 debt items (по метрикам)
  • Добавляем тесты на критичные модули
  • Обновляем документацию

Пример:

Sprint 1-4: Features
Sprint 5: Debt Sprint
  - Task 1: Покрыть тестами payment gateway (coverage 0% → 80%)
  - Task 2: Рефакторинг UserModel (complexity 45 → 12)
  - Task 3: Добавить индексы на топ-10 slow queries

2. Boy Scout Rule (правило бойскаута)

Правило: Оставляй код чище, чем нашёл.

Как применять:

# Ты фиксишь баг в функции process_payment()
# Видишь, что там complexity 25
# Тратишь +30 минут на рефакторинг
# Complexity становится 10
# Commit: "Fix payment bug + refactor for clarity"

Ограничения:

  • Не больше 30% времени на рефакторинг
  • Не меняй архитектуру (только локальные улучшения)

3. Debt Dashboard (визуализация долга)

Метрики на dashboard:

  • Debt Ratio (SonarQube)
  • Test Coverage (Codecov)
  • Lead Time (Jira/GitLab)
  • Code Churn (Git)

Пример:

┌─────────────────────────────────────────┐
│ Technical Debt Dashboard        Q4 2025 │
├─────────────────────────────────────────┤
│ Debt Ratio:          12% ⚠️ (was 8%)    │
│ Test Coverage:       78% ✅ (was 75%)   │
│ Lead Time:          6 days 📈 (was 5)   │
│ Critical Issues:         3 🚨           │
└─────────────────────────────────────────┘

Review: Раз в квартал с командой. Обсуждаем тренды.


4. Debt Tag в Pull Requests

Правило: Если PR увеличивает долг — ставим тег [DEBT].

Пример:

PR #123: [DEBT] Quick fix for payment bug

Debt:
- Added hardcode for Stripe API key (should be in env)
- Skipped tests (time constraint)
- Complexity increased from 12 to 18

Action item:
- Created task DEBT-456 to refactor in next sprint

Польза:

  • Осознанный долг (не случайный)
  • Трекинг (знаем, где копится долг)
  • Планирование (DEBT-задачи в backlog)

Последний совет: Технический долг — это не зло

Технический долг — это инструмент.

Кредит в банке — не зло. Кредит позволяет купить квартиру сейчас, а не через 20 лет. Но если брать кредиты бесконтрольно — банкротство.

То же с техническим долгом:

Взять долг ради MVP — правильно ✅ Взять долг ради deadline — иногда оправдано ❌ Не платить долг годами — самоубийство ❌ Брать долг ради "скорости" без плана возврата — глупость

Главное правило:

"Каждый технический долг должен иметь план возврата. Иначе это не долг, а дефолт."

Вопросы для себя:

  1. Знаем ли мы, где у нас долг? (метрики)
  2. Знаем ли мы, сколько он стоит? (Cost of Debt)
  3. Есть ли план возврата? (backlog задачи)
  4. Когда мы заплатим? (debt sprint)

Если хотя бы на 2 вопроса ответ "нет" — у вас проблемы.


Что делать прямо сейчас

Шаг 1: Измерить долг (30 минут)

# 1. Code Churn
git log --since="6 months ago" --pretty=format: --name-only \
  | sort | uniq -c | sort -rg | head -10
 
# 2. Cyclomatic Complexity (Python)
pip install radon
radon cc -a src/
 
# 3. Test Coverage
pytest --cov=src --cov-report=term-missing
 
# 4. Bus Factor
git ls-files | xargs -n1 git blame --line-porcelain \
  | grep "^author " | sort | uniq -c | sort -rg

Шаг 2: Приоритизировать (1 час)

Создайте таблицу:

Debt ItemImpactEffortROIДействие
Payment gatewayHighMedium5.0REFACTOR NOW
Admin панельLowHigh0.2IGNORE
User modelMediumLow2.5SCHEDULE

Шаг 3: Запланировать (10 минут)

  • Создайте задачи в Jira/GitLab для top-3 debt items
  • Добавьте в следующий спринт 1-2 задачи
  • Запланируйте Debt Sprint через 4 недели

Шаг 4: Автоматизировать (1 час)

# .gitlab-ci.yml
debt-analysis:
  stage: test
  script:
    - pip install radon
    - radon cc -a src/ --total-average
    - pytest --cov=src --cov-report=term
  only:
    - main

Полезные ссылки


Делитесь опытом!

Я рассказал про метрики и framework. Теперь ваша очередь:

  • Какой самый дорогой технический долг был у вас?
  • Как вы принимаете решения о рефакторинге?
  • Были случаи, когда НЕ рефакторить было правильным решением?

Пишите в комментариях или в Telegram. Обсудим метрики, поделимся кейсами.

Нужна помощь с аудитом технического долга? Пишите на почту — проведу анализ вашего проекта, дам рекомендации по приоритизации рефакторинга. Первая консультация бесплатно.

Понравилась статья? Поделитесь с коллегой, который говорит "рефакторинг — это трата времени" или "надо всё переписать". Спасёте его проект (и карьеру).


Подписывайтесь на обновления в Telegram — пишу про архитектуру, метрики и управление разработкой. Без воды, только практика.

Похожие материалы