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

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

## Signs and Symptoms

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

* **Копипаст с правками**: новая функция на 90% идентична существующей, отличаясь литералом, именем поля или одним лишним `if`.
* **Параллельные почти-дубликаты**: `formatUserCsv`, `formatAdminCsv`, `formatGuestCsv` — все вручную пишут один и тот же цикл вместо одной параметризованной функции.
* **Разрастание ветвлений вместо выделения**: длинный метод обрастает ещё одним `else if` вместо того, чтобы модель вынесла стратегию/таблицу поиска.
* **Изобретение заново**: написанные вручную `deepClone`/`debounce`/парсер дат, тогда как репозиторий уже импортирует lodash, date-fns и т. п. (OX Security называет это «vanilla style».)
* **Избыточная специализация**: гиперспецифичный одноразовый хелпер там, где универсальный уже существует двумя файлами дальше.
* **«Дежавю багов»**: одну и ту же правку приходится применять в трёх копиях, потому что дубликат так и не свели по DRY.

```ts
// Уже есть в репозитории:
function priceWithTax(items: Item[]) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * 1.2; // НДС 20%
}

// Что модель добавляет для нового случая со "скидкой" —
// целая вторая копия вместо параметра:
function priceWithTaxDiscounted(items: Item[], discount: number) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0); // дублируется
  return subtotal * (1 - discount) * 1.2;                          // дублированная логика НДС
}

```

Запах структурный, поэтому он заметнее всего по _всему_ изменению целиком, а не по одному фрагменту: много зелёного, мало перемещённого кода и сканер дублирования, загорающийся на новых строках.

## Reasons for the Problem

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

* **Аддитивное смещение предсказания следующего токена.** LLM дополняет тот промпт, который находится перед ней. Выдать самодостаточный новый блок — это локально наиболее вероятное и наименее рискованное продолжение; редактирование удалённой абстракции требует удерживать в рабочем контексте весь модуль и предсказать глобально согласованную правку, что сложнее и не вознаграждается непосредственным промптом.
* **Скудный контекст репозитория.** Кодовые ассистенты редко загружают всю кодовую базу. Если модель не _видит_ существующий хелпер, библиотеку или базовый класс, она не может его переиспользовать — поэтому она пишет заново. Отчёт OX Security описывает это как эффект «армии джуниоров»: масса локально работающего кода без архитектурной памяти.
* **Рефакторинг рискован и не вознаграждается.** Перестройка затрагивает код, который модель не просили менять, и может сломать вызывающий код и тесты. Угодливый ассистент в духе «просто заставь работать» минимизирует радиус поражения, _не трогая_ работающий код, — ровно то поведение, которое OX измерил как «избегание рефакторингов» в 80–90% сгенерированного ИИ кода и «избыточную специализацию» (одноразовое вместо переиспользуемого) ещё в 80–90%.
* **Устаревание обучающих данных.** Модель может не знать, что репозиторий перешёл на util-модуль или обновил библиотеку уже после её момента отсечения, поэтому вручную пишет то, что уже существует.
* **Генерация дёшева, удаление пугает.** Выдать 40 новых строк модели ничего не стоит; убедить её удалить и консолидировать 40 существующих строк противоречит её инстинкту сохранять.

**Чем это вредит**

* **Дублирование накапливается.** Анализ GitClear 2025 года, охвативший 211 млн изменённых строк, показал, что доля скопированных строк выросла с \~8,3% (2021) до 12,3% (2024) — это первый год, когда дублирование превысило «перемещённый» (отрефакторенный) код, — тогда как доля отрефакторенных строк упала с \~25% до менее чем 10%, то есть примерно на 60%. Число блоков из 5+ дублирующихся строк выросло примерно в 8 раз в 2024 году.
* **Сопровождаемость и корректность.** Каждый клон — это место, где про будущую правку можно забыть: «дежавю багов», когда один и тот же дефект повторяется и его приходится латать N раз (OX: 70–80% ИИ-кода нарушают принципы переиспользования именно так).
* **Накопление техдолга.** Рефакторинг — это переработка, удерживающая энтропию на низком уровне; подавляя его, вы создаёте долг, который никогда не погашается. Кодовая база растёт быстрее, чем улучшается.
* **Нагрузка на ревью.** Ревьюерам теперь приходится на глаз сравнивать почти идентичные блоки, чтобы убедиться, что они _намеренно_ одинаковы, — это сдвиг когнитивной нагрузки, который описывает обсуждение «запахов кода для ИИ-агентов» на Stack Overflow: работа смещается от написания к ревью и консолидации.

## Treatment

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

* **Укажите модели на то, что уже есть.** «Прежде чем добавлять код, поищи в репозитории существующий хелпер/util/базовый класс и переиспользуй его; если ничего не подходит, обобщи ближайший». Вставьте соответствующий модуль в контекст, чтобы модель действительно _видела_ абстракцию.
* **Ограничьте форму диффа.** «Предпочитай редактировать существующие функции, а не добавлять новые. Если два пути исполнения разделяют общую логику, вынеси общую функцию (Extract Function), а не дублируй».
* **Заставьте её запускать инструменты.** Требуйте, чтобы ассистент запускал сканер дублирования (jscpd / PMD CPD) и линтер и устранял любые новые находки дублированных блоков, прежде чем объявить задачу выполненной, — это превращает невидимый запах в падающий гейт.
* **Запрашивайте рефакторинг явно, отдельным шагом.** Генерация и консолидация — разные задачи; сначала «заставь работать», затем отдельно «теперь приведи к DRY и убери дублирование» — это модели делают гораздо лучше, когда их просят напрямую.
* **Добавьте гейт качества в CI**, чтобы дублирование не нарастало незаметно (порог дублированных строк в Sonar или `jscpd --threshold`).

**Собственно рефакторинг** — назовите классические приёмы: **Extract Function**, **Parameterize Function** и **Pull Up / Replace Conditional with Polymorphism или таблица поиска**, чтобы устранить **Duplicate Code**.

```ts
// До: две почти идентичные функции (рефакторинг избегается)
function priceWithTax(items: Item[]) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * 1.2;
}
function priceWithTaxDiscounted(items: Item[], discount: number) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * (1 - discount) * 1.2;
}

// После: Extract Function + Parameterize Function
const VAT = 1.2;
const subtotalOf = (items: Item[]) =>
  items.reduce((s, i) => s + i.price * i.qty, 0);

function priceWithTax(items: Item[], discount = 0) {
  return subtotalOf(items) * (1 - discount) * VAT;
}

```

Единый источник истины для промежуточной суммы и НДС: будущее изменение налогового правила теперь становится правкой в одну строку вместо поиска по N местам.

## Detected by

- **jscpd** `duplication above --threshold / --min-tokens` — Обнаружение копирования/вставки (https://github.com/kucherenko/jscpd)
- **PMD** `Copy/Paste Detector (minimum-tokens duplication)` — CPD (https://pmd.github.io/pmd/pmd_userdocs_cpd.html)
- **SonarQube** `duplicated_lines_density / quality-gate duplicated-blocks condition` — Дублирования (https://docs.sonarsource.com/sonarqube-server/latest/user-guide/code-metrics/metrics-definition/)
- **ESLint (eslint-plugin-sonarjs)** `sonarjs/no-identical-functions` — no-identical-functions (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-identical-functions.md)
- **ESLint (eslint-plugin-sonarjs)** `sonarjs/no-duplicate-string` — no-duplicate-string (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-duplicate-string.md)
