Разработка ПО

Что находит аудит AI-кода: 6 проблем почти в каждом проекте на AI

18 июня 2026 г.
Что находит аудит AI-кода: 6 проблем почти в каждом проекте на AI

Коротко. Мы делаем много аудитов приложений, собранных в Cursor, Claude Code, Bolt, Lovable и в долгих сессиях с ChatGPT. Кодовые базы разные, а вот находки — почти всегда одни и те же. Шесть проблем всплывают снова и снова: нет тестов (из-за чего каждый деплой — это лотерея), дублирование переменных и функций (одна и та же логика скопирована с небольшими расхождениями), нет единой архитектуры (два экрана в одном проекте написаны как два разных приложения), состояния на стримах избегаются (поэтому асинхронная логика сваливается в callback hell, а сложные формы толком не работают), модель не знает контекст окружения (состояние в памяти и локальные файлы, которые ломаются, как только приложение работает больше чем на одном инстансе) и безопасность нараспашку (.env-файлы в репозитории, токены захардкожены в коде). Ничего экзотического. Всё это предсказуемо — и всё чинится без переписывания с нуля. Это инженерное дополнение к нашему гайду для основателей о выводе AI-прототипа в продакшен; а если хотите передать это нам — именно этим занимается аудит AI-кода.


Почему находки повторяются

AI-инструменты для написания кода оптимизируют одну вещь: кратчайший путь к коду, который запускается. Это правда полезно — идея превращается в рабочее состояние за часы. Но «работает в демо» и «поддерживаемо в продакшене» — разные цели, и разрыв между ними удивительно одинаков от проекта к проекту.

Причина структурная. Модель, генерирующая код, видит узкое окно контекста и не помнит решений, которые приняла три файла назад. Она не может протестировать то, что только что написала, у неё нет представления о вашем бюджете или модели безопасности, и нет стимула удерживать кодовую базу связной во времени. Поэтому она каждый раз делает локально оптимальный выбор — а сумма локально оптимальных выборов даёт код, который работает сегодня и сопротивляется любому изменению завтра.

После достаточного числа аудитов сбои собираются в одни и те же шесть корзин. Вот они — в порядке того, как сильно они бьют.


1. Нет тестов — каждый деплой это лотерея

Самая частая находка: тестов нет вообще. Не тонкий набор, не флакающие тесты — ноль. Приложение проверяли кликами по нему, и это вся страховочная сетка.

Это незаметно ровно до того момента, когда становится катастрофой. Без тестов нельзя узнать, сломало ли изменение что-то ещё, иначе как выкатив его и дождавшись жалобы пользователя. Каждый деплой превращается в ручной регресс, который никто на самом деле не проводит, поэтому рефакторинг становится страшным, обновления зависимостей пропускаются, а кодовая база костенеет — не потому что код хорош, а потому что никто не решается его трогать.

Почему AI так делает. Сгенерировать фичу и сгенерировать тесты к ней — это два разных запроса, и второй никто не сделал. Модель охотно напишет тесты, если попросить, но предоставленная сама себе она выдаёт happy path и останавливается.

Как мы это чиним. Мы не гонимся за 100% покрытием в первый день. Мы добавляем тонкий слой там, где он окупается сильнее всего: smoke-тест, что приложение стартует, тесты вокруг логики с деньгами и авторизацией, и регрессионный тест на каждый баг, который чиним по ходу аудита. Уже это превращает деплой из лотереи в рутину. Дальше покрытие растёт вместе с кодовой базой, а не становится стеной, на которую надо лезть.


2. Дублирование переменных и функций

Откройте AI-кодовую базу и поищите один и тот же хелпер форматирования дат. Часто вы найдёте его три-четыре раза — каждый чуть отличается, потому что каждый сгенерирован изолированно под экран, которому он понадобился. То же с правилами валидации, API-клиентами, денежной арифметикой и константами конфигурации.

Дублирование не просто уродливо; это бомба замедленного действия для корректности. Когда логику нужно изменить — новое правило налога, исправленный баг округления, обновлённый эндпоинт — придётся найти каждую копию. Одну вы пропустите. И вот уже две части приложения расходятся в том, в чём должны были совпадать, и это расхождение — следующий инцидент в продакшене.

Почему AI так делает. Модель редко ищет в существующем коде хелпер, который могла бы переиспользовать. С её точки зрения дешевле перегенерировать функцию инлайн, чем найти и импортировать ту, что уже есть. Каждая генерация локально разумна; в сумме получается дрейф.

