8 lines
24 KiB
JSON
8 lines
24 KiB
JSON
{
|
||
"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>"
|
||
}
|