ConstructiCat Logo
CodeBust.
Browse section ▾

Контекстно-слепое именование.

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

##Signs and Symptoms

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

  • Обобщённые заглушки в доменном коде: data, result, temp, item, obj, payload, response, value, handleStuff, processData там, где модуль уже говорит на конкретном языке (grossPremium, Customer, getCustomerById).
  • Дрейф соглашений: символ в snake_case, помещённый в файл на camelCase, отсутствующий булев префикс is/has или новый CRUD-глагол (fetch*) в кодовой базе, стандартизированной на get*. Каждый дифф "следует тому стилю, который встретил последним, вводя четвёртое соглашение. Затем пятое".
  • Разрастание синонимов / дублирующиеся понятия: ИИ выдумывает fetchUser, когда getCustomerById уже существует, или смешивает customer/client/user для одной сущности — реализуя заново вместо переиспользования.
  • Имена, описывающие механизм, а не намерение — или лгущие о поведении: processData(), который на деле вычисляет налог с продаж. Агенты (и следующий агент) "читают processData() и действуют так, будто это имя рассказывает всю историю", поэтому неверный смысл распространяется на каждое место вызова.
// Репозиторий уже экспортирует getCustomerById(id: CustomerId): Promise<Customer>
// ИИ добавляет почти-дубликат с контекстно-слепыми именами:
async function fetchData(id: string) {          // обобщённый глагол, размытый тип
  const result = await db.query("select * from customers where id = $1", [id]);
  const temp = result.rows[0];                  // 'temp' скрывает, что это Customer
  return temp;                                  // ничто здесь не говорит "Customer"
}

##Reasons for the Problem

Почему модели это порождают

  • Смещение к частотности следующего токена. По всему обучающему корпусу data/result/temp/foo — идентификаторы с наивысшей вероятностью, особенно в учебном и шаблонном коде, который LLM усваивают в больших объёмах. Генерация статистически среднего имени — это ровно то, на что оптимизирован предсказатель следующего токена, и это сглаживает доменный смысл (Towards Data Science).
  • Отсутствие (или усечённый) контекст репозитория. Модель редко видит глоссарий соседнего модуля или существующий getCustomerById. GitClear напрямую связывает с этим всплеск дублирования: ассистент "с меньшей вероятностью предложит переиспользовать похожую функцию из другого места... отчасти из-за ограниченного размера контекста" (GitClear 2025).
  • Локальная оптимизация / слабый инстинкт рефакторинга. Каждый ход оптимизирует непосредственный промпт, "не учитывая накопительное архитектурное влияние". OX Security обнаружила Избегание рефакторингов в 80–90% ИИ-кода, поэтому модель добавляет свежеименованный символ, а не переименовывает или переиспользует существующий (отчёт OX).
  • Устаревание из-за порога обучения. Соглашения и имена API из более старых корпусов всплывают вновь даже после того, как проект ушёл вперёд.

Почему это вредит

  • Читаемость/сопровождаемость: имена — это первичная документация кодовой базы; обобщённые имена вынуждают каждого читателя заново выводить намерение из тела.
  • Дублирование и дефекты: переименование понятия порождает параллельную реализацию. GitClear измерил рост дублированных блоков примерно в 8 раз, а копипаста впервые обогнала перемещённые (отрефакторенные) строки в 2024 году; клоны несут, по оценкам, на 15–50% больше дефектов.
  • Петля обратной связи агента (специфичный для ИИ вред): имена — это интерфейс, который следующий агент воспринимает буквально. Вводящее в заблуждение или обобщённое имя "распространяет ошибки по всему сгенерированному агентом коду, который на нём строится" (AI Pattern Book).
  • Нагрузка на ревью и корректность: ревьюерам приходится мысленно отображать temp/data обратно на доменные понятия, что прячет ошибки; вводящие в заблуждение имена приводят к неправильному использованию в местах вызова.
  • Слепые зоны безопасности/аудита: секрет или токен, припаркованный в переменной с именем data/tmp, проскальзывает мимо grep-ов по именам и внимания ревью.

Примечание: исследования вроде arXiv 2509.20491 показывают, что статические инструменты хорошо ловят локальные, явные запахи, но грань доменного смысла здесь зависит от значения/намерения и в значительной мере ускользает от автоматического обнаружения.

##Treatment

Тактики ревью и промптинга

  • Подайте соглашения и глоссарий в контекст. Держите краткое руководство по именованию (регистр, булевы префиксы, CRUD-глаголы, доменные термины) в CLAUDE.md/документах по стилю и требуйте, чтобы модель ему следовала. Применяйте термины доменного глоссария последовательно, чтобы синонимы схлопнулись к одному слову.
  • Принуждайте к переиспользованию до создания. Промпт: "Найди в репозитории существующую функцию/тип для этого, прежде чем добавлять новую; переиспользуй её". Это прямо противодействует ошибке дублирования понятий, на которую указывают GitClear и OX.
  • Называйте вещи в промпте. "Назови обработчик createRefund" лучше, чем "добавь обработку возвратов". Указывайте доменные имена (monthlyRevenue, а не float1).
  • Требуйте запуска линтера + сканирования дублирования на диффе (naming-convention + id-denylist + jscpd) и пусть модель исправляет нарушения, а не вы вручную.

Рефакторинг — примените Rename Variable/Function (Переименование переменной/функции, у Фаулера "Change Function Declaration"), устраняя запах Mysterious Name (Загадочное имя), и Consolidate Duplicate Code (Консолидация дублирующегося кода), переиспользуя существующий символ вместо нового.

До:

async function fetchData(id: string) {
  const result = await db.query("select * from customers where id = $1", [id]);
  const temp = result.rows[0];
  return temp;
}

После (переиспользуйте существующую функцию репозитория; раскрывающие намерение, соответствующие соглашениям имена и типы):

// Не делай повторный запрос — переиспользуй getCustomerById и сохрани доменный словарь.
async function getCustomerById(id: CustomerId): Promise<Customer | null> {
  const { rows } = await db.query<Customer>(
    "select * from customers where id = $1",
    [id],
  );
  return rows[0] ?? null;
}

Если вводящее в заблуждение имя уже попало в продакшен, переименуйте его в соответствие с поведением (processDatacalculateSalesTax) до того, как строить на нём, чтобы последующие агенты и люди унаследовали верный сигнал.

##Detected by

  • ESLint (core) id-denylistЗапрет указанных идентификаторов
  • ESLint (core) id-lengthОбеспечение минимальной/максимальной длины идентификатора
  • typescript-eslint @typescript-eslint/naming-conventionОбеспечение соглашений об именовании (регистр/префиксы)
  • eslint-plugin-unicorn unicorn/prevent-abbreviationsПредотвращение сокращений / чрезмерно обобщённых имён
  • SonarQube / SonarSource typescript:S117Имена локальных переменных и параметров должны соответствовать соглашению об именовании
  • jscpd copy-paste-detectionОбнаруживает дублированные блоки, возникающие, когда переименованное понятие дублирует существующее