инженерный вайбкодингИИ + инженерная дисциплина
санкт-петербург · москва© 2026
// часть 0 · первый вечер

Часть 0. Первый вечер

Главное событие вечера будет, впрочем, не в ней. Примерно в середине работы агент вернёт результат, который выглядит готовым: страница открывается, форма отправляется, посетитель видит «Спасибо» – а обращение при этом исчезает бесследно. Это не сбой: в задаче не будет сказано, что обращение надо сохранить. Так мы подстроили. Встретить такое лучше здесь, на учебной странице, чем через полгода на своём настоящем проекте. Что с этим делать, разберём в тот же вечер; вся остальная книга – о том, как встречать реже.

Что нужно для этого вечера. Два-три часа. Опыт программирования не требуется. Ставить заранее ничего не нужно – всё поставим вместе по ходу. Одна просьба: идти по шагам и не забегать вперёд.

0.1. Двадцать минут до первой страницы

Соберём рабочее место. Его три части: язык, на котором будет написан проект, папка проекта под присмотром системы контроля версий и агент, которому мы будем ставить задачи.

Языком будет Python. Причина выбора приземлённая: он ставится одной командой, ничего не требует сверх себя, и всё, что мы соберём, запустится на любой машине – Windows, Mac, Linux. Это позволяет нам пообещать: у вас получится то же, что у нас. Обещание дороже рассуждений о том, какой язык лучше.

Откройте терминал и проверьте, что Python есть:

python3 --version

Если в ответ пришёл номер версии – всё на месте. Если команда не найдена, поставьте Python с официального сайта; на Windows команда называется python, без тройки. Это единственная развилка на весь вечер.

Теперь папка проекта:

mkdir zayavki
cd zayavki
python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn python-multipart

Три последние строки заводят так называемое окружение – отдельный ящик с инструментами именно для этого проекта, чтобы он не перемешивался с остальной машиной, – и кладут в него три инструмента: FastAPI, на котором пишется серверная часть, uvicorn, который её запускает, и python-multipart, без которого не будут работать формы. На Windows строка активации другая: .venv\Scripts\activate.

Про третий пакет стоит сказать отдельно, потому что это первый урок вечера. Формы – настолько обычное дело, что ожидаешь их работы «из коробки». Но FastAPI не станет разбирать отправленную форму, пока не найдёт python-multipart, и сообщит об этом только в момент запуска. Мы узнали это ровно так же, как узнаете вы: запустив. Никакое чтение документации по диагонали такие вещи не выявляет – их выявляет прогон.

Дальше – система контроля версий. Сначала скажем ей, чего запоминать не надо, иначе в историю проекта уедет папка с инструментами и файл базы с данными. Создайте файл .gitignore:

.venv/
__pycache__/
*.db

И заводите хранилище истории:

git init

Одна команда. Git запоминает состояния проекта: каждый раз, когда вы фиксируете рабочую точку, к ней можно вернуться. Пока вы работаете один и всё идёт хорошо, git кажется лишним. Он перестаёт казаться лишним в тот момент, когда агент переписал половину проекта и стало хуже, а вчерашней версии больше нет. У нас в практике был случай ещё неприятнее: две рабочие сессии правили одну папку без git, и одна молча затёрла работу другой – на опубликованный сайт ушёл чужой вариант страницы. Заметили по факту, восстанавливали по обрывкам. С git это стоило бы одной команды.

Теперь первая страница. Создайте файл main.py:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/", response_class=HTMLResponse)
def home():
    return "<h1>Приём обращений</h1><p>Сервис работает.</p>"

И запустите:

uvicorn main:app --reload

Откройте в браузере адрес http://127.0.0.1:8000. Если на экране заголовок «Приём обращений» – у вас работает собственный сервер. Он живёт только на вашей машине и виден только вам, но это уже не картинка: браузер отправил запрос, программа его обработала и ответила.

Зафиксируем рабочую точку:

git add .
git commit -m "Пустое приложение запускается"

Слово «коммит» дальше будет встречаться часто – это и есть зафиксированное состояние проекта, точка, к которой можно вернуться. Правило вечера: коммит после каждого работающего шага. Привычка «зафиксирую потом разом» не срабатывает никогда.