Как мы это чиним. Мы находим кластеры почти идентичного кода, выносим единый источник правды и пускаем через него все точки вызова. В большинстве аудитов это уборка с самым большим рычагом: она сжимает кодовую базу, убирает целые классы багов «починили здесь, но не там» и превращает следующее изменение в правку одной строки вместо охоты за сокровищами.


3. Нет единой архитектуры

Эту вещь больно видеть впервые. Два экрана в одном проекте написаны так, будто их делали две разные команды: один тянет данные в компоненте, другой — через сервисный слой; один хранит состояние одним способом, следующий — совершенно иначе; именование, структура папок и обработка ошибок меняются от фичи к фиче. Нет хребта.

Кодовая база без единой архитектуры — это та, где каждый файл, который вы открываете, оказывается сюрпризом. Онбординг разработчика занимает недели, потому что учить нечего — есть только сотня частных случаев, которые надо запомнить. Хуже того: когда паттерны конфликтуют, именно на стыках между ними плодятся баги — данные, полученные двумя разными способами; ошибки, обработанные в одном месте и проглоченные в другом.

Почему AI так делает. У модели нет устойчивой картины «как устроено это приложение». Каждый запрос — чистый старт, поэтому она хватает любой паттерн, подходящий под этот один запрос. За жизнь проекта это даёт лоскутное одеяло — каждый кусок разумен по отдельности, целое бессвязно.

Как мы это чиним. Мы выбираем одну архитектуру, подходящую проекту — не догматичную, а подходящую — и инкрементально сводим к ней кодовую базу. Доступ к данным, состояние, обработка ошибок и именование получают единую согласованную форму. Цель не в чистоте; цель в том, чтобы разработчик, разобравшийся в одной фиче, мог предсказать, как работает следующая.


4. Состояния на стримах избегаются — прямиком в callback hell

Это технически самый интересный сбой и тот, что тихо ломает самые сложные фичи. Код, сгенерированный AI, склонен избегать стримовых и реактивных моделей состояния в пользу императивных колбэков. Вместо того чтобы смоделировать «это значение меняется во времени, и UI на него реагирует», он навешивает колбэк, который дёргает другой колбэк, который ставит флаг, который запускает третий — и получается callback hell.

На простых экранах вы этого почти не замечаете. Но как только состояние действительно сложное — многошаговая форма с кросс-валидацией полей, дашборд с живыми обновлениями, что угодно с дебаунсом, ретраями, отменой или оптимистичными апдейтами — подход на колбэках разваливается. Классический симптом — форма, которая почти работает: она валидирует, но ошибка пропадает не в тот момент; она отправляет, но двойной тап шлёт её дважды; поле обновляется, но зависимое поле отстаёт на одно нажатие. Это не случайные баги. Это неизбежный результат ручной координации сложного асинхронного состояния вместо того, чтобы смоделировать его как стрим.

Почему AI так делает. Императивные колбэки — самый частый паттерн в её обучающих данных и самый простой для генерации по кусочку. Реактивные и стримовые модели требуют держать в голове весь конечный автомат сразу — ровно то, в чём генератор с ограниченным контекстом слабее всего.

Как мы это чиним. Мы выявляем фичи со сложным состоянием и перестраиваем их слой состояния правильно — как стримы или реактивную модель, подходящую стеку, — чтобы UI стал функцией состояния, а не кучей колбэков, гоняющихся друг за другом. Фичи из callback hell, которые «вроде работали», начинают работать полностью и становятся изменяемыми без страха.


5. Модель не знает контекст окружения

Модель пишет код так, будто он будет работать одним процессом на одной машине — потому что изнутри промпта это единственное окружение, которое она видит. Она не знает, сколько инстансов будет запущено, какие управляемые сервисы уже есть, как маршрутизируется трафик и что выполняется параллельно. Поэтому она по умолчанию берёт простейшую топологию — и этот дефолт тихо ломается, как только приложение разворачивают по-настоящему.

Симптомы всегда одни и те же. Состояние, живущее в памяти процесса — кеш, сессии, счётчики rate-limit, захваченный лок — отлично работает на одном инстансе и незаметно расходится, как только за балансировщиком поднимается вторая реплика. Фоновые задачи и шедулеры срабатывают на каждом инстансе вместо одного, поэтому письмо уходит трижды, а ночная чистка выполняется в трёх экземплярах. Загруженные файлы пишутся на локальный диск, который исчезает при следующем деплое и всё равно никогда не был общим между инстансами. Код корректен для одной машины и неверен для кластера, в котором реально работает.

