Аналоговая «рефакторинг-война»: как проиграть настольные сценарии, прежде чем трогать хрупкую кодовую базу
Как использовать настольные «военные игры», мышление в духе безопасности и практики работы с легаси‑кодом — такие как характеризационные тесты и 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: сначала тест, потом реализация.
Это означает:
- Сначала вы пишете тест, описывающий желаемое поведение нового кода.
- Затем реализуете ровно столько логики, чтобы тест прошёл.
- После этого спокойно рефакторите новый код, опираясь на тест как на страховочную сетку.
Плюсы:
- Новый код безопаснее менять и проще понимать.
- Вы постепенно создаёте внутри легаси‑болота островки тестируемости и надёжности.
- Вы не раздуваете дальше зону неподконтрольного, нетестируемого легаси.
TDD не исправит прошлые грехи, но не даст совершать новые.
Шаг 5. Защищаем легаси‑поведение характеризационными тестами
В легаси‑коде редко бывает надёжное покрытие тестами. Перед тем как трогать хрупкую область, вам нужен инструмент, который позволит сказать:
«Если я это поменяю, я узнаю, нарушил ли я текущее поведение.»
Здесь в дело вступают характеризационные тесты.
Характеризационный тест не утверждает, каким поведение должно быть; он фиксирует, каким оно является сейчас.
Как работать:
- Выберите легаси‑функцию или класс, которые предстоит менять.
- Вызывайте их с реальными входными данными (из логов, боеподобных фикстур и т.п.).
- Зафиксируйте текущие выходы и побочные эффекты.
- Напишите тесты с утверждением: «Для входа X сейчас получаю результат Y».
Даже если поведение странное или неидеальное, вы документируете реальность. Теперь при рефакторинге тесты покажут, изменили ли вы эту реальность — специально или случайно.
Со временем можно:
- Постепенно улучшать поведение
- Ужесточать assert’ы
- Заменять легаси‑функции более чистыми эквивалентами
Но всё это — под защитой сетки из тестов.
Шаг 6. Используем Golden Master для сложного поведения
Иногда поведение слишком сложно, чтобы описать его парой‑тройкой тестов:
- Выход зависит от большого числа параметров
- Много граничных случаев
- Путь выполнения длинный и запутанный
Здесь пригодятся golden master‑тесты.
Как работают Golden Master
- Запись (Record): Соберите большой набор реалистичных входных данных и соответствующие им выходы от текущей системы.
- Заморозка (Freeze): Сохраните эти выходы как ваш «golden master» — эталон.
- Рефакторинг (Refactor): Меняйте внутреннюю структуру, алгоритмы, организации кода.
- Сравнение (Compare): После каждой серии изменений прогоняйте все входы через новую версию и сравнивайте выходы с golden master.
Если что‑то изменилось неожиданно — вы знаете, куда смотреть.
Golden master особенно полезны для:
- Генерации сложных отчётов
- Преобразований данных и ETL‑процессов
- Легаси‑движков бизнес‑правил
Во время «военной игры» определите модули, которые хорошо подходят под golden master, и запланируйте, как будете собирать репрезентативные данные.
Шаг 7. Создаём «швы» (seams), чтобы контролировать изменения
Легаси‑код часто сопротивляется тестированию, потому что всё переплетено:
- Глобальное состояние
- Жёстко захардкоженные зависимости
- Статические синглтоны
Шов (seam) — это место в коде, где вы можете изменить поведение, не правя существующий код повсюду.
Примеры:
- Введение интерфейса и адаптера вокруг внешнего API
- Обёртка над статическими вызовами в виде тонкого делегирующего класса, который можно замокать
- Вставка конфигурационного объекта вместо прямого чтения переменных окружения
На вашей «электрической схеме» задайте вопросы:
- Где мы можем добавить швы, чтобы сделать этот участок тестируемым?
- Где можно добавить уровень косвенности, не меняя поведения?
Часто первым шагом «рефакторинг‑кампании» бывает не «улучшить дизайн», а «добавить шов, чтобы стало безопасно тестировать и менять код».
Как провести «военную игру»: пример повестки
Сессия варгейма длительностью 90–120 минут может выглядеть так:
-
10 минут — Миссия и ограничения
Сформулировать цель рефакторинга, дедлайны и «красные линии» (аптайм, безопасность). -
20 минут — Маппинг системы
Нарисовать «электрическую схему»: критические потоки и зависимости. -
20 минут — Разбор attack surface
Определить потенциальные риски для безопасности и надёжности от планируемых изменений. -
20 минут — Стратегия тестирования
Решить:- Где применять TDD для нового кода
- Какие зоны прикрыть характеризационными тестами
- Какие компоненты покрыть golden master
- Где сначала нужно добавить швы (seams)
-
20–30 минут — Прогон сценариев «что если…»
Проиграть конкретные случаи:- Что если этот вызов начнёт возвращать
null? - Что если этот SQL‑запрос станет выполняться в 2 раза медленнее?
- Что если этот шаблон начнёт экранировать данные по‑другому?
- Что если этот вызов начнёт возвращать
-
10 минут — План действий
Зафиксировать конкретные задачи и ответственных за:- Создание тестовых стендов и хелперов
- Построение наборов данных для golden master
- Введение швов
- Планирование ревью кода в зонах повышенного риска
Вы уходите не с размытым ощущением «ну, вроде понятно», а с картой и планом боя.
Заключение: выиграть рефакторинг до первой строки кода
Самые успешные рефакторинги часто выигрываются до первого pull request’а:
- Вы промапили систему как сложную электрическую схему.
- Отнеслись к архитектурным изменениям как к риску для безопасности и надёжности, а не только как к упражнению по дизайну.
- Взяли на себя обязательство писать весь новый код по TDD и не раздувать дальше легаси‑болото.
- Обернули хрупкие зоны характеризационными тестами и golden master.
- Ввели швы (seams), позволяющие безопасно менять поведение.
Настольные «военные игры» превращают рефакторинг из прыжка веры в дисциплинированную кампанию. Вместо «посмотрим, что отвалится на стейджинге» вы сначала моделируете и планируете, а уже потом исполняете.
Прежде чем открывать IDE над хрупкой кодовой базой, возьмите маркер, доску и пару коллег. Проведите «военную игру». Выиграйте рефакторинг на бумаге — а потом закрепите победу в коде.