8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 106,
|
||
"slug": "editorial-2025-01-field-ai-coding-assistant",
|
||
"title": "AI-помощник перед merge: как проверить diff, а не впечатление",
|
||
"excerpt": "Сгенерированный diff может пройти happy path и всё равно выйти за scope, изменить контракт или не иметь теста для изменённой ветки. Разбираем проверяемый gate перед merge: граница задачи, отрицательный путь, evidence и критерий готовности.",
|
||
"contentHtml": "<p>Перед merge лежит небольшой сгенерированный diff. Он исправляет видимый симптом, тест рядом зелёный, а код выглядит аккуратно. Через час выясняется, что правка изменила ветку отказа, добавила доступ к данным или превратила ошибочный вход в допустимый. Цена ошибки — не только откат. Нужно найти затронутый контракт, остановить выпуск, проверить уже собранные артефакты и вернуть доверие к проверке.</p>\n<p>Тезис простой: ответ AI-помощника — кандидат на изменение, а не доказательство корректности. Перед merge нужно проверить три границы: diff меняет только разрешённый scope, сохраняет контракт и имеет evidence для каждой изменённой ветки. Линтер и успешный happy path закрывают лишь часть вопросов.</p>\n<h2>Что именно проверяет gate</h2>\n<p>Verification gate отделяет candidate diff от решения человека. Он не одобряет pull request автоматически и не обещает безопасность. Он собирает четыре наблюдаемых поля: задачу, разрешённые пути, условия контракта и проверку результата. Если поле не заполнено, verdict должен остановиться на <code>stop</code>, а не превращаться в «скорее всего, всё хорошо».</p>\n<pre><code>const review = {\n task: 'normalize invoice key',\n allowedPaths: ['src/invoice/normalizeKey.ts'],\n forbiddenEffects: ['change authorization', 'write on invalid input'],\n evidence: [\n { input: 'valid-key', expected: 'valid-key', writes: 0 },\n { input: 'invalid-key', expected: 'error', writes: 0 },\n ],\n};\n\nfunction canMerge(candidate, contract) {\n const pathsOk = candidate.paths.every((path) => contract.allowedPaths.includes(path));\n const behaviorOk = candidate.invalidInput.writes === 0;\n return pathsOk && behaviorOk;\n}\n\n// Учебный пример: он не запускает CI и не проверяет реальный репозиторий.\n// false означает остановку, а не разрешение исправить scope молча.</code></pre>\n<p>В примере <code>canMerge</code> показывает только форму решения. Реальный проект должен связать каждое условие с тестом, ревьюером и фактическим diff. Нельзя считать этот фрагмент защитой доступа, транзакций или всех потребителей API.</p>\n<h2>Три способа принять неверный diff</h2>\n<h3>Scope mismatch</h3>\n<p>Задача просит нормализовать ключ счёта. Помощник меняет parser и соседний <code>accessDecision</code>. Изменение авторизации может быть технически небольшим, но его риск не равен риску форматирования строки. Если path не назван в задаче, его нельзя включать в тот же merge под видом удобного сопутствующего исправления.</p>\n<p>Правильный отрицательный путь — остановить diff и удалить лишний hunk либо вынести его в отдельный запрос. Не нужно объяснять расширение scope красивым комментарием. Сначала возвращают границу, затем отдельно обсуждают новую задачу и её владельца.</p>\n<h3>Contract mismatch</h3>\n<p>Контракт различает три входа: непустой ключ, пустую строку и недопустимый маркер. Первый нужно нормализовать. Второй означает отсутствие значения. Третий возвращает ошибку. Сгенерированный код может свести последние два случая к пустой строке. Такой код короче и проходит happy path, но теряет смысл ошибки.</p>\n<p>Проверка должна сравнивать не только типы и снимки ответа. Для каждого входа нужно назвать ожидаемый результат и запрещённый побочный эффект. Если invalid input не должен писать в базу, это условие обязано появиться в тесте. Иначе тест докажет лишь то, что функция что-то вернула.</p>\n<h3>Test-evidence mismatch</h3>\n<p>Тест может быть зелёным и не относиться к изменённой ветке. Например, он проверяет уникальный ключ, а diff добавляет запись до того, как обработает duplicate key. На duplicate-ветке результат должен быть ошибкой, а write helper не должен вызываться. Наличие файла с тестом не доказывает эту связь.</p>\n<p>Для каждой changed branch запишите три значения: input, expected output и forbidden side effect. Если ветка не имеет отдельного наблюдаемого условия, её следует считать непроверенной. Это правило действует и для кода, написанного человеком.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Типовые ошибки перед merge</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>В diff появился файл, которого нет в задаче</td><td>Помощник расширил scope по соседнему контексту</td><td>Сопоставить каждый changed path с карточкой задачи</td><td>Остановить merge и вынести лишний hunk в отдельную задачу</td></tr><tr><td>Happy path зелёный, invalid input принят</td><td>Код изменил смысл ошибки или absence</td><td>Прогнать фиксированные valid, absent и invalid входы</td><td>Вернуть различие в контракт и добавить negative test</td></tr><tr><td>Тест есть, но побочный эффект не проверен</td><td>Тест видит output, но не вызовы write helper</td><td>Проверить число вызовов и порядок до error</td><td>Зафиксировать forbidden side effect и повторить тест</td></tr><tr><td>Линтер прошёл, поведение неизвестно</td><td>Правило стиля не видит доменный контракт</td><td>Сверить ветки, статус, данные и права отдельно</td><td>Не считать lint verdict доказательством correctness</td></tr><tr><td>Исправление выглядит локальным, но меняет доступ</td><td>Соседний security-sensitive код попал в контекст</td><td>Показать владельцу access path и отрицательные случаи</td><td>Остановить merge до отдельного security review</td></tr></tbody></table></div>\n<h2>Иллюстрация границы</h2>\n<figure><img src=\"/assets/editorial/2025/ai-coding-assistant-2025-verification-gate.svg\" alt=\"Схема проверки AI-assisted diff: prompt-card, bounded diff, contract rows и focused tests проходят три проверки и только затем ведут к решению человека\" loading=\"lazy\" /><figcaption>Gate проверяет scope, контракт и evidence. Красная ветка означает stop до merge. Схема учебная: она не запускает CI, не выполняет rollback и не доказывает свойства production-системы.</figcaption></figure>\n<p>Схема полезна как напоминание о порядке. Сначала фиксируют границу задачи. Затем читают diff. После этого проверяют контракт и тест. Решение человека появляется в конце. Если начать с впечатления от кода, предыдущие вопросы легко пропустить.</p>\n<h2>Порядок проверки перед merge</h2>\n<ol><li><strong>Сформулируйте задачу.</strong> Запишите цель, разрешённые файлы, запреты, владельца решения и ожидаемые evidence. Уберите секреты и персональные данные из контекста помощника.</li><li><strong>Прочитайте весь diff.</strong> Проверьте каждый path, импорт, условие, изменение схемы и вызов внешнего сервиса. Не ограничивайтесь hunk, который объясняет исходный симптом.</li><li><strong>Сверьте контракт.</strong> Назовите valid, absent и invalid входы. Для каждого укажите результат, статус или исключение и запрещённые побочные эффекты.</li><li><strong>Привяжите тест к ветке.</strong> Найдите тест, который действительно выполняет изменённое условие. Проверьте output, вызовы helper, права и состояние после ошибки.</li><li><strong>Прогоните отрицательный путь.</strong> Передайте неизвестное значение, пропущенное поле, неверный тип, duplicate или отказ в доступе — в зависимости от контракта. Ожидайте точный отказ, а не только отсутствие падения.</li><li><strong>Проверьте неизвестные.</strong> Отдельно запишите, чего не видно: всех ли потребителей нашли, совпадает ли runtime-конфигурация, проверены ли миграции, права и конкурентные вызовы. Unknown не равен pass.</li><li><strong>Примите ограниченное решение.</strong> Если всё доказано, человек одобряет конкретный diff. Если нет, reviewer запрашивает изменения, сужает scope или открывает отдельный риск. Не расширяйте разрешение молча.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Ни один слой не гарантирует correctness для всей системы. Модель может не знать скрытый consumer. Unit test может не увидеть реальный сериализатор. Линтер не знает, что <code>403</code> нельзя превращать в повторяемый <code>400</code>. CI подтверждает только запущенные сценарии и окружение, в котором они запустились.</p>\n<p>Учебный код выше не заменяет review, contract test, security analysis, интеграционный запуск и процедуру отката. Он также не измеряет качество модели и не подтверждает экономию времени. В статье нет production-метрик и результатов реального внедрения. Примеры нужны только для проверки границы: что изменилось, какой вход это наблюдает и какой эффект запрещён.</p>\n<p>Если помощник удалил тест, ослабил проверку прав, изменил миграцию или добавил неизвестную зависимость, остановка важнее скорости. Верните diff к минимальному scope. Сохраните причину отказа. Повторно запросите только тот фрагмент, который можно проверить отдельным контрактом.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Diff готов к решению о merge, когда reviewer может показать: каждый изменённый path разрешён задачей; для каждой изменённой ветки есть вход, ожидаемый результат и проверка запрещённого эффекта; отрицательные случаи возвращают согласованный отказ; lint и тесты выполнены в заявленном окружении; неизвестные записаны отдельно и не выданы за pass. Для изменения доступа, схемы или внешнего контракта нужен отдельный владелец соответствующего риска.</p>\n<p>Проверяемый результат — не фраза «код выглядит правильно». Это короткий набор ссылок на diff, contract rows и focused tests. Если хотя бы одна changed branch не связана с evidence, merge не готов. Такой критерий одинаково применим к ручному и AI-assisted коду.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://docs.github.com/en/copilot/tutorials/review-ai-generated-code\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Review AI-generated code</a> — официальная рекомендация проверять AI-generated code, его контекст и тесты; источник не подтверждает результаты этого учебного материала.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/a/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218A: Secure Software Development Practices for Generative AI and Dual-Use Foundation Models</a> — официальный профиль практик безопасной разработки для AI-контекста; он задаёт рекомендации, а не доказательство конкретного production-эффекта.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218: Secure Software Development Framework</a> — официальная рамка secure-development practices и общего языка риска; она не заменяет проверку доменного контракта.</li></ul>"
|
||
}
|