Навайбкодили - не трогай. Как понять, что проект можно выпускать в прод
Друзья, здравствуйте.
Сегодня мы с вами становимся свидетелями того, как вайбкодинг делает с разработкой примерно то же самое, что когда-то сделал GitHub с опенсорсом (а паддл — с теннисом): резко падает порог входа.
Вам больше не нужно детально описывать ТЗ, искать разработчиков, долго и упорно объяснять им, что, по вашим прикидкам, должно получиться, дожидаться результата первой итерации, чтобы наконец обнаружить, что вы представляли продукт по-разному.
Теперь всё иначе: после нескольких часов токсичного ночного общения (да-да, мы все это делаем) с Cursor, утром вы получаете работающий MVP.
С ума сойти! У вас тут и регистрация, и PostgreSQL, и красивый интерфейс, у вас даже оплата прикручена и работает.
Только вот занудные разработчики, которым вы гордо показываете результат вашей интеллектуальной деятельности, говорят вам, что работающий MVP и готовый к продакшену продукт — две совершенно разные вещи.
Мы собрались в группу «Рефактория» как раз с этой целью — нам интересно посмотреть, что происходит с вайбкод-проектами после нескольких недель или месяцев их существования.
В таких сервисах, написанных в четыре руки с нейросетью, прослеживается одна и та же картина: код работает, иногда работает очень хорошо. Но всё это в статике. Выясняется, что попытка добавить фичу немедленно ломает работоспособность, секреты лежат прямо в репозитории, ручки не закрыты авторизацией, тесты легче выпилить, чем поддерживать, а права доступа существуют только в интерфейсе.
И вот на очередной попытке договориться о «вроде изян доработке» вы получаете всё тот же ответ: «Надо переписывать».
Да, иногда действительно надо.
Но сначала давайте всё-таки попробуем спасти то, что есть.
Важно! AI не пишет плохой код!
Это абсолютно не та мысль, которую нам хочется транслировать. AI пишет и хороший код, и плохой. Примерно как человек.
Проблема в другом: скорость и объём генерации кода стали выше скорости его инженерной проверки. То есть раньше редкий разработчик мог выдать вам MR на несколько тысяч строк кода за вечер. А вот теперь может каждый. При этом ведь объём значимых архитектурных решений, принимаемых специалистами, никуда не делся:
- Кто должен иметь доступ к этим данным?
- Как здесь работает бизнес-логика?
- Что должно происходить при попытке обратиться к архаизированной сущности?
- Что делать, если смежный сервис не отвечает?
- Можно ли вызывать эту ручку дважды?
- Что делать, если завтра таблица вырастет в тысячу раз?
Конечно, ИИ обоснованно ответит Вам по каждому из вышесказанных вопросов.
Но вы ему это позволите? Серьёзно?
Нам кажется, ответ — нет, и это догма. В этом и есть разница между вайбкодом и разработкой.
Итак, погнали.Что будем проверять
1. Авторизация: «можно залогиниться/разлогиниться» — этого недостаточно
Самый простой сценарий выглядит отлично:
- пользователь регистрируется;
- входит;
- видит свои данные;
- выходит.
Ваше пятиминутное демо проходит отлично, всё работает.
А давайте попробуем другой сценарий?
Что, если вместо указанного в вашем URL ID перебирать случайные?
GET /api/orders/12345
GET /api/orders/67890
Что произойдёт?
Вуаля! Сервер просто доверяет ID из запроса, мы получаем чужие данные. Поздравляю, вы хакер!Это особенно неприятный класс ошибок, потому что интерфейс здесь вообще ни при чём. Кнопка «Мои заказы» может быть идеально реализована. Проблема кроется в безопасности на стороне сервера.
Чек-лист — что проверять
- проверяются ли права доступа на сервере;
- проверяется ли принадлежность объекта пользователю;
- как устроены роли;
- можно ли вызвать API напрямую;
- что происходит после logout;
- истекают ли сессии;
- как работает reset password;
- можно ли повторно использовать ссылку восстановления.
Главное правило: Безопасность нельзя проверять по интерфейсу. Если кнопка «Удалить пользователя» недоступна обычному пользователю, это ещё ничего не значит. Нужно проверить, что будет, если пользователь с обычными правами самостоятельно отправит соответствующий запрос.
2. База данных: маленькому MVP почти всё сходит с рук
Поверьте, 10 пользователей переживут почти любую фантазию создателя по части структуры базы данных.
Тысяча — уже не факт.
В AI-сгенерированном проекте мы бы рекомендовали отдельно смотреть на:
- индексы;
- связи между сущностями;
- дублирование данных;
- миграции;
- ограничения;
- транзакции;
- удаление связанных объектов;
- работу с персональными данными;
- нормализацию.
Если сложно во всё это вникнуть разом — попробуйте идти по сценариям. Например, пользователь удаляет аккаунт.
Что должно исчезнуть?
Сам пользователь?
Заказы?
Файлы?
Платежи?
А что, наоборот, должно остаться?
Что важно для истории, где требуется мягкое удаление?
Очень легко реализовать функцию delete_user:
DELETE FROM users WHERE id = ?
Только вот работает ли это в рамках реализованной вами системы?
3. Платежи: самая опасная кнопка в приложении. Здесь вы отвечаете собственной шкурой.
Если в продукте есть работа с деньгами, ставки немедленно возрастают.
Типичный AI-MVP делает примерно следующее:
- пользователь нажимает «Оплатить»;
- его отправляют в платёжный сервис;
- он возвращается на /success;
- приложение активирует подписку.
Опять же — на вашем демо всё именно так и будет. Но кто именно сообщил системе, что деньги пришли? Если ответ — «пользователь вернулся на страницу успеха», Хьюстон, у нас проблемы.
По пути пользователь может закрыть вкладку. Сеть может отвалиться. Вебхук может задержаться или прийти дважды. Может повториться запрос. Да что угодно может произойти. Учитывая это, можно ли поселить платёжную логику в happy path?
Кажется, нет. Она должна жить на сервере.
Проверяем:
- подпись webhook;
- повторную обработку событий;
- idempotency;
- состояние подписки;
- возвраты;
- отмену подписки;
- повторные webhook;
- задержанные webhook.
Всё это особенно важно потому, что платёжные ошибки редко выглядят как «приложение упало».
Гораздо хуже, что приложение работает неправильно.
4. Frontend может убедительно скрывать проблему
Это одна из причин, почему тестировать AI-приложение только руками через браузер недостаточно.
Что видит пользователь: «Сохранено»
Что происходит на самом деле: Сервер получил 500 Internal Server Error
Что видит пользователь: «Оплата прошла»
Что происходит на самом деле: Webhook от платёжного сервиса не обработан
«Бесконфликтный» интерфейс — очень плохой индикатор качества backend. Звучит странно, признаем, но так оно и есть: вам, как первому заинтересованному в успехе вашего сервиса, нужно смотреть не только на браузер, но и на то, что происходит под капотом: API, БД, межсервисные взаимодействия.
5. API: пользователь не обязан вести себя прилично
AI обычно хорошо реализует тот сценарий, который ему показали.
Например:
{
"name": "Maria",
"age": 38
}
Работает.
А что будет, если отправить:
{
"name": null,
"age": -500
}
Или вообще похулиганить и :
{
"role": "admin"
}
Если сервер принимает это без проверки, проблема уже не в UI.
Проверяем:
- типы данных;
- обязательные поля;
- диапазоны значений;
- права доступа;
- размер запросов;
- размер файлов;
- rate limiting;
- обработку ошибок;
- неожиданные значения;
- доступность внутренних endpoint'ов.
Хороший frontend говорит:
«Пользователь не сможет этого сделать».
Хороший backend отвечает:
«Даже если сможет — ничего страшного».
6. Архитектура приложения выдерживает изменения
Итак, вы добавляли ряд фичей.
Первые несколько работают как часы.
Потом ещё одну. И ещё одну.
И вот вам уже приходится добавлять умоляющее «только, пожалуйста, ничего не ломай!»
Постепенно вы приобретаете архитектуру, в которой вы не уверены, динамику которой вы не отследили и в которой, по сути, перестали понимать, что происходит.
Это не обязательно выглядит вот так:
users.py
users2.py
users-final.py
users-final-v2.py
В коде появляются:
- функции на несколько сотен строк;
- бизнес-логика в UI;
- дублирование;
- несколько способов сделать одно и то же;
- неиспользуемые зависимости, функции или целые файлы;
- странные абстракции;
- временные решения, которые пережили три релиза.
И здесь есть важный момент. AI не обязан знать, что временное решение стало архитектурой. Он видит существующий код и следующую задачу. Если в UserService уже 800 строк, новая фича вполне может стать 801-й.
7. «Почему бы не попросить AI всё отрефакторить»
Можно. Но и это не панацея.
«Проведи рефакторинг проекта и сделай хорошо».
Модель будет оптимизировать локальную задачу, находясь в какой-то точке определённого видения, вместо того чтобы от этого видения абстрагироваться и сделать действительно хорошо.
На какие не самые приятные вопросы стоит ответить перед рефакторингом:
- где находится бизнес-логика;
- какие данные критичны;
- какие части системы связаны между собой;
- что можно переписать;
- что лучше не трогать;
- где точки отказа;
- какие зависимости являются обязательными;
- какие решения были временными.
И только после этого давать AI конкретную работу. Лучше всё это, разумеется, поместить в контекст.
AI — ваш верный помощник в реализации инженерного решения. Но он гораздо хуже справляется с ролью CEO, CTO, тимлида и единственного человека, который принимает решения.
8. Тесты тоже можно сгенерировать не глядя. Да здравствует ложное спокойствие
Представьте:
312 tests passed
Всё зелёненькое, выглядит солидно.
Что именно проверяют эти 312 тестов?
Как правило, сгенерированные тесты повторяют текущую реализацию. Если она неправильная — они и её успешно подтвердят.
Возможно, стоит начинать не с количества тестов.
А с человеческого описания инвариантов:
Пользователь A никогда не должен получить данные пользователя B.
Вот это правило имеет смысл защищать тестом.
А вот тест:
GET /users/123 → 200
сам по себе почти ничего не говорит.
Особенно если endpoint должен возвращать данные только владельцу.
9. Что проверять перед первым продом
Собрав всё вышесказанное в единый чек-лист, получим примерно такую картину.
Security
- аутентификация;
- авторизация на уровне API;
- принадлежность данных пользователю;
- роли и права доступа;
- сессии и logout;
- reset password;
- секреты и ключи;
- доступность внутренних endpoint'ов.
Data
- схема базы;
- связи между сущностями;
- индексы;
- дублирование данных;
- миграции;
- ограничения;
- транзакции;
- удаление связанных объектов;
- персональные данные.
Payments
- подтверждение оплаты на стороне сервера;
- подпись webhook;
- повторная обработка событий;
- idempotency;
- состояние подписки;
- возвраты;
- отмена подписки;
- задержанные и повторные webhook.
API
- валидация входных данных;
- обязательные поля;
- типы и диапазоны значений;
- права доступа;
- неожиданные значения;
- размер запросов и файлов;
- rate limiting;
- обработка ошибок;
- повторные запросы.
Architecture
- дублирование логики;
- бизнес-логика в UI;
- границы модулей;
- зависимости;
- неиспользуемый код;
- временные решения;
- критические точки отказа;
- способность системы переживать изменения.
Frontend
- корректное отображение ошибок backend;
- loading/error states;
- права доступа не только на уровне UI;
- работа с API;
- хранение токенов;
- отсутствие утечек данных.
Tests
- критические пользовательские сценарии;
- негативные сценарии;
- права доступа;
- работа с данными;
- платежные сценарии;
- инварианты системы.
9. Что проверять перед первым продом
Собрав всё вышесказанное в единый чек-лист, мы получим что-то такое:
Security
- аутентификация;
- авторизация;
- роли;
- сессии;
- секреты;
- публичные endpoint'ы;
- валидация входных данных.
Data
- схема базы;
- индексы;
- миграции;
- транзакции;
- удаление;
- целостность;
- персональные данные;
- backup.
Payments
- webhook;
- подписи;
- idempotency;
- подписки;
- возвраты;
- повторные события.
Backend
- обработка ошибок;
- rate limiting;
- логирование;
- таймауты;
- внешние интеграции;
- валидация.
Architecture
- дублирование;
- зависимости;
- границы модулей;
- бизнес-логика;
- масштабирование;
- разделение окружений.
Frontend
- обработка ошибок;
- loading/error states;
- права доступа;
- хранение токенов;
- работа с API;
- утечки данных.
Tests
- критические сценарии;
- негативные сценарии;
- права доступа;
- платежи;
- работа с данными.
Итак, мы провели такое ревью, получили 47 замечаний.
Спасибо.
А что со всем этим делать-то? Куда бежать?
Давайте разделим всё это по предполагаемым последствиям:
П0 — можно потерять деньги или данные.
П1 — можно сломать критический пользовательский сценарий.
П2 — мешает развитию и поддержке.
П3 — технический долг, который пока можно оставить в покое.
Это уже похоже на системный здравый подход, согласитесь: плохой код и опасный код — не одно и то же. Функция на 300 строк может быть уродливой, но абсолютно безопасной. А две аккуратные строки могут дать пользователю доступ к чужому аккаунту.
Что мы видим в проектах после AI
Собственно, на этом и построена работа «Рефактории».
Мы не разрабатываем с нуля — нам посчастливилось видеть проекты, которые уже существуют и которыми пользуются. И это правда очень интересно!
Чаще всего это как раз тот самый сценарий:
Быстрый MVP ->
Первые пользователи ->
Требуется развитие ->
... ->
Страшно трогать код
Мы читаем репозиторий, запускаем проект, смотрим архитектуру, зависимости, данные, API и критические сценарии.
Всё как на приёме у врача: на первом этапе ничего не переписываем. Ведь сначала нужен диагноз, а не хирургия. По результатам аудита можно честно разделить проект на три типа:
Всё отлично
Нужно лечение
Увы, пациент скорее мёртв, чем жив!
И только после детального обсуждения нашей точки зрения как второго мнения (кстати, термин тоже врачебный) имеет смысл обсуждать реализацию.
Почему мы вообще называем это «капремонтом»
Задача никогда не ставится как «сделать код красивым».
У проекта уже есть история.
Есть работающие пользовательские сценарии.
Есть данные.
Есть интеграции.
Есть люди, которые привыкли к тому, как всё работает.
Хороший результат для нас — когда через некоторое время ваш разработчик открывает репозиторий и не звонит вам каждые 20 минут в ужасе.
Вайбкодинг здесь вообще ни при чём
И, пожалуй, это главный вывод на сегодня — проблема не в Курсоре и даже не в том, что MVP написал человек, который никогда не открывал IDE.
Проблема появляется в тот момент, когда прототип начинают эксплуатировать как продукт, не пытаясь адаптировать его под инженерные требования этой задачи.
Вайбкодинг — это роскошный инструмент для проверки гипотез, прототипирования или ваших внутренних инструментов-помощников.
Для того чтобы за вечер понять, стоит ли тратить на идею следующие полгода. Но если проект прошёл эту проверку и теперь должен жить дальше, ему нужен следующий этап. Кажется, это лучший момент для аудита.
И если считать этот технический сигнал поводом остановиться и посмотреть на проект целиком, следующий шаг не будет для вас ультра дорогим.
А дальше вариантов всего три:
Всё отлично
Нужно лечение
Увы, пациент скорее мёртв, чем жив!
Главное — сначала выяснить, какой сценарий именно ваш.