Как вычистить следы нейросетей в коде: реальные маркеры генерации на код-ревью
Во вторник полтора часа ковырял логи биллинга на клиентском проекте: очередь чеков встала колом. Полез в гит смотреть последний мерж. Ну так и есть, наш мидл прикрутил выгрузку транзакций, которую тупо вытащил из чата с ботом. С виду прилично все, даже пайплайн прошел. Только внутри перебора на две тысячи записей дергался обычный курл вообще без таймаута соединения; и на каждую итерацию создавался новый инстанс логгера в память. А чтобы вся эта конструкция не валилась при отвале банка, сетка заботливо укутала вызов в глухой 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 на фронте. Автоформаттер моментально сбивает специфическую нейросетевую разбивку строк, сортирует импорты по нашим правилам и приводит код к общему знаменателю. В итоге бот делает всю черную рутину со скелетом, но в гит уходит нормальный сухой код, за который на ревью не прилетит.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.