
Думаю, вам начинают надоедать тексты про вайб-кодинг. Но не волнуйтесь, мой интерес не в том, чтобы рассказывать о новых невероятных достижениях, меняющих мир, и бла-бла-бла... Интереснее поискать места, в которых начинается сбой при генерации кода. Это позволит адаптировать работу статических анализаторов для новых задач контроля кода, который создаётся с помощью таких систем.
Опыты с генерацией кода
Я немного экспериментировал с генерацией кода с помощью GigaChat и DeepSeek. Это не рабочие задачи и не полноценное исследование. Мне просто было интересно найти примеры пересечения некой грани сложности при решении задачи, после которой начинаются проблемы с генерацией C++ кода.
Если просить сгенерировать код уровня лабораторных и даже, наверное, некоторых курсовых работ, то проблем нет. Великолепно получается код сортировки массива или код подсчёта количества единичных бит в массиве байт. Причём для подсчёта единичных бит предлагаются такие интересные оптимизированные по скорости решения, про которые я даже не знал, хотя и читал давно в книге про различные подходы к этой задаче.
Кстати, здесь вырисовывается потенциальная проблема при обучении программированию: слишком велик соблазн сразу получить ответ, так и не разобравшись, как всё работает. Видимо, придётся обратно возвращаться к написанию кода на бумаге :) Но сейчас не будем углубляться в эту тему.
Для одного из экспериментов я выбрал создание программы, которая будет читать файлы с исходным кодом и выполнять ряд операций над названиями переменных или их составными частями. Одна из задач — это получить из имени переменной "RedColor" три токена: "RedColor", "Red" и "Color".
Думаю, задача разбития на токены часто встречается в программировании, и существует много открытого кода на эту тему. Поэтому в такой постановке GigaChat и DeepSeek с этой задачей справляются хорошо.
Однако мир сложнее, и нужно работать не просто со стандартной ANSI кодировкой, но и, например, с UTF-8. Поэтому я начал просить писать код, обрабатывающий файлы в том числе в формате UTF-8. И в этот момент генераторы кода поплыли.
Полагаю, количество открытого кода, учитывающего кодировки, в том числе и UTF-8, в несколько раз меньше. Дополнительно мои формулировки задания стали сложнее и от этого расплывчатее. Кстати, за пару подходов у меня не получилось добиться работающего кода. Думаю, это можно сделать, проведя ещё несколько генераций с уточнениями задания. Но у меня нет цели написать конечную программу и мне интересно экспериментировать именно со сбоями :)
Баги
Итак, приведённый ниже код сделал для меня GigaChat. Интересно, кстати, что пока он схалтурил: решил не делать настоящую конвертацию кода. Но при этом сам момент, что надо обрабатывать UTF-8, уже приводит к возникновению некорректного C++ кода.
....
// Вспомогательная функция для преобразования
// char-вектора в utf-8 строку
std::string to_utf8(const std::vector<char>& data)
{
// Для простоты считаем, что вектор представлен в UTF-8 изначально
return { data.begin(), data.end() };
}
....
// Главная функция для разделения строки на токены
std::vector<std::string_view>
tokenize_string_giga(const std::vector<char>& input_data)
{
std::string text = to_utf8(input_data);
std::vector<std::string_view> result;
size_t pos = 0;
while (pos < text.size())
{
if (is_letter(text[pos]) || is_digit(text[pos]))
{
size_t begin_pos = pos++;
// ищем конец текущего набора алфавитно-цифровых символов
while (pos < text.size() &&
(is_letter(text[pos]) || is_digit(text[pos])))
++pos;
// Извлекаем полное слово
std::string_view current_word =
text.substr(begin_pos, pos - begin_pos);
// Дробим слово на отдельные токены
split_word_into_tokens(current_word, result);
}
else
{
++pos; // пропускаем неалфавитно-цифровые символы
}
}
return result;
}
Ошибка связана со временем жизни объекта. Обратите внимание на фрагмент, где извлекаются слова:
std::string_view current_word = text.substr(begin_pos, pos - begin_pos);
Функция substr
создаёт временный объект типа std::string
, который затем разрушается. В результате в current_word
сохраняются указатели на уже разрушенный объект.
Естественно, я попробовал запустить PVS-Studio. Он выявляет эту ошибку следующим предупреждением: V1017 [CWE-416] Variable of the 'string_view' type references a temporary object which will be removed after evaluation of an expression. Tokenizer_giga.cpp 79
Что интересно, приблизительно на том же споткнулся и DeepSeek. Он сгенерировал больше кода, так как не поленился повозиться с UTF-8, поэтому приведу здесь сразу выжимку:
std::vector<std::string_view>
tokenize_string(const std::vector<char>& buffer)
{
std::vector<std::string_view> tokens;
....
std::string utf8_text = detect_and_convert_to_utf8(buffer);
....
std::string_view token(utf8_text.data() + token_start, pos - token_start);
....
tokens.push_back(token);
....
return tokens;
}
В функции создаётся локальная переменная utf8_text
типа std::string
. Её разбивают на токены и записывают указатели на них в выходной массив tokens
. Естественно, после выхода из функции переменная utf8_text
уничтожается и ссылки становятся невалидными. К сожалению, здесь PVS-Studio пока помочь не смог: у него не получилось сопоставить время жизни utf8_text
и tokens
.
Осмысление
Было интересно наблюдать, как повышение сложности задачи привело к сбою в сгенерированном коде.
Наверное, нет одной причины, почему так получается, и влияние следующих моментов суммируется:
Более обобщённая и, следовательно, менее чёткая постановка задачи.
В мире меньше кода, который "умеет в UTF-8". Соответственно, система хуже обучена. Из этого следует, что чем более нетиповая задача решается, тем хуже будет результат.
Класс
std::string_view
относительно молодой (C++17), и ещё создан не такой огромный объём кода с его участием, как, например, сstd::string
.Возможно, концепция времени жизни объектов сложна для обучения.
Пара слов про статический анализ. Наверное, может поменяться стиль использования инструментов для проверки кода. Здесь нет смысла править руками ошибку — проще заново сгенерировать весь код функции, уточнив задание. Анализатор поможет человеку быстрее понять, почему код не работает как ожидалось. И уже на основании этих знаний человек может указать генератору, что он сделал не так. В общем, поможет уточнить или переформулировать задачу. Или разбить её на несколько подзадач.
В противном случае остаётся или генерировать код, меняя формулировки, пока он не заработает, или самому делать обзор кода. Оба сценария так себе. Конечно, статический анализ не панацея, но если он поможет быстрее исправить ошибку, то замечательно. Собственно, так всегда и было :) В этом его суть.
P.S. Как и ранее, приглашаю в комментариях поделится подобными случаями.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Stumbling block for AI: UTF-8.