{ "index": 106, "slug": "editorial-2025-01-field-ai-coding-assistant", "title": "AI-помощник в задаче: как проверить diff перед merge", "excerpt": "Сгенерированный код может пройти happy path и всё равно выйти за границу задачи, изменить контракт или добавить непроверенную зависимость. Разбираем практический gate перед merge: scope, отрицательные ветки, evidence и ограничения человеческого решения.", "contentHtml": "
На ревью приходит небольшой diff, который подготовил AI-помощник. Основной тест зелёный, названия аккуратные, объяснение звучит уверенно. При чтении полного изменения обнаруживается второй файл с правами доступа, новый пакет в lock-файле или ветка ошибки, в которой запись выполняется до проверки входа. Такой diff выглядит локальным только в окне редактора. Цена пропуска — сломанный контракт, утечка данных или откат, который придётся готовить уже после merge.
\nРазберём учебный сценарий: помощник должен нормализовать входящее событие, но предложил изменить ещё и вызывающий код. Главный вывод не зависит от конкретного инструмента: ответ модели — кандидат на изменение, а не доказательство корректности. Перед merge человек должен связать каждый изменённый путь с задачей, каждую изменённую ветку — с контрактом, а каждое существенное утверждение — с воспроизводимой проверкой.
\nГотовность здесь означает не «код выглядит правильно», а возможность показать короткую цепочку evidence. Сначала есть формулировка задачи и список разрешённых областей. Затем — diff, который не вышел за эти области. Для изменённого поведения записаны вход, ожидаемый результат и запрещённый побочный эффект. Наконец, тест или другой запуск действительно проходит через эту ветку в окружении, которое указано в результате проверки.
\nУ gate есть три исхода. approve означает, что reviewer готов принять конкретный diff. request-changes означает, что проблему можно исправить в той же задаче. stop означает, что обнаружена неизвестная граница, security-sensitive изменение или несоответствие контракта; такой случай сначала возвращают владельцу риска.
Первый вопрос к diff — не «хорошо ли написан код», а «имеет ли он право здесь находиться». Для pull request можно получить список изменённых путей и отдельную проверку пробелов или конфликтных маркеров:
\ngit diff --name-status origin/main...HEAD\ngit diff --check origin/main...HEAD\ngit diff --stat origin/main...HEAD\nЭти команды показывают состав изменения и базовые технические дефекты, но не знают смысла задачи. Сопоставьте каждый путь с карточкой: исходный модуль, тест, схема, миграция, lock-файл и конфигурация — это разные виды риска. Если помощник добавил пакет «для удобства», изменил генератор или затронул авторизацию, не прячьте это в общий diff. Уточните границу либо вынесите изменение в отдельную задачу с отдельным владельцем.
\nОсобенно легко пропустить сгенерированные файлы. Они могут появиться после команды сборки, хотя не были частью замысла. Проверьте статус репозитория до и после запуска инструментов, а также правила, по которым артефакты попадают в commit. Для rename и удаления смотрите не только имя нового файла: контракт мог переехать, а тест — остаться привязанным к старому пути.
\nНебольшая функция часто имеет больше одного смысла входа. В учебном обработчике события есть валидное сообщение, отсутствие обязательного поля и неизвестный статус. Если все три случая свести к пустому объекту, вызывающий код может принять ошибку за отсутствие данных и продолжить обработку. Это не косметическая разница: меняется решение на границе системы.
\n| Вход | Ожидаемый результат | Запрещённый эффект | Evidence |
|---|---|---|---|
Поле type известно, payload полный | Нормализованный объект передан дальше | Не создавать запись дважды | Unit test и проверка вызова downstream |
Нет type | invalid-input | Не вызывать запись и повторную доставку | Negative test с проверкой числа вызовов |
type неизвестен | unsupported-type | Не угадывать обработчик по похожему имени | Тест на неизвестное значение и лог причины |
| Payload содержит лишнее поле | Результат зависит от версии контракта | Не молча терять данные, если контракт их запрещает | Сверка схемы и тест сериализации |
Таблица не заменяет спецификацию API. Она делает видимым место, где тест должен отличать результат от побочного эффекта. Проектные значения — имена статусов, политика лишних полей и способ дедупликации — нельзя переносить в другой сервис без сверки его контракта.
\nМинимальный воспроизводимый пример можно выполнить обычным Node.js без внешних пакетов. Он проверяет три входа и отдельно фиксирует, что downstream не вызывается при ошибке:
\nconst calls = [];\n\nfunction normalizeEvent(event) {\n if (!event?.type) return { status: 'invalid-input' };\n if (event.type !== 'invoice.created') return { status: 'unsupported-type' };\n return { status: 'ok', type: event.type, invoiceId: event.invoiceId };\n}\n\nfunction handle(event) {\n const result = normalizeEvent(event);\n if (result.status !== 'ok') return result;\n calls.push(result.invoiceId); // downstream: только после проверки\n return result;\n}\n\nconsole.log(handle({ type: 'invoice.created', invoiceId: 'inv-7' }));\nconsole.log(handle({ invoiceId: 'inv-8' }));\nconsole.log(handle({ type: 'invoice.deleted', invoiceId: 'inv-9' }));\nconsole.log({ calls });\n\n// Ожидается: два отказа и calls только с ['inv-7'].\nЗапустите этот фрагмент как node example.mjs или вставьте его в Node REPL. Важно не само число строк, а наблюдаемый инвариант: ошибки возвращаются до побочного действия. Если сгенерированный вариант вызывает calls.push до проверки статуса, happy path останется зелёным, а отрицательный тест покажет нарушение.
Тест должен быть связан с изменённой веткой, а не просто лежать рядом по имени. Прочитайте assertion: он проверяет значение, тип ошибки, количество вызовов и состояние после отказа? Если проверяется только непустой ответ, неизвестно, что произойдёт с записью, повторной доставкой или метрикой.
\nAI-помощник может предложить несуществующий пакет, устаревший API или библиотеку с неподходящей лицензией. Может также перенести в prompt секрет, персональный идентификатор или фрагмент закрытого кода. Поэтому просмотр diff должен включать не только строки программы.
\nGitHub в руководстве по проверке AI-generated code отдельно выделяет функциональные проверки, контекст и намерение, зависимости, AI-специфичные ошибки и человеческое ревью. Это полезная карта вопросов, но не сертификат качества конкретного diff. NIST SSDF и профиль для generative AI задают практики безопасной разработки и управления рисками; они не подтверждают, что учебный код или конкретная модель безопасны.
\nПри mismatch не исправляйте сразу весь diff новым широким запросом к помощнику. Сначала зафиксируйте наблюдение: какой path лишний, какая строка контракта нарушена, какая проверка отсутствует и какой эффект запрещён. Затем выберите узкое действие.
\nstop.Остановка не означает автоматический rollback. До merge обычно достаточно сузить кандидат и запросить изменения. После поставки нужна отдельная процедура инцидента и отката, зависящая от системы. Не смешивайте эти решения: gate отвечает на вопрос «можно ли принимать этот diff сейчас», а не «как восстановить production после уже случившегося эффекта».
\ngit diff --name-status, прочитайте весь diff и проверьте незапланированные артефакты.git diff --check; сохраните команды и фактический результат.Этот gate снижает риск пропустить очевидное несоответствие, но не доказывает полноту поиска потребителей, отсутствие уязвимостей или корректность бизнес-решения. Unit test может не увидеть реальный сериализатор, конкурентный вызов, миграцию или конфигурацию в другом окружении. Static analysis может найти подозрительный путь, но не понять, что конкретный статус запрещено менять в этом продукте.
\nПример с invoice.created синтетический: он не подключён к очереди, базе и системе прав. Значения статусов, формат идентификатора и правило дедупликации — иллюстрация, а не универсальный API-контракт. Для платежей, персональных данных, авторизации и миграций нужен профильный владелец, проверка реального окружения и принятый в проекте план отката.
Не следует приписывать модели результат, которого не измеряли. Без сравнимых задач и заранее заданной метрики этот процесс не доказывает экономию времени, рост качества или отсутствие дефектов. Он лишь делает решение о конкретном diff проверяемым и оставляет явное место для остановки.
\nDiff можно передавать на решение о merge, когда reviewer показывает: каждый path разрешён задачей; каждая изменённая ветка связана с тестом или другим воспроизводимым evidence; valid, absent и invalid входы не смешаны; запрещённые побочные эффекты проверены; зависимости, данные и права просмотрены; команды и окружение записаны; неизвестные не выданы за доказанный факт. Если хотя бы один пункт не выполнен, корректный результат — запросить конкретную проверку или остановить merge.
\n