Есть такая старая программистская байка, что нельзя платить программистам за строки кода, так как тогда они будут писать длинный бестолковый код и любить метод copy-paste. Будущее наступило. Только теперь этими “программистами” является генеративный ИИ (GenAI), которому как раз платят за строки кода. Иронично.

Один из моих интересов — изучение сгенерированного С++ кода, чтобы понимать, как развивается индустрия создания ПО, какие проблемы уходят, а какие наоборот возникают. После заметки “Дайте посмотреть на нормальный С++ проект, созданный вайб-кодингом” мне предложили заглянуть в проект VibeTensor, что я и сделал.
VibeTensor: System Software for Deep Learning, Fully Generated by AI Agents
Я проверил его с помощью статического анализатора PVS-Studio, а также посмотрел С++ код глазами. Было интересно узнать, как много ошибок в нём можно найти с помощью классического обзора кода и статического анализа.
Так вот, у меня нет ответа на этот вопрос. Непонятно, потому что главная проблема этого кода в том, что он ужасно раздут. Это сильно мешает его обзору. Мне тяжело продираться сквозь это болото, а вместе со мной “вязнет” и статический анализатор.
Впрочем, ожидать большого количества ошибок здесь тоже не стоит: по-настоящему полезного кода в этом проекте кот наплакал.Как же так? Проект вроде не такой уж маленький. Проанализированных C++ файлов более 400, а количество строк кода — около 100,000.
Да, но это только кажется, что там есть что смотреть и анализировать. Повторюсь: основная проблема качества этого кода — его избыточность. Она выражена как в постоянном повторении блоков кода, так и просто в бессмысленных лишних действиях, растягивающих код.
Раньше бы сказали, что этот проект писался методом copy-paste. В данном случае это не так, но генерация кода приводит ровно к таким же последствиям. Вместо выноса обобщённой функциональности в функции, вновь и вновь генерируется код для решения схожим проблем.
Можете полистать файлы, и через некоторое время вас начнёт преследовать дежавю, что вы вновь и вновь видите одни и те же блоки кода. Они вроде как и разные, а вроде как и нет. Вот что я имею в виду:

Например, я уже писал в статье “C++: Пиши, сокращай, оптимизируй”, что этот блок кода можно встретить 9 раз в разных тестах:
const std::size_t nd = sizes.size(); std::vector<int64_t> strides(nd, 0); int64_t acc = 1; for (std::ptrdiff_t i = static_cast<std::ptrdiff_t>(nd) - 1; i >= 0; --i) { strides[static_cast<std::size_t>(i)] = acc; const auto sz = sizes[static_cast<std::size_t>(i)]; acc *= (sz == 0 ? 1 : sz); } int64_t ne = 1; bool any_zero = false; for (auto s : sizes) { if (s == 0) { any_zero = true; break; } ne *= s; } if (any_zero) { ne = 0; }
Однако это ещё не всё. PVS-Studio сыплет предупреждениями про постоянную избыточность. Иногда это касается мелочей:
for (int i = 0; i < dl.ndim; ++i) { int64_t n = (dl.ndim == 0) ? 1 : dl.shape[i]; int64_t d = n > 0 ? (n - 1) : 0; if (d == 0) continue; int64_t st = (dl.ndim == 0) ? 1 : strides[static_cast<std::size_t>(i)];
PVS-Studio дважды выдаёт V547 Expression ‘dl.ndim == 0’ is always false. Действительно, если цикл выполняется, то dl.ndim не может быть равен нулю. Код упрощается до:
for (int i = 0; i < dl.ndim; ++i) { int64_t d = std::max(0ll, dl.shape[i] - 1); if (d == 0) continue; int64_t st = strides[i];
В других местах пухлость кода мелочью уже не назовёшь. Там PVS-Studio выдаёт сразу группы предупреждений:
V547 [CWE-570] Expression ‘is_empty’ is always false. tensor_bindings.cc 3007
V547 [CWE-570] Expression ‘print_size’ is always false. tensor_bindings.cc 3015
V547 [CWE-571] Expression ‘!parts.empty()’ is always true. tensor_bindings.cc 3023
Код, на который выданы предупреждения, на первый взгляд умный, с массивом, с циклом... А если присмотреться — лабуда.
bool is_empty = false; // handled above; always false here bool print_size = is_empty && (self.sizes().size() != 1); bool suppress_dtype_non_empty = (!is_empty) && (self.dtype() == ScalarType::Float32 || self.dtype() == ScalarType::Int64 || self.dtype() == ScalarType::Bool); bool print_dtype = !suppress_dtype_non_empty; if (is_empty) { // For empty tensors, only print dtype when dtype != default float32 print_dtype = (self.dtype() != ScalarType::Float32); } std::string out = "tensor("; out += body; std::vector<std::string> parts; if (print_size) { parts.push_back(std::string("size=") + format_sizes(self.sizes())); } if (print_dtype) { parts.push_back(std::string("dtype=") + dtype_name(self.dtype())); } // Always include device suffix for CUDA tensors parts.push_back(std::string("device='cuda:") + std::to_string((int)self.device().index) + "'"); if (!parts.empty()) { out += ", "; for (std::size_t i = 0; i < parts.size(); ++i) { if (i) out += ", "; out += parts[i]; } } out += ")"; return out;
Как минимум, ручной цикл формирования сообщения можно сразу заменить на:
return std::format("tensor({})", parts | std::views::join_with(", "sv));
Если присмотреться получше, то вообще всю эту избыточную фиговину можно сократить в три раза:
std::string out = "tensor(" + body + ", "; if (self.dtype() != ScalarType::Float32 && self.dtype() != ScalarType::Int64 && self.dtype() != ScalarType::Bool) { out += std::string("dtype=") + dtype_name(self.dtype()) + ", "; } out += "device='cuda:" + std::to_string((int)self.device().index) + "')"; return out;
Вот что здесь интересно: если рассуждать об анализе исходного варианта, то вроде как ошибок не находится. Код сложный, правильно работает, выхода за границу массива нет. Почтение к ИИ.
Когда же код схлопывается до своей сути, то понимаешь, что там и ошибаться то негде. Он просто написан длиннее. Не к чему тут относиться с почтением.
Итого: нет в проекте никаких настоящих 100,000 строк С++ кода. Думаю, что если вынести дубликаты в функции и провести рефакторинг, количество кода сократится раз в 5. Проект на 20,000 строк кода — это баловство. Вот и вижу в нём не ошибки, а проблему раздутого кода и предупреждения анализатора про большое количество ложных/истинных условий и т.п.
Ну получается код длиннее, и что? Он и не предназначен для рефакторинга человеком. Если надо — новый сгенерируем.
Если хочется продать GenAI, а на судьбу проекта всё равно, то ради бога. Если же вам нужен проект, то “плата за строки кода” куда выше, чем кажется.
Следствия раздутого кода:
Больше строк кода — больше плата за их генерацию.
Если Pull Requests ревьювит другой ИИ, то и ему больше плати.
Раздутые функции — дороже генерация юнит-тестов.
Любая модификация кода с помощью ИИ дороже, так как требуется больше строк кода принять и отдать.
Много контекста — больше вероятность ошибок при внесении изменений (например, можно просто что-то не исправить в одном из 100500 похожих мест).
Если человеку самому придётся править код или искать баг — у него вытекут глаза. Очень тяжело продираться сквозь нагромождение избыточных сущностей и конструкций.
Когда код сложнее, чем нужно, компилятор будет хуже его оптимизировать.
Проще сгенерировать ещё одну функцию, похожую на другие, чем найти и переработать уже существующие десятки однотипных функций.
Лишние конструкции мешают не только человеку, но и статическому анализатору искать ошибки.
Код медленнее компилируется.
Излишнее “словоблудие” увеличивает вероятность столкнуться с неопределённым поведением, или что код будет работать не так, как задумывалось.
Можете сами продолжить список.
Под пунктом №11 я имел в виду, что если не понимаешь смысл слов, то не надо их использовать для красоты. Использованный GenAI не знает суть noexcept, но считает, что с ним “красивее”. Результат — множество мест в коде, где кидается исключение там, где его быть не должно:
vt_status vt_tensor_iter_binary_cpu_host(const vt_iter_config* cfg, vt_tensor out_h, vt_tensor a_h, vt_tensor b_h, vt_tensor_iter_loop1d_fn loop, void* user_ctx) noexcept { .... if (effective.check_mem_overlap != VT_ITER_OVERLAP_DISABLE && effective.check_mem_overlap != VT_ITER_OVERLAP_ENABLE) { throw std::invalid_argument( "vt_tensor_iter_binary_cpu: invalid vt_iter_overlap_mode"); } .... }
При этом проблема разбухшего кода — это ваша проблема, а не продавцов ИИ. Вам платить за токены.
Что можно сделать? Готовых решений предложить не могу. Но, по крайней мере, предупреждён — значит вооружён.
Я же всё больше склоняюсь к мнению, что нужно развить в статическом анализаторе PVS-Studio направление по выявлению схожих фрагментов кода. Тогда можно будет замкнуть GenAI и PVS-Studio в петлю обратной связи. Тогда код будет считается доделанным, если анализатор не только молчит про баги, но и нет попыток дублирования функциональности.
Пока это ещё не роадмап развития PVS-Studio, но уже вырисовывается картина новых бед, и как инструмент сможет помочь с ними справиться.
Дополнительные ссылки:
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Bloated C++ code.
Комментарии (12)

rukhi7
19.08.2026 18:11Следствия раздутого кода:
только я уже наверно больше 20 лет не видел НЕ-раздутого кода :(, какая разница кто или что его пишет? Чем больше такого кода тем ближе перезагрузка матрицы, тем быстрее появится необходимость перевести агентов в какое-то новое качество, что-то полезное из них сделать.

Chemist_modeler
19.08.2026 18:11Имхо, чтобы писать НЕ раздутый код, нужны, как минимум, две вещи: 1. Точная спецификация заранее, 2. Достаточный резерв времени. (Не говорю здесь о 3. квалификация кодера.) А вы когда-нибудь видели (1) + (2) вместе в реальном проекте?

gybson_63
19.08.2026 18:11Времена новые - правила старые. Лучше сделать и рефакторить, чем не сделать.

ruomserg
19.08.2026 18:11На самом деле, это вот дублирование кода - единственное что позволяет LLM моделям достигать успеха! Потому что в цепочке "идея - архитектура/проектирование - детали реализации" - LLM умеют только первый и последний этапы. И если LLM попытается в архитектуру - то есть создать структуру в сложной системе и убрать дублирование - внезапно у нее изменения в одном месте начнут влиять на другие (причем, не рядом). И дальше начнется игра "одно - лечим, другое - калечим". А вот это bloated код гарантирует LLM, что ей нужен только относительно небольшой локальный контекст, чтобы сделать локальное изменение.
Но в целом - да, автономная разработка с текущими LLM упирается в одну из двух вещей:
Если пытаться в архитектуру - то в отсутствие здравого смысла и неумение удерживать строгую и правильную структуру проекта (и дальше в невозможность изменить одно чтобы не сломать другое)
Если не пытаться - то в размеры контекстного окна из-за распухания кода в разы, если не на порядки
cher11
Готовые решения есть. Как на уровне agents.md и скилов (явно указывать писать простой код в лоб), так и на уровне промежуточного ревью (ничего не мешает отметить, что такие-то новые участки нужно упростить).
На уровне общей архитектуры проекта и тестов тоже - если LLM видит, что, образно, фичи лежат там, вспомогательные файлы там, общий UI - там, то и косяков становится практически ноль - нужные методы дописывает куда надо. Ну и если вдруг тесты ломает, вполне себе чинит.
Andrey2008 Автор
Почему ими не пользуются? В чём неудобство/сложность/затратность?
artden111
Это из разряда "можно, но зачем?"
cher11
А кто сказал, что не пользуются?
Вы взяли проект, у которого в ридми явно написано «This repository is released for agentic system research purposes only.
Everything in this repo is generated by AI agents. Do not use this project for production». То есть он не заявлялся ни как пример хорошей архитектуры, ни как образец кода без ошибок. Он заброшен уже полгода как.
Вполне возможно, что он сгенерирован парой промптов и в принципе не ревьювился.
Удивительно было бы не найти в нем косяки. Только ценность исследования факты выше здорово снижают
Andrey2008 Автор
И тут мы возвращаемся к "Дайте посмотреть на нормальный С++ проект, созданный вайб-кодингом" :)
cher11
Возможно, таких проектов и нет практически. Нужно, чтобы сошлись сразу три звезды - и открытость проекта, и высокие требования к скорости его работы, и какая-никакая новизна.
Если делать для себя движок игры или торгового бота - зачем открывать код? Если делать какую-то утилиту без строгих требований к скорости - зачем делать её на плюсах? Ну а существующие проекты, понятно, уже существуют.
Посмотрите ради интереса на тот же Zed. Вот его очень активно пилят нейронками. Если бы не существовало Rust, наверное, он мог бы быть на C++ :)
dugalb
Эти проекты есть. Их меньше, чем генераций *.ts или python проектов, но найти можно.
slonopotamus
https://github.com/ufna/VaCuus