12k
All articles

借助智能体编程时,你仍需掌握的基础知识

掌握审查 AI 生成代码所需的基础知识,了解 HTTP、浏览器行为、状态管理、安全、调试、Git 和架构设计。

OpenReplay Team
OpenReplay Team
借助智能体编程时,你仍需掌握的基础知识

你需要具备足够的编程基础来评判智能体编写的代码——不是为了亲手敲出这些代码,而是因为能运行的代码依然可能存在设计糟糕、不安全或性能低下的问题。

常见的情形是:智能体说已经完成了,页面能正常加载,测试也全部通过,但如果评审者问起,你仍然解释不清这份 diff。这种疑虑是合理的,它通常指向你知识体系中某个具体的短板。

本文不涉及提示词技巧或工具配置。相关内容请参阅如何从 AI 编程工具中获得最佳效果以及 AI 编程技巧与窍门。本文将选取七项 Web 基础知识,每一项都搭配一个真实的智能体常见错误,以及发现该错误所需的知识。

核心要点

  • 浏览器的 fetch() 只在请求本身失败(例如网络错误)时才会 reject,因此从不检查 response.ok 的代码会把 404 或 500 当作成功处理。
  • 任何被打包进前端 JavaScript 的值都是公开的,包括框架暴露给客户端的环境变量。
  • 隐藏按钮不等于授权:服务端必须对每个请求都进行权限校验。
  • git add -p 让你能逐个 hunk 地接受智能体的修改,git revert 则可以在不改写历史的前提下撤销某次提交。
基础知识智能体典型错误发现问题的依据
HTTP返回 200 但响应体是错误信息状态码语义
浏览器监听器未清理Effect 生命周期
状态将服务端数据复制到 store 中单一数据源
安全密钥出现在客户端 bundle 中清楚哪些内容会发送到浏览器
调试错误被吞掉堆栈跟踪、DevTools
Git一次性接受全部修改按 hunk 暂存、revert
架构为一行代码就能解决的事引入新依赖平台原生 API

为什么 HTTP 状态码和缓存依然重要?

状态码和缓存头是客户端、代理和监控工具了解请求结果的依据。一个返回 200 OK 却在响应体中携带错误信息的接口,等于告诉所有客户端、缓存和监控工具:请求成功了。RFC 9110 将 2xx 定义为成功,因此错误追踪和重试机制都不会触发。评审处理函数时,建议把 HTTP 状态码参考放在手边。

客户端也有一个与之对应的陷阱。fetch() 不会因 HTTP 错误状态码而 reject,只有在请求本身失败时才会 reject,所以你必须自己检查 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 写入都有各自的生命周期,忽视它们,浏览器就会让你付出代价。智能体常犯的一个错误是:在一个既没有清理函数、也没有依赖数组的 effect 中添加监听器。React 会在每次渲染后执行该 effect,于是每次渲染都会多添加一个 resize 监听器。修复方法是返回一个清理函数并声明依赖:

useEffect(() => {
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize);
}, [onResize]);

返回的清理函数会在下一次 effect 执行前以及组件卸载时运行。第二个需要识别的浏览器问题模式是布局抖动(layout thrashing):在循环中反复先写入样式、再读取 offsetHeight。每次在写入之后进行读取,都可能强制触发同步布局。解决办法是先集中完成所有读取,再统一执行写入。

状态应该放在哪里?数据应该如何流动?

每一份数据都应该只有一个所有者。把服务端数据复制到客户端 store 中,就会产生第二个数据源,一旦其中一份发生变化而另一份没有同步,两者就会出现偏差。智能体的典型写法是:获取一个列表,将其放进 useState 或 Zustand store,然后在本地修改它,而重新请求时更新的却是另一份副本。

React 关于状态结构的指南明确警告不要重复存储状态。对于服务端数据,应由 TanStack Query 或 SWR 这类缓存层来持有数据并负责失效处理。而对于任何能从现有状态计算得出的值,直接在渲染时派生即可。

安全基础:认证、XSS、密钥与不可信输入

前端安全的关键在于清楚哪些代码运行在浏览器中,哪些运行在服务端。任何被打包进前端 JavaScript 的值都是公开的。在 Next.js 中,环境变量默认仅在服务端可用。添加 NEXT_PUBLIC_ 前缀会在构建时将变量的值内联到客户端 bundle 中,所以如果智能体给某个 API 密钥加上了这个前缀,这个密钥就等于被公开发布了。

