前面两篇文章把开发 Agent 拆成了模型、中间层和应用层三层,又讲了中间层的上下文管理、工具调用和权限设计。这篇文章换一个角度,用一个具体的开发任务从头走到尾,看看这些概念在实际工作中是怎么协同的。
案例任务是这样的。一个 Tauri 桌面应用,React 前端加 Rust 后端,数据存在 SQLite 里。需要增加一个数据库管理页面,支持导入 CSV 文件、搜索数据库记录、编辑字段,并补充测试。这不算小需求。CSV 导入涉及文件选择对话框、编码检测、字段映射、数据校验、批量写入、错误行收集和进度展示。搜索涉及多条件组合、模糊匹配、分页、排序。编辑涉及行内编辑或弹窗编辑、乐观锁或版本检查、保存校验。测试涉及单元测试和集成测试,前后端各一套。一个中级工程师大概需要两到三天。Agent 取决于模型能力、中间层配置和任务复杂度,可能 20 分钟,可能两个小时,也可能中途卡住需要人接手。

输入:一句需求远远不够

任务从一句自然语言开始:增加一个数据库管理页面,支持 CSV 导入、搜索和编辑。只把这句话发给 Agent,它能做的事非常有限。它不知道技术栈,不知道数据库里有什么表,不知道项目用了什么 UI 框架和路由方案,不知道已有的代码风格和抽象模式。
真实场景中,Agent 能获得的信息来自多个来源。
项目目录结构是第一个来源。Agent 第一步几乎总是列出顶层目录。看到 Cargo.toml 和 src-tauri 就知道是 Rust 后端,看到 package.json 和 src 就知道是 React 前端,看到 vite.config.ts 就知道构建工具是 Vite,看到 vitest.config.ts 就知道测试框架是 Vitest。这些信息不需要开发者手动告诉 Agent,读文件名和配置文件就能自动推断。
项目规范文件是第二个来源。AGENTS.mdCLAUDE.md、.cursorrules,这些文件是开发者写给 Agent 的团队手册。里面可能写了:使用 TypeScript 严格模式、组件放在 src/components 目录下、每个组件对应一个 CSS Module 文件、使用 React Router v6 的路由方案、Tauri command 放在 src-tauri/src/commands 目录下、每个 command 函数需要加上权限注解。规范文件的质量直接影响 Agent 的输出。一个写清楚不要在组件中直接调用 Tauri invoke、封装到 src/api 目录下的 service 函数中的规范文件,能让 Agent 生成的代码直接符合项目架构。空的规范文件,Agent 只能靠猜,可能会把 API 调用直接写在组件里,因为这在很多教程和模板中是常见做法。
已有代码是第三个也是信息量最大的来源。Agent 会搜索项目中已有的数据库相关代码。在 Tauri 项目中,通常是 Rust 端的 command 函数,用 #[tauri::command] 宏标记,前端通过 @tauri-apps/api 的 invoke 函数调用。Agent 会找到这些已有的 command,理解它们的模式:参数怎么定义、返回值怎么序列化、错误怎么处理。它还会搜索已有的页面组件,理解 UI 怎么组织:是用 React Router 的 lazy loading 还是直接 import,页面组件放在哪个目录下,有没有共享的 layout 组件,状态管理用的是 Zustand 还是 Redux 还是 Context。
数据库 schema 是第四个来源。对于 SQLite 项目,Agent 可以通过读取 Rust 端的数据库初始化代码来理解表结构,通常是创建表的 SQL 字符串或 ORM migration 文件。它需要知道有哪些表、每个表的字段名和类型、主键和外键约束、索引情况。没有这些信息,生成的 SQL 查询就是盲写的,字段名可能不对,类型可能不匹配。
设计截图、错误日志、API 文档在特定场景下也是重要输入。有 UI 设计稿截图的话,Agent 可以基于截图生成更准确的页面布局。遇到特定 bug 时,把错误堆栈发给 Agent 比口头描述程序崩了有效得多。
Agent 不会自动知道哪些信息是相关的。它依赖中间层的上下文管理策略来决定先读什么、后读什么、读到什么程度算够。这个策略本身可配置,配置好坏直接影响结果。一个只读了 package.json 就开始写代码的 Agent,和一个读完了 package.json、Cargo.toml、tsconfig.json、已有相似页面、数据库 schema、AGENTS.md 之后才开始写代码的 Agent,产出质量不在一个级别上。

