{ "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

Что считается готовым diff

\n

Готовность здесь означает не «код выглядит правильно», а возможность показать короткую цепочку evidence. Сначала есть формулировка задачи и список разрешённых областей. Затем — diff, который не вышел за эти области. Для изменённого поведения записаны вход, ожидаемый результат и запрещённый побочный эффект. Наконец, тест или другой запуск действительно проходит через эту ветку в окружении, которое указано в результате проверки.

\n

У gate есть три исхода. approve означает, что reviewer готов принять конкретный diff. request-changes означает, что проблему можно исправить в той же задаче. stop означает, что обнаружена неизвестная граница, security-sensitive изменение или несоответствие контракта; такой случай сначала возвращают владельцу риска.

\n
\"Схема
Gate не оценивает убедительность ответа модели. Он сопоставляет scope, контракт и evidence. Красная ветка означает остановку до merge; схема не запускает CI и не доказывает безопасность production-системы.
\n

Сначала проверьте границу изменения

\n

Первый вопрос к diff — не «хорошо ли написан код», а «имеет ли он право здесь находиться». Для pull request можно получить список изменённых путей и отдельную проверку пробелов или конфликтных маркеров:

\n
git 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

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

\n
Минимальная матрица проверки для обработчика события
ВходОжидаемый результатЗапрещённый эффектEvidence
Поле type известно, payload полныйНормализованный объект передан дальшеНе создавать запись дваждыUnit test и проверка вызова downstream
Нет typeinvalid-inputНе вызывать запись и повторную доставкуNegative test с проверкой числа вызовов
type неизвестенunsupported-typeНе угадывать обработчик по похожему имениТест на неизвестное значение и лог причины
Payload содержит лишнее полеРезультат зависит от версии контрактаНе молча терять данные, если контракт их запрещаетСверка схемы и тест сериализации
\n

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

\n

Положительный тест без отрицательного пути недостаточен

\n

Минимальный воспроизводимый пример можно выполнить обычным Node.js без внешних пакетов. Он проверяет три входа и отдельно фиксирует, что downstream не вызывается при ошибке:

\n
const 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 останется зелёным, а отрицательный тест покажет нарушение.

\n

Тест должен быть связан с изменённой веткой, а не просто лежать рядом по имени. Прочитайте assertion: он проверяет значение, тип ошибки, количество вызовов и состояние после отказа? Если проверяется только непустой ответ, неизвестно, что произойдёт с записью, повторной доставкой или метрикой.

\n

Проверьте зависимости и границы данных

\n

AI-помощник может предложить несуществующий пакет, устаревший API или библиотеку с неподходящей лицензией. Может также перенести в prompt секрет, персональный идентификатор или фрагмент закрытого кода. Поэтому просмотр diff должен включать не только строки программы.

\n
  1. Зависимости. Сверьте имя пакета с реестром и документацией, версию — с поддерживаемым runtime, а лицензию и транзитивные зависимости — с правилами проекта.
  2. Секреты. Уберите токены, cookies, персональные данные и закрытый исходный код из контекста помощника. Проверьте diff, логи и артефакты на случайное попадание таких данных.
  3. Права. Если изменение касается авторизации, фильтрации данных, SQL, файловой системы или внешнего вызова, назначьте ревьюера с соответствующей ответственностью. Статический анализ не заменяет такой review.
  4. Схема и сериализация. Сравните фактический JSON, заголовки, коды ошибок и обязательность полей с владельцем API. Компилируемый тип не доказывает совместимость с потребителем.
  5. Окружение. Запишите версии Node.js, команды и набор тестов. Результат локального запуска нельзя расширять до гарантии другой ОС, базы или feature flag.
\n

GitHub в руководстве по проверке AI-generated code отдельно выделяет функциональные проверки, контекст и намерение, зависимости, AI-специфичные ошибки и человеческое ревью. Это полезная карта вопросов, но не сертификат качества конкретного diff. NIST SSDF и профиль для generative AI задают практики безопасной разработки и управления рисками; они не подтверждают, что учебный код или конкретная модель безопасны.

\n

Что делать при несоответствии

\n

При mismatch не исправляйте сразу весь diff новым широким запросом к помощнику. Сначала зафиксируйте наблюдение: какой path лишний, какая строка контракта нарушена, какая проверка отсутствует и какой эффект запрещён. Затем выберите узкое действие.

\n\n

Остановка не означает автоматический rollback. До merge обычно достаточно сузить кандидат и запросить изменения. После поставки нужна отдельная процедура инцидента и отката, зависящая от системы. Не смешивайте эти решения: gate отвечает на вопрос «можно ли принимать этот diff сейчас», а не «как восстановить production после уже случившегося эффекта».

\n

Порядок проверки перед merge

\n
  1. Сформулируйте одно решение: принять diff, запросить правки или остановить работу.
  2. Запишите разрешённые пути, запреты, владельца задачи и чувствительные области.
  3. Снимите git diff --name-status, прочитайте весь diff и проверьте незапланированные артефакты.
  4. Для каждой изменённой ветки заполните вход, результат, статус или исключение и запрещённый эффект.
  5. Найдите тест, который выполняет ветку, и добавьте отрицательный случай для неверного входа, отсутствующего поля или отказа в доступе.
  6. Проверьте зависимости, схему, сериализацию, права, секреты и окружение.
  7. Запустите заявленные тесты и git diff --check; сохраните команды и фактический результат.
  8. Отдельно перечислите неизвестные. Не превращайте отсутствие evidence в pass и не расширяйте доказательство за пределы выполненного запуска.
\n

Ограничения применимости

\n

Этот gate снижает риск пропустить очевидное несоответствие, но не доказывает полноту поиска потребителей, отсутствие уязвимостей или корректность бизнес-решения. Unit test может не увидеть реальный сериализатор, конкурентный вызов, миграцию или конфигурацию в другом окружении. Static analysis может найти подозрительный путь, но не понять, что конкретный статус запрещено менять в этом продукте.

\n

Пример с invoice.created синтетический: он не подключён к очереди, базе и системе прав. Значения статусов, формат идентификатора и правило дедупликации — иллюстрация, а не универсальный API-контракт. Для платежей, персональных данных, авторизации и миграций нужен профильный владелец, проверка реального окружения и принятый в проекте план отката.

\n

Не следует приписывать модели результат, которого не измеряли. Без сравнимых задач и заранее заданной метрики этот процесс не доказывает экономию времени, рост качества или отсутствие дефектов. Он лишь делает решение о конкретном diff проверяемым и оставляет явное место для остановки.

\n

Проверяемый критерий готовности

\n

Diff можно передавать на решение о merge, когда reviewer показывает: каждый path разрешён задачей; каждая изменённая ветка связана с тестом или другим воспроизводимым evidence; valid, absent и invalid входы не смешаны; запрещённые побочные эффекты проверены; зависимости, данные и права просмотрены; команды и окружение записаны; неизвестные не выданы за доказанный факт. Если хотя бы один пункт не выполнен, корректный результат — запросить конкретную проверку или остановить merge.

\n

Проверяемые источники

" }