Rain Lag

Аналоговый рефакторинг в стиле тилт-шифт: как «уменьшить» кодовую базу на бумаге и увидеть скрытые зависимости

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

Аналоговый рефакторинг в стиле тилт-шифт: как «уменьшить» кодовую базу на бумаге и увидеть скрытые зависимости

Если вы когда‑нибудь видели фотографии с эффектом тилт‑шифт, то понимаете, о чём речь: полноразмерный город вдруг выглядит как игрушечный макет. Меняется перспектива — и сложность становится обозримой. Вы начинаете видеть закономерности, которые раньше ускользали.

То же самое можно сделать с архитектурой программных систем.

Когда ваша облачная система разрослась до десятков или сотен сервисов, о рефакторинге уже почти невозможно рассуждать только «в голове» — или даже только в пределах IDE. Решение — уменьшить систему, визуализировать её зависимости и использовать этот новый ракурс, чтобы планировать более безопасные и эффективные изменения.

Это и есть аналоговый рефакторинг в стиле тилт‑шифт: миниатюризация вашей кодовой базы на бумаге (или её цифровом эквиваленте), чтобы скрытые зависимости и опасные связности стали очевидными.


Почему систему нужно видеть, а не только читать код

Современные архитектуры — особенно микросервисы и event‑driven системы — образуют плотную сеть зависимостей:

  • Сервис A синхронно вызывает B.
  • B публикует события, которые потребляют C и D.
  • E зависит от общей таблицы в базе данных, которую также изменяет B.
  • F по расписанию выполняет джоб, который чистит артефакты, созданные A.

По отдельности каждое взаимодействие логично. Вместе они становятся непрозрачными.

Визуализация зависимостей между сервисами

Инструменты, которые строят графы зависимостей (например, по трейсингу, логам, манифестам или анализу кода), дают вам тот самый тилт‑шифт‑ракурс. Граф с силовой разметкой (force‑directed graph), где узлы отталкиваются друг от друга, а рёбра стягивают связанные сервисы, превращает расплывчатое «у нас много сервисов» во что‑то, с чем можно работать:

  • Кластеры тесно связанных сервисов становятся заметными «районами».
  • Сквозные зависимости (вроде общего auth‑ или billing‑сервиса) проявляются как хабы.
  • Осиротевшие или редко трогаемые сервисы торчат по краям.

Когда вы печатаете или от руки рисуете такие графы, вы буквально начинаете гулять по своей архитектуре с ручкой в руках. Вы можете:

  • Обводить кружком горячие точки сложности.
  • Подписывать рёбра («синхронный REST‑вызов», «асинхронный pub/sub», «прямой доступ к БД»).
  • Помечать кандидатов на укрупнение, вынос или выведение из эксплуатации.

Это не просто красивая картинка. Это предпосылка для осмысленного рефакторинга.


От красивых картинок к практическим решениям

Визуализация зависимостей особенно полезна, когда она привязана к конкретным управленческим и инженерным решениям:

  • Помощь владельцам сервисов: владелец сервиса может одним взглядом увидеть, кто от него зависит и от кого зависит он сам. Размытое «мы критичный сервис» превращается в конкретную карту: «Эти шесть сервисов вызывают нас, эти три подписаны на наши события, а вот этот незаметно опрашивает нашу БД.»
  • Планирование миграций: когда вы хотите изменить базовую способность системы — например, идентификацию пользователей, биллинг или инвентаризацию — граф показывает все затронутые пути. Можно планировать миграцию по этапам, а не гадать.
  • Поиск скрытых связей: вы можете обнаружить:
    • Сервис, который обходит API и читает базу данных другого сервиса напрямую.
    • Несколько сервисов, каждый из которых реализует кусок одной и той же логики.
    • «Временные» feature‑флаги или routing‑слои, которые так и не убрали.

Но сам по себе граф не говорит, насколько плоха та или иная зависимость. Для этого нужен язык для описания связности.


