Зелёный CI — это ловушка. Особенно с ИИ
Вы просите Claude 4.0 или его более нового коллегу из 2026 года написать конвертер JSON для Telegram API. Модель выдаёт 200 строк кода. Тесты проходят. CI зелёный. Вы чувствуете магию вайбкодинга — всё работает с первого раза.
А потом открываете код. И видите это: жёсткие зависимости, бизнес-логика в сервисном слое, пять разных способов обработки ошибок, импорты по кругу. Знакомо? Это не код. Это бомба замедленного действия, которая взорвётся на следующем спринте. Я прошёл через это десятки раз и вывел 4 чёткие стадии рефакторинга, которые превращают AI-хаос в работающую архитектуру.
ВАЖНО: Эта статья для тех, кто уже попал в ловушку «зелёного CI». Если вы только начинаете использовать AI-кодинг, сначала прочтите «Зелёный CI и пустая архитектура», чтобы понять механику проблемы.
Наш пациент: конвертер JSON Telegram от Claude
Давайте возьмём реальный пример. В 2025-2026 году многие используют AI-агенты для генерации кода быстрее, чем успевают его проверить. Результат — технический долг, в котором можно утонуть. Наш пример — конвертер, который трансформирует внутренние структуры данных в JSON для Telegram Bot API.
TelegramJsonConverter, который делает всё: парсит данные, валидирует, форматирует JSON, логирует ошибки и даже отправляет метрики в Prometheus. Классический монолит в микросервисной одежде.1 Стадия 0: Диагностика AI-хаоса (что мы имеем на старте)
Перед рефакторингом нужно понять, с чем имеем дело. AI-модели 2026 года (те же Claude 4.0, GPT-5, DeepSeek Coder V3) страдают одинаковыми болезнями:
- Циклические зависимости: Модуль A импортирует B, B импортирует C, C импортирует A. CI молчит, пока не попробуешь собрать production-бандл.
- Жёсткая связность: Бизнес-логика приклеена к фреймворку, к базе данных, к внешнему API.
- Отсутствие абстракций: Вместо интерфейсов — конкретные реализации. Вместо dependency injection — оператор
newв середине метода. - Разношёрстная обработка ошибок: В одном месте исключения, в другом возврат
null, в третьем — кортеж(result, error).
Вот как выглядит типичный AI-сгенерированный код нашего конвертера:
# ТАК НЕ НАДО ДЕЛАТЬ (код от Claude 4.0)
class TelegramJsonConverter:
def __init__(self):
self.validator = DataValidator() # Жёсткая зависимость
self.metrics = PrometheusMetrics() # Ещё одна
self.logger = logging.getLogger(__name__) # И ещё
def convert_message(self, message_data):
# 50 строк бизнес-логики, валидации и форматирования
if not self.validator.validate(message_data):
self.metrics.increment_error("validation_failed")
self.logger.error(f"Validation failed: {message_data}")
return None
# ... ещё 40 строк кода
return json.dumps(result)
Проблема не в том, что код не работает. Он работает. Проблема в том, что его невозможно тестировать, модифицировать и поддерживать. Это как дом, построенный на песке — выглядит нормально, пока не пойдёт дождь.
2 Стадия 1: Разделение ответственностей (выжимаем логику из сервиса)
Первое, что нужно сделать — вытащить бизнес-логику из сервисного класса. AI любит всё складывать в один класс, потому что в тренировочных данных так делают новички на Stack Overflow.
Шаги:
- Выделяем чистые функции для трансформации данных
- Создаём отдельные классы-валидаторы
- Выносим логирование и метрики в декораторы или aspect-ориентированный код
После рефакторинга:
# Чистые функции для бизнес-логики
def transform_message_content(content: str, options: dict) -> dict:
"""Преобразует контент сообщения в структуру Telegram."""
# Только логика трансформации, без валидации, без логирования
return {
"text": content,
"entities": parse_entities(content),
"parse_mode": options.get("parse_mode", "HTML")
}
# Валидатор как отдельный класс
class MessageDataValidator:
def validate(self, data: dict) -> ValidationResult:
# Только валидация
pass
# Основной сервис становится тоньше
class TelegramJsonConverter:
def __init__(self, validator: MessageDataValidator):
self.validator = validator # Зависимость через конструктор!
Почему это важно: Чистые функции легко тестировать. Вы можете написать unit-тесты без моков для базы данных, HTTP-клиентов или логгеров. Это сокращает время тестирования на 70%.
3 Стадия 2: Внедрение зависимостей (убиваем оператор new)
Самая частая ошибка AI-кода — создание зависимостей внутри методов. Класс сам создаёт валидаторы, клиенты БД, HTTP-сессии. Результат — невозможно подменить реализацию для тестов, невозможно использовать другой источник данных.
Решение: Dependency Injection (DI). Но не тот сложный фреймворк, который требует конфигурационных файлов на 100 строк. Простой, явный DI через конструктор.
# ПОСЛЕ рефакторинга
class TelegramJsonConverter:
def __init__(
self,
validator: MessageDataValidator,
metrics_client: Optional[MetricsClient] = None,
logger: Optional[logging.Logger] = None
):
self.validator = validator
self.metrics = metrics_client or NoOpMetricsClient()
self.logger = logger or logging.getLogger(__name__)
def convert_message(self, message_data: dict) -> Optional[str]:
# Теперь зависимости инжектятся, а не создаются внутри
if not self.validator.validate(message_data):
self.metrics.increment("validation_failed")
self.logger.warning("Validation failed")
return None
# ... остальная логика
Теперь вы можете:
- Передать Mock-валидатор в тестах
- Использовать другой клиент метрик в production
- Легко заменять реализации без изменения кода конвертера
Если вы думаете: «Это же очевидно!» — спросите у AI-агентов, которые генерируют код быстрее, чем вы успеваете его проверить. Они так не думают. ИИ обучен на коде с GitHub, а там DI используют в 20% проектов, если повезёт.
4 Стадия 3: Определение контрактов (интерфейсы вместо имплементаций)
Теперь, когда зависимости инжектятся, нужно определить контракты. AI редко генерирует интерфейсы, потому что в примерах кода в интернетах их почти нет.
Создаём абстракции:
from abc import ABC, abstractmethod
from typing import Protocol
# Контракт для валидатора
class DataValidator(Protocol):
def validate(self, data: dict) -> ValidationResult:
...
# Контракт для клиента метрик
class MetricsClient(Protocol):
def increment(self, metric_name: str, tags: dict = None):
...
def timing(self, metric_name: str, value_ms: float):
...
# Рефакторенный конвертер с контрактами
class TelegramJsonConverter:
def __init__(
self,
validator: DataValidator, # Теперь интерфейс!
metrics: MetricsClient,
logger: logging.Logger
):
self.validator = validator
self.metrics = metrics
self.logger = logger
Зачем это нужно? Если завтра потребуется валидация не в памяти, а через внешний сервис, вы создадите RemoteDataValidator, реализующий тот же протокол, и замените зависимость в DI-контейнере. Код конвертера не изменится ни на строчку.
5 Стадия 4: Чистая архитектура (собираем пазл)
Финальная стадия — организация кода по слоям. AI генерирует плоский код: всё в одной директории, всё импортирует всё. Мы создаём чёткие границы:
| Слой | Что содержит | Зависимости |
|---|---|---|
| Domain (Ядро) | Контракты, бизнес-модели, чистые функции | Нет внешних зависимостей |
| Application (Приложение) | Сервисы (наш конвертер), use cases | Только Domain |
| Infrastructure (Инфраструктура) | Реализации валидаторов, клиенты API, репозитории | Может зависеть от Domain и Application |
Структура проекта после рефакторинга:
telegram_converter/
├── domain/ # Ядро
│ ├── contracts/ # Интерфейсы
│ │ ├── validators.py
│ │ └── metrics.py
│ └── models/ # Бизнес-модели
│ └── message.py
├── application/ # Слой приложения
│ └── services/
│ └── telegram_converter.py # Наш главный сервис
└── infrastructure/ # Реализации
├── validators/
│ └── message_validator.py
└── metrics/
└── prometheus_client.py
Теперь зависимости идут только в одну сторону: Infrastructure → Application → Domain. Никаких циклических импортов. Никаких неожиданных зависимостей от фреймворка в бизнес-логике.
Типичные ошибки при рефакторинге AI-кода
Даже зная теорию, можно наступить на грабли. Вот самые частые ошибки:
1. Рефакторинг без тестов
Вы начинаете переписывать код, который сгенерировал ИИ, без покрытия тестами. Результат — ломаете функциональность, которую не до конца понимаете. Сначала напишите интеграционные тесты на существующий код, потом рефакторите.
2. Слишком глубокие абстракции с первого дня
Создаёте интерфейс для класса, который используется только в одном месте. Это over-engineering. Добавляйте абстракции, когда появляется вторая реализация или необходимость мокинга для тестов.
3. Игнорирование существующей архитектуры
Рефакторите AI-код в вакууме, не учитывая общую архитектуру проекта. Получается островок чистого кода в море хаоса. Интегрируйте отрефакторенный код в существующую систему, даже если это требует адаптеров.
Инструменты 2026 года, которые реально помогают
Рефакторинг вручную — это долго. К счастью, к 2026 году появились инструменты, которые помогают:
- Cursor IDE с AI-агентами для рефакторинга. Можете попросить: «Выдели валидацию в отдельный класс с интерфейсом» — и агент сделает это за 30 секунд.
- ArchUnit для Python (аналог Java ArchUnit). Пишете тесты на архитектурные правила: «Слой domain не должен импортировать infrastructure» — и CI будет падать при нарушении.
- CodeClimate с AI-движком — автоматически находит архитектурные проблемы в AI-сгенерированном коде.
Но помните: инструменты не заменяют понимания. Вы должны знать, какую архитектуру строите. Иначе получится вайб-кодинг на стероидах — быстрее, но так же бессмысленно.
Что делать, если нет времени на полный рефакторинг?
Бывает. Спринт горят, фичу нужно вчера. Минимальный план действий:
- Изолируйте AI-код: Оберните его в Facade или Adapter, чтобы грязный код не просачивался в чистую часть системы.
- Напишите интеграционные тесты: Хотя бы убедитесь, что текущая функциональность работает.
- Запланируйте рефакторинг в следующих спринтах: Добавьте технический долг в бэклог с конкретными шагами (те самые 4 стадии).
Самое опасное — оставить AI-код как есть, потому что «работает же». Через полгода вы не сможете его изменить, не сломав пол-системы. Это тот самый неосознанный вайб-кодинг, который кажется эффективным, пока не станет слишком поздно.
AI-кодинг — это не магия. Это инструмент, который требует навыков и дисциплины. Четыре стадии рефакторинга — не догма, а карта, которая помогает не заблудиться в лесу AI-сгенерированного кода. Используйте её, и ваша архитектура выживет даже в эпоху, когда AI сжимает команды с 40 до 10 человек. Потому что код пишут люди. Даже если им помогает искусственный интеллект.