---
title: "Код только счастливого пути"
type: "ai-smell"
slug: "happy-path-only"
url: "http://localhost:3000/ru/ai-smells/happy-path-only.md"
category: "Корректность"
description: "ИИ-ассистенты склонны генерировать код, который обрабатывает только успешный, корректный случай — пропуская валидацию ввода, обработку ошибок, проверки на null/пустоту и краевые случаи — поэтому код работает в демо и ломается в продакшене."
---
# Код только счастливого пути

> ИИ-ассистенты склонны генерировать код, который обрабатывает только успешный, корректный случай — пропуская валидацию ввода, обработку ошибок, проверки на null/пустоту и краевые случаи — поэтому код работает в демо и ломается в продакшене.

## Signs and Symptoms

Ревьюер замечает код только счастливого пути, когда каждая строка предполагает, что предыдущая завершилась успехом: сетевые вызовы не проверяются на статус не-2xx, `JSON.parse`/`await res.json()` никогда не оборачиваются, nullable-поля разыменовываются напрямую, к массивам обращаются по индексу без проверки длины, а внешний ввод течёт прямо в логику без валидации. Обычно есть ровно один путь возврата и нет `throw`, нет охранных условий и нет `catch` (или `catch`, который пуст или содержит лишь `console.log`).

```ts
// Сгенерировано ИИ: существует только сценарий успеха
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"` вместо падения).

```ts
// после: режимы сбоев — первоклассные граждане
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) — типичный симптом счастливого пути для асинхронных вызовов. (https://typescript-eslint.io/rules/no-floating-promises/)
- **ESLint** `no-empty` — Отмечает пустые блоки, включая пустые блоки catch — заглушку 'обработки ошибок', которую модель оставляет после себя. (https://eslint.org/docs/latest/rules/no-empty)
- **SonarSource (JS/TS)** `javascript:S2486` — Исключения не должны игнорироваться — отмечает пойманные, но проглоченные ошибки, вырожденный родственник полного отсутствия обработки. (https://rules.sonarsource.com/javascript/RSPEC-2486/)