理解项目

有了信息来源,下一步是理解项目结构。这一步有经验的开发者会跳过,因为他们脑子里已经有项目的心智模型。但是Agent 没有这个模型,这一步不能省。
Agent 的探索路径通常是这样的。
先看顶层结构。读 Cargo.toml 确认 Rust 依赖(tauri、rusqlite、serde、csv 等),读 package.json 确认前端依赖(react、react-router-dom、@tauri-apps/api、zustand、tanstack/react-table 等),读 tsconfig.json 确认 TypeScript 配置(路径别名、严格模式、目标版本)。
然后找入口文件。前端入口通常是 src/main.tsx 或 src/App.tsx,后端入口是 src-tauri/src/main.rs 或 src-tauri/src/lib.rs。从入口文件出发,追踪路由定义:有哪些页面、URL 结构怎样、有没有嵌套路由、路由守卫怎么写的。
接着找数据库层。在 Tauri 项目中,数据库连接通常在 Rust 端初始化,通过 Tauri 的 setup hook 或 lazy initialization 创建连接池。Agent 读取数据库初始化代码,理解连接管理方式(单连接还是连接池)、表创建逻辑、migration 策略。
然后读已有的页面组件,理解 UI 模式。已有页面是函数组件还是类组件、用 CSS Module 还是 Tailwind、表单组件是自己封装的还是直接用 antd 或 mui、错误状态和加载状态怎么处理、空数据状态怎么展示。
最后读测试文件。项目用什么测试框架、测试文件放在哪里(和源文件同目录还是单独 tests 目录)、测试命名规范、mock 策略。Agent 新写的测试需要和已有测试在风格和结构上保持一致。
这个过程不是线性的。Agent 可能在读路由文件时发现一个没见过的 layout 组件,跳过去读它。可能在读数据库层时发现用了一个自定义错误类型,需要理解那个类型的定义。这种读一点、理解一点、决定下一步读什么的模式,和人类接手新项目时的探索方式差不多。区别是 Agent 读得快得多,但理解的深度和准确性可能不如人类。

计划:确定边界比确定实现重要

理解项目之后,Agent 需要形成修改计划。常见的误解是以为计划就是列出要写哪些代码。实际上,一个好的计划主要回答边界问题:哪些文件要动,哪些绝对不能动。
对于这个案例,合理的边界是:
需要新增的文件:前端数据管理页面组件、CSV 导入子组件、搜索子组件、编辑子组件、对应类型定义文件、前端 API service 函数(封装 Tauri invoke 调用)、Rust 端的 CSV 导入 command、搜索 command、编辑 command、前端测试文件、Rust 测试文件。
需要修改的文件:路由配置文件(注册新页面路由)、前端导航组件(添加管理页面入口链接)、Rust 端 command 注册文件(注册新增 command)、数据库初始化模块(如果 CSV 导入需要新增表来记录导入历史)。
不应该动的文件:现有认证和授权逻辑、其他页面业务代码、构建配置(Cargo.toml 和 package.json 除非需要新增依赖)、数据库连接管理代码(除非需要扩展只读连接)、已有 migration 文件。
这个边界的重要性怎么强调都不过分。Agent 最容易出的问题是蔓延。它在修改路由时顺便重构了路由文件结构,在添加数据库查询时顺手改了已有函数签名,在写新组件时优化了旁边旧组件的样式。这些修改单独看可能都有道理,但超出了当前任务范围,增加审查负担和引入 bug 的概率。
好的 Agent 会克制,只做任务要求的事,其他的留给下一个 PR。做到这一点需要项目规范文件中有明确约束(比如不要重构和当前任务无关的代码),也需要模型本身有足够纪律性。这是基模型之间的一个能力差异点,有些模型更容易蔓延,有些能更好守住边界。

执行:从底层到上层

Agent 的执行顺序通常自底向上:先数据层,再逻辑层,最后 UI 层。这个顺序有工程原因。
先做数据层是因为可以立即验证。Agent 在 Rust 端写 CSV 解析和导入的 command 函数,写完马上可以写对应 Rust 测试,cargo test 跑一下就能确认逻辑是否正确。先做 UI 的话,必须等数据层完成后才能测试整个链路,反馈周期变长。
CSV 导入看起来简单,边界情况不少。Agent 需要处理文件编码检测(UTF-8 还是 GBK,有没有 BOM)、字段数不匹配(CSV 有 5 列但数据库表有 6 个字段)、空值表示(空字符串、NULL、N/A 怎么处理)、重复记录(主键冲突是跳过、覆盖还是报错)、数据格式错误(日期字段收到 2024/13/45,数字字段收到 abc)、大文件(10 万行 CSV 不能一次性加载到内存)。
负责任的 Agent 会在写代码时考虑这些边界,并在测试中覆盖关键边界场景。不负责任的 Agent 只处理正常路径,假设 CSV 格式完美、字段完全匹配、没有重复、没有空值。两种 Agent 的区别在于项目规范中有没有要求处理边界情况,以及中间层有没有把已有代码中的错误处理模式作为上下文发给模型。
搜索功能的工程要点是查询构建。Agent 需要支持多字段组合,用户可以按名称搜索、按创建时间范围筛选、按某个状态过滤,条件可以自由组合。Agent 生成的 SQL 需要动态构建 WHERE 子句,不能写死几个固定查询。这涉及 SQL 拼接的安全性,Agent 必须使用参数化查询,不能直接把用户输入拼到 SQL 字符串里。检验 Agent 代码质量的方法之一是看有没有用参数化查询。看到字符串拼接或 f-string 嵌入 SQL,就是不合格的代码。
编辑功能的工程要点是并发安全。两个用户同时编辑同一条记录怎么办。Agent 可能用乐观锁(加 version 字段,更新时检查版本号是否匹配),可能用悲观锁(SELECT FOR UPDATE),也可能最简单 last-write-wins(后提交的覆盖先提交的,丢失一次更新)。不同策略有不同适用场景和代价,Agent 需要选择一种并在代码中一致实现。
前端方面,Agent 需要新增数据管理页面,包含三个功能区。CSV 导入区:文件选择按钮(调用 Tauri 文件对话框 API)、导入预览(展示前 5 行数据和字段映射)、导入进度条、导入结果摘要(成功 X 行、失败 Y 行、失败详情)。搜索区:条件构建表单(字段名、操作符、值的组合,支持 AND/OR)、结果表格(分页、排序)、导出按钮。编辑区:行内编辑或弹窗编辑、字段校验提示、保存和取消。
Agent 写 UI 时必须复用项目已有的 UI 模式和组件。项目已经在用某个表格组件库,Agent 不应该自己从零写一个表格。项目有统一的表单校验方式,Agent 应该沿用。项目有统一的错误提示组件,Agent 应该使用它而不是自己用 alert 弹窗。这些的前提是 Agent 在理解项目阶段读到了这些已有模式,并且在写代码时确实应用了它们。

验证:代码写完之后才是重头戏

代码写完,Agent 进入验证循环。运行检查、分析错误、修改代码、再次运行,可能要重复 5 到 10 次。
类型检查是第一关。Rust 端跑 cargo check,前端跑 tsc --noEmit。类型错误最常见:导入路径不对(Agent 猜了路径但实际文件在别处)、类型不匹配(Agent 用了 string 但实际类型是 number 或 null)、缺少必需 props(Agent 用了某个组件但漏传必填参数)。类型检查的好处是错误信息具体,文件路径、行号、期望类型、实际类型都有,Agent 可以精准定位和修复。
Lint 和 Format 是第二关。Agent 跑 cargo fmt、cargo clippy、eslint、prettier。这些工具确保代码风格和项目规范一致。这一步应该在类型检查之后,格式问题和类型错误同时出现时很难分辨。
测试是第三关,也是最关键的一关。Agent 先跑已有测试,确保没破坏已有功能。再跑新写测试,验证新功能是否正确。测试失败时,Agent 需要判断是测试写错了(断言条件不对、mock 不完整)还是实现有 bug。这个判断对 Agent 来说不总是容易的,它可能把测试本身改掉来修复测试失败,而不是修复真正的实现问题。所以测试应该在实现之前写,先写测试再写实现的话,测试失败就意味着实现有问题,不存在测试写错了的歧义。
验证循环的效率取决于错误信息质量和 Agent 纠错能力。类型错误通常一到两轮能修完。逻辑错误可能需要三到五轮,Agent 需要理解为什么结果不对,可能需要读更多上下文来定位根因。有时候 Agent 会陷入修复、失败、修复、失败的死循环,每次修复引入新问题,或者修的不是真正根因。这时候需要人介入,指出正确方向。
验证通过后,Agent 生成两个交付物。完整的 diff,所有文件变更汇总,包括新增、修改、删除。交付说明,写了修改什么、为什么这样修改、需要注意的边界情况、测试覆盖说明、已知局限。

