{ "index": 107, "slug": "editorial-2025-01-mechanism-ai-coding-assistant", "title": "Как принять код, предложенный AI-помощником", "excerpt": "AI-помощник ускоряет черновик, но не принимает решение за инженера. Разбираем, как проверить область изменения, контракт, отрицательный путь и зависимости до merge.", "contentHtml": "
Самая дорогая ошибка AI-помощника выглядит как удачный результат: diff компилируется, имена понятны, линтер зелёный, а happy path возвращает ожидаемое значение. После merge выясняется, что невалидный маркер превратился в пустое значение, новый пакет оказался вымышленным или повторный ключ успел вызвать запись до возврата ошибки. Команда платит не только за исправление. Она восстанавливает прежний контракт и ищет затронутых потребителей.
\nОтвет модели нужно считать кандидатом на изменение, а не доказательством корректности. Инженер проверяет четыре независимые границы: что разрешено менять, какое поведение описывает контракт, что происходит на отрицательном пути и какие данные ещё неизвестны. Если одна граница не подтверждена, естественный вид кода не делает его готовым к слиянию.
\nAI-кодинг-помощник продолжает код или предлагает фрагмент по доступному ему контексту: текущему файлу, открытым файлам, запросу и настройкам продукта. Такой контекст может быть полезным, но он не равен архитектуре репозитория. Скрытый consumer, правило авторизации, особый смысл пустого поля или ограничение версии API могут остаться за пределами запроса.
\nОфициальная документация GitHub описывает Copilot как инструмент, который помогает писать код и тесты, но не заменяет экспертизу пользователя. Для другого помощника нельзя автоматически переносить детали о контексте, фильтрах или хранении данных: их нужно сверять с документацией поставщика и политикой своей организации.
\n| Слой | Вопрос | Наблюдаемое свидетельство | Чего он не доказывает |
|---|---|---|---|
| Область изменения | Какие пути и строки разрешены? | Список файлов и hunks совпадает с задачей | Что новое поведение соответствует бизнес-правилу |
| Контракт | Что разрешено для каждого класса входа? | Таблица input → output и запрет побочного эффекта | Что реализация соблюдает контракт |
| Тест | Что реально произошло на выбранном входе? | Тест проверяет changed branch и состояние после ошибки | Что проверены все consumers, версии и нагрузки |
| Человек | Кто принимает остаточный риск? | Reviewer и owner видят diff, ограничения и результат проверок | Что неизвестных границ не существует |
| Инструмент | Какие автоматические свойства проверены? | Сборка, lint, security- и dependency-checks | Что инструмент понял доменный смысл |
До первого запроса сформулируйте не «сделай функцию», а маленькую карточку решения. В ней должны быть результат, разрешённые пути, запрещённые изменения, классы входов и способ проверки. Это не гарантирует хороший ответ. Зато объяснение модели не сможет незаметно заменить отсутствующее требование правдоподобной догадкой.
\nКонтракт должен различать хотя бы нормальный, пустой и невалидный вход. Для операции записи добавьте повторную операцию и запрет записи при ошибке. Если неизвестно, означает ли пустая строка «нет значения» или «ошибка», работу нельзя продолжать как будто это одно состояние: сначала нужен владелец контракта.
\nconst 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.\nПоследняя оговорка важна: пример не восстанавливает контракт реального приложения. В рабочем проекте значения берут из схемы, требований, существующих тестов и поведения совместимых клиентов. Если эти источники расходятся, сначала фиксируют расхождение, а не просят модель выбрать наиболее частый вариант.
\nСначала смотрите не на объяснение помощника, а на область изменения. Лишний файл, новый пакет, изменение прав доступа, удалённый тест или новый default — повод остановиться. Малый размер diff не является доказательством низкого риска: одна строка в фильтре может изменить поведение всех пользователей.
\nЗатем прочитайте каждую изменённую ветку как условие: какой вход в неё попадает, какой результат выходит и какой побочный эффект запрещён. Отдельно проверьте код до первого write-вызова. Ошибка, возвращённая после записи, не эквивалентна ошибке без записи.
\ngit 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\nКоманды предполагают Git и npm-скрипт test; в проекте с pnpm, другой тестовой оболочкой или иной базовой ревизией замените только команду запуска. git diff --check ловит пробелы и конфликтные маркеры, но не проверяет семантику. rg помогает найти consumers, однако поиск по тексту не заменяет анализ динамического вызова или конфигурации.
Рассмотрим учебный parser, который различает ключ, отсутствие значения и недопустимый маркер. На нормальном входе invoice-42 возвращается тот же ключ. Пустая строка означает отсутствие значения. Символ ? означает ошибку. Помощник может предложить вернуть пустую строку для любого нераспознанного значения: позитивный тест останется зелёным, но два разных состояния сольются.
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}\nСохраните фрагмент в файл review-example.mjs и запустите node review-example.mjs: он должен вывести три строки с true. Такой результат подтверждает только локальную функцию. Он не доказывает, что вызывающий код не пишет в базу при kind: 'invalid', что регулярное выражение подходит вашему формату или что параллельные запросы безопасны.
Поэтому для реального кода тестируйте не только значение. Заставьте mock write-helper считать вызовы и проверьте ноль вызовов для недопустимого входа. Для повторной операции проверьте идемпотентность и состояние после второго запроса. Для authorization проверьте deny-ветку и отсутствие разрешения по умолчанию. Эти проверки должны быть привязаны к конкретному изменённому пути, иначе зелёный тест может относиться к старой реализации.
\n| Симптом | Вероятная причина | Проверка | Действие |
|---|---|---|---|
| Появился файл вне задачи | Контекст расширился по соседнему коду | Сверить каждый path с allowedPaths и владельцем | Удалить hunk или оформить отдельное решение |
| Happy path зелёный, invalid input не описан | Default подменил контракт | Добавить таблицу классов входа и негативный тест | Не принимать diff до решения владельца |
| Ошибка возвращается после write-вызова | Проверили output, но не side effect | Проверить число и аргументы write-helper | Валидировать до записи и покрыть тестом |
| Добавлен пакет с незнакомым именем | Модель предложила несуществующую или неподходящую зависимость | Проверить registry, репозиторий, версию, лицензию и lockfile | Не устанавливать до независимой проверки |
| Удалён падающий тест | Симптом скрыли вместо исправления причины | Сравнить diff тестов и причину исходного падения | Вернуть тест или зафиксировать изменение требования |
| Линтер и сборка зелёные, результат неверен | Инструменты не знают доменный смысл | Сопоставить branch с contract row и consumer | Добавить поведенческий тест и review owner |
Новая зависимость заслуживает отдельной проверки. Убедитесь, что пакет существует в нужном реестре, поддерживает используемую версию runtime, имеет приемлемую лицензию и действительно нужен. Проверьте lockfile после установки и просмотрев diff убедитесь, что транзитивные пакеты не расширили риск неожиданно. Название, которое модель уверенно упомянула, не является фактом.
\nТочно так же нельзя принимать объяснение «это стандартный API». Откройте документацию именно той версии библиотеки, которой пользуется проект, и найдите сигнатуру, ограничения и пример ошибки. Если помощник сослался на URL, проверьте его отдельно: ссылка должна вести на официальную документацию, а не подтверждать автоматически утверждение из ответа.
\nПеред отправкой контекста удалите секреты, токены, персональные данные и ненужные фрагменты истории. Конкретные правила хранения и использования запросов зависят от поставщика, тарифного плана и настроек организации. Их нельзя выводить из поведения интерфейса. Для финансовых, медицинских, юридических и security-critical изменений заранее согласуйте допустимый инструмент и обязательный human review.
\ngit diff до чтения объяснения модели. Любое расширение scope остановите.git diff --check.Метод снижает риск, но не даёт гарантии. Небольшой тестовый набор не покрывает все комбинации, статический анализ не моделирует каждый runtime-путь, а reviewer может не знать скрытого потребителя. Даже официальные рекомендации конкретного поставщика описывают практику использования его продукта, а не корректность вашего доменного контракта.
\nДля критичного изменения нужны дополнительные меры: владелец предметной области, security review, интеграционный тест, проверка миграции и план отката. Если нельзя проверить права, версию API или происхождение зависимости, правильный результат проверки — остановка и явно названный пробел. Не следует компенсировать отсутствие данных более уверенным prompt.
\nУчебный parser и команды выше не являются готовым production-рецептом. Они не обращаются к базе, сети, CI или модели и не дают данных о скорости разработки. Их назначение уже: показать, как отделить input, output и forbidden side effect, затем связать их с изменённым кодом. В своём проекте замените значения примера на реальные правила и сохраните их рядом с тестом.
\nРешение можно обсуждать на merge, когда reviewer видит пять связей: каждый changed path разрешён задачей; каждая ветка связана с контрактом; отрицательный путь наблюдает и output, и отсутствие запрещённого side effect; зависимости и контекст проверены независимо; владелец принял остаточный риск. Это критерий достаточности свидетельств, а не обещание безошибочности.
\nЕсли одна связь не видна, действие должно быть конкретным: сузить diff, добавить тест, проверить пакет, привлечь владельца или остановить изменение. Такая дисциплина сохраняет скорость черновика и не передаёт помощнику ответственность за контракт, которую может принять только команда.
\n