Я решил провести простой эксперимент: попросил одну нейросеть написать небольшой сервис, а другую — провести ревью этого кода.
Задача выглядела безобидно: сделать API для учета расходов. Пользователь отправляет сумму и категорию, а сервис возвращает общую сумму расходов за месяц.
описание задачи
→ AI пишет код
→ другой AI проводит ревью
→ тесты проверяют замечания
→ человек принимает решение
Я специально выбрал небольшую задачу. Хотелось проверить не то, сможет ли нейросеть написать тысячу строк, а заметит ли другая модель проблемы в относительно коротком коде.
Что попросил сделать первый AI
Запрос был примерно таким:
Напиши небольшое FastAPI-приложение для учета расходов.
Требования:
- POST /expenses добавляет расход;
- GET /expenses/month возвращает сумму расходов за месяц;
- расход содержит amount, category и date;
- данные хранятся в SQLite;
- добавь Pydantic-модели;
- напиши тесты для основных сценариев.
Модель быстро подготовила структуру проекта:
На первый взгляд все выглядело убедительно: были endpoint, схема данных, подключение SQLite и несколько тестов.
Первый тест проверял добавление расхода:
def test_create_expense(client):
response = client.post(
"/expenses",
json={
"amount": 1000,
"category": "food",
"date": "2026-09-01"
}
)
assert response.status_code == 201
assert response.json()["amount"] == 1000
Второй проверял сумму за месяц:
def test_month_total(client):
response = client.get("/expenses/month?month=2026-09")
assert response.status_code == 200
assert response.json()["total"] == 1000
Тесты проходили. Можно было решить, что задача выполнена.
Но именно в этот момент в работу вступила вторая модель.
Что нашел AI-ревьюер
Я передал ей исходное требование, структуру проекта и diff изменений. Попросил не переписывать код, а составить список проблем с указанием серьезности и способом воспроизведения.
Ревьюер нашел четыре замечания.
Ошибка № 1. Неправильная граница месяца
В запросе к базе использовалось условие:
Expense.date >= start_date
Expense.date <= end_date
Для сентября end_date был сформирован как 2026-09-30 00:00:00.
Это означало, что расход, добавленный 30 сентября в 14:00, в выборку не попадал.
Правильнее использовать полуоткрытый интервал:
Expense.date >= start_date
Expense.date < next_month_start
То есть для сентября нужно выбирать даты:
от 2026-09-01 00:00:00 включительно
до 2026-10-01 00:00:00 исключительно
Такой подход работает и для месяцев разной длины, и для времени внутри последнего дня.
Ошибка № 2. Отрицательные суммы
В Pydantic-схеме было написано:
class ExpenseCreate(BaseModel):
amount: float
category: str
date: date
Сервис принимал такой запрос:
{
"amount": -5000,
"category": "food",
"date": "2026-09-10"
}
Технически это был валидный JSON, но с точки зрения продукта отрицательный расход не имел смысла.
Ревьюер предложил добавить ограничение:
from pydantic import BaseModel, Field
class ExpenseCreate(BaseModel):
amount: float = Field(gt=0)
category: str
date: date
Ошибка № 3. Деньги хранились как float
Первый AI выбрал тип float для суммы. Для учебного прототипа это выглядит нормально, но для денежных значений может привести к ошибкам округления.
Ревьюер предложил хранить сумму в минимальных единицах, например в копейках:
Тогда 199,99 рубля хранится как:
А не как число с плавающей точкой.
Ошибка № 4. Тесты проверяли только счастливый путь
В тестах были только корректные данные:
положительная сумма;
существующая категория;
дата в начале месяца;
успешный запрос.
После ревью стало понятно: первый AI написал код, который демонстрировал работу на идеальном примере, но не описывал поведение системы в реальности.
Но и ревьюер ошибся
На этом эксперимент можно было бы остановиться и сказать: вторая модель все исправила.
Но я попросил ее самостоятельно предложить исправленную версию кода. И здесь обнаружилась новая проблема.
Ревьюер исправил фильтрацию дат, добавил проверку суммы и заменил float на целое число. Но в предложенной версии он изменил формат ответа API:
{
"total_kopecks": 125000
}
С технической точки зрения новый формат был понятнее. Но frontend уже ожидал поле total. Если применить исправление без проверки, интерфейс перестал бы отображать итоговую сумму.
Кроме того, AI-ревьюер добавил новую зависимость для работы с валютой, хотя задача не требовала поддержки нескольких валют. Это увеличивало сложность проекта без пользы для текущей версии.
Получился важный результат:
Первый AI ошибся в логике. Второй AI нашел часть проблем, но сам предложил изменение, нарушающее существующий контракт.
AI проверил AI, однако окончательное решение все равно пришлось принимать человеку.
Почему тесты не спасли сразу
Возникает вопрос: почему исходные тесты прошли, если в коде были ошибки?
Потому что тесты проверяли не все требования, а только те сценарии, которые придумал первый AI.
Например, тест для суммы за месяц добавлял расход 1 сентября. Поэтому ошибка с 30 сентября оставалась незаметной.
Чтобы проверить реальное поведение, добавили тест:
def test_expense_on_last_day_of_month_is_included(client):
client.post(
"/expenses",
json={
"amount_kopecks": 19999,
"category": "food",
"date": "2026-09-30T14:00:00"
}
)
response = client.get("/expenses/month?month=2026-09")
assert response.status_code == 200
assert response.json()["total"] == 19999
И тест на отрицательную сумму:
def test_negative_amount_is_rejected(client):
response = client.post(
"/expenses",
json={
"amount_kopecks": -500,
"category": "food",
"date": "2026-09-10"
}
)
assert response.status_code == 422
После этого тесты уже проверяли не только то, что функция работает в идеальных условиях, но и то, что она отказывается принимать некорректные данные.
Что изменилось после эксперимента
Изначально задача выглядела так:
Сделать API для учета расходов.
После ревью она превратилась в набор конкретных требований:
сумма хранится в копейках;
отрицательные значения запрещены;
последний день месяца входит в выборку;
формат ответа API не меняется без отдельного решения;
новые зависимости не добавляются без необходимости;
граничные случаи покрываются тестами;
изменения проверяются относительно существующего frontend.
Это уже не просто генерация кода. Это управляемый процесс.
Как я проводил этот эксперимент
Для эксперимента я использовал NormaHub — сервис, через который можно работать с разными AI-моделями для текста, кода, анализа и генерации контента. Вместо того чтобы собирать отдельный workflow под каждого провайдера, я выбрал нужные модели в каталоге NormaHub и использовал их на разных этапах проверки.
Сначала одна модель подготовила реализацию API для учета расходов. Затем я передал код другой модели для независимого ревью: она проверила логику, нашла проблему с последним днем месяца, обратила внимание на отрицательные суммы и предложила дополнительные тесты. После этого я использовал AI для повторной проверки исправлений и сравнил результат с исходными требованиями.
В итоге нормахаб стал для меня рабочей точкой всего эксперимента:
задача → генерация кода → AI-ревью → дополнительные тесты → повторная проверка
Итог эксперимента
Ответ на вопрос из заголовка получился неоднозначным.
написать рабочий черновик;
найти ошибки в чужом коде;
предложить дополнительные тесты;
объяснить проблемное место;
сравнить реализацию с требованиями.
Но AI-ревью не является доказательством того, что проект корректен.
В эксперименте первая модель пропустила ошибку на границе месяца, разрешила отрицательные суммы и выбрала неподходящий тип для денег. Вторая модель нашла эти проблемы, но сама предложила изменить API-контракт и добавить лишнюю зависимость.
Без человека можно было получить код, который:
Поэтому разработчик все еще нужен. Его задача постепенно меняется: он меньше пишет типовой код вручную, но больше занимается постановкой требований, проверкой решений, архитектурой и ответственностью за результат.
AI может написать код. AI может проверить код. AI может найти ошибки.
Но только человек должен решить, какую проблему на самом деле решает программа и можно ли доверять ей в реальной работе.