Если вы заказчик. Вы не обязаны выполнять эти команды – но вопрос «покажите репозиторий» стоит выучить. Репозиторий – это история проекта: что менялось, когда и в каком порядке. Если подрядчик не может его показать, у проекта нет истории, а значит, нет ни защиты от потери работы, ни возможности разобраться, кто и что сломал. Вы платите в том числе за это.

На часах должно быть минут двадцать. Идём к агенту.

0.2. Форма, которая врёт

Теперь поставим первую задачу агенту. Какому именно – решите сами: подойдёт любой из инструментов, где модель пишет код по описанию. Слова договоримся использовать так: модель – сама нейросеть, агент – инструмент, в котором она умеет работать с файлами вашего проекта. Мы сознательно не называем в книге конкретные продукты и версии: они меняются быстрее, чем печатается тираж. Актуальный список с нашими пометками живёт на странице книги: notevibe.ru/kniga.html – там он обновляется, с датой сверки у каждой строки.

Задача звучит так. Скопируйте её целиком:

Собери форму приёма обращений для страницы на FastAPI.
Поля формы: imya, kontakt, tekst. Отправка методом POST на /otpravit.
После отправки человек должен видеть подтверждение, что обращение принято.
Сделай минимально: одна страница, без оформления, без регистрации.

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

Агент вернёт код – скорее всего, охотно и быстро, с формой, кнопкой и сообщением «Спасибо, ваше обращение принято». Вставьте его результат в проект, как он предложит, перезапустите, откройте страницу.

Заполните форму. Нажмите кнопку. Прочитайте «Спасибо».

Приятный момент. Форма выглядит как настоящая, отвечает как настоящая, и внутри у вас, скорее всего, возникло ощущение «почти готово». Ощущение это стоит запомнить: мы будем к нему возвращаться.

Теперь обновите страницу и попробуйте найти отправленное обращение.

Его нет. Не в списке – списка тоже нет. Не в файле – файла не появилось. Обращение не легло никуда: сообщение «Спасибо» было нарисовано в момент нажатия кнопки, и на этом всё закончилось. Если бы такой формой пользовался настоящий человек с настоящей проблемой – его обращение исчезло бы молча, а он остался бы с уверенностью, что его услышали.

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

Такое случается не только в учебных проектах. В части III мы разберём, как эта же поломка несколько месяцев прожила у нас, и почему её не заметила ни одна проверка.

Отсюда – главный инструмент этой книги. Шесть вопросов, которые задаются любому результату генерации, пока не станут рефлексом:

  1. Где хранятся данные?
  2. Что останется после обновления страницы?
  3. Что произойдёт при ошибке – и увижу ли я её?
  4. Какие действия на самом деле уходят на сервер?
  5. Что увидит человек, если заполнит форму неправильно или не заполнит вовсе?
  6. Смогу ли я запустить это по инструкции, на другой машине, а не только в текущем окне?

Прогоните нашу форму по списку: первый же вопрос не имеет ответа, второй отвечает «ничего». Этого достаточно, чтобы понять уровень результата – картинка. Картинка полезна: её можно показать, по ней можно обсуждать, как это будет выглядеть. Просто она пока не продукт и не обязана им быть.

Зафиксируйте и это состояние тоже: git commit -m "Форма без хранения – картинка". История проекта имеет право содержать неудачные точки, она для того и история.

0.3. Хранение: обращение перестаёт исчезать

Почин очевиден: обращению нужно место, где оно будет лежать. Поставим агенту вторую задачу – и обратите внимание, насколько она отличается от первой:

Обращения из формы должны сохраняться в базе SQLite.
Путь к файлу базы брать из переменной окружения ZAYAVKI_DB,
по умолчанию zayavki.db.
Таблица obrasheniya: id, imya, kontakt, tekst, sozdano, status
(status по умолчанию "новое").
Добавь страницу /spisok - список всех обращений из базы, новые сверху.
После отправки формы - подтверждение с номером обращения.
Не переписывай проект целиком: измени только то, что нужно
для хранения и списка.

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

