Правила код-ревью с AI-ассистентами: практика для инженеров 2025 | AiManual
AiManual Logo Ai / Manual.
06 Янв 2026 Гайд

Код-ревью умерло? Да здравствует код-ревью с LLM

Как проводить код-ревью когда код пишут LLM. Конкретные правила, примеры проблем (лишние проверки, защитный код), работа с Claude Code и PyTorch.

Открываешь пул-реквест, а там 400 строк кода. Красиво отформатировано, комментарии на месте, типы проставлены. Но что-то не так. Слишком... идеально. Слишком шаблонно. Поздравляю, ты только что наткнулся на код, написанный LLM. И твоя работа как ревьювера только что усложнилась втрое.

Раньше код-ревью было про поиск ошибок, проверку архитектуры, соблюдение конвенций. Теперь это детективная работа: отличить человеческую логику от машинной, найти избыточные паттерны безопасности, выловить странные оптимизации, которые «в теории» работают. LLM не ошибаются как люди — они ошибаются системно, предсказуемо и с уверенностью senior-разработчика.

Что не так с этим кодом? (Подсказка: его написал ИИ)

Давай посмотрим на реальный пример из PyTorch-проекта, где Claude Code «помог» с обработкой тензоров:

def safe_tensor_operation(input_tensor: torch.Tensor, operation: str) -> Optional[torch.Tensor]:
    """
    Безопасно выполняет операцию над тензором с проверкой всех возможных условий.
    
    Args:
        input_tensor: Входной тензор
        operation: Название операции ('sum', 'mean', 'max')
    
    Returns:
        Результат операции или None если что-то пошло не так
    """
    # Проверка 1: существует ли тензор
    if input_tensor is None:
        logger.warning("Input tensor is None")
        return None
    
    # Проверка 2: является ли тензором
    if not isinstance(input_tensor, torch.Tensor):
        logger.warning(f"Input is not a tensor: {type(input_tensor)}")
        return None
    
    # Проверка 3: есть ли данные
    if input_tensor.numel() == 0:
        logger.warning("Tensor is empty")
        return None
    
    # Проверка 4: корректная операция
    valid_operations = {'sum', 'mean', 'max'}
    if operation not in valid_operations:
        logger.warning(f"Invalid operation: {operation}")
        return None
    
    # Проверка 5: поддерживает ли устройство
    if not input_tensor.device.type in ['cpu', 'cuda']:
        logger.warning(f"Unsupported device: {input_tensor.device}")
        return None
    
    # Сама операция (наконец-то!)
    try:
        if operation == 'sum':
            result = torch.sum(input_tensor)
        elif operation == 'mean':
            result = torch.mean(input_tensor)
        else:  # operation == 'max'
            result = torch.max(input_tensor)
        
        # Проверка 6: корректный ли результат
        if result is None:
            logger.warning("Operation returned None")
            return None
            
        return result
        
    except Exception as e:
        logger.error(f"Operation failed: {e}")
        return None

Видишь проблему? LLM перестраховывается. 6 проверок перед одной операцией, логирование на каждом шагу, возврат None вместо исключений. Код работает, но он уродлив, неидиоматичен и скрывает реальные ошибки. В production это превратится в кучу "warning" в логах, которые все игнорируют.

Пять правил, которые спасут вашу команду от AI-хаоса

1 Ищи паранойю, а не баги

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

Что делать:

  • Вычеркни проверки на None там, где параметры гарантированно не None (например, в приватных методах)
  • Удали try-catch блоки, которые ловят Exception (это почти всегда антипаттерн)
  • Задай вопрос: «Какая реальная ошибка может скрыться за этим warning?» Если ответа нет — удаляй проверку

2 Декомпозируй или умри

LLM обожают длинные методы. Им проще написать одну функцию на 100 строк, чем 10 маленьких. Потому что в их тренировочных данных полно legacy-кода.

# Плохо (как делает LLM):
def process_user_data(user_data):
    # 80 строк валидации, преобразований, сохранения и логирования
    ...

# Хорошо (как должен человек):
def validate_user_data(data):
    ...

def transform_user_data(data):
    ...

def save_user_data(data):
    ...

Твое правило: если метод делает больше трех вещей — требуй декомпозиции. Даже если «оно работает».

3 Тесты — твой детектор лжи

LLM умеют генерировать тесты. Иногда даже правильные. Но чаще они создают тесты, которые проверяют не то, что нужно.

💡
Ссылаясь на статью «Тестируем недетерминированные LLM», помни: тесты для LLM-кода должны проверять поведение, а не реализацию. Иначе при каждом изменении кода тесты сломаются.

