Rain Lag

Правило трёх трасс: минималистская стратегия понимания любых легаси‑код‑путей

Как разбираться в запутанном легаси‑коде, не читая весь репозиторий: используем Правило трёх трасс, современные инструменты и умный логгинг, чтобы превратить неизвестные системы в понятную территорию.

Введение

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

Этот порыв — ошибочный.

Не нужно понимать всю систему, чтобы безопасно изменить одно поведение. Нужно уметь надёжно ориентироваться в конкретном пути выполнения кода. Здесь и помогает Правило трёх трасс — минималистская стратегия, которая опирается на несколько реальных трасс выполнения вместо полного тура по кодовой базе.

В этом материале разберём:

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

Правило трёх трасс в двух словах

Правило трёх трасс — это прагматичный подход к пониманию легаси‑поведения:

Чтобы понять легаси‑поведение, соберите и разберите три показательные трассы выполнения этого поведения вместо попытки прочитать весь код.

Эти трассы могут быть:

  • лог‑сообщения из реального продакшена или стейджинга;
  • стеки ошибок или stack trace’ы;
  • трассы из распределённой трассировки (например, OpenTelemetry, Jaeger, Zipkin);
  • сессии отладки, когда вы один раз пошагово проходите путь от начала до конца.

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

Почему именно три?

  • Одна трасса показывает какой‑то путь.
  • Две трассы дают понять, что поведение может различаться.
  • Три трассы обычно уже выявляют основные точки ветвления и вариативность, которую нужно понять, чтобы безопасно вносить изменения.

Во многих случаях трёх хорошо подобранных трасс достаточно, чтобы:

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

Вы не пытаетесь овладеть всей системой. Ваша цель — овладеть конкретным поведением.


Шаг 1. Начинайте с рантайма, а не с исходников

Классический подход к легаси — начать с исходного кода: открыть репозиторий, просканировать дерево директорий, читать файлы и надеяться, что проявятся какие‑то закономерности. Это медленно и демотивирует.

Вместо этого начните с реального выполнения:

  1. Воспроизведите нужное поведение в контролируемой среде (dev, staging, test). Если это баг — научитесь воспроизводить его по запросу. Если это фича — прогоните её end‑to‑end.
  2. Снимите трассу этого запуска с помощью уже имеющихся инструментов:
    • логи приложения (request ID, correlation ID и т.п.);
    • стеки ошибок;
    • распределённые трассы из APM/observability‑систем;
    • простые debug‑логи, которые вы временно добавите для расследования.
  3. Повторите три запуска, варьируя условия:
    • разные входные параметры;
    • успешный и неуспешный сценарий;
    • разные роли пользователей или разные тенанты.

Каждый запуск должен оставить после себя подробный «след» — от точки входа до выхода.

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


Шаг 2. Статический анализ и метрики — как карта, а не как роман

Имея трассы, вы уже знаете названия файлов, функций и модулей. Теперь можно использовать статические инструменты, чтобы перемещаться прямо к нужным местам, не бродя вслепую по всему репозиторию.

Полезные инструменты и метрики:

  • Инструменты построения графа вызовов и зависимостей (например, go callgraph, call hierarchy в IntelliJ, расширения VSCode), чтобы увидеть, кто кого вызывает вокруг интересующих функций.
  • Поиск по коду (ripgrep, Sourcegraph, GitHub code search), чтобы находить использования символов, текст лог‑сообщений или ошибок, которые фигурируют в трассах.
  • Метрики сложности (цикломатическая сложность, длина функций), чтобы выявлять горячие точки, которые рискованны или особенно нуждаются в рефакторинге.
  • Метрики владения и «чёрна» (churn) (git blame, отчёты по частоте изменений), чтобы узнать:
    • кто последний трогал этот код;
    • какие файлы меняются часто и могут быть особенно хрупкими.

Важно: вы не просматриваете весь код. Вы используете статический анализ как карту, а координаты вам дают трассы.


Шаг 3. Визуализируйте путь, а не всю систему

Когда вы сузили область до нескольких модулей, визуальные инструменты могут сильно ускорить понимание:

  • Диаграммы архитектуры и зависимостей: многие IDE и плагины умеют строить графы зависимостей пакетов или модулей.
  • Диаграммы последовательностей (sequence diagrams): перевод одной из трасс в диаграмму последовательностей (даже вручную или с помощью утилит, генерирующих диаграммы из логов) делает взаимодействие компонентов более наглядным.
  • Визуализаторы БД и запросов: если трасса показывает SQL‑запросы, сопоставьте их с таблицами и связями.

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

«При оформлении заказа с промокодом, когда оплата падает, что кого вызывает и в каком порядке?»

Ограничьтесь теми несколькими сервисами и функциями, которые участвуют в ваших трёх трассах.