Коннасценция: удобный язык для обсуждения связности

Коннасценция (connascence) — концепция в проектировании ПО, которая даёт структурированный способ говорить о связности компонентов. Она выделяет три практических измерения:

  1. Сила (strength) – Насколько сложно изменить эту зависимость?
  2. Локальность (locality) – Насколько далеко друг от друга находятся зависимые элементы (в коде, в архитектуре, в организации)?
  3. Степень (degree) – Сколько элементов связаны этой зависимостью?

Сила: насколько болезненно менять?

Чем сильнее коннасценция, тем дороже по усилиям рефакторинг при изменениях. Например:

  • Две функции, которые приходится менять вместе, потому что они разделяют «магическую» строку: умеренная сила.
  • Десять сервисов, зависящих от конкретного имени поля в JSON‑схеме: более высокая сила.
  • Общие таблицы, где несколько сервисов зависят от одних и тех же неявных инвариантов: очень высокая сила.

Смотря на граф зависимостей, помечайте рёбра уровнем боли при изменении:

  • «Изменение этого поля ломает 7 сервисов».
  • «Этот эндпоинт использует только один внутренний админ‑инструмент».

Рёбра с сильной коннасценцией — хорошие кандидаты в приоритетные задачи рефакторинга.

Локальность: насколько растянута зависимость?

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

  • Локальная коннасценция (в пределах одного файла или сервиса) проще управляется.
  • Нелокальная коннасценция (между сервисами, командами или репозиториями) увеличивает стоимость координации и радиус поражения при сбоях.

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

Степень: сколько всего связано?

  • Зависимость «один к одному»: низкая степень.
  • Изменение схемы, затрагивающее 30 потребителей: очень высокая степень.

На вашей миниатюрной архитектуре степень проявляется как фан‑аут рёбер от узла. Много стрелок, исходящих из «user‑profile»‑сервиса? Это зона высокой степени связности.


Как приоритизировать рефакторинг с помощью коннасценции

Нельзя починить всё сразу. Коннасценция помогает направить ограниченную энергию на самые значимые проблемы.

Практичный рабочий процесс:

  1. Картируйте систему

    • Сгенерируйте или набросайте высокоуровневый граф зависимостей сервисов.
    • Отметьте типы соединений (HTTP, gRPC, очередь, БД, файловая система и т. д.).
  2. Примерно оцените рёбра:

    • Сила: 1–5 (насколько больно менять?).
    • Локальность: 1–5 (один файл → один сервис → одна команда → межкомандное/межорганизационное взаимодействие).
    • Степень: число зависимых (логарифмически масштабируйте, если очень много).
  3. Найдите горячие зоны

    • Ищите зависимости с высокой силой, низкой локальностью (то есть сильно растянутые) и высокой степенью.
    • Пример: общая таблица, в которую пишут 5 сервисов и из которой читают 8, с хрупкими, нигде не зафиксированными инвариантами.
  4. Выберите цели для рефакторинга, опираясь на:

    • Снижение рисков: разрыв этой зависимости делает множество будущих изменений безопаснее.
    • Рост скорости: этот участок постоянно блокирует работу.
    • Стратегическую ценность: он лежит на пути крупных планируемых фич или миграций.

Такой подход превращает рефакторинг из «чиню то, что раздражает сегодня» в осознанную инвестицию на уровне архитектуры.


Общая ответственность за рефакторинг

Даже когда понятно, что именно надо рефакторить, командам часто сложно дойти до дела. Нагрузка и риски кажутся слишком высокими для одного разработчика в рамках спринта.

Рабочая модель — разделённая ответственность за рефакторинг, разбитая на две фазы:

  1. Подготовка (автором изменений)

    • Добавьте тесты вокруг поведения, с которым вы работаете.
    • Задокументируйте известные инварианты и предположения.
    • Внесите «швы»: feature‑флаги, адаптеры, anti‑corruption‑слои.
    • Добавьте телеметрию, чтобы измерить текущее использование.
  2. Исполнение (следующими разработчиками)

    • Используйте подготовленные швы и тесты для безопасного изменения внутренней реализации.
    • Постепенно переводите вызывающие стороны на новые API или модели данных.
    • Удаляйте старые пути, когда метрики и логи показывают, что они больше не используются.