Проверяй:

  • Тесты покрывают edge cases или только happy path?
  • Мокируются ли внешние зависимости (база данных, API)?
  • Есть ли тесты на ошибки, которые код должен выбрасывать?

4 Именование против шаблонов

LLM генерируют имена переменных как среднестатистический junior: data, result, temp_value. Они не понимают доменную область твоего проекта.

LLM-имя Человеческое имя Почему лучше
processed_data normalized_prices Конкретика вместо абстракции
config_dict feature_flags Суть вместо типа
calculate_result estimate_delivery_time Действие вместо вычисления

5 Архитектурная слепота — главный грех

Самая опасная черта LLM: они не видят картину целиком. Могут написать идеальную функцию, которая ломает всю архитектуру приложения.

Пример: LLM генерирует функцию для кэширования в Redis, но в проекте уже есть единый CacheService со своей стратегией инвалидации. Получается два способа кэширования — гарантированный баг в будущем.

Твой вопрос при ревью: «Где еще в проекте решалась похожая задача?» Если ответ — «в трех других местах», код нужно переделывать.

Когда доверять, а когда проверять: философия ревью в эпоху LLM

Старая модель: «разработчик пишет, ревьювер проверяет». Новая модель: «LLM генерирует, разработчик редактирует, ревьювер задает вопросы».

Изменился сам процесс. Теперь в пул-реквесте нужно спрашивать:

  • «Какой промпт ты использовал?» (да, это стало валидным вопросом)
  • «Что из этого кода написал ты, а что — ИИ?»
  • «Почему ты оставил эту проверку, хотя она избыточна?»

Используй LLM в своем процессе ревью. Скопируй подозрительный код в Claude Code и спроси: «Какие проблемы ты видишь в этом коде?» Иногда они находят то, что пропустил человек. Но помни про проблему потери контекста в середине — большие куски кода они анализируют хуже.

Код, который нельзя принимать никогда

Есть паттерны, которые LLM генерируют постоянно и которые должны быть красной тряпкой для любого ревьювера:

# 1. Магические числа с комментариями (вместо констант)
timeout = 30  # 30 секунд таймаут
retries = 3   # 3 попытки ретрая

# 2. Избыточные преобразования типов
value = str(str(input_data).strip())

# 3. Логирование вместо исключений
if not user_exists:
    logger.error("User not found")
    return {"error": "User not found"}

# 4. Многоуровневые if-else вместо guard clauses
def process_order(order):
    if order.is_valid:
        if order.payment_completed:
            if order.items_in_stock:
                # настоящая логика
            else:
                return "Out of stock"
        else:
            return "Payment failed"
    else:
        return "Invalid order"

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

💡
Если хочешь глубже понять, как LLM принимают решения (и почему иногда дают опасные советы), посмотри статью «Провал LLM: Почему нейросети понимают вашу боль, но всё равно дают опасный совет». Это поможет предсказывать их ошибки.

Инструменты, которые реально помогают

Статические анализаторы не справляются с LLM-кодом. Они ищут синтаксические ошибки, а не логические паттерны. Нужны новые инструменты.

  1. Собственные линтеры. Напиши правила, специфичные для твоего проекта:
    # .lintrules.yml
    forbidden_patterns:
      - "try:.*except Exception:"  # Слишком широкий except
      - "if .* is not None and .*"  # Избыточные проверки
      - "logger\.(warning|error)\(.*\)\s*return None"  # Логирование вместо исключений
  2. Автоматическое ревью через LLM. Настрой GitHub Action, который прогоняет код через GPT-4 с промптом: «Найди избыточные проверки, магические числа, нарушение архитектурных принципов». Результат — как первый проход, не больше.
  3. Шаблоны пул-реквестов. Обязательное поле: «Использовался ли ИИ-ассистент? Какие промпты?» Без этого — не ревьювить.

Что будет через год? (Спойлер: все станет еще страннее)

Код-ревью превратится в диалог: не «правильно/неправильно», а «почему ты выбрал это решение?». Разработчики будут больше объяснять, чем писать. Ревьюверы будут больше спрашивать, чем исправлять.

Появятся специализированные LLM, обученные на коде конкретной компании. Они будут знать ваши конвенции, архитектуру, домен. Но пока их нет — читай обзор локальных LLM с Tool Calling, выбирай ту, которую можно дообучить на твоем коде.

Самое важное: код-ревью теперь не про поиск ошибок. Ошибки найдет LLM. Это про поиск смысла. Зачем эта функция? Почему эта архитектура? Что происходит, если...?

Если после ревью ты не понял, как работает код — это провал. Даже если код технически безупречен. Потому что через месяц его будет читать другой человек. Или другая LLM. И они тоже должны понять.