Открываешь пул-реквест, а там 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 умеют генерировать тесты. Иногда даже правильные. Но чаще они создают тесты, которые проверяют не то, что нужно.
Проверяй:
- Тесты покрывают 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-кодом. Они ищут синтаксические ошибки, а не логические паттерны. Нужны новые инструменты.
- Собственные линтеры. Напиши правила, специфичные для твоего проекта:
# .lintrules.yml forbidden_patterns: - "try:.*except Exception:" # Слишком широкий except - "if .* is not None and .*" # Избыточные проверки - "logger\.(warning|error)\(.*\)\s*return None" # Логирование вместо исключений - Автоматическое ревью через LLM. Настрой GitHub Action, который прогоняет код через GPT-4 с промптом: «Найди избыточные проверки, магические числа, нарушение архитектурных принципов». Результат — как первый проход, не больше.
- Шаблоны пул-реквестов. Обязательное поле: «Использовался ли ИИ-ассистент? Какие промпты?» Без этого — не ревьювить.
Что будет через год? (Спойлер: все станет еще страннее)
Код-ревью превратится в диалог: не «правильно/неправильно», а «почему ты выбрал это решение?». Разработчики будут больше объяснять, чем писать. Ревьюверы будут больше спрашивать, чем исправлять.
Появятся специализированные LLM, обученные на коде конкретной компании. Они будут знать ваши конвенции, архитектуру, домен. Но пока их нет — читай обзор локальных LLM с Tool Calling, выбирай ту, которую можно дообучить на твоем коде.
Самое важное: код-ревью теперь не про поиск ошибок. Ошибки найдет LLM. Это про поиск смысла. Зачем эта функция? Почему эта архитектура? Что происходит, если...?
Если после ревью ты не понял, как работает код — это провал. Даже если код технически безупречен. Потому что через месяц его будет читать другой человек. Или другая LLM. И они тоже должны понять.