Как вычистить следы нейросетей в коде: реальные маркеры генерации на код-ревью

mr. Cooper • 51 минуту назад • Нейросети и AI
Как вычистить следы нейросетей в коде: реальные маркеры генерации на код-ревью

Во вторник полтора часа ковырял логи биллинга на клиентском проекте: очередь чеков встала колом. Полез в гит смотреть последний мерж. Ну так и есть, наш мидл прикрутил выгрузку транзакций, которую тупо вытащил из чата с ботом. С виду прилично все, даже пайплайн прошел. Только внутри перебора на две тысячи записей дергался обычный курл вообще без таймаута соединения; и на каждую итерацию создавался новый инстанс логгера в память. А чтобы вся эта конструкция не валилась при отвале банка, сетка заботливо укутала вызов в глухой try-catch. Поймала таймаут - вернула пустой массив. И дальше пошла. В логах чистота, в Сентри тишина, а воркеры висят мертвыми процессами на сервере.

Честно говоря, в начале осени я сам примерно так же наступил на грабли, когда только поставил себе локальную Олламу в связке с курсором. Вечером башка уже не варит, надо было быстро распарсить кривой ответ от эквайринга. Кинул промпт - через секунду готовая функция строк на сорок. Пробежался по диагонали: типы на месте, переменные вроде названы по делу, запушил в дев. А через три дня у нас посыпались отчеты у бухгалтерии. Оказалось, моделька вместо нашего проектного Карбона затащила в расчеты базовый пхпшный DateTime без учета часового пояса площадки, да еще и руками воткнула запись текущего времени в поле updated_at. И молча перетерла логику наших обсерверов в Ларавел. С тех пор я чужую копипасту на ревью выкупаю сразу, даже не вникая в суть задачи. Открываешь дифф: если над элементарным поиском по первичному ключу висит комментарий на русском языке, а строчкой ниже объявлена переменная data или какой-нибудь tempResult - к гадалке не ходи, человек просто нажал Ctrl+V.

//php

public function syncUserBalance(int $userId, string $apiUrl): array
{
    // Получаем пользователя из базы данных по его ID
    $user = User::find($userId);

    // Проверяем, существует ли пользователь
    if (!$user) {
        return ['status' => 'error', 'message' => 'User not found'];
    }

    // Инициализируем cURL сессию для отправки запроса
    $ch = curl_init($apiUrl . '?user_id=' . $userId);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    // TODO: Добавить валидацию при необходимости
    if (isset($data['balance'])) {
        $user->balance = $data['balance'];
        $user->updated_at = date('Y-m-d H:i:s');
        $user->save();
    }

    return ['status' => 'success', 'data' => $data];
}

У нас в базе нет и не было никакой абстрактной «даты» или «ответа». Есть вполне конкретные платежные поручения, чанки, копейки баланса. И проверять модель пользователя на пустоту внутри сервисного слоя у нас не принято - она туда уже проверенная прилетает через привязку маршрута. Поэтому я такой сгенерированный код сразу режу впятеро, выкидывая все проверки и нативные велосипеды.

//php

public function syncBalance(User $user, BillingClient $client): BalanceDto
{
    $payload = $client->fetchAccountState($user->uuid);

    $user->update([
        'balance_cents' => $payload->availableAmount,
    ]);

    return BalanceDto::fromModel($user);
}

На фронтенде эта нейросетевая перестраховка вылезает еще сильнее. Переписывал тут на днях старый калькулятор скидок в корзине на Тайпскрипте. Дал боту интерфейс позиции заказа, попросил посчитать итоговую сумму с учетом купона. Мне вывалилась простыня сороковой длины. Зачем-то три проверки подряд через typeof, потом проверка на массив, следом проверка на не-число; и вишенкой опять локальный перехват ошибок вокруг элементарного умножения цены на количество. То есть у меня на входе стоит строгая схема валидации, компилятор следит за типами, но внутри обычной математической функции сидит вахтер и проверяет каждое поле по новой. Выкинул этот мусор, убрал цикл со счетчиком, оставил обычный редьюс - получилось четыре строчки.

//TypeScript

