← назад к разделу

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

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

одна сессия трактовка ошибочна код по ней тест по коду ревью: зелёное свой эталон требование тест из требования код агента тест красный

Слепое пятно: код, тест и ревью выросли из одной трактовки требования, и ошибка проходит всех троих зелёной. Останавливает её только эталон, который из кода не выводится: тест, написанный от требования.

Обязательно

Агент как первый проход ревью

Ревью человека дорого, и на большом потоке кода от агента его не хватает. Первый проход можно отдать агенту, но результат зависит от того, как его попросить. «Посмотри диф» даёт общие слова про читаемость; конкретный запрос даёт конкретные находки. Что агент на первом проходе ловит хорошо:

  • явное — забытую обработку ошибки, неучтённый крайний случай, нарушение стиля, подозрительное место, где переменная называется одно, а хранит другое;
  • незнакомый код — объясняет, что делает кусок, и подсказывает, на что смотреть внимательнее;
  • проверку по заданным правилам, если правила у него есть: свод правил проекта из настройки агента или исполняемый стандарт.

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

Вот диф изменения «оплата заказа» (git diff main..HEAD).
Проверь по правилам из docs/rules/: контракт операции, крайние случаи
(пусто, нет данных, повтор запроса, отказ шлюза), транзакции.
Ответ: список находок, у каждой файл, строка, какое правило и почему.
Ничего не исправляй.

Последняя строка важна: агент-ревьюер, которому разрешили править, начинает чинить и заодно переписывать, и вместо списка находок вы получаете второй диф. Что на первом проходе агенту не отдают, потому что он этого не видит: контракт на стыках с соседними сервисами, смысл операции (то ли вообще сделано), архитектурные компромиссы. Это смотрит человек, и подробный порядок такого просмотра, от контракта к крайним случаям, разбирает статья «Как ревьюить код, который написал агент».

Развести автора и ревьюера

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

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

Полностью это слепое пятно не закрывает: две модели ошибаются похоже. Закрывает его независимый эталон, о котором дальше.

Какие тесты отдать агенту, а какие нет

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

  • рутинные случаи по готовому образцу — «вот тест на создание заказа, напиши такие же на отмену и на повтор»;
  • крайние значения — пустая коллекция, ноль, граница диапазона, отсутствие данных, слишком длинная строка;
  • параметризованные наборы — десять вариантов входа с ожидаемым выходом из таблицы в требовании;
  • обвязку — заглушки, фикстуры, подготовку данных, всё, что занимает время и не требует суждения.

Не отдают тест, который определяет, что считать правильным: тест на бизнес-правило из требования. Причина в двух ловушках.

Тест закрепляет то, что делает код. Агент, который не понял требование, пишет код по своей трактовке и тест по этому коду. Требование: скидка 10% на заказ от 5000. Агент понял «на каждую позицию от 5000» и написал тест на заказ из одной дорогой позиции. Тест зелёный, код неверный, и теперь ещё и защищён тестом: кто исправит логику, увидит красный тест и решит, что сломал.

Тест ничего не проверяет. Он выглядит как тест и всегда проходит: проверяет, что объект не пустой, что метод вызвался, что исключения не было. Такой тест от агента получается особенно легко, потому что агент видит код и подтверждает его, а не спрашивает с него.

@Test
void appliesDiscount() {
    var order = service.place(new PlaceOrder(customer, items));
    assertNotNull(order);
    verify(repository).save(any());
}
func TestAppliesDiscount(t *testing.T) {
	order, err := service.Place(ctx, PlaceOrder{Customer: customer, Items: items})
	require.NoError(t, err)
	require.NotNil(t, order)
	require.True(t, repository.SaveCalled)
}
it('applies discount', async () => {
  const order = await service.place(new PlaceOrder(customer, items));
  expect(order).toBeDefined();
  expect(repository.save).toHaveBeenCalled();
});
def test_applies_discount():
    order = service.place(PlaceOrder(customer, items))
    assert order is not None
    repository.save.assert_called()