Про границу стоит сказать прямо. Без этой строки агенты любят возвращать «улучшенный» проект целиком, где заодно переименовано, переставлено и сломано то, что работало. Границу правки мы будем ставить в каждой задаче до конца книги.

SQLite мы выбрали по той же логике, что и Python: это база данных в одном файле, без установки, без службы, без пароля – и при этом настоящая. Для учебного проекта и для доброй половины небольших рабочих сервисов её достаточно.

Примите результат, перезапустите, откройте страницу. Заполните форму. Теперь – ритуал, ради которого всё затевалось:

Обновите страницу. Откройте /spisok. Обращение на месте.

Остановите сервер целиком (Ctrl+C в терминале) и запустите заново. Откройте список ещё раз. Обращение на месте.

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

Пройдитесь по шести вопросам из 0.2 ещё раз. Теперь у первых двух есть ответы: данные в файле zayavki.db, обновление страницы их не трогает. Вопросы три и пять пока висят – к ним вернёмся. Это нормальное состояние: важно знать, что именно ещё не сделано, вместо ощущения, что «в целом готово».

git add .
git commit -m "Обращения сохраняются в SQLite, список работает"

0.4. Первый тест: проверяет машина

Сейчас вы проверяете проект руками: заполнить, отправить, обновить, посмотреть. Для одного сценария это терпимо. Но проект будет расти, и с каждой правкой руки должны будут перепроверять всё, что работало раньше, – а руки ленивы и склонны верить, что «там-то ничего не сломалось». Именно на этой вере агент и ломает проекты: правя одно место, он задевает другое, а вы узнаёте об этом через неделю.

Выход – проверка, которую выполняет машина. Автотест: маленькая программа, которая делает то же, что делали ваши руки, и сравнивает результат с ожидаемым.

Первый тест напишем сами, без агента, и это принципиально. Тест – это формулировка того, что проект обязан делать. Если попросить агента написать тесты к готовому коду, он добросовестно зафиксирует то, что код делает сейчас, включая ошибки: кривое поведение станет узаконенным. Контракт пишет заказчик работы. В нашем проекте заказчик – вы.

Поставьте инструменты для тестов и создайте файл test_zayavki.py:

pip install pytest httpx2

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

import os
import pathlib

# База для проверок - отдельная, иначе тесты засорят рабочие обращения.
# Переменную выставляем ДО импорта main: он читает её при загрузке.
TESTOVAYA_BAZA = "test_zayavki.db"
os.environ["ZAYAVKI_DB"] = TESTOVAYA_BAZA
pathlib.Path(TESTOVAYA_BAZA).unlink(missing_ok=True)

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)


def test_obrashenie_sohranyaetsya():
    otvet = client.post("/otpravit", data={
        "imya": "Проверочный Пётр",
        "kontakt": "petr@primer.ru",
        "tekst": "Проверочное обращение",
    })
    assert otvet.status_code == 200

    spisok = client.get("/spisok")
    assert "Проверочный Пётр" in spisok.text

Первые строки объясняются просто: проверка обязана работать в своей песочнице. Если пустить её в настоящую базу, каждый прогон будет добавлять туда Проверочного Петра, и через неделю в рабочем списке обращений окажется толпа фантомов. Переменная окружения – это настройка, которую программа читает при запуске: удобный способ сказать одному и тому же коду «работай вот с этим файлом», не правя сам код.

Слово assert в тесте означает «утверждаю, что это верно». Если утверждение не выполняется, тест падает и показывает, что именно не сошлось. Больше в нём ничего нет: тест – это список утверждений о вашем проекте.

Сам тест читается почти как ваши действия руками: отправить обращение – убедиться, что отправилось – открыть список – убедиться, что обращение в нём есть. Запуск:

pytest

Зелёная строка с 1 passed – проверка прошла.

А теперь самое поучительное: сломайте сохранение. Причём сломайте незаметно, как это и происходит в жизни.

Откройте код обработки отправки. Дальше две ветки, потому что агент мог написать работу с базой одним из двух способов – посмотрите, какой у вас.