// Что генерирует модель (кусок с лишними проверками и try-catch):
function calculateCartTotal(items: OrderItem[], discountPercent: number): number {
    if (!items || !Array.isArray(items) || items.length === 0) {
        console.warn('Items array is empty or invalid');
        return 0;
    }
    try {
        let totalResult = 0;
        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            if (item && typeof item.price === 'number') {
                totalResult += item.price * item.quantity;
            }
        }
        return Number((totalResult * (1 - discountPercent / 100)).toFixed(2));
    } catch (error) {
        console.error('Error:', error);
        return 0;
    }
}

// Что остается после чистки в продакшен:
export const calcTotalWithDiscount = (items: OrderItem[], discount: number): number => {
    const rawTotal = items.reduce((acc, { price, quantity }) => acc + price * quantity, 0);
    const factor = Math.max(0, Math.min(100, discount)) / 100;

    return Math.round(rawTotal * (1 - factor) * 100) / 100;
};

С версткой ровно та же беда. В голове у любой большой модели перемешана документация за последние лет шесть. Просишь собрать выпадашку на Альпайне и четвертом Тейлвинде - получаешь франкенштейна. Половина классов из старой тройки с раздельными шириной и высотой вместо короткого size, прозрачность фона задана через устаревший модификатор вместо косой черты, а внутри реактивного обработчика клика внезапно торчит прямой поиск элемента по айдишнику из ванильного джаваскрипта. Пока руками не заменишь инлайновый стиль скрытия на штатную директиву x-cloak и не вычистишь обращения к документу, в проект такое заливать стыдно.

//HTML

<!-- Сырой гибрид из старых классов и прямого доступа к DOM -->
<div x-data="{ isOpen: false }" class="bg-gray-900 bg-opacity-80 rounded-lg p-4">
    <button @click="isOpen = !isOpen; document.getElementById('icon').classList.toggle('rotate-180')" class="flex justify-between w-full text-white">
        <span>Настройки профиля</span>
        <svg id="icon" class="w-5 h-5 transition-transform" viewBox="0 0 20 20"></svg>
    </button>
    <div x-show="isOpen" style="display: none;" class="mt-4 text-gray-300">Контент...</div>
</div>

<!-- Чистый компонент под актуальный стек -->
<div x-data="{ open: false }" class="bg-slate-900/80 rounded-xl p-4">
    <button @click="open = !open" type="button" class="flex w-full items-center justify-between font-medium text-white">
        <span>Настройки профиля</span>
        <svg :class="open && 'rotate-180'" class="size-5 transition-transform" viewBox="0 0 20 20"></svg>
    </button>
    <div x-show="open" x-cloak class="mt-3 text-slate-300">Контент...</div>
</div>

Подтирать за роботом каждую вторую строчку мне надоело быстро. Выход оказался простой: вообще никогда не просить модель писать код в пустом контексте. Сейчас, если мне нужен новый класс, я принудительно скармливаю агенту два соседних файла через символ собаки. Прямо тыкаю его носом в готовый сервис оплаты и требую сделать возвраты строго по тем же лекалам, с теми же объектами переноса данных и нашей кастомной оберткой над клиентом. Плюс в корень каждого рабочего репозитория положил текстовый файлик с правилами для редактора. Там жесткий бан на любые поясняющие комментарии, полный запрет на локальный перехват исключений внутри бизнес-логики (пусть все летит в общий обработчик фреймворка) и черный список для имен переменных вроде item, val или arr.

//Markdown

- Stack: PHP 8.3, Laravel 13, strict_types=1.
- NO COMMENTS: Никаких комментариев над кодом и заглушек TODO.
- NO TRY-CATCH: Запрещено глушить ошибки локально в сервисах, все исключения летят в глобальный Handler.
- NAMING: Бан на имена $data, $result, $item, $temp, $response. Только словарь домена.
- FRONTEND: Только Tailwind v4 (size-* вместо w-* h-*, прозрачность через слэш). Без проверок typeof внутри типизированных TS-функций.

Ну и перед самым коммитом обязательно прогоняю измененные файлы через Pint на бэке и ESLint на фронте. Автоформаттер моментально сбивает специфическую нейросетевую разбивку строк, сортирует импорты по нашим правилам и приводит код к общему знаменателю. В итоге бот делает всю черную рутину со скелетом, но в гит уходит нормальный сухой код, за который на ревью не прилетит.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи