---
title: "Защитный перебор"
type: "ai-smell"
slug: "defensive-overkill"
url: "http://localhost:3000/ru/ai-smells/defensive-overkill.md"
category: "Сопровождение"
description: "ИИ-ассистенты подстраховываются от сбоев, которые не могут произойти, оборачивая уже безопасный код в избыточные проверки на null, мёртвые охранные условия и всеохватные блоки try/except, добавляющие сложность без добавления безопасности."
---
# Защитный перебор

> ИИ-ассистенты подстраховываются от сбоев, которые не могут произойти, оборачивая уже безопасный код в избыточные проверки на null, мёртвые охранные условия и всеохватные блоки try/except, добавляющие сложность без добавления безопасности.

## Signs and Symptoms

Ревьюер замечает защитный перебор, когда функция тратит больше строк на защиту от невозможных состояний, чем на собственно работу. Характерные признаки:

* **Охранные условия для случаев, которые система типов уже гарантирует** — проверки на null/undefined у non-nullable типизированного параметра, `typeof x === "string"` для `string`.
* **Дублирующиеся или поглощённые охранные условия** — `if (!user) return`, за которым тут же следует `if (user === null || user === undefined) return`.
* **Всеохватный `try/catch` вокруг кода, который не может бросить исключение**, часто проглатывающий ошибку или просто перебрасывающий её ("на всякий случай").
* **Логика для "фантомных" краевых случаев** — ветки, обрабатывающие ввод, который ни один вызывающий не может породить. OX Security обнаружила, что ИИ регулярно добавляет "логику для воображаемых краевых случаев".
* **Защитные блоки скопированы**, а не извлечены, поэтому одно и то же охранное условие появляется в пяти местах.

```ts
// User — NON-nullable тип: { profile: { name: string } }
function getDisplayName(user: User): string {
  if (!user) return "Unknown";                         // мёртвая: user non-nullable
  if (user === null || user === undefined) return "?"; // дубликат, тоже мёртвая
  try {
    if (user.profile && typeof user.profile.name === "string") { // тип уже это гарантирует
      const name = user.profile.name;
      if (name.length > 0) {
        return name.trim() !== "" ? name.trim() : "Unknown";
      }
    }
    return "Unknown";
  } catch (e) {
    console.error("could not get name", e); // этот блок не может бросить исключение
    return "Unknown";
  }
}

```

Девять строк подстраховки оборачивают одну строку намерения. Противоречие, отмеченное ревьюерами: _та же_ модель часто опускает единственную важную проверку (например, валидацию недоверенного внешнего ввода), при этом чрезмерно защищая внутренние вызовы — защитность раскидана по наитию, а не по модели угроз.

## Reasons for the Problem

### Почему модели это порождают

* **Избегание риска на уровне следующего токена / "услужливость" RLHF.** Модели настроены выглядеть основательными и избегать "ошибочности". Выдать лишний охранный блок или `try/catch` малорискованно для цели следующего токена и читается как добросовестность, поэтому это пересэмплируется. Ревьюер, работающий с большими объёмами, отметил, что повторяющиеся цепочки `if (array && array.length > 0)` — "признак того, что модель не вполне уверена в потоке кода" — охрана служит подстраховкой против собственной неуверенности модели.
* **Нет контекста потока данных всего репозитория.** Модель не видит, что вызывающий код уже провалидировал аргумент или что тип TypeScript делает ветку недостижимой. arXiv 2509.20491 (_Специфичные для ИИ запахи кода_) показывает, что LLM "испытывают трудности с запахами кода, зависящими от потока или чувствительными к значениям" — когда они не могут рассуждать о потоке, они по умолчанию защищают всё локально.
* **Карго-культ безопасных промптов.** Исследования промптинга безопасного кода обнаружили, что на просьбу "сделай это безопасным" LLM "добавляют блоки try-catch как самостоятельную меру безопасности без других улучшений безопасности... обычно когда не могут выявить конкретные уязвимости". Защитность подменяет понимание.
* **Смещение обучающих данных к многословному, учебному коду**, демонстрирующему каждую проверку ради педагогики, плюс отказ от рефакторинга: OX Security обнаружила **избегание рефакторингов в 80–90%** ИИ-кода и **сверхспецификацию в 80–90%** — модель добавляет, удаляет редко.

### Почему это вредит

* **Сопровождаемость и нагрузка на ревью.** Таксономия неэффективностей LLM на Python (arXiv 2503.06327) каталогизирует _избыточную валидацию ввода_, _чрезмерно защитную обработку ошибок_ и _ненужные охранные условия_, заключая, что эти "защитные паттерны добавляют сложность без пропорциональной выгоды для безопасности". Ревьюерам приходится прочитывать каждую мёртвую ветку, чтобы убедиться, что она _действительно_ мертва.
* **Корректность, а не только захламление.** Всеохватные блоки, проглатывающие или обобщённо обрабатывающие ошибки, прячут настоящие сбои; `try/catch`, возвращающий значение по умолчанию, превращает ошибку в молчаливое неверное поведение. Охранные условия также создают мёртвые ветки, которые тесты исправно покрывают, раздувая покрытие бессмысленными тестами (ещё одна находка OX).
* **Накопление техдолга.** Анализ GitClear 2025 года обнаружил, что доля рефакторинга в изменённых строках упала с **25% (2021) до менее 10% (2024)**, тогда как скопированные строки выросли до **12,3%**, а дублированные блоки выросли примерно в 8 раз. Защитный шаблонный код — это ровно тот код, который клонируется вместо извлечения, а клонированные блоки коррелируют с **на 15–50% большим числом дефектов**.
* **Более высокая когнитивная сложность** на функцию затрудняет поиск настоящей логики, замедляя каждое будущее изменение.

## Treatment

### Тактики ревью и промптинга

* **Сделайте контракт явным, чтобы охранные условия стали доказуемо ненужными.** Скажите модели: _"`user` non-nullable и уже провалидирован вызывающим — не перепроверяй его"._ Опирайтесь на типы: при включённом `strictNullChecks` правило `@typescript-eslint/no-unnecessary-condition` само отметит мёртвые охранные условия.
* **Просите соразмерную защиту, а не сплошную.** Спрашивайте: _"Обрабатывай только те ошибки, для которых можешь описать конкретный триггер. Валидируй недоверенный/внешний ввод на границе; доверяй внутренним вызовам"._ Это отделяет настоящую валидацию ввода (оставьте) от фантомных краевых случаев (удалите).
* **Требуйте, чтобы модель запускала линтер/проверку типов и удаляла то, что они отмечают**, до возврата кода — замкните петлю так, как рекомендуют практики работы с агентами (линтеры, проверки типов, тесты как автоматические сигналы обратной связи).
* **Просите её удалять, а не только добавлять:** _"Отрефактори до минимума кода, удовлетворяющего спецификации; удали недостижимые ветки и блоки catch, которые не могут сработать"._ Это противодействует задокументированному смещению "избегания рефакторингов".

### Рефакторинг

Назовите классические приёмы: **Remove Dead Code** (Удаление мёртвого кода), **Consolidate Conditional Expression** (Консолидация условного выражения), **Replace Nested Conditional with Guard Clauses** (Замена вложенных условий охранными выражениями) и (для ввода, который _действительно_ нуждается в проверке) **Introduce Assertion / валидируйте один раз на границе** вместо повторов. Там, где одно и то же охранное условие было скопировано, — **Extract Function** (Извлечение функции).

```ts
// ПОСЛЕ — тип гарантирует non-null; валидируем один раз, без фантомного catch
function getDisplayName(user: User): string {
  const name = user.profile.name.trim();
  return name || "Unknown";
}

```

Если значение действительно _является_ недоверенным, валидируйте его **один раз** на краю и позвольте остальному коду доверять теперь сужённому типу:

```ts
// граница: разбираем/валидируем недоверенный ввод единожды
const user = UserSchema.parse(rawInput); // бросает исключение на плохих данных, здесь, намеренно
// ...всё ниже по потоку получает провалидированный `User` и не нуждается в перепроверке

```

Эмпирическое правило для ревьюеров: каждое охранное условие и каждый `catch` должны отвечать на вопрос _"какой конкретный вызывающий или ввод это запускает?"_ Если ответ — "ничего — на всякий случай", удалите его.

## Detected by

- **typescript-eslint** `@typescript-eslint/no-unnecessary-condition` — no-unnecessary-condition (https://typescript-eslint.io/rules/no-unnecessary-condition/)
- **ESLint** `no-useless-catch` — no-useless-catch (https://eslint.org/docs/latest/rules/no-useless-catch)
- **SonarSource (SonarQube / eslint-plugin-sonarjs)** `RSPEC-2589 — Boolean expressions should not be gratuitous (always-true/false conditions)` — no-gratuitous-expressions (https://rules.sonarsource.com/javascript/RSPEC-2589/)
- **SonarSource (SonarQube / eslint-plugin-sonarjs)** `RSPEC-3776 — Cognitive Complexity (nested defensive guards inflate it; proxy detector)` — cognitive-complexity (https://rules.sonarsource.com/javascript/RSPEC-3776/)