Шаг 4. Инструментируйте обработку ошибок для точных путей

В легаси‑системах сбои часто выглядят как Something went wrong или Null reference exception — без каких‑либо намёков где и как это произошло.

Чтобы Правило трёх трасс работало по‑настоящему хорошо, нужны детализированные пути ошибок.

Оборачивайте и аннотируйте ошибки

Сделайте так, чтобы при распространении ошибки вверх по стеку она накапливала контекст:

  • файл и строку (из stack trace или своих обёрток);
  • имя функции или модуля;
  • ключевые параметры (с учётом обезличивания/маскировки, где нужно);
  • высокоуровневое название операции (applyDiscount, chargeCustomer, sendEmail).

Во многих языках это выглядит так:

  • Go: обёртка вида fmt.Errorf("applyDiscount: %w", err) и захват stack trace в месте возникновения;
  • Java / C# / Node.js / Python: сохранение исходного исключения и добавление сообщений с контекстом по мере пробрасывания вверх.

Цель — чтобы финальный лог или отчёт об ошибке содержал хлебные крошки:

OrderService.createOrder → DiscountService.applyDiscount → RuleEngine.evaluate → DatabaseTimeoutError

Тогда один отчёт об ошибке фактически становится одной из ваших трёх трасс.

Сделайте трассировку ошибок простой

  • Используйте структурированное логирование (JSON или key‑value), чтобы инструменты могли фильтровать по типу ошибки, request ID, user ID.
  • Стандартизируйте error ID или correlation ID, чтобы можно было прослеживать один сбой через несколько сервисов.
  • Обеспечьте единый, парсабельный формат stack trace’ов.

Когда обработка ошибок грамотно проинструментирована, вам больше не нужно гадать, по какому пути пошла система — вы видите это.


Шаг 5. Пусть трассы определяют, что читать и где менять код

Допустим, у вас уже есть три хороших трассы и настроенная инструментированная обработка ошибок:

  1. Начните с точки входа, которая видна из трассы (HTTP‑хендлер, CLI‑команда, consumer очереди, cron‑задача).
  2. Следуйте по цепочке вызовов, используя трассу как навигатор. Читайте только:
    • функции, которые фигурируют в трассах;
    • функции, которые явно влияют на поведение (проверки условий, ветвление, правило‑движки и т.п.).
  3. Пропускайте нерелевантные ветки. Если функция имеет несколько путей выполнения, но ваши три трассы стабильно проходят только по одному подмножеству веток, сперва сосредоточьтесь именно на них.
  4. Найдите минимальную точку изменения:
    • где вычисляется нужное значение;
    • где принимается решение (if/else, switch, rule engine);
    • где происходит побочный эффект (запись в БД, внешний API‑вызов, отправка письма).

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


Шаг 6. Постоянно улучшайте логирование и трассировку

Каждое расследование пути в легаси‑коде — шанс сделать следующее расследование проще.

По ходу работы:

  • Прокачивайте лог‑сообщения, которые оказались бесполезными:
    • добавляйте ключевые идентификаторы (order ID, user ID, регион и т.д.);
    • добавляйте маркеры этапов ("start", "after validation", "before DB write").
  • Унифицируйте форматы логов между сервисами, чтобы их было проще коррелировать.
  • Добавьте trace ID и span ID, если уже есть или можно внедрить распределённую трассировку.
  • Отстройте стратегии семплирования, чтобы редкие или падающие пути всегда трассировались полностью.

Со временем система эволюционирует из непрозрачного легаси в наблюдаемую и трассируемую платформу, где на большинство новых вопросов можно ответить, просто:

  1. воспроизведя поведение;
  2. изучив несколько трасс;
  3. сделав небольшой точечный фикс в коде.

Как управлять большими кодовыми базами с помощью современных практик

Правило трёх трасс лучше всего работает в связке с современными инструментами и практиками управления большими системами:

  • Монорепозиторий или хорошо структурированные multi‑repo‑сетапы — для облегчения анализа зависимостей;
  • Автоматические тесты (даже если вы добавляете их задним числом вокруг критичных легаси‑путей);
  • Политики code review, поощряющие небольшие, сфокусированные изменения;
  • Continuous Integration, чтобы быстро ловить регрессии;
  • Пайплайны статического анализа (линтеры, сканеры уязвимостей), чтобы вскрывать скрытые проблемы.

Эти практики не делают легаси волшебным образом современным, но они гарантируют, что, как только вы разобрались с каким‑то путём и улучшили его, система с меньшей вероятностью «откатится назад».


Заключение

Не нужно покорять легаси‑систему целиком. Нужно иметь надёжный способ понимать и изменять одно конкретное поведение за раз.

Правило трёх трасс даёт такой способ:

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

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