30 дней ИИ-разработки без рук: пять архитектурных ловушек в настоящем SaaS
Месяц, когда весь код production-микро-SaaS писал ИИ, вскрыл дрейф архитектуры, неправильную обработку вебхуков, бесконечную отладку, раздувание и проблему джунов.
Конец 2026 года принёс привычные крики: программная инженерия мертва, весь код теперь пишет ИИ, обычный язык — единственный синтаксис, который нужен. Чтобы понять, где заканчивается хайп и начинается продакшен, 30-дневный эксперимент ввёл одно строгое правило: ни строчки кода приложения вручную. ИИ здесь не был помощником при наборе или автодополнением. Человек выступал архитектурным надзирателем и ревьюером: задавал схемы, писал требования, читал диффы и одобрял изменения, а модель писала логику, запросы и интеграции.
Проект не был списком дел. Это настоящий микро-SaaS: вход, миграции, пользовательские дашборды, вебхуки Stripe и публичные API с ограничением частоты запросов. Он работает, развёрнут и обслуживает живой трафик. Первые 40% разработки ощущались как волшебство. Потом репозиторий перевалил за 15 000 строк, появилось распределённое состояние, и идея «ИИ пишет всё» упёрлась в жёсткие инженерные пределы. Было зафиксировано семь типов сбоев. Ниже пять из них и уроки, которые они преподают.
Первые дни действительно быстрые
Сначала по-честному: поднять каркас нового проекта с нуля — задача практически решённая.
- Современный full-stack на Next.js, Tailwind, PostgreSQL, Drizzle ORM с middleware авторизации и конфигами Docker занял полдня.
- Однообразные схемы Zod, хелперы валидации, сортируемые таблицы и пагинация появлялись без усилий, а предсказуемые CRUD-эндпоинты выходили чистыми, без единой опечатки.
- Когда у одного отдельного запроса оказался плохой план выполнения, модель быстро заметила внешний ключ без индекса и предложила точную миграцию с индексом.
Шесть дней казалось, что традиционная разработка, возможно, устарела. Потом началась вторая неделя.
Ловушка 1: локальный фокус, глобальный дрейф
Самая коварная слабость — локальная близорукость. Модель с поразительной точностью оценивает файл или функцию в своём контекстном окне, но не видит соглашений, заданных тремя каталогами дальше.
Architectural Drift Anti-Pattern
┌─────────────────────────────────────────────────────────────┐
│ Core Utilities (/src/lib/utils/format.ts): │
│ └── export const formatCurrency = (cents: number) => ... │
└─────────────────────────────────────────────────────────────┘
▲
(The AI ignores this file)
│
┌─────────────────────────────────────────────────────────────┐
│ New Billing Module (/src/modules/billing/invoice.ts): │
│ └── export const formatPriceUSD = (amount: number) => ... │
│ (Re-implements logic with different rounding edge cases)│
└─────────────────────────────────────────────────────────────┘
За две недели агент написал три немного разных хелпера форматирования валюты в отдельных модулях, заново реализовал встроенную проверку сессии внутри нового обработчика маршрута вместо существующего конвейера middleware и объявил два конфликтующих интерфейса UserProfile в разных пакетах, которые совпадали на 90% полей, но по-разному сериализовали даты.
Ничто из этого не вызвало ошибки компилятора, и тесты оставались зелёными. Но в кодовой базе копился тихий архитектурный дрейф — та структурная гниль, которая примерно за полгода может превратить гибкий проект в неподдерживаемое наследие.
Ловушка 2: правдоподобный, но неверный код
Самые опасные баги в коде от ИИ — не падения и не синтаксические ошибки: первые ловит компилятор, вторые линтер. Страшны те, что хорошо читаются, гладко проходят локальные тесты и тихо ломают транзакционные гарантии. Вот реальный шаблон, который ИИ выдал для вебхука оплаты Stripe:
// The AI's Initial Webhook Handler (Dangerous Anti-Pattern)
export async function handleStripeWebhook(req: Request) {
const event = await parseStripeEvent(req);
if (event.type === 'checkout.session.completed') {
// 1. Immediately acknowledge event to Stripe
sendImmediate200Response(); // 2. Perform async database update in unhandled background promise
db.users.update({
where: { customerId: event.data.object.customer },
data: { isSubscribed: true }
}).catch(err => logError(err));
}
}
На машине разработчика с тестовыми данными это проходит все проверки. Код сразу подтверждает событие, возвращает 200 OK и обновляет локальную базу SQLite. В продакшене под нагрузкой это бомба замедленного действия:
- Если база словит кратковременный таймаут соединения или дедлок внутри этого фонового промиса без await, ошибка просто исчезнет.
- С карты клиента деньги списаны, Stripe считает вебхук доставленным, а аккаунт так и не повышается.
- Раз вернулся
200, Stripe никогда не повторит попытку, и транзакция потеряна навсегда.
Любой, кто отлаживал гонки в платежах на продакшене, увидит это сразу: вебхуки должны быть идемпотентными, обработка должна завершаться внутри атомарной транзакции, а 200 OK можно возвращать только после того, как состояние надёжно сохранено. Джун, копирующий аккуратный на вид дифф, одобрил бы его не задумываясь.
Ловушка 3: бесконечная самоотладка
Когда баг зависел от состояния, которое менялось за пределами открытого файла, просьба к ИИ починить собственный код запускала изматывающий цикл:
The Debugging Despair Loop
┌────────────────────────────────────────────────────────┐
│ 1. Bug Identified: State race condition in worker │
└──────────────────────────┬─────────────────────────────┘
│
▼
[Prompt AI to Fix] ──► Confidently rewrites worker.ts
│
▼
[Run Tests] ─────────► Bug disappears; introduces memory leak
│
▼
[Point out Leak] ────► Confidently rewrites worker.ts again
│
▼
[Run Tests] ─────────► Re-introduces the original race condition!
У модели не было отладчика времени выполнения, чтобы пройти по трассировке, заглянуть в кучу или понаблюдать за распределённым таймингом, поэтому она полагалась на вероятностные догадки по текстам ошибок. Она бодро объявляла баг исправленным, показывала чистый дифф, а через две итерации возвращала исходную ошибку. Чтобы решить такие проблемы, приходилось вмешиваться вручную с интерактивным отладчиком и смотреть на сырое состояние потоков.
Ловушка 4: неспособность сказать «нет»
Хорошая инженерия зависит от того, что вы отказываетесь строить, не меньше, чем от того, что выпускаете. Получив задачу спроектировать, например, экран настроек аккаунта, модель по умолчанию раздувала всё без меры. Она выдала 14 настраиваемых переключателей, замысловатые матрицы предпочтений уведомлений и вложенные вкладки, о которых не просил ни один пользователь. Попросите обработку ошибок — и она обернёт каждую внутреннюю функцию в общие блоки try/catch, которые глотают исходное исключение и возвращают "Something went wrong", уничтожая наблюдаемость.
У модели нет продуктового чутья, и больше кода для неё значит больше ценности. Держать интерфейсы компактными, вырезать лишние абстракции и сохранять читаемые стек-трейсы — по-прежнему человеческие навыки.
Ловушка 5: последние 10% съедают всё время
Разработка подчиняется суровому эмпирическому правилу: примерно первые 80% функций съедают 20% графика, а последние 20% — остальные 80%. ИИ полностью автоматизирует первую часть. Каркас интерфейса, REST-эндпоинты и стандартные формы заняли меньше недели. Остальные три недели ушли на грязную работу реального мира:
- Сетевой джиттер и повторы с экспоненциальной задержкой
- Идемпотентные записи в базу через распределённые вебхуки
- Состояния восстановления для пограничных случаев, например когда пользователь жмёт «Обновить» посреди OAuth-колбэка
- Утечки памяти в долгоживущих фоновых воркерах
ИИ не думает о пограничных случаях, если вы не знаете, что надо велеть ему их искать, а это знание даётся годами шрамов на продакшене.
Парадокс джуна
Эксперимент разрушил простое утверждение, что ИИ заменит программистов. Автоматизировал он работу начального уровня, которая раньше доставалась джунам, а не сеньорскую инженерию.
The Knowledge Pipeline Dilemma
Traditional Career Path:
[Junior: Scaffolding / CRUD] ──► [Mid-Level: Debugging / APIs] ──► [Senior: System Design]
▲
Today's Reality: │
[AUTOMATED BY AI] ──► [AUTOMATED BY AI] ──► [Senior Judgment Required]
│
Question: Where do tomorrow's senior engineers build their intuition? ──┘
Настройка шаблонного кода, каркасы юнит-тестов, CSS-сетки и простые CRUD-запросы — как раз то, на чём новички когда-то выстраивали ментальную модель программных систем. Нельзя сразу прыгнуть в архитектуру распределённых систем без сотен часов борьбы с синтаксисом, охоты за ошибками на единицу и понимания, почему плохо спроектированные схемы рушатся под нагрузкой. ИИ поднял ценность сеньорского суждения: замечать правдоподобно неверный код, отклонять ненужные абстракции и держать всю архитектуру системы в голове, чтобы не допустить дрейфа. И одновременно убрал лестницу, по которой джуны поднимались к этой экспертизе.
Что изменилось в процессе
ИИ по-прежнему используется, в том числе для генерации большей части сырого текста, но иллюзия, что производить код — то же самое, что заниматься инженерией, исчезла. Теперь действуют три правила:
- Автор никогда не бывает ревьюером. Агент, написавший дифф, не должен его проверять. Запускайте отдельного агента-оппонента, которому велено искать сбои в пограничных случаях, или читайте каждую строку сами со скептическим настроем.
- Прошедшие тесты — лишь стартовая линия. Никогда не принимайте пулл-реквест только потому, что тесты зелёные: модели хорошо умеют писать тесты, подтверждающие их собственные ошибочные допущения.
- ИИ печатает, архитектурой владеет человек. Пусть машина производит однообразный синтаксис, но модели данных, границы системы, транзакционные гарантии и эксплуатационные инварианты остаются под управлением человека.
Машина пишет код быстрее, чем любой человек в истории. И всё же код — это обязательство, а актив — система, которую он образует.