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