Попалась функция на языке С++. На её примере прям просится показать, что, делая рефакторинг, можно не только эстетично сократить код, но и оптимизировать его. Давайте разомнём мозги, они нам ещё пригодятся, несмотря на эпоху вайб-кодинга. Кто-то ведь должен понимать, как делать надо, а как не надо.
Приведённый ниже код я встретил в вайб-код проекте VibeTensor. Я исследую подобные проекты в качестве натуралиста. Мне интересен генезис новых видов дефектов и недостатков в коде.
Одно из наблюдений – генерированный код более "пухлый", что затрудняет его восприятие человеком и оптимизацию компиляторами. Следующий фрагмент кода как раз это хорошо демонстрирует.
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
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;
}
....
}С одной стороны, размер и скорость этого кода некритичны, так как он относится к тестам.
Однако этот код размножен по 9 файлам. Оставим за скобками, что такого по-хорошему вообще быть не должно. Но раз код размножается почкованием, желательно чтобы он был бы тогда по возможности компактным.
А ещё важно, что генерируется С++ код, и он обязан быть оптимальным. Само назначение языка – высокоэффективные приложения. В рассматриваемом месте – это просто медленный тест. Но в другом месте что-то подобное приведёт к существенному замедлению приложения. Или скажется кумулятивный эффект множества неудачных фрагментов сгенерированного кода.
Медленный С++ код – это противоестественно. Поэтому по-прежнему полезно развивать свою экспертность в понимании, удачным ли получился код и как его можно улучшить, сократить, оптимизировать. Или сгенерировать снова, используя уточнения.
В общем давайте потренируемся и проведём рефакторинг. Начнём с этого фрагмента:
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;
}
Здесь перемножаются все элементы массива. Если встретится 0, то цикл прервётся, чтобы зря не обрабатывать оставшиеся элементы массива. Всё равно ведь ноль получится.
Чтобы обнулить переменную, где хранится произведение, используется флаг
any_zero. Этот подход избыточен, можно проще.int64_t ne = 1;
for (auto s : sizes) {
if (s == 0) {
ne = 0;
break;
}
ne *= s;
}
Когда встретится 0, обнулятся
ne и цикл завершится. Можно продолжить упрощение. Современные процессоры быстро выполняют операции умножения. Поэтому можно в начале перемножить, а потом уже проверить.int64_t ne = 1;
for (auto s : sizes) {
ne *= s;
if (s == 0) {
break;
}
}
Функциональность кода не изменилась, но он стал короче и, на мой взгляд, даже понятнее.