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

> Модель вручную пишет самописный код для того, что уже делает стандартная библиотека, имеющаяся зависимость или хелпер уже в репозитории, — добавляя дублированную, хуже протестированную логику вместо вызова того, что уже есть.

## Signs and Symptoms

Ревьюер замечает изобретение велосипеда, когда дифф вводит нетривиальный кусок логики «с нуля» для решения задачи, которая уже решена — рантаймом языка, зависимостью, уже присутствующей в `package.json`, или утилитой, которая уже существует где-то в репозитории. Типичные приметы:

* Самописные `deepClone`, `debounce`, `groupBy`, `chunk`, `retry`, `slugify`, `deepMerge` или `uuid`, тогда как их предоставляет рантайм или установленная библиотека.
* Самописное **регулярное выражение** для email/URL/UUID вместо валидатора, который уже является зависимостью.
* Самописная арифметика дат, разбор query-строки или логика пагинации, переизобретающие `Intl`/`URLSearchParams`/возможность ORM.
* Два-три почти идентичных приватных хелпера в разных файлах (один и тот же велосипед, вырезанный заново под каждую фичу) — то дублирование, что всплывает в метриках копипаста.
* Переизобретённая версия слегка **неверна**: она обрабатывает счастливый путь, но упускает граничные случаи, покрытые проверенным временем оригиналом.

```ts
// Написано ИИ: самописный глубокий клон, переизобретённый inline
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out; // молча теряет Date, Map, Set, RegExp; зацикливается на циклах
}
// ...хотя рантайм поставляет structuredClone(), А ТАКЖЕ
// репозиторий уже экспортирует cloneDeep() из src/utils/object.ts

```

Самая быстрая проверка: прежде чем принимать новый код, поищите эту возможность в репозитории и `node_modules`. Если `structuredClone`, `lodash`, `date-fns` или локальный хелпер уже покрывают её, новая функция — изобретённый велосипед.

## Reasons for the Problem

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

* **По умолчанию нет контекста репозитория.** Модель часто не видит ваш `src/utils`, ваши установленные зависимости или ваши внутренние соглашения, поэтому тянется к статистически наиболее вероятному завершению: самодостаточной inline-реализации. Она заново выводит велосипед, потому что никогда не видела вашего.
* **Локальность предсказания токенов.** LLM оптимизируют локально правдоподобное продолжение, а не глобальную минимальность. Написать `function groupBy(...)` — это высоковероятная последовательность; остановиться и обнаружить, что `lodash.groupBy` уже импортирован тремя файлами дальше, предсказание токенов не делает.
* **Обучающие стимулы вознаграждают самодостаточность.** Огромная доля обучающих данных — это туториалы, ответы со Stack Overflow и сниппеты, намеренно показывающие реализацию целиком. Модель усвоила, что «хорошо отвечать» — значит выдавать полный, самостоятельный код, — ровно неверный инстинкт внутри зрелой кодовой базы.
* **Устаревание из-за момента отсечения обучения.** Модель может не знать, что некая возможность была добавлена в стандартную библиотеку (например, `structuredClone`, `Array.prototype.at`, `Object.groupBy`) или что ваш репозиторий принял хелпер после её отсечения, поэтому она пишет полифил для того, что уже существует.
* **Угодливость / путь наименьшего сопротивления.** На просьбу «добавь X» модель добавляет X самым прямым способом, а не возражает «у нас это уже есть». Она редко по своей инициативе говорит «вам не нужно это писать».
* **Избыточная специализация.** Анализ 300+ репозиториев от OX Security показал, что ИИ склонен к «vanilla-style» кодированию, перестраивая общую функциональность вместо использования проверенных библиотек, с узкоспециализированными, непереиспользуемыми решениями примерно в 80–90% случаев — каждая новая вариация получает свежий код вместо переиспользования.

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

* **Сопровождаемость и техдолг.** Это измеримое лицо упадка качества ИИ-кода. Анализ 211 млн строк от GitClear показал, что доля скопированного кода выросла с 8,3% (2020) до 12,3% (2024), число блоков из 5+ дублирующихся строк выросло примерно в 8 раз в 2024 году, а доля «перемещённых» (отрефакторенных) строк упала с 24,1% до 9,5% — 2024-й стал первым годом, когда копипаст превысил рефакторинг. Изобретённые велосипеды — это то, как входит такое дублирование.
* **Корректность.** Самописная версия пропускает граничные случаи, которые зрелая реализация заслужила годами баг-репортов (часовые пояса, Unicode, циклы, экранирование). Она выглядит верной и подводит на длинном хвосте.
* **Безопасность.** Самостоятельная перереализация крипто, аутентификации, санитизации или валидации вместо проверенной библиотеки — это то, как написанный ИИ код «нарушает лучшие инженерные практики» в масштабе (эффект «армии джуниоров» от OX): уязвимые паттерны доходят до продакшена быстрее, чем ревью успевает их поймать.
* **Нагрузка на ревью.** Ревьюерам теперь приходится читать, осмысливать и тестировать 40 строк самописной логики вместо того, чтобы распознать один доверенный вызов библиотеки, — помноженное на каждый PR.

## Treatment

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

* **Дайте модели недостающий контекст.** Перед генерацией укажите ей на ваши утилиты и зависимости: «Переиспользуй хелперы из `src/utils/*` и библиотеки, уже имеющиеся в `package.json`; не добавляй новые без согласования». Вставьте соответствующие зависимости из `package.json` и индекс ваших утилит.
* **Сделайте «сначала поищи» правилом.** Инструктируйте: «Прежде чем писать любой хелпер, проверь, не делает ли это уже стандартная библиотека, существующая зависимость или утилита репозитория; если да — вызови её». Агентные инструменты должны сначала грепать репозиторий.
* **Оспаривайте каждый новый приватный хелпер на ревью.** Для каждой самописной утилиты спросите: делает ли это рантайм? делает ли это зависимость? есть ли это у нас уже? Если да — это изобретение велосипеда, замените его.
* **Запускайте тулчейн.** Сканер дублирования (jscpd / PMD CPD / SonarQube) в CI автоматически отмечает грань с копипастом; встройте его в гейт, чтобы заново вырезанные велосипеды роняли сборку.

**Рефакторинг** — это классические **Duplicate Code** и **Reinvent the Wheel**; лечение — **Substitute Algorithm** (поменять самописное тело на вызов библиотеки/стандарта), а там, где существует несколько копий, — **Extract Function** / подъём к единому общему хелперу.

```ts
// ДО — переизобретённый, частично корректный, дублированный под каждую фичу
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out;
}
const copy = deepClone(state);

```

```ts
// ПОСЛЕ — вызвать то, что уже существует (верные граничные случаи, ноль нового кода)
const copy = structuredClone(state);
// или, если стандарт репозитория — локальный хелпер:
import { cloneDeep } from "@/utils/object";
const copy = cloneDeep(state);

```

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

## Detected by

- **jscpd** `duplication` — Обнаружение копирования/вставки (min-tokens / порог) (https://github.com/kucherenko/jscpd)
- **PMD** `cpd` — CPD (Copy/Paste Detector) (https://docs.pmd-code.org/latest/pmd_userdocs_cpd.html)
- **SonarQube** `common-duplications` — Дублированные блоки / duplicated_lines_density (https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/duplications/)
