Код только счастливого пути.
ИИ-ассистенты склонны генерировать код, который обрабатывает только успешный, корректный случай — пропуская валидацию ввода, обработку ошибок, проверки на null/пустоту и краевые случаи — поэтому код работает в демо и ломается в продакшене.
##Signs and Symptoms
Ревьюер замечает код только счастливого пути, когда каждая строка предполагает, что предыдущая завершилась успехом: сетевые вызовы не проверяются на статус не-2xx, JSON.parse/await res.json() никогда не оборачиваются, nullable-поля разыменовываются напрямую, к массивам обращаются по индексу без проверки длины, а внешний ввод течёт прямо в логику без валидации. Обычно есть ровно один путь возврата и нет throw, нет охранных условий и нет catch (или catch, который пуст или содержит лишь console.log).
// Сгенерировано ИИ: существует только сценарий успеха
async function getUserCity(userId) {
const res = await fetch(`/api/users/${userId}`);
const user = await res.json();
return user.address.city.toUpperCase();
}
Режимы сбоев молча отсутствуют: res.ok никогда не проверяется (404/500 возвращает тело ошибки, на котором .json() может бросить исключение), user может быть {}, address может быть null, city может быть undefined → Cannot read properties of undefined. Функция "работает" против счастливой заглушки и бросает исключение против реальности.
Характерные признаки в диффе:
- Новая
async-функция безtry/catchи без.catch()на промисе. - Прямые цепочки свойств (
a.b.c.d) на данных, пересёкших границу доверия/ввода-вывода. - Непроверенные
arr[0],find(...)!, приведенияasили утверждения non-null (!), подменяющие настоящую обработку. // TODO: handle errorsили голыйcatch (e) {}, оставленный как заглушка.- Описание PR гласит "обрабатывает X", но реализована только ветка, где X завершается успехом.
##Reasons for the Problem
Почему модели это порождают
- Вероятность следующего токена благоволит каноническому потоку. Наиболее вероятное продолжение после
const user = await res.json()— этоreturn user.something, а не проверка статуса. Обработка ошибок — это высоковариативный шаблонный код, различающийся от базы к базе, поэтому статистически он "неожиданный" и отбрасывается. - Обучающие данные смещены к счастливому пути. Туториалы, сниппеты README, посты блогов и принятые ответы Stack Overflow вырезают валидацию и обработку ошибок ради краткости ("обработка ошибок опущена для ясности"). Модель училась на тексте, который намеренно убрал ровно тот код, который вам нужен.
- Подхалимство / награда за опрятный вид. Настроенные через RLHF ассистенты вознаграждаются за лаконичные, прямо отвечающие ответы. Защитный код выглядит как шум, поэтому модель оптимизирует аккуратный сниппет, который кажется ответом на промпт.
- Нет контекста репозитория. Модель не знает, что у вас есть тип
AppError, обёрткаResult<T>, схемаzodили соглашение о логировании, поэтому не может их переиспользовать — и по умолчанию выбирает "ничего", вместо того чтобы угадывать ваши соглашения. - Задокументированные поведенческие тенденции. Анализ 300+ репозиториев от OX Security называет Избегание рефакторингов и Фиксацию на учебнике среди главных ИИ-антипаттернов — модель выдаёт функциональный код под непосредственный промпт и никогда его не укрепляет. arXiv 2509.20491 каталогизирует специфичные для ИИ запахи вокруг тихих сбоев; arXiv 2510.03029 находит повышенный уровень запахов реализации вроде Пустого блока catch в выводе LLM.
Почему это вредит
- Корректность: падения на
null, пустых коллекциях, тайм-аутах и ответах с кодом не 200 — ровно те входные данные, которые не появляются в быстром ручном тесте. - Безопасность: счастливый путь неявно доверяет своему вводу. Пропущенная валидация на границах — это то, как проникают ошибки внедрения, обхода пути и загрязнения прототипа. Отчёт OX называет это кодом, "небезопасным по глупости", выпущенным быстро и без суждения.
- Нагрузка на ревью и отладку: в опросе разработчиков Stack Overflow 2025 года 66% разработчиков называют "ИИ-решения, которые почти верны, но не совсем" своим главным разочарованием, а 45% говорят, что отладка сгенерированного ИИ кода более трудозатратна. Код счастливого пути — архетип "почти верного": читается нормально и падает во время выполнения.
- Накопление техдолга: поскольку модель не рефакторит и не переиспользует существующие утилиты для ошибок, каждая функция счастливого пути — это свежий одноразовый ком. Данные GitClear 2025 года показывают более широкий паттерн — скопированные строки выросли с 8,3% (2021) до 12,3% (2024), блоки-дубликаты из 5+ строк выросли примерно в 8 раз в 2024 году, тогда как рефакторинг (перемещённые строки) упал с ~25% до менее 10%. Отсутствующая обработка ошибок прикручивается позже, дублируется в каждом месте вызова, никогда не централизуется.
##Treatment
Тактики ревью и промптинга
- Заставьте модель сначала перечислить режимы сбоев. Промпт: "Прежде чем писать код, перечисли режимы сбоев для этой функции (плохой ввод, сетевая ошибка, не-200, пустой/некорректный ответ, отсутствующие поля, конкурентность). Затем реализуй обработку для каждого". Принуждение к шагу перечисления противодействует склонности следующего токена их пропускать.
- Укажите на соглашения для переиспользования. "Используй наш существующий тип
AppError/Resultизlib/errors.tsиloggerизlib/log.ts; валидируй ответ схемойzodизschemas/user.ts". Это превращает "отсутствие обработки" в переиспользование вместо самодельного кома (избегая Duplicate Code). - Требуйте прохождения проверок. "Запусти
eslintиtsc --noEmitи исправь каждое предупреждение, включая@typescript-eslint/no-floating-promises". Проверка типов сstrictNullChecksпревращает тихие разыменования null в ошибки компиляции, которые модель обязана устранить. - Требуйте негативных тестов. Просите модульные тесты, покрывающие пустой/null/ошибочный ввод, а не только счастливый случай — отсутствие этих тестов само по себе является запахом.
Рефакторинг
Добавьте валидацию на границах и охранные условия и централизуйте обработку. Именованные приёмы: Introduce Guard Clause (Введение охранного выражения, ранний throw при недопустимом состоянии), Introduce Assertion / валидация на границе (parse-don't-validate на краях ввода-вывода) и Introduce Special Case / Null Object (Введение особого случая / Null-объект, ?? "UNKNOWN" вместо падения).
// после: режимы сбоев — первоклассные граждане
async function getUserCity(userId: string): Promise<string> {
if (!userId) throw new InvalidArgumentError("userId required"); // охранное выражение
const res = await fetch(`/api/users/${encodeURIComponent(userId)}`);
if (!res.ok) throw new ApiError(`user fetch failed: ${res.status}`);
const user = UserSchema.parse(await res.json()); // валидируем на границе
return user.address?.city?.toUpperCase() ?? "UNKNOWN"; // обрабатываем пробел как особый случай
}
Если один и тот же паттерн try/validate/log начинает повторяться в местах вызова, Extract Function (Извлеките функцию) его в общую вспомогательную функцию fetchJson<T>(url, schema), чтобы обработка ошибок жила в одном месте, а не копировалась (ловушка дублирования GitClear).
##Detected by
- typescript-eslint @typescript-eslint/no-floating-promises — Отмечает промисы, путь отклонения которых никогда не обрабатывается (нет await/catch) — типичный симптом счастливого пути для асинхронных вызовов.
- ESLint no-empty — Отмечает пустые блоки, включая пустые блоки catch — заглушку 'обработки ошибок', которую модель оставляет после себя.
- SonarSource (JS/TS) javascript:S2486 — Исключения не должны игнорироваться — отмечает пойманные, но проглоченные ошибки, вырожденный родственник полного отсутствия обработки.