Взрываем легаси: почему миграция Feature Flags — это ад
Ты открываешь старый сервис, написанный на Java, и видишь это. Повсюду if (FeatureFlags.isEnabled("NEW_CHECKOUT")). Флаги живут в статичных методах, конфиг-файлах, а некоторые — в памяти у тимлида, который уволился год назад. Миграция на нормальную систему вроде Unleash кажется квестом на тысячу строк кода. Ручная правка — гарантия ошибки и неделя потраченного времени.
В 2026 году так делать — преступление против собственной продуктивности. Современные LLM (даже локальные, вроде Qwen2.5-Coder-32B или DeepSeek-Coder-V2) отлично справляются с семантическим анализом кода. Они не просто ищут строки — они понимают контекст. И этот контекст можно использовать для массового, но точного рефакторинга.
От хака к стратегии: как LLM видит твой код
Простого поиска и замены по регуляркам недостаточно. Старый код — это зоопарк подходов:
- Статические утилитные классы:
LegacyFeatureToggle.check("flag") - Прямые чтения из properties:
config.getBoolean("features.awesome") - Самопальные аннотации:
@Feature("beta") - Флаги, захардкоженные в логике ветвления (да, такое еще встречается).
LLM, обученная на коде (а в 2026 году таких большинство), видит не просто строки, а паттерны. Она может сопоставить, что вызов isEnabled("flag") в классе OrderService и вызов check("flag") в PaymentProcessor — это одно и то же концептуально. Это и есть семантический рефакторинг. Если ты сталкивался с проблемой деградации контекста при длинных промптах, этот гайд покажет, как ее обойти.
1 Готовим поле боя: инструменты и анализ
Не бросайся сразу писать промпт. Сначала сделай инвентаризацию.
- Выбери цель миграции. Допустим, это Unleash версии 5.x (актуальной на 2026 год). Установи SDK в проект.
- Просканируй код. Используй простой скрипт, чтобы найти все упоминания флагов. Тебе нужен не просто список, а контекст — в каких методах, с какими параметрами они вызываются.
# Быстрый поиск по проекту для оценки масштаба grep -r "isEnabled\|checkFeature\|FeatureFlags" --include="*.java" ./src - Определи шаблоны. Сгруппируй находки. "Прямые вызовы статического метода", "чтение из конфигурации", "использование в условиях аннотаций". Это поможет создавать targeted промпты.
Предупреждение: Не пытайся мигрировать всё за один промпт. LLM, особенно локальная, может "перегреться" и начать галлюцинировать на больших объемах контекста. Разбей код на логические модули (пакеты, сервисы) и работай с ними по очереди. О том, как избежать галлюцинаций, я подробно писал в статье про языки для машин.
2 Пишем промпт-киллер: не проси, а командуй
Плохой промпт: "Замени флаги на Unleash". Хороший промпт — это техническое задание для инженера.
// Пример КОДА ДО рефакторинга (старая система):
public class OrderService {
private Config config;
public BigDecimal calculateDiscount(Order order) {
if (FeatureFlags.isEnabled("DISCOUNT_V2", order.getUserId())) {
return newV2DiscountLogic(order);
}
if (config.getBoolean("features.legacy_discount_enabled")) {
return legacyDiscount(order);
}
return BigDecimal.ZERO;
}
}
Теперь сам промпт. Он должен быть конкретным, императивным и содержать пример вывода.
Ты выполняешь рефакторинг Java-кода для миграции системы feature flags на Unleash 5.x.
КОНТЕКСТ ПРОЕКТА:
- Уже добавлена зависимость: `io.getunleash:unleash-client-java:5.2.0`
- Внедрен бин `Unleash` (Spring Bean или аналогичный), доступный через `unleash`.
- Старая система использовала класс `FeatureFlags` со статическими методами.
- Также использовалось прямое чтение из `Config` (интерфейс, метод `getBoolean`).
ЗАДАЧА:
Проанализируй предоставленный Java-код. Найди ВСЕ использования старой системы feature flags и замени их на вызовы `unleash.isEnabled("FEATURE_NAME")`.
ПРАВИЛА ЗАМЕНЫ:
1. `FeatureFlags.isEnabled("FLAG_NAME", userId)` -> `unleash.isEnabled("FLAG_NAME", new UnleashContext.Builder().userId(userId).build())`
2. `FeatureFlags.isEnabled("FLAG_NAME")` -> `unleash.isEnabled("FLAG_NAME")`
3. `config.getBoolean("features.flag_name")` -> `unleash.isEnabled("FLAG_NAME")` (преобразуй kebab-case в FLAG_NAME).
4. Локальные переменные и параметры методов должны быть сохранены.
5. Импорты старого класса `FeatureFlags` должны быть удалены.
ВХОДНЫЙ КОД (который нужно отрефакторить):
```java
[Сюда будет вставлен реальный фрагмент кода]
```
ВЫВЕДИ ТОЛЬКО ОТРЕФАКТОРЕННЫЙ КОД, без пояснений. Сохрани исходное форматирование.
Такой промпт — это инструкция, а не просьба. Он ограничивает пространство для галлюцинаций. Для работы с большими файлами используй технику, описанную в статье про GestaltSyntax, чтобы впихнуть максимум контекста в окно модели.
3 Исполнение и валидация: доверяй, но проверяй
Запускаешь LLM (Claude 3.7 Sonnet, GPT-4o 2026, или локальную DeepSeek-Coder через llama.cpp). Получаешь результат.
// Пример КОДА ПОСЛЕ рефакторинга:
public class OrderService {
private Unleash unleash; // Внедрено через конструктор
// private Config config; // Удалено, если больше не используется
public BigDecimal calculateDiscount(Order order) {
if (unleash.isEnabled("DISCOUNT_V2", UnleashContext.builder().userId(order.getUserId()).build())) {
return newV2DiscountLogic(order);
}
if (unleash.isEnabled("LEGACY_DISCOUNT_ENABLED")) { // Имя флага преобразовано
return legacyDiscount(order);
}
return BigDecimal.ZERO;
}
}
И вот тут начинается настоящая работа. LLM — не волшебник.
- Запусти компиляцию. Первое же
javacпокажет, не удалила ли модель нужный импорт или не сломала ли сигнатуру метода. - Пиши юнит-тесты. Сейчас. Не после миграции всего проекта, а сразу для отрефакторенного модуля. Тесты — это твой автоматический чекер против семантических ошибок (флаг перепутан, логика инвертирована).
- Используй статический анализ. Инструменты вроде Checkstyle или SpotBugs могут найти проблемы, которые ускользнули от LLM (например, потенциальный NPE после удаления старой переменной `config`).
Помни: LLM — твой мощный помощник, но финальная ответственность за код лежит на тебе. Этот подход не отменяет необходимость код-ревью, а трансформирует его. Ты теперь ревьюируешь не каждую строку, а корректность трансформации.
Где споткнешься: подводные камни автоматического рефакторинга
| Ошибка | Почему происходит | Как предотвратить |
|---|---|---|
| Потеря контекста пользователя | LLM видит `isEnabled("flag")`, но не замечает, что выше по методу есть `userId`, который нужно передать в `UnleashContext`. | Явно укажи в промпте правила для методов с дополнительными параметрами. Разбивай большие методы на части перед анализом. |
| Некорректное именование флагов | Преобразование `"features.legacy_discount"` в `"LEGACY_DISCOUNT"` может не соответствовать реальным ключам в Unleash. | Создай и предоставь LLM mapping-таблицу старых ключей на новые в рамках промпта. |
| "Слепые" зоны в коде | Флаги, зашитые в строки, которые собираются динамически (`"FEATURE_" + stage`). LLM их не распознает как флаги. | Для такого кода автоматизация не подходит. Выявляй такие случаи на этапе анализа и обрабатывай вручную. |
Финальный акт: интеграция в пайплайн
Разовые победы — это круто, но устойчивый процесс — это профессионализм. Настрой автоматический пайплайн для проверки рефакторинга.
- Создай скрипт-обертку, который берет кусок кода, применяет промпт через LLM API (OpenAI, Anthropic, или локальный сервер с Ollama) и сохраняет результат.
- Интегрируй в pre-commit хуки. Перед коммитом изменений, запускай быстрые тесты на отрефакторенных файлах.
- Используй дифференциальный подход. Мигрируй флаги не во всем проекте сразу, а по функциональным блокам. После миграции каждого блока — пулл-реквест, ревью, мерж. Это снижает риск.
И главное — не становись заложником инструмента. Если видишь, что LLM стабильно ошибается в определенном паттерне, не исправляй вручную сто раз. Обнови промпт. Добавь новое правило или пример. Твой промпт — это живой документ, который должен становиться умнее с каждой итерацией. Это тот самый эволюционный процесс оптимизации кода.
К 2026 году ручной рефакторинг устаревших фич-флагов должен вызывать такую же реакцию, как правка продовой базы данных через консоль в пятницу вечером — легкую панику и вопрос "зачем?". Автоматизируй рутину, но делай это с холодной головой и четким планом. И тогда освободившееся время можно потратить на то, что ИИ пока не умеет — на архитектурные решения, которые не придется рефакторить через три года.