Контекстно-слепое именование.
ИИ-ассистент даёт новому коду обобщённые заглушки-имена или новое соглашение, игнорируя существующие идентификаторы и доменный словарь репозитория, что подрывает читаемость и плодит дублирующиеся, неверно описанные понятия.
##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;
}
Если вводящее в заблуждение имя уже попало в продакшен, переименуйте его в соответствие с поведением (processData → calculateSalesTax) до того, как строить на нём, чтобы последующие агенты и люди унаследовали верный сигнал.
##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 — Обнаруживает дублированные блоки, возникающие, когда переименованное понятие дублирует существующее