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

Многословный шаблонный код.

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