Files
progcode/editorial/agent-rewrites/107.json
T

8 lines
24 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 107,
"slug": "editorial-2025-01-mechanism-ai-coding-assistant",
"title": "Как принять код, предложенный AI-помощником",
"excerpt": "AI-помощник ускоряет черновик, но не принимает решение за инженера. Разбираем, как проверить область изменения, контракт, отрицательный путь и зависимости до merge.",
"contentHtml": "<p>Самая дорогая ошибка AI-помощника выглядит как удачный результат: diff компилируется, имена понятны, линтер зелёный, а happy path возвращает ожидаемое значение. После merge выясняется, что невалидный маркер превратился в пустое значение, новый пакет оказался вымышленным или повторный ключ успел вызвать запись до возврата ошибки. Команда платит не только за исправление. Она восстанавливает прежний контракт и ищет затронутых потребителей.</p>\n<p>Ответ модели нужно считать кандидатом на изменение, а не доказательством корректности. Инженер проверяет четыре независимые границы: что разрешено менять, какое поведение описывает контракт, что происходит на отрицательном пути и какие данные ещё неизвестны. Если одна граница не подтверждена, естественный вид кода не делает его готовым к слиянию.</p>\n<h2>Что именно делает помощник</h2>\n<p>AI-кодинг-помощник продолжает код или предлагает фрагмент по доступному ему контексту: текущему файлу, открытым файлам, запросу и настройкам продукта. Такой контекст может быть полезным, но он не равен архитектуре репозитория. Скрытый consumer, правило авторизации, особый смысл пустого поля или ограничение версии API могут остаться за пределами запроса.</p>\n<p>Официальная документация GitHub описывает Copilot как инструмент, который помогает писать код и тесты, но не заменяет экспертизу пользователя. Для другого помощника нельзя автоматически переносить детали о контексте, фильтрах или хранении данных: их нужно сверять с документацией поставщика и политикой своей организации.</p>\n<table><caption>Какой вопрос закрывает каждый слой проверки</caption><thead><tr><th scope='col'>Слой</th><th scope='col'>Вопрос</th><th scope='col'>Наблюдаемое свидетельство</th><th scope='col'>Чего он не доказывает</th></tr></thead><tbody><tr><td>Область изменения</td><td>Какие пути и строки разрешены?</td><td>Список файлов и hunks совпадает с задачей</td><td>Что новое поведение соответствует бизнес-правилу</td></tr><tr><td>Контракт</td><td>Что разрешено для каждого класса входа?</td><td>Таблица input → output и запрет побочного эффекта</td><td>Что реализация соблюдает контракт</td></tr><tr><td>Тест</td><td>Что реально произошло на выбранном входе?</td><td>Тест проверяет changed branch и состояние после ошибки</td><td>Что проверены все consumers, версии и нагрузки</td></tr><tr><td>Человек</td><td>Кто принимает остаточный риск?</td><td>Reviewer и owner видят diff, ограничения и результат проверок</td><td>Что неизвестных границ не существует</td></tr><tr><td>Инструмент</td><td>Какие автоматические свойства проверены?</td><td>Сборка, lint, security- и dependency-checks</td><td>Что инструмент понял доменный смысл</td></tr></tbody></table>\n<h2>Контракт нужно записать до diff</h2>\n<p>До первого запроса сформулируйте не «сделай функцию», а маленькую карточку решения. В ней должны быть результат, разрешённые пути, запрещённые изменения, классы входов и способ проверки. Это не гарантирует хороший ответ. Зато объяснение модели не сможет незаметно заменить отсутствующее требование правдоподобной догадкой.</p>\n<p>Контракт должен различать хотя бы нормальный, пустой и невалидный вход. Для операции записи добавьте повторную операцию и запрет записи при ошибке. Если неизвестно, означает ли пустая строка «нет значения» или «ошибка», работу нельзя продолжать как будто это одно состояние: сначала нужен владелец контракта.</p>\n<pre><code>const reviewCard = {\n result: 'normalize one invoice key',\n allowedPaths: ['src/parseInvoiceKey.js', 'test/parseInvoiceKey.test.js'],\n forbidden: ['change authorization', 'add a default', 'add a dependency'],\n cases: [\n { input: 'invoice-42', output: 'invoice-42' },\n { input: '', output: 'absent' },\n { input: '?', output: 'invalid', writes: 0 }\n ]\n};\n\n// Любое изменение вне allowedPaths требует нового решения.\n// Значения cases — часть учебного контракта, а не правило вашего API.</code></pre>\n<p>Последняя оговорка важна: пример не восстанавливает контракт реального приложения. В рабочем проекте значения берут из схемы, требований, существующих тестов и поведения совместимых клиентов. Если эти источники расходятся, сначала фиксируют расхождение, а не просят модель выбрать наиболее частый вариант.</p>\n<h2>Как прочитать candidate diff</h2>\n<p>Сначала смотрите не на объяснение помощника, а на область изменения. Лишний файл, новый пакет, изменение прав доступа, удалённый тест или новый default — повод остановиться. Малый размер diff не является доказательством низкого риска: одна строка в фильтре может изменить поведение всех пользователей.</p>\n<p>Затем прочитайте каждую изменённую ветку как условие: какой вход в неё попадает, какой результат выходит и какой побочный эффект запрещён. Отдельно проверьте код до первого write-вызова. Ошибка, возвращённая после записи, не эквивалентна ошибке без записи.</p>\n<pre><code>git diff --name-only --diff-filter=ACMRT HEAD^ HEAD\ngit diff --check HEAD^ HEAD\ngit diff -- src/parseInvoiceKey.js test/parseInvoiceKey.test.js\nrg -n \"parseInvoiceKey|writeInvoice|authorization\" src test\nnpm test -- --runInBand</code></pre>\n<p>Команды предполагают Git и npm-скрипт <code>test</code>; в проекте с pnpm, другой тестовой оболочкой или иной базовой ревизией замените только команду запуска. <code>git diff --check</code> ловит пробелы и конфликтные маркеры, но не проверяет семантику. <code>rg</code> помогает найти consumers, однако поиск по тексту не заменяет анализ динамического вызова или конфигурации.</p>\n<figure><img src='/assets/editorial/2025/ai-coding-assistant-2025-error-matrix.svg' alt='Матрица проверки AI-предложенного diff: область изменения, контракт, тест, человеческое решение и неизвестные границы.' loading='lazy' /><figcaption>Пять независимых вопросов перед merge. Схема показывает порядок рассуждения, а не измерение качества конкретной модели и не описание production-пайплайна.</figcaption></figure>\n<h2>Отрицательный путь важнее красивого happy path</h2>\n<p>Рассмотрим учебный parser, который различает ключ, отсутствие значения и недопустимый маркер. На нормальном входе <code>invoice-42</code> возвращается тот же ключ. Пустая строка означает отсутствие значения. Символ <code>?</code> означает ошибку. Помощник может предложить вернуть пустую строку для любого нераспознанного значения: позитивный тест останется зелёным, но два разных состояния сольются.</p>\n<pre><code>function parseInvoiceKey(input) {\n if (input === '') return { kind: 'absent' };\n if (!/^invoice-[0-9]+$/.test(input)) return { kind: 'invalid' };\n return { kind: 'value', value: input };\n}\n\nconst cases = [\n ['invoice-42', { kind: 'value', value: 'invoice-42' }],\n ['', { kind: 'absent' }],\n ['?', { kind: 'invalid' }]\n];\n\nfor (const [input, expected] of cases) {\n console.log(input, JSON.stringify(parseInvoiceKey(input)) === JSON.stringify(expected));\n}</code></pre>\n<p>Сохраните фрагмент в файл <code>review-example.mjs</code> и запустите <code>node review-example.mjs</code>: он должен вывести три строки с <code>true</code>. Такой результат подтверждает только локальную функцию. Он не доказывает, что вызывающий код не пишет в базу при <code>kind: 'invalid'</code>, что регулярное выражение подходит вашему формату или что параллельные запросы безопасны.</p>\n<p>Поэтому для реального кода тестируйте не только значение. Заставьте mock write-helper считать вызовы и проверьте ноль вызовов для недопустимого входа. Для повторной операции проверьте идемпотентность и состояние после второго запроса. Для authorization проверьте deny-ветку и отсутствие разрешения по умолчанию. Эти проверки должны быть привязаны к конкретному изменённому пути, иначе зелёный тест может относиться к старой реализации.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностика правдоподобных ошибок в предложенном коде</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Вероятная причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Появился файл вне задачи</td><td>Контекст расширился по соседнему коду</td><td>Сверить каждый path с allowedPaths и владельцем</td><td>Удалить hunk или оформить отдельное решение</td></tr><tr><td>Happy path зелёный, invalid input не описан</td><td>Default подменил контракт</td><td>Добавить таблицу классов входа и негативный тест</td><td>Не принимать diff до решения владельца</td></tr><tr><td>Ошибка возвращается после write-вызова</td><td>Проверили output, но не side effect</td><td>Проверить число и аргументы write-helper</td><td>Валидировать до записи и покрыть тестом</td></tr><tr><td>Добавлен пакет с незнакомым именем</td><td>Модель предложила несуществующую или неподходящую зависимость</td><td>Проверить registry, репозиторий, версию, лицензию и lockfile</td><td>Не устанавливать до независимой проверки</td></tr><tr><td>Удалён падающий тест</td><td>Симптом скрыли вместо исправления причины</td><td>Сравнить diff тестов и причину исходного падения</td><td>Вернуть тест или зафиксировать изменение требования</td></tr><tr><td>Линтер и сборка зелёные, результат неверен</td><td>Инструменты не знают доменный смысл</td><td>Сопоставить branch с contract row и consumer</td><td>Добавить поведенческий тест и review owner</td></tr></tbody></table>\n<h2>Проверка зависимости и контекста</h2>\n<p>Новая зависимость заслуживает отдельной проверки. Убедитесь, что пакет существует в нужном реестре, поддерживает используемую версию runtime, имеет приемлемую лицензию и действительно нужен. Проверьте lockfile после установки и просмотрев diff убедитесь, что транзитивные пакеты не расширили риск неожиданно. Название, которое модель уверенно упомянула, не является фактом.</p>\n<p>Точно так же нельзя принимать объяснение «это стандартный API». Откройте документацию именно той версии библиотеки, которой пользуется проект, и найдите сигнатуру, ограничения и пример ошибки. Если помощник сослался на URL, проверьте его отдельно: ссылка должна вести на официальную документацию, а не подтверждать автоматически утверждение из ответа.</p>\n<p>Перед отправкой контекста удалите секреты, токены, персональные данные и ненужные фрагменты истории. Конкретные правила хранения и использования запросов зависят от поставщика, тарифного плана и настроек организации. Их нельзя выводить из поведения интерфейса. Для финансовых, медицинских, юридических и security-critical изменений заранее согласуйте допустимый инструмент и обязательный human review.</p>\n<h2>Порядок действий до merge</h2>\n<ol><li>Опишите симптом и цену ошибки: какой вход, endpoint или пользователь затронут и какое наблюдаемое поведение сейчас неверно.</li><li>Запишите контракт до генерации: нормальный, пустой, невалидный и повторный входы, ожидаемый результат и запрещённый side effect.</li><li>Назовите allowed paths, владельца контракта и список запретов: authorization, public format, dependencies, миграции или другие чувствительные границы.</li><li>Просмотрите список файлов и hunks командой <code>git diff</code> до чтения объяснения модели. Любое расширение scope остановите.</li><li>Сопоставьте каждую изменённую ветку с одной строкой контракта и найдите всех известных consumers через поиск и типы.</li><li>Проверьте отрицательный путь: ошибку, число write-вызовов, состояние после отказа, повтор и права доступа, если они участвуют.</li><li>Запустите focused test, сборку, lint и доступные security/dependency checks. Отдельно выполните <code>git diff --check</code>.</li><li>Попросите reviewer проверить смысл и остаточный риск. Ответ помощника может быть входом в review, но не его результатом.</li><li>Зафиксируйте неизвестное: скрытые consumers, нагрузка, совместимость версий, политика данных и то, что не запускалось в текущем окружении.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Метод снижает риск, но не даёт гарантии. Небольшой тестовый набор не покрывает все комбинации, статический анализ не моделирует каждый runtime-путь, а reviewer может не знать скрытого потребителя. Даже официальные рекомендации конкретного поставщика описывают практику использования его продукта, а не корректность вашего доменного контракта.</p>\n<p>Для критичного изменения нужны дополнительные меры: владелец предметной области, security review, интеграционный тест, проверка миграции и план отката. Если нельзя проверить права, версию API или происхождение зависимости, правильный результат проверки — остановка и явно названный пробел. Не следует компенсировать отсутствие данных более уверенным prompt.</p>\n<p>Учебный parser и команды выше не являются готовым production-рецептом. Они не обращаются к базе, сети, CI или модели и не дают данных о скорости разработки. Их назначение уже: показать, как отделить input, output и forbidden side effect, затем связать их с изменённым кодом. В своём проекте замените значения примера на реальные правила и сохраните их рядом с тестом.</p>\n<h2>Критерий готовности решения</h2>\n<p>Решение можно обсуждать на merge, когда reviewer видит пять связей: каждый changed path разрешён задачей; каждая ветка связана с контрактом; отрицательный путь наблюдает и output, и отсутствие запрещённого side effect; зависимости и контекст проверены независимо; владелец принял остаточный риск. Это критерий достаточности свидетельств, а не обещание безошибочности.</p>\n<p>Если одна связь не видна, действие должно быть конкретным: сузить diff, добавить тест, проверить пакет, привлечь владельца или остановить изменение. Такая дисциплина сохраняет скорость черновика и не передаёт помощнику ответственность за контракт, которую может принять только команда.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://docs.github.com/en/copilot/get-started/best-practices' target='_blank' rel='noopener noreferrer'>GitHub Docs: Best practices for using GitHub Copilot</a> — официальные рекомендации по контексту, формулировке задачи, проверке предложений и автоматическим инструментам.</li><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-специфичных ошибок и человеческого review.</li><li><a href='https://docs.github.com/en/copilot/responsible-use/inline-suggestions' target='_blank' rel='noopener noreferrer'>GitHub Docs: Application card for inline suggestions</a> — ограничения по контексту, неточности, безопасности, совпадениям с публичным кодом и ответственности пользователя.</li><li><a href='https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-generative-artificial-intelligence' target='_blank' rel='noopener noreferrer'>NIST: Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile</a> — официальный профиль управления рисками generative AI; он добровольный и не заменяет требования конкретного проекта.</li><li><a href='https://git-scm.com/docs/git-diff' target='_blank' rel='noopener noreferrer'>Git documentation: git-diff</a> — официальная документация команд просмотра diff и проверки пробельных ошибок.</li></ul>"
}