Почему AI так делает. У неё нет картины вашей инфраструктуры. Она не знает, что у вас уже есть Redis, очередь сообщений, объектное хранилище и управляемая база — поэтому переизобретает их в памяти. Она не знает, что у вас три реплики, поэтому считает, что инстанс один. Топология деплоя — это ровно тот контекст, который промпт не может вместить, поэтому модель закрывает пробел простейшим допущением и идёт дальше.

Как мы это чиним. Мы составляем карту реального деплоя — сколько инстансов, какие управляемые сервисы есть, как параллелится работа — и переносим общее состояние туда, где ему место: кеш, сессии и локи в Redis или базу, файлы в объектное хранилище, периодические задачи на нормальный шедулер или очередь с однократным выполнением вместо запуска на каждом ноде. В итоге код масштабируется горизонтально — добавление инстанса делает приложение быстрее, а не портит его состояние.


6. Безопасность нараспашку

Каждый аудит находит одни и те же запахи безопасности, и это ровно те, что стоят реальных денег: .env-файлы, закоммиченные в репозиторий, API-ключи и токены, захардкоженные прямо в коде, учётные данные, вшитые в клиентские бандлы. Один утёкший сторонний ключ может за часы намотать тысячи на несанкционированных списаниях — или отдать атакующему ваше хранилище данных.

Рядом с ключами и токенами — обычные спутники: аутентификация без реальной авторизации за ней (экран логина есть, но эндпоинты никогда не проверяют, кто спрашивает), пользовательский ввод, склеенный прямо в запросы и промпты, и чувствительные данные в открытом виде или в логах. Сторону безопасности мы подробно разобрали для основателей в материале что ломается, когда AI-прототип доходит до продакшена, а как правильно обращаться с данными — в гайде про шифрование при хранении и передаче.

Почему AI так делает. Кратчайший путь к рабочей фиче почти никогда не безопасный. Захардкодить ключ работает сразу; настроить менеджер паролей — нет. Модель выбирает путь, который запускается.

Как мы это чиним. Секреты уезжают из кода в нормальное управление окружением, ключи ротируются (всё, что попало в git, уже скомпрометировано), проверки авторизации ставятся на каждый эндпоинт, которому они нужны, а ввод валидируется и параметризуется. Это часть аудита, по которой компромиссов нет — утёкший ключ или обход авторизации это ЧП, и чинится оно первым.


Паттерн за паттерном

Отступите на шаг — и у шести находок один общий корень: AI оптимизирует каждую генерацию локально, а кодовую базу глобально не оптимизирует никто. Тесты, дедупликация, архитектура, моделирование состояния, учёт окружения развёртывания и безопасность — всё это свойства всей системы. Они не могут возникнуть по одному промпту за раз, потому что ни один промпт не видит целого. Именно этот разрыв закрывает человеческое ревью.

Обнадёживает то, что ничего из этого не значит, будто фундамент на AI потрачен зря. Фичи работают; продукт настоящий. Не хватает соединительной ткани — и добавить её куда быстрее, чем переписывать с нуля.


Частые вопросы

В десятках кодовых баз, написанных AI, почти каждый раз повторяются шесть инженерных проблем: нет автоматических тестов, сильное дублирование переменных и функций, нет единой архитектуры по проекту, сложное асинхронное состояние реализовано как callback hell вместо стримов, код написан без учёта окружения развёртывания, и дыры в безопасности вроде закоммиченных .env-файлов и захардкоженных токенов. Конкретный код от проекта к проекту разный, но эти шесть категорий всплывают снова и снова.

Получите находки по своей кодовой базе

Если у вас приложение на AI и вы узнаёте хотя бы одну из этих шести проблем — вы не отстаёте, вы ровно там, где оказывается почти любая AI-кодовая база. Решение не в переписывании; это сфокусированный аудит, который добавляет соединительную ткань, которую AI не смог.

Прогоните свою кодовую базу через аудит AI-кода или закажите бесплатную оценку — мы скажем, какая из шести проблем у вас самый большой риск, что нужно, чтобы её починить, и дадим фиксированную смету, а не догадку.

Илья Никсан

Илья Никсан

Основатель и ведущий разработчик

Ещё от Илья Никсан