Вебинар: Go-Go-Gadg...Error? Смотрим, как ошибаются Go разработчики! - 26.08
Есть такая старая программистская байка, что нельзя платить программистам за строки кода, так как тогда они будут писать длинный бестолковый код и любить метод 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 выдаёт сразу группы предупреждений:
Код, на который выданы предупреждения, на первый взгляд умный, с массивом, с циклом... А если присмотреться — лабуда.
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, а на судьбу проекта всё равно, то ради бога. Если же вам нужен проект, то "плата за строки кода" куда выше, чем кажется.
Следствия раздутого кода:
Под пунктом №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, но уже вырисовывается картина новых бед, и как инструмент сможет помочь с ними справиться.
Дополнительные ссылки:
0