Rain Lag

Аналоговая «рефакторинг-война»: как проиграть настольные сценарии, прежде чем трогать хрупкую кодовую базу

Как использовать настольные «военные игры», мышление в духе безопасности и практики работы с легаси‑кодом — такие как характеризационные тесты и golden master — чтобы безопасно рефакторить хрупкие системы и не взорвать их по пути.

Аналоговая «рефакторинг-война»: как проиграть настольные сценарии, прежде чем трогать хрупкую кодовую базу

Рефакторинг хрупкой легаси‑кодовой базы часто больше похож не на садоводство, а на обезвреживание неразорвавшейся бомбы. Одно неосторожное движение — и вот уже: падение продакшена, инцидент безопасности, потеря данных, сорванные выходные.

Вместо того чтобы сразу нырять в код, представьте, что вы сначала проводите настольную «военную игру». Никакого IDE. Никаких коммитов. Только диаграммы, карточки и кросс‑функциональная команда, проигрывающая сценарии «а что если?»:

  • Что если этот сервис начнёт отвечать с тайм-аутами?
  • Что если это поле вдруг окажется null, а база уверена, что такого быть не может?
  • Что если небольшой правкой шаблона мы откроем дыру для XSS?

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

В этом посте разберём, как:

  • Проводить «рефакторинг-войну» в формате настольного упражнения
  • Думать как инженер по безопасности: видеть в рефакторинге изменение attack surface
  • Использовать TDD для всего нового кода, который появляется во время рефакторинга
  • Защищать легаси‑поведение с помощью характеризационных тестов, golden master и «швов» (seams)
  • Картировать систему как сложную электрическую схему перед «хирургическим вмешательством»

Зачем превращать рефакторинг в «военную игру»

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

  • Выявлять слепые зоны
  • Обнажать скрытые допущения
  • Тренировать принятие решений в условиях ограничений

Рефакторинг легаси‑кодовой базы обладает похожими чертами:

  • Знания о системе неполные
  • Система уже крутится в продакшене
  • Небольшие изменения могут иметь непропорционально большие последствия
  • Есть давление по срокам и по надёжности

Поэтому вместо «просто начнём рефакторить» относитесь к работе как к кампании, а к переговорной — как к военному штабу.


Шаг 1. Организуем настольную «рефакторинг-войну»

Не нужны никакие особые инструменты. Нужны люди, бумага и структура.

Кто должен быть в комнате

  • Синьор‑разработчики / техлид — понимают архитектуру и компромиссы
  • Инженеры, знакомые с целевой легаси‑областью — знают странные кейсы и подводные камни
  • QA / тест‑инженеры — видят паттерны сбоев и слепые зоны
  • Инженер по безопасности (или человек с таким мышлением) — поможет промапить уязвимости
  • Ops / SRE (если есть) — понимают поведение системы в рантайме и возможный blast radius

Какие артефакты взять с собой

  • Высокоуровневая архитектурная диаграмма
  • Sequence diagram или call graph (можно сгенерировать по трейсингам)
  • Недавние отчёты об ошибках и инцидентах в целевой области
  • Отчёты о текущем покрытии тестами

Сформулируйте цель

Запишите одним предложением:

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

Это предложение становится вашим манифестом и миссией для «военной игры».


Шаг 2. Картируем кодовую базу как сложную электрическую схему

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

С кодовой базой стоит поступать так же.

На доске (реальной или виртуальной) нарисуйте:

  • Критические потоки (например, регистрация пользователя, checkout, экспорт данных)
  • Внешние интерфейсы (API, очереди сообщений, импорт/экспорт файлов)
  • Хранилища данных (базы данных, кэши, сторонние сервисы)
  • Общие модули (utility‑библиотеки, легаси‑фреймворки, от которых зависятся все)

Затем аннотируйте схему:

  • Области с пометкой «не трогать», кроме крайних случаев
  • Скрытая связность (общий глобальный стейт, синглтоны, статические хелперы)
  • Известные опасные зоны («как‑то уже меняли это место — и неделя без биллинга»)

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


Шаг 3. Относитесь к рефакторингу как к security‑варгейму

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

В рамках «военной игры» явно промапьте attack surface, который вы рискуете случайно изменить:

  • Code injection — Появляются ли новые точки динамического eval, шаблонизаторы или механизмы подключения плагинов?
  • SQL injection — Затрагиваете ли вы query builder или слои ORM, обходите ли их напрямую?
  • XSS (Cross‑Site Scripting) — Меняете ли вы то, как пользовательский ввод попадает в шаблоны или API, которыми пользуется UI?
  • Пути аутентификации/авторизации — Переносите ли вы проверки доступа или дублируете ли их в других местах?

Пройдитесь по сценариям:

  • «Мы выносим слой доступа к данным в отдельный модуль. Может ли теперь какой‑то неочищенный ввод добраться до SQL?»
  • «Мы вводим новый слой DTO. Не получается ли так, что HTML доезжает до UI без экранирования?»
  • «Мы переносим валидацию. Может ли появиться путь, по которому она будет пропущена?»

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

  • Тесты, фиксирующие поведение
  • Возможно, дополнительные security‑ревью или статический анализ

Шаг 4. Обязуемся использовать TDD для всего нового кода

Пытаться задним числом покрыть TDD весь легаси‑монолит — утопия. Но вы можете провести границу:

