Изобретение велосипеда.
Модель вручную пишет самописный код для того, что уже делает стандартная библиотека, имеющаяся зависимость или хелпер уже в репозитории, — добавляя дублированную, хуже протестированную логику вместо вызова того, что уже есть.
##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 cpd — CPD (Copy/Paste Detector)
- SonarQube common-duplications — Дублированные блоки / duplicated_lines_density