Если в коде есть отдельная строка со словом commit – закомментируйте её. Именно она отдаёт базе команду «сохрани изменения окончательно».

Если такой строки нет, а соединение открыто через with, как у нас:

    with soedinenie() as db:
        kursor = db.execute("INSERT INTO obrasheniya ...", (...))
        nomer = kursor.lastrowid

то сохранение делает сам with, когда блок заканчивается. Отнимите у него эту работу – откройте соединение обычной строкой и закройте вручную, без сохранения:

    db = soedinenie()
    kursor = db.execute("INSERT INTO obrasheniya ...", (...))
    nomer = kursor.lastrowid
    db.close()

Обратите внимание: строки внутри стали на один уровень левее, потому что блока with больше нет. Код рабочий, ошибок в нём нет. Если сомневаетесь, что получилось то же самое, сверьтесь с готовым проектом по адресу со страницы книги.

Запустите сервис руками, отправьте обращение через браузер. Вы увидите «Обращение принято», номер, никаких ошибок, ничего красного в терминале. Всё как в 0.2 – убедительная картинка. А теперь pytest:

assert 'Проверочный Пётр' in "<h1>Обращений пока нет</h1>..."
1 failed

Проверка поймала то, чего не видно глазами. Обращения не сохранялись, страница как ни в чём не бывало отвечала успехом, ошибок не было ни одной – и без теста вы узнали бы об этом от первого человека, чьё обращение пропало. Верните with на место и убедитесь, что снова зелено.

Это и есть ответ на вопрос, зачем нужны автопроверки, если можно щёлкать руками. Руками вы проверяете то, что видно. Тест проверяет то, что должно быть верно.

Одно предостережение, выстраданное. Тест должен проверять суть. Тест «страница открылась и вернула код 200» почти всегда зелёный и почти ничего не значит: страница может открываться, а сохранение быть мертво. Проверяйте то, ради чего сервис существует: обращение дошло до хранилища. Всё остальное – гарнир.

git add .
git commit -m "Первый тест: обращение сохраняется"

Если вы заказчик. Спросите подрядчика: «Какие проверки выполняются автоматически и что они проверяют?» Ответ «мы всё тестируем руками» означает, что каждая правка проекта может незаметно ломать любое из прежних мест. Это не приговор подрядчику, но это ваша информация о рисках.

0.5. Отметка «обработано» и граница первой версии

Сервис принимает и хранит. Для полного маленького цикла не хватает одного: отметить обращение обработанным, иначе список будет только расти и в нём утонет всё.

Задача агенту – по уже знакомому образцу, с данными и границей правки:

Добавь к каждому обращению в /spisok кнопку "Обработано".
Нажатие меняет статус обращения на "обработано" в базе.
Обработанные показывай в конце списка, серым.
Меняй только то, что относится к статусу. Форму и сохранение не трогай.

Примите, проверьте руками: нажали – уехало вниз, обновили страницу – состояние держится, перезапустили сервер – держится. Прогоните pytest: старый тест обязан остаться зелёным, ведь форму и сохранение мы трогать запрещали. Если он покраснел – агент вышел за границу правки, и вы узнали об этом сейчас, а не через неделю. Это автотест только что отработал свою цену.

Заметьте, как прошёл вечер с точки зрения ритма: одна задача – одна мысль – проверка – коммит. Форма отдельно, хранение отдельно, статус отдельно. Ни разу мы не просили «сделай всё сразу». Этот ритм скучнее, чем «собери мне сервис целиком», и держится он на простом свойстве: большие запросы порождают большие непроверяемые ответы, маленькие проверяются за минуту.

Теперь – граница первой версии. Возьмите файл PLAN.md и запишите в него, чего в проекте осознанно нет:

Не входит в первую версию:
- вход по паролю и роли
- уведомления на почту
- оформление и стили
- поиск и фильтры по списку
- удаление обращений

Список отложенного, существующий на бумаге, отличается от списка в голове одним свойством: он снимает тревогу. Пока «нет авторизации» живёт в голове, оно звучит как упрёк и провоцирует немедленно докручивать. Записанное – превращается в решение: этого нет, потому что так решено, вернёмся, когда дойдёт очередь. Проекты гибнут по обоим сценариям – и когда бросают недоделанными, и когда докручивают без остановки, пока не перестаёт запускаться. Письменная граница защищает от второго.