Каждый новый класс, функция или модуль, созданный в процессе рефакторинга, пишется по TDD: сначала тест, потом реализация.

Это означает:

  1. Сначала вы пишете тест, описывающий желаемое поведение нового кода.
  2. Затем реализуете ровно столько логики, чтобы тест прошёл.
  3. После этого спокойно рефакторите новый код, опираясь на тест как на страховочную сетку.

Плюсы:

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

TDD не исправит прошлые грехи, но не даст совершать новые.


Шаг 5. Защищаем легаси‑поведение характеризационными тестами

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

«Если я это поменяю, я узнаю, нарушил ли я текущее поведение.»

Здесь в дело вступают характеризационные тесты.

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

Как работать:

  1. Выберите легаси‑функцию или класс, которые предстоит менять.
  2. Вызывайте их с реальными входными данными (из логов, боеподобных фикстур и т.п.).
  3. Зафиксируйте текущие выходы и побочные эффекты.
  4. Напишите тесты с утверждением: «Для входа X сейчас получаю результат Y».

Даже если поведение странное или неидеальное, вы документируете реальность. Теперь при рефакторинге тесты покажут, изменили ли вы эту реальность — специально или случайно.

Со временем можно:

  • Постепенно улучшать поведение
  • Ужесточать assert’ы
  • Заменять легаси‑функции более чистыми эквивалентами

Но всё это — под защитой сетки из тестов.


Шаг 6. Используем Golden Master для сложного поведения

Иногда поведение слишком сложно, чтобы описать его парой‑тройкой тестов:

  • Выход зависит от большого числа параметров
  • Много граничных случаев
  • Путь выполнения длинный и запутанный

Здесь пригодятся golden master‑тесты.

Как работают Golden Master

  1. Запись (Record): Соберите большой набор реалистичных входных данных и соответствующие им выходы от текущей системы.
  2. Заморозка (Freeze): Сохраните эти выходы как ваш «golden master» — эталон.
  3. Рефакторинг (Refactor): Меняйте внутреннюю структуру, алгоритмы, организации кода.
  4. Сравнение (Compare): После каждой серии изменений прогоняйте все входы через новую версию и сравнивайте выходы с golden master.

Если что‑то изменилось неожиданно — вы знаете, куда смотреть.

Golden master особенно полезны для:

  • Генерации сложных отчётов
  • Преобразований данных и ETL‑процессов
  • Легаси‑движков бизнес‑правил

Во время «военной игры» определите модули, которые хорошо подходят под golden master, и запланируйте, как будете собирать репрезентативные данные.


Шаг 7. Создаём «швы» (seams), чтобы контролировать изменения

Легаси‑код часто сопротивляется тестированию, потому что всё переплетено:

  • Глобальное состояние
  • Жёстко захардкоженные зависимости
  • Статические синглтоны

Шов (seam) — это место в коде, где вы можете изменить поведение, не правя существующий код повсюду.

Примеры:

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

На вашей «электрической схеме» задайте вопросы:

  • Где мы можем добавить швы, чтобы сделать этот участок тестируемым?
  • Где можно добавить уровень косвенности, не меняя поведения?

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


Как провести «военную игру»: пример повестки

Сессия варгейма длительностью 90–120 минут может выглядеть так:

  1. 10 минут — Миссия и ограничения
    Сформулировать цель рефакторинга, дедлайны и «красные линии» (аптайм, безопасность).

  2. 20 минут — Маппинг системы
    Нарисовать «электрическую схему»: критические потоки и зависимости.

  3. 20 минут — Разбор attack surface
    Определить потенциальные риски для безопасности и надёжности от планируемых изменений.

  4. 20 минут — Стратегия тестирования
    Решить:

    • Где применять TDD для нового кода
    • Какие зоны прикрыть характеризационными тестами
    • Какие компоненты покрыть golden master
    • Где сначала нужно добавить швы (seams)
  5. 20–30 минут — Прогон сценариев «что если…»
    Проиграть конкретные случаи:

    • Что если этот вызов начнёт возвращать null?
    • Что если этот SQL‑запрос станет выполняться в 2 раза медленнее?
    • Что если этот шаблон начнёт экранировать данные по‑другому?
  6. 10 минут — План действий
    Зафиксировать конкретные задачи и ответственных за:

    • Создание тестовых стендов и хелперов
    • Построение наборов данных для golden master
    • Введение швов
    • Планирование ревью кода в зонах повышенного риска

Вы уходите не с размытым ощущением «ну, вроде понятно», а с картой и планом боя.


Заключение: выиграть рефакторинг до первой строки кода

Самые успешные рефакторинги часто выигрываются до первого pull request’а:

  • Вы промапили систему как сложную электрическую схему.
  • Отнеслись к архитектурным изменениям как к риску для безопасности и надёжности, а не только как к упражнению по дизайну.
  • Взяли на себя обязательство писать весь новый код по TDD и не раздувать дальше легаси‑болото.
  • Обернули хрупкие зоны характеризационными тестами и golden master.
  • Ввели швы (seams), позволяющие безопасно менять поведение.

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

Прежде чем открывать IDE над хрупкой кодовой базой, возьмите маркер, доску и пару коллег. Проведите «военную игру». Выиграйте рефакторинг на бумаге — а потом закрепите победу в коде.

Аналоговая «рефакторинг-война»: как проиграть настольные сценарии, прежде чем трогать хрупкую кодовую базу | Rain Lag