AI выдаёт код, который выглядит правильно. В этом и опасность. Компилируется, тесты зелёные, руками кликнул — работает. И ты закрываешь задачу. А через две недели выясняется, что при пустой корзине заказ всё равно создаётся, при повторном нажатии списывается дважды, а «ошибка платежа» тихо превращается в успешный ответ. Код был не сломан — он был правдоподобен. Он делал не то, что нужно, но делал это уверенно.
Приёмка — это фаза, где ты отделяешь «сделано» от «выглядит как сделано». И это отдельная работа, не побочный эффект того, что «я же прочитал diff».
Приёмка — это не code review
Их легко перепутать, потому что обе происходят над одним и тем же PR-ом. Но смотрят они в разные стороны.
Code review отвечает на вопрос «хорошо ли это написано». Контракт на стыках, крайние случаи, тесты на поведение, соответствие методологии — про это отдельный разговор в «Как ревьюить код, который написал AI». Ревью смотрит внутрь кода: чистые ли слои, нет ли выдуманного API, там ли транзакция.
Приёмка отвечает на вопрос «то ли это вообще сделано». Ей всё равно, красивый ли Handler, если он реализует не ту операцию. Можно написать безупречный по всем правилам код, который решает соседнюю задачу. Code review его пропустит — там всё чисто. Приёмка обязана его завернуть.
Разница практическая. Ревью можно частично отдать линтерам и AI-скиллам — качество кода машинно-проверяемо. Приёмку нельзя делегировать целиком: эталон, с которым ты сравниваешь результат, живёт не в коде. Он живёт в контракте.
Эталон приёмки — контракт, а не код
В предыдущей фазе — «От продуктового среза к UCP-контракту» — ты превратил наименьший ценный срез в контракт: границы (что входит, что явно нет), критерии приёмки (наблюдаемые условия «готово»), интерфейсы на стыках. Это и есть эталон. Приёмка — это сверка результата AI с этим документом, пункт за пунктом.
Три оси сверки:
- Критерии приёмки выполнены? Каждый критерий из контракта — это проверяемое утверждение о поведении. «При оплате заказа с недостаточным балансом заказ остаётся в pending, платёж не проходит, пользователь видит понятную ошибку». Не «платёж работает», а конкретное наблюдаемое условие. Пройди по списку — каждый пункт либо подтверждён тестом, либо ты смотришь на него руками.
- Границы соблюдены? AI любит сделать «заодно». Ты просил оплату — он добавил отмену, потому что «логично рядом». Всё, что за границей среза, — не бонус, а незапрошенное поведение, которое ты теперь обязан поддерживать и которое никто не проектировал. Заворачивай.
- Интерфейсы на стыках совпадают? Сигнатуры, имена полей, коды ошибок, формат события — ровно то, что записано в контракте. Тихое переименование поля «ради удобства» ломает всё, что снаружи.
Ключевой сдвиг: ты сравниваешь не «код с тем, как я его себе представлял», а код с документом, написанным до генерации. Если контракта нет, приёмки нет — есть только «мне кажется, похоже на правду», а это ровно то состояние, в котором правдоподобный код проходит.
Где AI врёт правдоподобно
Правдоподобная ошибка — та, которую не видно на успешном пути. AI силён в успешном сценарии: его в обучающей выборке подавляющее большинство. Ошибается он там, где данных было меньше, — и ровно там, где ошибка не бросается в глаза.
Крайние случаи. Пустая коллекция, отсутствие данных, ноль, граница диапазона. Пустой заказ создаётся «успешно», деление на ноль прячется за средним значением по пустому списку, findById вернул «не найдено» и код пошёл дальше с null. Успешный путь при этом безупречен.
Обработка ошибок. Самое типичное правдоподобное враньё. Внешний вызов упал — а catch-блок вернул пустой результат или дефолт. Формально «обработано», по факту ошибка проглочена. Ответ 200, тело правдоподобное, беда — молчаливая. Проверяй не «есть ли try-catch», а «что именно происходит в ветке отказа»: пробрасывается типизированная ошибка, откатывается транзакция, повторяется идемпотентно — или тихо возвращается заглушка.
Скрытые требования. То, что в контракте есть, но в промпте ты не повторил, потому что «это же очевидно». Идемпотентность повторного платежа. Проверка прав: этот пользователь вообще может отменять этот заказ? Согласованность события и записи в одной транзакции. AI не додумывает неявное — он заполняет пропуски средним по выборке. Если требование не проговорено в контракте и не проверяется — считай, что его нет.
Общее у всех трёх: они проходят демонстрацию. Поэтому «я прокликал, работает» — не приёмка, а первый и самый слабый её слой.
Почему тесты пишутся из контракта, а не из кода агента
Если AI написал и код, и тесты к нему, — тест проверяет, что код делает то, что код делает. Тавтология в зелёном. Он зафиксирует текущее поведение, включая баги, и будет их защищать при каждом рефакторинге. Такой тест — регрессия от правильного поведения, а не защита от неправильного.
Тест имеет ценность только когда его эталон независим от реализации. Источник эталона — контракт: критерии приёмки прямо превращаются в тест-кейсы. «Заказ с недостаточным балансом остаётся в pending» — это готовый тест, написанный до того, как агент притронулся к коду. Он проверяет поведение из спеки, а не форму кода агента.
Отсюда практика: критерии приёмки формулируются до генерации и превращаются в тесты до приёмки. Тогда «зелёные тесты» означают «поведение из контракта соблюдено», а не «код не падает». Разница между этими двумя смыслами зелёного — и есть разница между приёмкой и её имитацией. Формализованный, версионируемый эталон, по которому это проверяется, — тема «Executable engineering standard»: правило, которое машина умеет проверить, стоит десяти правил в голове.
Что руками, что автотестами
Разделение простое: что можно выразить как проверяемое условие — автотест; что требует суждения — руками.
Автотестами закрывай:
- Каждый критерий приёмки, сформулированный как наблюдаемое условие.
- Крайние случаи из контракта: null, пустая коллекция, граница, конкурентный доступ.
- Ветки отказа: внешний сервис упал — проверь, что происходит именно то, что задумано.
- Стыки: формат ответа, коды ошибок, поля события — с конкретными значениями, не «не null».
Руками смотри то, где нет объективного эталона:
- Границы среза — сделал ли AI лишнее. Машина не знает, что за границей; знаешь ты.
- Смысл, а не форма — решает ли это исходную продуктовую задачу, а не соседнюю.
- Скрытые требования, которые ты мог не записать в контракт. Приёмка — последний момент их поймать и дописать.
- Правдоподобность ответа при отказе — не выглядит ли «успехом» то, что на деле проглоченная ошибка.
Работает это только с честным эталоном. Нет контракта — приёмка сводится к «на глаз похоже на правду», и правдоподобный код проходит именно потому, что он правдоподобен. Контракт — это то, что переводит приёмку из ощущения в проверку.
Коротко
- Опасность AI-кода не в том, что он падает, а в том, что он выглядит правильным, делая не то.
- Приёмка ≠ code review. Ревью — про качество кода (внутрь). Приёмка — про «то ли сделано» (сверка с контрактом).
- Эталон приёмки — контракт, написанный до генерации: критерии приёмки, границы, интерфейсы. Нет контракта — нет приёмки.
- AI врёт правдоподобно в крайних случаях, ветках отказа и скрытых требованиях — там, где ошибку не видно на успешном пути.
- Тесты пишутся из контракта, а не из кода агента — иначе они защищают баги, а не ловят их.
- Автотест — на всё проверяемое; руками — границы, смысл и скрытые требования.