ConstructiCat Logo
CodeBust.
Browse section ▾

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

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

##Signs and Symptoms

Ревьюер замечает изобретение велосипеда, когда дифф вводит нетривиальный кусок логики «с нуля» для решения задачи, которая уже решена — рантаймом языка, зависимостью, уже присутствующей в 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 или локальный хелпер уже покрывают её, новая функция — изобретённый велосипед.

##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 / подъём к единому общему хелперу.

// ДО — переизобретённый, частично корректный, дублированный под каждую фичу
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);

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

##Detected by

  • jscpd duplicationОбнаружение копирования/вставки (min-tokens / порог)
  • PMD cpdCPD (Copy/Paste Detector)
  • SonarQube common-duplicationsДублированные блоки / duplicated_lines_density