审查:人还是最后一道防线

到了审查阶段,开发者需要做的事比点 Approve 按钮多得多。
diff 审查是第一件事。逐文件看,Agent 有没有在不知不觉中修改了不该动的文件。diff 里有没有和需求无关的变更,比如 Agent 顺便改了某个 import 顺序、调整了某段注释措辞、删除了某个它认为没用但实际被动态引用的导出。Agent 最容易产生的蔓延是在修改文件 A 时,发现文件 A 引用了文件 B 的某个函数,觉得那个函数实现不够好,顺手改了。这个修改不在计划内,没有测试覆盖,可能引入新 bug。
数据库操作审查是第二件事。所有 SQL 是否用了参数化查询。写操作是否有事务保护(批量导入 1000 行,第 500 行出错时前 499 行是已提交还是回滚)。CSV 导入是否有行数上限(有人上传 50 万行 CSV 会不会导致内存溢出)。搜索查询是否考虑了索引(WHERE 子句中使用的字段有没有对应索引,会不会全表扫描)。
测试覆盖审查是第三件事。新增代码的测试是否覆盖了正常路径和关键边界情况。断言是否具体(是 assert(result.success) 还是 assert(result.success === true && result.importedRows === 42))。有没有异常情况测试(CSV 格式错误时的处理、数据库连接失败时的处理、网络中断时的处理)。Agent 生成的测试常常偏向快乐路径,所有输入都完美,所有操作都成功。真实工程需要更多不快乐路径测试。
抽象程度审查是第四件事。Agent 有时候写重复代码,CSV 导入里的字段校验逻辑和编辑表单里的字段校验逻辑是同一套,但 Agent 写了两遍。有时候 Agent 过度抽象,为了处理两种 CSV 格式,抽象出一个五层策略模式,让代码比原始需求更难理解。开发者需要判断当前抽象程度是否服务于可维护性。
提交信息审查是第五件事。Agent 可以生成提交信息,但需要确认它准确描述了变更。好提交信息说明做了什么和为什么,不是罗列修改文件名。Agent 写 modified 8 files, added 5 files, deleted 1 file,没有信息量。写 feat: add database management page with CSV import, search, and edit capabilities,概括了做什么但没说明为什么。更好的是解释设计决策:为什么 CSV 导入用批量插入而不是逐行插入,为什么编辑用了乐观锁而不是悲观锁。
这些审查步骤和 Agent 是否可信无关,它们是正常工程纪律。人工审查代码变更在 Agent 出现之前就存在。Agent 出现没有消除审查必要,它只是把审查对象从人类同事的代码变成了 AI 生成的代码。因为 Agent 能快速产生大量代码,审查反而更重要。一个 20 分钟的 Agent 任务可能产生 500 行 diff,同样任务人类工程师可能只产生 300 行,因为人类会下意识克制改动范围。

vibe coding 的位置