В этой модели вам не нужно завершать весь рефакторинг, когда вы уже под давлением сроков по фичам. Достаточно оставить кодовую базу в состоянии, в котором её проще менять в следующий раз.

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


Извлечение структуры перед изменениями

Содержательный рефакторинг опирается на понимание реальной структуры и моделей данных вашей системы.

Прежде чем дёргать зависимости, нужно:

  • Выявить канонические модели данных (User, Order, Invoice) и их варианты.
  • Обнаружить дублирующиеся или немного разошедшиеся схемы.
  • Понять, какой сервис является истинным источником правды для каких доменных сущностей.

Способы это сделать:

  • Анализ схем через introspection БД и codegen.
  • Анализ payload’ов событий и их эволюции во времени.
  • Просмотр API‑контрактов и фактического runtime‑использования (например, по трейсингу).

После этого можно наложить эти модели на граф сервисов:

  • Отрисовать, где живут и как текут данные User.
  • Показать, какие сервисы мутируют какую сущность.
  • Отметить зоны владения и границы данных.

Эта структурная карта — ваш чертёж для рефакторинга. Без неё вы просто переставляете коробки, не зная, что в них лежит.


Открытый контур против итеративных планов рефакторинга

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

  • Планирование с открытым контуром (open‑loop): вы один раз составили план, полностью его выполняете и надеетесь на лучшее.
    • Аналог: «big‑bang»‑рефакторинг десятков сервисов в одном гигантском PR.
  • Закрытый контур, итеративное планирование: сделать небольшой шаг, посмотреть на систему, скорректировать план, повторить.
    • Аналог: поэтапный рефакторинг, управляемый телеметрией и обратной связью.

В сложных распределённых системах планирование по открытому контуру хрупко:

  • Поздно всплывают зависимости, которых вы не видели.
  • Реальные паттерны трафика не совпадают с предположениями.
  • Краевые случаи проявляются только в продакшене.

Вместо этого используйте миниатюрную архитектуру и карту коннасценции, чтобы планировать мелкие, управляемые обратной связью шаги:

  1. Выберите зависимость с высокой ценностью для ослабления.
  2. Вставьте шов (адаптер, фасад или слой совместимости).
  3. Переведите часть трафика на новый путь.
  4. Наблюдайте метрики, ошибки и трейсы.
  5. Корректируйте, расширяйте или откатывайте.

Ваш граф будет эволюционировать: рёбра истончаются, одни исчезают, новые появляются в более удачных местах. Тилт‑шифт‑вид позволяет видеть прогресс во времени, а не только diff’ы кода.


Сводим всё воедино

Аналоговый рефакторинг в стиле тилт‑шифт — это не ностальгия по бумаге. Это способ сменить перспективу:

  • Миниатюризируйте систему, чтобы мозг мог видеть паттерны.
  • Используйте визуализацию зависимостей, чтобы вскрыть реальные (а не предполагаемые) связи.
  • Применяйте коннасценцию, чтобы решить, какие зависимости действительно вам вредят.
  • Делите ответственность за рефакторинг, разделяя подготовку и исполнение.
  • Сначала извлеките и опишите структуры и модели данных, а уже потом переносите их.
  • Отдавайте предпочтение итеративным рефакторингам с обратной связью, а не героическим «big‑bang»‑подходам.

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

Иногда, чтобы безопасно изменить что‑то большое, нужно сначала сделать его достаточно маленьким, чтобы по‑настоящему увидеть.

Аналоговый рефакторинг в стиле тилт-шифт: как «уменьшить» кодовую базу на бумаге и увидеть скрытые зависимости | Rain Lag