Базовые знания, без которых не обойтись при программировании с агентами
Уверенно проверяйте код, созданный ИИ. Разберитесь в основах веб-разработки: HTTP, браузере, состоянии, безопасности, отладке, Git и архитектуре.
Базовые знания программирования нужны, чтобы оценивать код, написанный агентом, а не чтобы набирать его самому: работающий код всё равно может быть плохо спроектирован, небезопасен или медленен.
Типичная ситуация: агент сообщает, что задача выполнена, страница загружается, тесты проходят, а вы всё равно не смогли бы объяснить дифф, если бы о нём спросил ревьюер. Такое сомнение обоснованно. Обычно оно указывает на конкретный пробел в ваших знаниях.
Эта статья не о промптинге и не о настройке инструментов. Об этом читайте в материалах о том, как получать лучшие результаты от AI-инструментов для программирования, и советах и приёмах программирования с ИИ. Здесь же разобраны семь основ веб-разработки: для каждой приведена реалистичная ошибка агента и знания, которые помогут её заметить.
Ключевые выводы
- Браузерный
fetch()отклоняет промис, только если не удался сам запрос, например из-за сетевой ошибки. Поэтому код, который не проверяетresponse.ok, считает ответы 404 и 500 успешными. - Любое значение, попавшее в бандл фронтенд-JavaScript, публично, в том числе переменные окружения, которые фреймворк открывает клиенту.
- Скрытая кнопка — это не авторизация: сервер обязан проверять права доступа при каждом запросе.
git add -pпозволяет принимать изменения агента по одному фрагменту (hunk), аgit revertотменяет коммит, не переписывая историю.
| Основа | Типичная ошибка агента | Что помогает её выявить |
|---|---|---|
| HTTP | Код 200 с ошибкой в теле ответа | Семантика кодов состояния |
| Браузер | Обработчик событий без очистки | Жизненный цикл эффектов |
| Состояние | Серверные данные скопированы в стор | Единый источник истины |
| Безопасность | Секрет в клиентском бандле | Понимание того, что уходит в браузер |
| Отладка | Проглоченная ошибка | Стек вызовов, DevTools |
| Git | Принятие всех изменений разом | Поблочное индексирование, revert |
| Архитектура | Новая зависимость ради одной строки | API платформы |
Почему коды состояния HTTP и кеширование по-прежнему важны?
По кодам состояния и заголовкам кеширования клиенты, прокси и системы мониторинга узнают, что произошло с запросом. Эндпоинт, который возвращает 200 OK с сообщением об ошибке в теле, сообщает каждому клиенту, кешу и инструменту мониторинга, что запрос выполнен успешно. RFC 9110 определяет коды 2xx как успешные, поэтому ни отслеживание ошибок, ни повторные попытки не сработают. Держите под рукой справочник кодов состояния HTTP, когда ревьюите обработчики.
На стороне клиента есть аналогичная ловушка. fetch() не отклоняет промис при HTTP-кодах ошибок — только когда не удался сам запрос, поэтому проверять Response.ok нужно самостоятельно:
const res = await fetch('/api/save', { method: 'POST', body });
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
Кроме того, агенты часто опрашивают данные, которые почти не меняются. Для конфигурации или справочных списков заголовок Cache-Control (определён в RFC 9111) вместе с ревалидацией по ETag (с ответом 304 Not Modified, определённым в RFC 9110) избавляет от лишних запросов. Данные, которые действительно меняются в реальном времени, — отдельная задача.
Как браузер выполняет рендеринг и обрабатывает события?
У обработчиков событий и операций записи в DOM есть жизненный цикл, и браузер заставляет платить за его игнорирование. Типичная ошибка агента — добавить обработчик внутри эффекта без функции очистки и без массива зависимостей. React запускает такой эффект после каждого рендера, так что каждый рендер добавляет ещё один обработчик resize. Исправление — вернуть функцию очистки и объявить зависимости:
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
Возвращаемая функция очистки выполняется перед следующим запуском эффекта и при размонтировании. Второй паттерн, который стоит замечать, — layout thrashing: цикл, который снова и снова записывает стиль, а затем читает offsetHeight. Каждое чтение после записи может вызывать синхронный пересчёт раскладки. Решение — сначала выполнить все чтения, а затем все записи.
Где должно храниться состояние и как должны передаваться данные?
У каждого фрагмента данных должен быть один владелец. Копирование серверных данных в клиентский стор создаёт второй источник истины, и две копии расходятся, как только одна из них меняется без другой. Типичный вариант от агента: загрузить список, положить его в useState или стор Zustand и редактировать локально, в то время как повторный запрос обновляет другую копию.
Рекомендации React по структуре состояния предостерегают от дублирования состояния. Серверными данными должен владеть слой кеширования, например TanStack Query или SWR, который также отвечает за инвалидацию. Всё, что можно вычислить из существующего состояния, вычисляйте во время рендера.
Основы безопасности: аутентификация, XSS, секреты и недоверенный ввод
Безопасность фронтенда держится на понимании того, какой код выполняется в браузере, а какой — на сервере. Любое значение, попавшее в бандл фронтенд-JavaScript, публично. В Next.js переменные окружения по умолчанию доступны только на сервере. Префикс NEXT_PUBLIC_ встраивает значение переменной в клиентский бандл на этапе сборки, поэтому агент, добавивший этот префикс к API-ключу, фактически опубликовал ключ.
Скрыть кнопку в интерфейсе — не значит реализовать авторизацию. Кто угодно может обратиться к API напрямую, поэтому сервер обязан проверять права доступа при каждом запросе. Эта уязвимость относится к категории OWASP Top 10:2025, A01 Broken Access Control.
Присваивание пользовательского ввода свойству innerHTML заставляет браузер разбирать его как разметку, включая атрибуты вроде onerror, которые выполняют JavaScript. textContent вставляет тот же ввод как обычный текст. JSX по умолчанию экранирует интерполируемые строки, а dangerouslySetInnerHTML эту защиту отключает. Остальное описано в OWASP XSS Prevention Cheat Sheet.
Как отлаживать код, который написали не вы?
Отладка — это поиск причины бага, а не устранение симптома. Агенты часто «исправляют» ошибку, оборачивая её в try/catch, который её проглатывает, или добавляя опциональную цепочку, которая скрывает значение undefined вместо того, чтобы выяснить, откуда оно взялось. Ошибка исчезает из консоли, но баг остаётся.
Прежде чем принять исправление, прочитайте стек вызовов, воспроизведите баг и загляните во вкладку Network в Chrome DevTools, чтобы увидеть, что на самом деле содержали запрос и ответ. Некоторые баги проявляются только у реальных пользователей. Session replay показывает, как код, написанный агентом, ведёт себя у этих пользователей в продакшене: например, цикл повторных попыток, который заваливает запросами эндпоинт, или форма, которая молча не отправляется. Именно там часто и становятся видны пробелы в базовых знаниях.
Git на уровне, достаточном для ревью и отката изменений агента
С помощью Git вы принимаете часть работы агента и отменяете остальное. Агенты склонны вносить масштабные правки, и если принять их разом, среди них затеряются несвязанные изменения. Четыре команды покрывают большую часть того, что нужно:
git diff # read every change
git add -p # stage hunk by hunk
git restore -p src/x.ts # discard unwanted hunks
git revert <commit> # undo a committed change
git add -p спрашивает о каждом фрагменте, прежде чем добавить его в индекс, а git restore -p по одному отменяет фрагменты в рабочем дереве. git revert создаёт новый коммит, отменяющий предыдущий. Поэтому его безопасно использовать в общих ветках, тогда как reset переписывает историю.
Архитектура на уровне, достаточном, чтобы распознать переусложнённое решение
Хорошая архитектура решает сегодняшнюю задачу средствами, которые в проекте уже есть. Агенты часто тянутся к новой зависимости или к слою фабрик и стратегий ради разовой задачи. Типичный случай — установка библиотеки для работы с датами, чтобы отформатировать одну-единственную дату, хотя Intl.DateTimeFormat делает это нативно. Когда дифф добавляет файлы, пакеты или абстракции, спросите себя, не справился бы с задачей существующий код с меньшим числом файлов. Если да, отклоните изменение или попросите сделать его компактнее.
Привычки, которые помогают сохранять контроль
- Просите агента объяснять свои решения. Если вы не можете оценить объяснение, значит, вы нашли тему, которую стоит изучить следующей.
- Читайте дифф перед тем, как принять изменения. Просматривайте каждый фрагмент, каждый раз.
- Вносите небольшие изменения вручную. Когда вы сами набираете исправление, ваше понимание кода остаётся актуальным.
Тесты превращают ваши суждения в проверки, которые запускаются при каждом изменении.
Заключение
Агенты могут писать код, но отвечаете за его поведение вы. Освойте семантику HTTP, модель рендеринга браузера, принципы владения состоянием, границы безопасности, отладку, Git и умение сдерживать разрастание решений — и вы сможете отклонить плохой дифф и объяснить почему. В следующий раз, когда агент передаст вам изменение, начните с git diff и найдите одно решение, которое вы пока не можете объяснить.
Часто задаваемые вопросы
Есть ли у axios та же проблема с HTTP-ошибками, что и у fetch?
Нет. По умолчанию axios отклоняет промис для любого кода состояния вне диапазона 200–299, поэтому ответы 404 и 500 попадают в блок catch. Этим поведением управляет опция validateStatus, которую можно переопределить. Однако API, возвращающий 200 с сообщением об ошибке в теле, по-прежнему успешно разрешается и в axios, и в fetch, поэтому сервер должен отправлять корректные коды состояния.
Безопасно ли хранить API-ключи в переменных окружения Vite?
Только если у переменной нет префикса VITE_. Vite открывает клиентскому коду переменные с префиксом VITE_ через import.meta.env и статически подставляет их на этапе сборки, поэтому их значения оказываются в клиентском бандле. Переменные без префикса клиентскому коду недоступны. Как и NEXT_PUBLIC_ в Next.js, этот префикс означает согласие на публикацию значения, а не его защиту, поэтому секреты должны оставаться на сервере.
Как безопасно отобразить HTML от пользователя, если textContent не подходит?
Санитизируйте HTML перед вставкой. OWASP рекомендует DOMPurify: пропустите строку через DOMPurify.sanitize и используйте результат с innerHTML или dangerouslySetInnerHTML. Не изменяйте очищенный результат после санитизации, поскольку правки могут вернуть опасную разметку. Своевременно обновляйте библиотеку-санитайзер: новые способы обхода обнаруживаются регулярно. Типичные случаи, когда это нужно, — визуальные редакторы текста и Markdown, написанный пользователями.
Почему мой эффект в React выполняется дважды в режиме разработки?
В режиме разработки React Strict Mode монтирует компоненты дополнительный раз, чтобы выявить эффекты без функции очистки. В продакшене этого не происходит. Если второй запуск создаёт дублирующиеся обработчики, запросы или подписки, эффекту нужна функция очистки, которая отменяет его настройку. Отключение Strict Mode или добавление проверки через ref, чтобы пропустить второй запуск, лишь скрывает баг.