过去一年 vibe coding 这个词被反复提起。它的含义大致是:用自然语言描述需求,Agent 生成代码,开发者看着差不多就接受,不深究实现细节,能跑就行。
这种方式在某些场景完全合理。原型验证(需要一个能跑的东西给团队看,明天可能就扔掉)。脚手架搭建(生成标准 CRUD 模块,和已有 10 个模块结构一样)。测试补全(给已有函数补单元测试,预期结果心里有数)。小型重构(把 5 个重复的 useEffect 提取成自定义 hook)。文档生成(给已有 API 函数生成 JSDoc 注释)。依赖升级(批量更新版本号并处理 breaking changes)。配置迁移(从 webpack 迁到 vite,从 jest 迁到 vitest)。
这些场景的共同点是模式固定、风险可控、回滚容易、错误后果有限。vibe coding 让开发者把精力集中在需要深度思考的部分,把重复的模式化部分交给 Agent。
但 vibe coding 有明确边界。这个边界由错误代价决定,不由 Agent 能力决定。
涉及支付的逻辑不能 vibe。少一个校验条件、搞错金额单位、漏掉状态检查,都可能造成实际资金损失。支付 bug 的发现往往在生产环境,测试环境通常不接真实支付通道。
权限控制的代码不能 vibe。权限逻辑是安全模型最后一道防线。放错一个检查条件,用户就能访问不该访问的数据或执行不该执行的操作。权限 bug 在生产环境被利用,后果可能是数据泄漏或合规问题。
加密相关的实现不能 vibe。加密算法选择、密钥长度设置、初始化向量生成、填充方式选择,任何一个细节错误都可能导致整个安全模型失效。加密实现 bug 通常不会让程序崩溃或报错,它们会静默产生不安全的输出。
生产数据库迁移不能 vibe。缺少 WHERE 子句的 UPDATE、没做备份的 DROP COLUMN、没考虑锁表时间的 ALTER TABLE,这些操作不可逆或恢复成本极高。迁移应该在 staging 环境验证过,有 dry-run 结果,有回滚脚本。
复杂并发逻辑不能 vibe。竞态条件、死锁、活锁、线程安全,这些 bug 在测试中很难复现,因为出现依赖特定时序条件。Agent 生成的并发代码可能看起来逻辑正确,特定时序下会产生错误结果。这类代码需要人工逐行分析执行顺序。
不可回滚的操作不能 vibe。push 到 main 分支、发布到生产环境、删除用户数据、发送群发通知,这些操作一旦执行就没有回头路。执行前需要人工确认,不管代码是谁写的。
vibe coding 和严谨工程不是二选一。同一个项目,CRUD 接口可以 vibe,支付模块必须逐行审查。同一个任务,第一版可以用 vibe 快速出原型,审查阶段切换到严谨模式,把看起来能用变成确认没问题。Agent 的速度优势让两种模式可以灵活切换,前提是清楚知道当前代码的风险等级。

开发者的角色

看完整个流程,自然的问题是:既然 Agent 能做这么多,开发者还做什么。
开发者定义目标。Agent 不会主动判断什么功能应该存在、优先级是什么、和其他功能的交互是否合理、做了这个会不会让系统变复杂。这些判断需要对产品、用户和系统整体的理解,Agent 只看到当前任务范围内的代码。
开发者设置约束。Agent 不会自动知道哪些代码不能动、哪些数据敏感、哪些操作有风险。这些约束需要开发者以项目规范、权限配置、审查标准、测试覆盖要求的形式预先定义。约束质量决定了 Agent 能在多大程度上自主工作而不会闯祸。
开发者审查输出。Agent 的代码可能正确,可能错误,可能部分正确但引入意料之外的副作用。可能在一个地方修了 bug 在另一个地方制造新 bug。可能写了一段看起来正确但性能很差的代码,比如在循环里做数据库查询。只有开发者能结合项目上下文和团队目标,判断变更是否应该被接受。
开发者控制风险。Agent 建议直接查询生产数据库时,开发者说不行。Agent 生成的代码缺少错误处理时,开发者要求补上。Agent 陷入修复失败的死循环时,开发者接管指出正确方向。任务超出 Agent 当前能力范围时,开发者自己写。
这些角色在 Agent 出现之前就存在。code review 审查人类同事的代码,现在是审查 AI 生成的代码。技术设计自己想方案然后实现,现在是指导 AI 想方案然后验证。结对编程两个人讨论思路,现在是一个人加一个 AI 讨论思路。
变化的是执行方式,不是责任归属。代码最终是开发者提交的,bug 最终是开发者修的,生产事故最终是开发者负责的。Agent 是一个工具,能力很强的工具,但仍然是一个工具。


本站由 Somnifex 使用 Stellar 1.33.1 主题创建。

本站由 又拍云提供CDN加速/云存储服务

本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。