Многословный шаблонный код.
ИИ-ассистенты раздувают простую логику комментариями, пересказывающими каждую строку, пошаговыми пояснениями, защитными «лестницами» проверок на null и многословными выражениями там, где хватило бы идиомы или существующего хелпера, — число строк растёт, а поведения не прибавляется.
##Признаки и симптомы
Ревьюер замечает многословный шаблонный код, когда дифф намного длиннее, чем требует задача: каждая строка озвучена комментарием, который просто повторяет её, простые выражения обёрнуты в защитные лестницы проверок null/undefined, а одна и та же форма кода появляется дважды или трижды вместо того, чтобы быть вынесенной в один хелпер. Выдаёт его высокое соотношение комментарии-к-логике и строки-к-поведению, плюс блоки, которые могли бы уместиться в три строки идиоматичного кода.
// Функция для получения полного имени пользователя
function getFullName(user: User): string {
// Проверяем, определён ли пользователь
if (user === null || user === undefined) {
// Возвращаем пустую строку, когда пользователь не передан
return "";
}
// Получаем имя, по умолчанию пустая строка
const firstName = user.firstName ? user.firstName : "";
// Получаем фамилию, по умолчанию пустая строка
const lastName = user.lastName ? user.lastName : "";
// Склеиваем имя и фамилию через пробел
const fullName = firstName + " " + lastName;
// Обрезаем пробелы и возвращаем результат
return fullName.trim();
}
Три строки поведения погребены под ~12 строками церемонии. Другие приметы: пустые транзитные конструкторы и обёртки, церемония try { ... } catch (e) { throw e }, x ? x : fallback там, где хватило бы ??, и самописный валидатор email/URL рядом с уже существующим в проекте модулем валидации. Если тот же многословный шаблон ещё и скопирован как getDisplayName, getLabel и getInitials, каждый из которых вручную повторяет логику, уже покрытую util formatName(), многословие превратилось в Дублирование кода — см. Дублирование вместо извлечения.
##Причины проблемы
Почему модели это порождают
- Смещение к многословию при предсказании токенов. LLM обучаются на огромных объёмах учебного, взятого со Stack Overflow и написанного новичками кода, где пошаговые комментарии и явная церемония — норма. Наиболее вероятное продолжение — это самое подробно прокомментированное, а не самое лаконичное.
- RLHF вознаграждает «вид основательности». Тюнинг на полезность/многословие подталкивает модели к выводу, который выглядит полным и самообъясняющим — комментарий на каждой строке, защитные проверки повсюду, — что оценщики и пользователи вознаграждают, даже когда ценности это не добавляет.
- Нет контекста репозитория — нет переиспользования. Без окружающей кодовой базы в контексте модель не может знать, что util
formatName()илиvalidateEmail()уже существует, и заново выводит его логику прямо на месте. Это совпадает с двумя шаблонами из отчёта OX Security: избыточной специализацией (Over-Specification — «гиперспецифичные одноразовые решения, которые нельзя приспособить для других целей», в 80–90% ИИ-кода) и избеганием рефакторинга (Avoidance of Refactors, 80–90%). - Генерация, а не консолидация. Агенты выдают свежий код на каждый промпт и редко возвращаются, чтобы его ужать. Анализ GitClear 2025 года, охвативший 211 млн изменённых строк, показал, что доля строк, связанных с рефакторингом («перемещённых»), упала с 25% изменённых строк в 2021 году до менее чем 10% в 2024-м, тогда как доля скопированных строк выросла с 8,3% до 12,3% и в 2024 году впервые обогнала перемещённые; число блоков из пяти и более дублирующихся строк за 2024 год выросло примерно в восемь раз.
- Дефолт «комментировать всё». OX Security обнаружила «комментарии повсюду» в 90–100% сгенерированного ИИ кода.
Чем это вредит
- Сопровождаемость: больше поверхности для чтения и изменения; избыточные комментарии расходятся с кодом и начинают активно вводить в заблуждение (запах Комментарии у Фаулера).
- Корректность и безопасность: стоит многословному блоку разойтись копиями — и баг или уязвимость придётся исправлять в N местах. Это шаблон OX «дежавю багов» (Bugs Déjà-Vu, 70–80%), из-за которого «одинаковые баги повторяются по всей кодовой базе и одну и ту же правку приходится вносить многократно».
- Нагрузка на ревью: большие малосодержательные диффы прячут реальные изменения и вызывают усталость ревьюера, поэтому настоящие проблемы проскальзывают.
- Нарастающий техдолг: каждый почти-дубликат усложняет следующее извлечение, укореняя проблему, которую модель не видит между файлами.
##Лечение
Тактики ревью / промптинга
- Принудите переиспользование до генерации: «Поищи в кодовой базе существующие хелперы (
formatName,validate*) и переиспользуй их; не реализуй заново». Предоставьте релевантные утилиты в контексте. - Заложите бюджет вывода: «Уложись примерно в 15 строк; никаких комментариев, повторяющих код, — только комментарии, объясняющие почему».
- Заставьте модель запускать инструменты: требуйте прогон
eslint/ruffи проверки дублированияjscpd, и пусть она отчитывается о нарушениях и исправляет их перед возвратом результата. - Просите минимальный дифф и явный шаг дедупликации: «Если какой-то блок повторяется, вынеси одну общую функцию и вызывай её» (Извлечение метода).
- Консолидируйте на ревью: увидев третью почти-копию, примените Извлечение метода, Подъём метода или Объединение дублирующихся фрагментов в условных операторах; там, где комментарий лишь объясняет, что делает выражение, примените Извлечение переменной или Переименование метода, чтобы объяснение нёс сам код, а комментарий можно было удалить.
Рефакторинг (до → после)
// после: комментарии убраны (код самоочевиден), церемония свёрнута,
// и переиспользован существующий util formatName() вместо повторного вывода логики на месте
import { formatName } from "@/utils/format";
export const getFullName = (user?: User): string =>
formatName(user?.firstName, user?.lastName);
И уберите дубликаты на корню — если getDisplayName/getLabel делали то же самое, направьте их вызывающий код на одну функцию и удалите копии (Встраивание метода); всё, что осталось без вызовов, — это Мёртвый код, удалите и его. Замените самописный валидатор импортом существующего модуля, а не держите параллельную копию.
Правило для каталога: любой ИИ-блок, где комментариев больше, чем строк логики, или где можно назвать существующий хелпер, который он проигнорировал, считайте кандидатом на удаление-через-переиспользование — самое быстрое лекарство от шаблонного кода обычно меньше кода, а не больше.
##Обнаружение анализаторами
- ESLint (core) no-useless-catch — Лишние блоки catch (церемония «поймать и пробросить»: catch (e) { throw e })
- ESLint (core) no-unneeded-ternary — Тернарные операторы там, где есть более простая запись (с defaultAssignment: false ловит x ? x : fallback)
- typescript-eslint @typescript-eslint/prefer-nullish-coalescing — ?? вместо || и тернарных операторов с явной проверкой на null
- ESLint (core) no-useless-constructor — Избыточный пустой конструктор / транзитный шаблонный код
- ESLint (core) max-lines-per-function — Раздутая/многословная длина функции (частичный прокси)
- jscpd --min-tokens / --threshold — Обнаружение копирования/вставки, когда многословные блоки начинают копировать (порог --min-tokens / --threshold)
- SonarQube / SonarCloud typescript:S4144 — Функции и методы не должны иметь одинаковую реализацию.
- ESLint (eslint-plugin-sonarjs) sonarjs/no-identical-functions — Функции не должны иметь идентичных реализаций