Этот тест пройдёт при любой скидке, при отсутствии скидки и при скидке в 200%. Тест, который проверяет требование, называет ожидаемое число и берёт его из требования, а не из кода:

@Test
void appliesTenPercentFromFiveThousand() {
    var order = service.place(new PlaceOrder(customer, itemsFor(Money.of(5000))));
    assertEquals(Money.of(4500), order.total());
}
func TestAppliesTenPercentFromFiveThousand(t *testing.T) {
	order, err := service.Place(ctx, PlaceOrder{Customer: customer, Items: itemsFor(money(5000))})
	require.NoError(t, err)
	require.Equal(t, money(4500), order.Total)
}
it('applies ten percent from five thousand', async () => {
  const order = await service.place(new PlaceOrder(customer, itemsFor(Money.of(5000))));
  expect(order.total).toEqual(Money.of(4500));
});
def test_applies_ten_percent_from_five_thousand():
    order = service.place(PlaceOrder(customer, items_for(Money(5000))))
    assert order.total == Money(4500)

Правило простое: тест от требования пишут от критериев приёмки, а не «покрыть, что есть». Агенту можно отдать набор таких тестов, если ожидаемые значения в задании написали вы.

тест, который пишет агент рутина, крайние значения, обвязка эталон известен: отдать агенту тест на бизнес-правило ожидание из требования пишете вы

Граница проходит по тому, откуда берётся ожидаемое значение. Если оно уже есть в образце или в таблице требования, агент справится сам; если его надо решить, решаете вы, а агент оформляет.

Проверить тест за минуту: сломать код

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

Поменяйте в коде скидку с 10% на 0, перепутайте знак сравнения, верните из метода пустой список. Прогоните тест. Если он остался зелёным, он не проверяет то, что должен, и его переписывают, а не принимают. Если покраснел, он держит ровно это поведение. На три-пять таких поломок уходит минута, и это дешевле любого ревью.

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

Проверяемый критерий важнее объёма

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

Отсюда и ценность правил в исполняемом виде: когда правила ревью вынесены в исполняемый стандарт, агент проверяет по одному и тому же своду на каждом изменении, а не «как получится в этот раз», и находка цитирует правило, а не мнение. Чем такой свод отличается от статических анализаторов, разбирает статья «Исполняемые правила против SonarQube и ESLint».

сборка и типы секунды тесты минуты правила и агент по своду человек смысл и компромиссы

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

Дополнительно: при первом чтении можно пропустить

Глубже: проверять агента серией, а не одним результатомрасширенное

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

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

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

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

Коротко

  • Агент-автор и агент-ревьюер ошибаются одинаково, если у них один контекст: ревью делают в новой сессии, лучше другой моделью, с требованием в исходном виде.
  • Первый проход ревью агенту отдают с конкретным запросом: что менялось, по каким правилам смотреть, в каком виде отвечать, ничего не править. Контракт на стыках, смысл операции и компромиссы смотрит человек.
  • Агенту отдают тесты с известным эталоном: рутинные случаи по образцу, крайние значения, параметризованные наборы, обвязку. Тест на бизнес-правило пишут от критериев приёмки, с ожидаемым числом из требования.
  • Две ловушки тестов от агента: тест закрепляет то, что делает код, и тест, который ничего не проверяет (assertNotNull, verify(any())).
  • Проверка теста за минуту: сломать код и посмотреть, покраснел ли тест. Остался зелёным — переписать.
  • Проверяемый критерий важнее объёма: исполняемый стандарт даёт одинаковую проверку на каждом изменении, и находка цитирует правило.
  • Изменения модели и правил проверяют серией: набор эталонных задач с критериями, несколько прогонов каждой, доля успехов, шаги и токены в сравнении с прошлым прогоном.

Что почитать дальше