Логотип ConstructiCat
CodeBust.
Навигация по разделу ▾

Изобретение велосипеда.

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

##Признаки и симптомы

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

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

##Причины проблемы

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

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

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

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

##Лечение

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

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

Рефакторинг — это классический запах Дублирование кода плюс антипаттерн «изобретение велосипеда»; лечение — Замена алгоритма (заменить самописное тело вызовом библиотеки или стандартной функции), а там, где копий несколько, — Извлечение метода / Подъём метода с переносом в единый общий хелпер.

// ДО — переизобретённый, частично корректный, дублированный под каждую фичу
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);
// ПОСЛЕ — вызвать то, что уже существует (верные граничные случаи, ноль нового кода)
const copy = structuredClone(state);
// или, если стандарт репозитория — локальный хелпер:
import { cloneDeep } from "@/utils/object";
const copy = cloneDeep(state);

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

##Обнаружение анализаторами

  • jscpd --min-tokens / --threshold — Обнаружение копирования/вставки (min-tokens / порог)
  • PMD CPD — CPD (Детектор копирования/вставки)
  • SonarQube duplicated_lines_density — Дублированные блоки / duplicated_lines_density