git add .
git commit -m "Статус обработано + граница первой версии"

0.6. Инструкция для того, кто откроет это через месяц

Остался последний файл, и он адресован конкретному человеку: вам через месяц. Этот человек не помнит ни как запускать проект, ни что в нём работает, ни почему нет авторизации. Проверено на всех, включая авторов: память – негодная система документации, особенно после пары выходных.

Файл называется README.md, лежит в корне проекта и отвечает на четыре вопроса. Что это. Как запустить. Что работает. Чего нет.

# Приём обращений

Учебный сервис: принимает обращения через форму,
хранит в SQLite, показывает список со статусами.

## Запуск
python3 -m venv .venv                      # только в первый раз
source .venv/bin/activate                  # Windows: .venv\Scripts\activate
pip install fastapi uvicorn python-multipart pytest httpx2
uvicorn main:app --reload
Открыть http://127.0.0.1:8000

## Проверка
pytest                                     # работает в test_zayavki.db, рабочую базу не трогает
Руками: отправить обращение -> увидеть в /spisok ->
перезапустить сервер -> обращение на месте.

## Работает
Форма, сохранение в zayavki.db, список, статус "обработано".

## Не входит в первую версию
См. PLAN.md

Пишется за пять минут, окупается в первый же день возвращения к проекту. Есть и второй адресат: любой человек, которому вы проект передадите – помощнику, подрядчику, покупателю. Инструкция запуска, работающая на чужой машине, – это признак того, что проект существует отдельно от вашего компьютера. До этого момента он существовал только вместе с вами.

git add .
git commit -m "README: запуск, проверка, границы"

Если вы заказчик. То, что вы сейчас видели, – зерно комплекта передачи: минимального набора, который должен оставаться у вас после любой сданной работы. Из чего он состоит целиком и как проверить, что вам его действительно отдали, – разбор в части IV. Требовать его можно уже сейчас, любыми словами.

0.7. Что произошло за вечер

Посмотрим на результат. В папке zayavki лежит сервис, который принимает обращения, хранит их, переживает перезапуск, показывает список и умеет отмечать сделанное. Рядом – история изменений с рабочими точками, одна автоматическая проверка сути, письменная граница версии и инструкция, по которой сервис запустит другой человек. По меркам промышленной разработки – зерно. Но это зерно, у которого есть ответы на все шесть вопросов из 0.2.

И у вас было главное событие вечера: форма, которая говорила «Спасибо» и выбрасывала обращения. Вы видели, как убедительно выглядит результат, у которого внутри пусто, – и как быстро это вскрывается, если знать, куда смотреть. Всё дальнейшее в книге выросло из этого зазора. Генерация даёт правдоподобное; проверка отличает правдоподобное от работающего; эксплуатация – искусство не дать работающему тихо испортиться. Агенты сегодня закрывают первое звено блестяще, второе – кое-как, третье – никак. Значит, второе и третье – ваша работа, и теперь вы знаете, из чего она состоит.

И оговорка на дорогу. Стек этого вечера – учебный: мы выбрали его за то, что он ставится одной командой и одинаково работает у всех. Если ваш проект живёт на другом языке или собран в другом инструменте – ничего из дальнейшего это не отменяет: правила книги про работу и владение, к конкретному стеку они не привязаны.

В части I мы займёмся ремеслом: как описывать задачу, чтобы агент реже заполнял пустоты догадками, как читать то, что он вернул, как держать его в границах – а ещё как устроена его память, что делать, когда окон с агентами два, и чем мерить размер затеи. Приёмы, которые вы применяли сегодня по нашей подсказке, там станут системой. Читать часть I лучше по одной-две главы за вечер, применяя каждую к своему сервису, – торопиться некуда, книга не убежит.

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

Сервис не выключайте. Он нам ещё пригодится.

♪ notevibe
главнаяпроектыуслугиметодкнигаконтактнаправление студии «Белый свет»