{ "index": 108, "slug": "editorial-2025-01-practice-ai-coding-assistant", "title": "AI-помощник в разработке: как принять только проверяемый diff", "excerpt": "Сгенерированный код может выглядеть готовым и всё же менять не тот контракт. Разбираем ограниченный контекст, отрицательные условия, проверку diff и критерий готовности до merge.", "contentHtml": "
Разработчик просит помощника исправить обработку ключа. В ответ приходит аккуратный diff: имена понятны, форматирование совпадает с проектом, основной тест проходит. Через день выясняется, что invalid input получает default, а соседний helper меняет право доступа. Симптом заметен не в ответе помощника, а на границе системы: функция вернула допустимую по типам, но неверную по смыслу строку. Цена ошибки — повторная проверка всех затронутых путей, задержка merge и риск выпустить изменение, за которое никто не взял явную ответственность.
\nПроблема начинается до первого ответа. Запрос без контракта просит правдоподобный текст. Он не говорит, какие файлы разрешено менять, какой результат запрещён, кто принимает расширение области и каким наблюдением подтверждается решение. Поэтому полезный фрагмент легко получает лишние полномочия. Правильная граница выглядит так: помощник предлагает candidate diff, инженер задаёт контракт, reviewer проверяет scope, а тест наблюдает изменённую ветку. Ни один из этих шагов нельзя заменить красивым объяснением.
\nУ ограниченной задачи есть пять частей. Сначала формулируют один наблюдаемый результат. Затем называют допустимый контекст: сигнатуру функции, строки контракта и связанные тесты. После этого фиксируют отрицательные условия: не менять authorization, не добавлять default, не трогать публичный формат. Владелец принимает смысл изменения. Наконец, команда называет evidence: список путей, contract cases, focused test и человеческий review.
\nТакой порядок разделяет разные вопросы. Scope отвечает на вопрос что изменилось. Контракт отвечает на вопрос какое поведение допустимо. Тест отвечает на вопрос что произошло на выбранном входе. Review отвечает на вопрос кто принимает остаточный риск. Если один зелёный тест используют как ответ на все четыре вопроса, появляется ложная уверенность.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Полезный hunk сопровождается изменением соседнего файла | В запросе нет allowed paths и запрета на расширение | Сверить каждый changed path с карточкой задачи | Удалить лишний hunk или открыть отдельное решение |
| Happy path проходит, invalid input получает новый default | До первого ответа не записан отрицательный результат | Сравнить input-output rows для normal, blank и invalid | Вернуть diff владельцу контракта |
| Тест зелёный, но side effect вызывается раньше ошибки | Тест наблюдает соседнюю ветку | Проверить вызовы helper на duplicate или forbidden input | Добавить focused negative case |
| Ответ выглядит убедительно, но область риска неизвестна | Explanation приняли за evidence | Перечислить неизвестные consumers, права и версии | Сузить задачу, привлечь owner или остановить merge |
Карточка не улучшает модель сама по себе. Она делает решение читаемым для инженера и reviewer. Для учебного parser достаточно записать: нормализовать один synthetic invoice key; разрешить только функцию parser и таблицу входов и выходов; не менять authorization, public labels и dependencies; назначить владельца контракта; проверить bounded diff и focused cases. Это не настоящий prompt и не доказательство качества модели. Карточка содержит только фиксированные учебные значения.
\nconst task = {\n result: 'normalize one invoice key',\n allowedContext: ['parseInvoiceKey signature', 'fixed input-output rows'],\n forbidden: ['authorization edits', 'new defaults', 'dependency changes'],\n owner: 'synthetic-parser-owner',\n evidence: ['changed paths', 'invalid-input case', 'human review']\n};\n\n// Candidate output is a proposal, not an approval.\n// Any access-policy change stops the review.\nВажен не размер diff, а его связь с задачей. Правильная правка иногда меняет два файла: реализацию и тест. Такой diff остаётся bounded, если оба пути названы, а новое поведение следует из контракта. Небольшой diff может быть опасным, если одна строка меняет default, разрешение или смысл ошибки. При обнаружении новой области нельзя задним числом включить её в исходную просьбу. Нужно остановиться, назвать новый риск и получить отдельное решение.
\nПредположим, parser различает нормальный ключ, пустое значение и недопустимый маркер. Вход invoice-42 даёт invoice-42. Пустой ввод означает отсутствие значения. Маркер ? означает ошибку. Помощник предлагает вернуть пустую строку для любого значения, которое не удалось распознать. Happy path остаётся зелёным. Ошибка скрывается в том, что invalid и absent стали одним состоянием.
const cases = [\n { input: 'invoice-42', expected: 'invoice-42' },\n { input: '', expected: 'absent' },\n { input: '?', expected: 'invalid' }\n];\n\n// Учебный контракт. Он не вызывает реальный parser.\n// Candidate, который сводит '?' к 'absent', отклоняется.\nПроверка должна увидеть не только значение. Если duplicate key запрещает запись, test обязан проверить ноль вызовов write helper. Если изменение касается authorization, нужно проверить deny-ветку и запрет на allow по умолчанию. Код может вернуть правильную ошибку после побочного эффекта. Поэтому expected result и forbidden side effect записывают рядом. Линтер проверяет форму. Unit test наблюдает сценарий. Reviewer связывает сценарий с задачей. Эти evidence не складываются в универсальную гарантию.
\nЭтот подход не превращает помощника в источник истины. Ограниченный prompt не знает скрытых consumers, если их не дали в контексте. Тест не доказывает поведение всех комбинаций. Review может пропустить доменную ошибку. Static analysis не заменяет threat model. Чем ближе изменение к authentication, платежам, персональным данным, миграции схемы или внешнему API, тем меньше допустимая область и тем сильнее нужны domain owner, security review и интеграционные проверки. Иногда разумное действие — не применять такой инструмент к чувствительному участку.
\nНе следует переносить учебный пример в production как готовую библиотеку. Учебный пример не вызывает модель, не обращается к репозиторию, сети, CI или реальным пользователям и не содержит production-метрик. Имена, ключи и outcomes намеренно зафиксированы. Они показывают только форму проверки: сопоставить вход с контрактом, увидеть отрицательный путь и остановить предложение при выходе за границу.
\nОфициальная документация GitHub рекомендует проверять функциональность, контекст, зависимости и AI-specific pitfalls, включая выдуманные API, пропущенные ограничения и удалённые тесты. Это последовательность вопросов, но не сертификат корректности. Профиль NIST для secure software development с generative AI помогает встроить практики безопасности в жизненный цикл. Он не знает доменный контракт и не заменяет решение владельца риска.
\nИзменение готово к решению о merge, если reviewer может показать пять вещей: каждый path связан с задачей; каждый changed branch имеет допустимый и запрещённый результат; focused test наблюдает эту ветку; owner назван и принял остаточный риск; неизвестные перечислены отдельно. Если пункт отсутствует, действие должно быть конкретным: сузить diff, добавить evidence, привлечь владельца или остановить merge.
\nИтог работы с помощником — не удачный ответ и не идеальный prompt. Итог — ограниченное изменение, для которого видно, что изменилось, почему это разрешено и как проверяется отказной путь. Так команда получает скорость черновика без передачи модели ответственности за контракт.
\n