在 UI 中隐藏按钮并不等于授权。任何人都可以直接调用 API,因此服务端必须对每个请求进行权限校验。这类缺陷属于 OWASP Top 10:2025 中的 A01 失效的访问控制(Broken Access Control)。

将用户输入赋值给 innerHTML 会让浏览器把它当作标记解析,其中也包括 onerror 这类会执行 JavaScript 的属性。而 textContent 会把同样的输入作为纯文本插入。JSX 默认会对插值字符串进行转义,而 dangerouslySetInnerHTML 会关闭这层保护。更多内容请参阅 OWASP XSS 防护速查表。

如何调试不是你写的代码?

调试意味着找到 bug 的根因,而不是让症状消失。智能体经常这样”修复”错误:用一个 try/catch 把错误包起来吞掉,或者添加可选链来掩盖 undefined 值,而不去追查它从何而来。错误从控制台消失了,但 bug 依然存在。

在接受一个修复之前,先阅读堆栈跟踪、复现 bug,并查看 Chrome DevTools 的 Network 面板,确认请求和响应中实际包含了什么。有些 bug 只会在真实用户那里出现。会话回放(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 会在暂存前逐个询问你每个 hunk 的处理方式,git restore -p 则会逐个丢弃工作区中的 hunk。git revert 会新增一个提交来反转之前的某次提交,因此在共享分支上使用是安全的,而 reset 则会改写历史。

具备足够的架构知识,识别过度设计的方案

好的架构是用项目现有的东西解决当下的问题。智能体常常为了处理一次性任务而引入新依赖,或者搭建一个工厂层或策略层。为格式化一个日期而安装日期库就是典型案例,因为 Intl.DateTimeFormat 原生就能做到。当一份 diff 新增了文件、依赖包或抽象层时,问问自己:现有代码能否用更少的文件完成这项工作?如果可以,就拒绝这个修改,或者要求一个更精简的版本。

让你始终掌控全局的习惯

  • 让智能体解释它的选择。 如果你无法判断它的解释是否合理,那你就找到了下一个需要学习的基础知识。
  • 接受之前先阅读 diff。 每次都审查每一个 hunk。
  • 小改动亲手完成。 亲自敲出修复代码能让你的理解始终保持最新。

测试则能把你的判断转化为每次修改时都会自动运行的检查。

结语

智能体可以编写代码,但你要为代码的行为负责。掌握 HTTP 语义、浏览器渲染模型、状态归属、安全边界、调试、Git,以及保持精简的克制力,你就能拒绝一份糟糕的 diff 并说清楚理由。下次智能体交给你一份修改时,先从阅读 git diff 开始,挑出一个你目前还无法解释的决策。

常见问题

axios 是否也存在和 fetch 一样的 HTTP 错误问题?

不存在。默认情况下,axios 对任何状态码不在 200 到 299 范围内的响应都会 reject 该 promise,因此 404 或 500 会进入 catch 块。这一行为由 validateStatus 选项控制,并且可以被覆盖。不过,如果 API 返回 200 却在响应体中携带错误信息,axios 和 fetch 都仍会成功 resolve,所以服务端必须返回正确的状态码。

把 API 密钥放在 Vite 环境变量中安全吗?

只有在变量不带 VITE_ 前缀时才安全。Vite 会通过 import.meta.env 将带有 VITE_ 前缀的变量暴露给客户端代码,并在构建时进行静态替换,因此它们的值最终会出现在客户端 bundle 中。不带前缀的变量不会暴露给客户端代码。与 Next.js 中的 NEXT_PUBLIC_ 一样,这个前缀的作用是让值选择公开,而不是保护它,所以密钥必须保留在服务端。

当 textContent 不够用时,如何安全地渲染用户提供的 HTML?

在插入之前对 HTML 进行净化。OWASP 推荐使用 DOMPurify:将字符串传入 DOMPurify.sanitize,再将结果用于 innerHTML 或 dangerouslySetInnerHTML。净化之后不要再修改输出结果,因为修改可能会重新引入危险标记。请及时更新净化库,因为新的绕过手段会不断被发现。富文本编辑器和用户编写的 Markdown 是需要这样处理的典型场景。

为什么我的 React effect 在开发环境中会执行两次?

在开发环境中,React 严格模式(Strict Mode)会额外挂载一次组件,以暴露缺少清理逻辑的 effect。这在生产环境中不会发生。如果第二次执行导致了重复的监听器、请求或订阅,说明该 effect 需要一个能撤销其设置操作的清理函数。移除严格模式,或者添加 ref 守卫来跳过第二次执行,都只是在掩盖 bug。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.