8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
|
"index": 107,
|
|
"slug": "editorial-2025-01-mechanism-ai-coding-assistant",
|
|
"title": "Как проверить код, предложенный AI-помощником",
|
|
"excerpt": "AI-помощник быстро создаёт правдоподобный diff, но не знает контракт конкретного репозитория. Разбираем, как найти нарушение границы, проверить отрицательный путь и принять решение по наблюдаемым данным.",
|
|
"contentHtml": "<p>Самая дорогая ошибка AI-помощника часто выглядит как хороший результат. Diff компилируется. Имена понятны. Форматирование проходит. Тест happy path возвращает ожидаемую строку. После merge выясняется, что невалидный маркер превратился в пустое значение или повторный ключ успел вызвать запись перед возвратом ошибки. Команда платит не только за исправление. Она восстанавливает прежний контракт, ищет затронутых потребителей и разбирает, почему зелёная проверка пропустила проблему.</p>\n<p>Тезис простой: ответ модели — кандидат на изменение, а не доказательство корректности. Чтобы принять такой diff, нужно раздельно проверить область изменения, контракт, отрицательный путь и неизвестные границы. Если хотя бы одна граница не подтверждена, код нельзя считать готовым только потому, что он выглядит естественно.</p>\n<h2>Откуда берётся правдоподобная ошибка</h2>\n<p>Помощник продолжает текст по контексту задачи. Он видит имена функций, соседний код и формулировку запроса. Но репозиторий хранит больше правил, чем попало в контекст: допустимые значения, права, порядок побочных эффектов, требования потребителей, версию внешнего API и смысл пустого результата. Когда правило не выражено явно, кандидат заполняет пробел самым удобным вариантом.</p>\n<p>Так возникает подмена ответственности. Модель выбирает поведение, которое кажется локально разумным. Инженер принимает его за восстановленное требование. Тест подтверждает только тот сценарий, который в него положили. Три разных утверждения сливаются в одно слово «проверено».</p>\n<table><caption>Что именно доказывает каждый слой проверки</caption><thead><tr><th>Слой</th><th>Вопрос</th><th>Что можно подтвердить</th><th>Чего это не доказывает</th></tr></thead><tbody><tr><td>Ответ модели</td><td>Какой код предложен?</td><td>Текст diff и его локальная гипотеза</td><td>Что поведение разрешено контрактом</td></tr><tr><td>Контракт</td><td>Что разрешено и запрещено?</td><td>Вход, результат и forbidden side effect</td><td>Что diff соблюдает правило</td></tr><tr><td>Тест</td><td>Что наблюдалось на конкретной ветке?</td><td>Связь input, output и побочного эффекта</td><td>Что покрыты все потребители и среды</td></tr><tr><td>Ревью</td><td>Почему принят этот scope?</td><td>Пути файлов, владельца и решение о границе</td><td>Что runtime ведёт себя так же</td></tr><tr><td>Неизвестное</td><td>Каких данных нет?</td><td>Честно названную непроверенную границу</td><td>Отсутствие риска</td></tr></tbody></table>\n<h2>Учебный пример: форматирование заметки</h2>\n<p>Ниже — изолированный учебный пример. Он не обращается к модели, базе данных, CI или production-сервису. В контракте есть три различающихся входа. Пустая строка означает отсутствие заметки. Невалидный маркер означает ошибку входа. Эти результаты нельзя объединять без решения владельца интерфейса.</p>\n<pre><code>const cases = [\n { input: 'note', expected: 'formatted-note' },\n { input: 'blank', expected: 'absent-note' },\n { input: 'invalid-marker', expected: 'invalid-note' },\n];\n\nfunction formatNote(input) {\n if (input === 'blank') return 'absent-note';\n if (input === 'invalid-marker') return 'invalid-note';\n return `formatted-${input}`;\n}\n\n// Учебный контракт: invalid-marker нельзя превращать в ''.</code></pre>\n<p>Правдоподобный кандидат может сократить функцию до одной условной ветки и вернуть пустую строку для всех неизвестных значений. Для видимого значения <code>note</code> результат останется правильным. Поэтому один позитивный тест ничего не скажет о границе. Нужны отдельные проверки для <code>blank</code> и <code>invalid-marker</code>. Если функция вызывается перед записью, нужно проверить ещё и запрет записи при ошибочном входе.</p>\n<pre><code>expect(formatNote('note')).toBe('formatted-note');\nexpect(formatNote('blank')).toBe('absent-note');\nexpect(formatNote('invalid-marker')).toBe('invalid-note');\n\n// Отдельное требование для вызывающего кода:\n// invalid-marker не должен вызывать writeNote().</code></pre>\n<p>Смысл примера не в конкретной функции. Он показывает способ чтения diff: для каждой изменённой ветки назовите разрешённый результат, запрещённый результат и наблюдение, которое отличит их. Объяснение модели может описать алгоритм, но не может само назначить смысл отсутствующего поля или разрешить побочный эффект.</p>\n<figure><img src=\"/assets/editorial/2025/ai-coding-assistant-2025-error-matrix.svg\" alt=\"Матрица ошибок при проверке AI-предложенного кода: область изменения, контракт, тестовое свидетельство и неизвестные границы\" loading=\"lazy\" /><figcaption>Схема помогает разделить четыре вопроса до merge. Это учебная модель проверки, а не классификация production-инцидентов и не измерение качества какой-либо модели.</figcaption></figure>\n<h2>Как читать diff по границам</h2>\n<p>Сначала проверьте scope. Сравните каждый изменённый путь с задачей. Соседний полезный hunk не становится разрешённым автоматически. Если помощник добавил обработчик, конфигурацию или вызов в другом модуле, остановите проверку и получите отдельное решение владельца. Иначе локальная оптимизация расширит поверхность изменения незаметно.</p>\n<p>Затем зафиксируйте контракт до обсуждения стиля. Запишите допустимые входы, результат для каждого класса входов и побочный эффект, которого быть не должно. Важны не только возвращаемые значения. Для операции создания записи дубликат может вернуть ошибку и не сделать ни одного write-вызова. Если такой запрет не назван, зелёный тест на ошибку не доказывает безопасность ветки.</p>\n<p>После этого свяжите тест с изменённой веткой. Тест должен называть вход, ожидаемый результат и запрещённое действие. Проверка соседней ветки не покрывает новую ветку. Линтер подтверждает форму кода. Типы подтверждают часть интерфейса. Ни один из них не восстанавливает доменное правило, которое нигде не записано.</p>\n<h2>Симптомы и точечные проверки</h2>\n<table><caption>Диагностическая таблица для candidate diff</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Изменён файл, которого нет в задаче</td><td>Scope расширился по соседнему контексту</td><td>Сверить каждый path с формулировкой и владельцем</td><td>Остановить diff или оформить отдельное решение</td></tr><tr><td>Happy path зелёный, invalid input не описан</td><td>Модель выбрала default вместо контракта</td><td>Добавить таблицу классов входа и негативный тест</td><td>Вернуть код к владельцу контракта</td></tr><tr><td>Ошибка возвращается после write-вызова</td><td>Результат проверили, side effect — нет</td><td>Проверить число и аргументы write-вызовов</td><td>Запретить запись до валидации</td></tr><tr><td>Есть тест, но он не касается changed branch</td><td>Тест подтверждает другую ветку</td><td>Связать branch с конкретным input и expected output</td><td>Добавить focused negative case</td></tr><tr><td>Ревьюер говорит «выглядит безопасно»</td><td>Неизвестная граница принята за отсутствие риска</td><td>Составить список непроверенных consumers, прав и версий</td><td>Сузить обещание или получить недостающее evidence</td></tr></tbody></table>\n<h2>Порядок действий перед принятием</h2>\n<ol><li>Сформулируйте задачу в одном абзаце: какие пути можно менять и какой результат нужен.</li><li>Отделите ответ помощника от решения. Сохраните candidate diff, но не называйте его исправлением.</li><li>Выпишите контракт для каждой изменённой ветки: вход, разрешённый результат и запрещённый side effect.</li><li>Проверьте scope по списку файлов и строк. Каждый выход за границу требует отдельного владельца и решения.</li><li>Запустите focused tests для happy path, пустого значения, невалидного значения и повторной операции, если она возможна.</li><li>Проверьте отрицательный путь по наблюдаемому следу: результат ошибки, количество вызовов и состояние после отказа.</li><li>Отдельно перечислите неизвестное: реальные потребители, совместимость версий, права, конкурентный доступ и нагрузка.</li><li>Примите diff только после того, как reviewer может показать конкретное evidence для каждой изменённой границы.</li></ol>\n<h2>Ограничения метода</h2>\n<p>Такая проверка снижает риск, но не превращает код в гарантированно корректный. Focused test может пропустить редкую последовательность. Ревью может не знать о скрытом потребителе. Статический анализ не моделирует все права и состояния. Само наличие источника или пояснения модели не заменяет запусков и проверки доменного контракта.</p>\n<p>Учебный пример выше намеренно мал. Он не даёт данных о конкретном помощнике, модели, репозитории, скорости разработки или production-ошибках. Для чувствительного кода нужно дополнительно ограничить доступ к контексту, проверить секреты, просмотреть зависимости и согласовать правила хранения исходников. Если нет данных о совместимости или владельце результата, корректное действие — остановиться и назвать пробел, а не заполнить его догадкой.</p>\n<p>Проверяемый критерий готовности можно сформулировать жёстко: для каждого изменённого пути есть владелец и разрешённый scope; для каждой изменённой ветки есть contract row; для отрицательного пути зафиксированы output и forbidden side effect; тест наблюдает именно эту ветку; неизвестные перечислены отдельно. Если один пункт отсутствует, готовность не доказана. Это не означает, что изменение нельзя сделать. Это означает, что решение требует ещё одного факта.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.github.com/en/copilot/responsible-use-of-github-copilot-features\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Responsible use of GitHub Copilot features</a> — ограничения, проверка и ответственность при использовании предложений Copilot.</li><li><a href=\"https://docs.github.com/en/copilot/getting-started/best-practices\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Best practices for using GitHub Copilot</a> — рекомендации по контексту, ревью и тестированию предложенного кода.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/r1/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218 Revision 1: Secure Software Development Framework</a> — официальная рамка практик безопасной разработки и управления риском.</li></ul>"
|
|
}
|