1. 项目概述:从“写代码”到“指挥代码”
最近和几个做架构和开发的朋友聊天,发现一个挺有意思的现象:大家用AI写代码的工具越来越多了,但抱怨的声音反而更大了。常见的吐槽是:“让它写个函数还行,一涉及到稍微复杂点的业务逻辑,就开始胡言乱语”、“上下文一长,它就忘了前面说过什么”、“生成的代码看着能用,但一跑起来全是坑,调试的时间比自己写还长”。
这其实点出了当前AI编程工具的一个核心痛点:它们大多还停留在“代码补全”或“单轮问答”的层面。你给一个指令,它吐出一段代码,至于这段代码是否符合你的整体架构、能否与现有模块无缝集成、后续如何迭代,它是不管的。这就像你指挥一个乐队,但每次只能对单个乐手下达一个音符的指令,无法进行整体协调。
而“Agentic 编码”这个概念,正是为了解决这个问题。它不再是让AI被动地响应你的单次请求,而是赋予它一个“智能体”的角色,让它能理解更宏观的上下文,并主动规划、执行、验证一系列编码任务。简单说,就是从“你写代码,AI辅助”变成“你定目标,AI写代码”。
在这个领域,Claude Code(通常指Anthropic公司Claude模型在代码生成方面的能力)因其强大的长上下文处理能力和对指令的精准理解,成为了实践“Agentic 编码”的绝佳载体。但光有强大的模型还不够,关键在于如何与它“沟通”——这就是“上下文工程”要解决的问题。它不再是简单的提示词技巧,而是一套系统工程,关乎如何构建、组织和管理提供给AI的“信息环境”,使其能像一个真正的开发伙伴一样工作。
这篇文章,我就结合自己近期的实践,拆解一下如何利用Claude Code进行Agentic编码,核心就是聊聊“上下文工程”该怎么搞。无论你是想提升日常开发效率,还是探索AI编程的新范式,相信这些实操经验都能给你带来一些启发。
2. 核心理念拆解:什么是Agentic编码与上下文工程
在深入具体操作之前,我们得先统一一下思想。这两个词听起来有点学术,但理解透了,后面的操作才能事半功倍。
2.1 Agentic编码:让AI成为有“主观能动性”的协作者
传统的AI编码助手,其交互模式是“反应式”的。你问,它答。任务边界非常清晰且微小,比如“写一个Python函数计算斐波那契数列”。AI完成这个孤立任务后,它的使命就结束了。
Agentic编码则引入了“智能体”的概念。在这里,AI被赋予了一个目标、一些工具(如运行代码、读写文件、搜索网络)和一定的自主决策权。它的工作模式变成了:
- 理解宏观目标:比如“为我们正在开发的电商平台,创建一个用户购物车服务,包含添加商品、移除商品、计算总价和清空购物车功能,要求使用TypeScript,遵循我们项目现有的DDD架构和代码风格。”
- 任务规划与分解:AI需要自己拆解这个目标。它可能会规划出:先定义领域实体(如Cart, CartItem),再编写仓储接口,接着实现具体的服务方法,最后编写单元测试。
- 自主执行与验证:AI可以按步骤生成代码,甚至模拟运行或调用工具来检查代码是否有语法错误,逻辑是否符合预期。
- 迭代与修正:如果发现问题(比如测试失败),它能分析错误,尝试自行修复,而不是停下来等你给新指令。
这带来的最大转变是交互粒度的变化。你从“微观管理者”变成了“目标制定者”和“结果验收者”。你的核心工作变成了清晰地定义“做什么”和“做到什么标准”,而把“怎么做”的细节很大程度上委托给了AI智能体。
2.2 上下文工程:为AI智能体构建“工作记忆”与“知识库”
要让Claude Code这样的模型胜任Agentic编码,仅仅在单次对话中给出一个好提示是远远不够的。你需要系统地管理提供给它的所有信息,这就是上下文工程。
你可以把模型的上下文窗口想象成它的“短期工作内存”和“当前可见的桌面”。上下文工程的目标,就是在这个桌面上,为它摆放好高效完成任务所需的一切:
- 项目蓝图(架构与目标):整个项目是干什么的?采用什么技术栈?什么架构模式?
- 工作环境(代码库):现有的代码文件、配置文件、依赖关系是什么样子的?
- 操作规范(风格与约束):代码规范是什么?命名约定是什么?有哪些安全或性能红线?
- 任务说明书(具体需求):当前要完成的具体任务是什么?验收标准有哪些?
上下文工程的核心挑战在于:如何在有限的上下文窗口内(比如Claude 3.5 Sonnet的200K tokens),高效、有序、无冲突地组织这些信息,并确保AI在生成每一行代码时,都能准确地关联到所有相关上下文。
这绝非简单地把所有文件内容粘贴进去那么简单。拙劣的上下文管理会导致:
- 信息过载与焦点丢失:AI被海量无关信息干扰,抓不住重点。
- 上下文污染:旧的、错误的或矛盾的信息影响了新任务的判断。
- 关键信息缺失:AI因为缺少某个核心依赖的接口定义,而写出了无法编译的代码。
因此,上下文工程是一套包含信息筛选、结构组织、动态更新和优先级管理的策略。接下来,我们就进入实战环节,看看具体怎么做。
3. 上下文工程的四大核心构件与实操
基于多次实践,我总结出一个有效的上下文工程通常需要构建四个核心部分。你可以把它们看作给AI智能体搭建工作台的四个步骤。
3.1 构件一:项目与角色锚定——确立“工作边界”
在开始任何具体编码任务前,首先要让AI明确“我在为谁工作”以及“我在做什么项目”。这能极大提升后续生成代码的针对性和一致性。
实操示例:不要一上来就说“写个函数”。而是先建立锚定信息:
## 项目背景与你的角色 **项目名称**:NeoShop - 下一代微服务电商平台 **核心技术栈**: - 后端:Node.js (v18+), NestJS框架,TypeScript - 数据库:PostgreSQL (主数据), Redis (缓存) - 消息队列:RabbitMQ - API风格:RESTful + 部分GraphQL端点 **架构模式**:领域驱动设计(DDD),六边形架构 **代码仓库规范**:Monorepo管理,使用 pnpm workspace **你在此次任务中的角色**: 你是我们后端团队的一名高级TypeScript开发工程师。你精通NestJS和DDD,对代码整洁度、可测试性和性能有很高要求。你的任务是理解业务需求,并产出符合项目现有架构和规范的生产级代码。为什么这么做?
- 设定技术边界:直接告诉AI不用考虑Python、Django等其他选项,避免它生成无关的技术建议。
- 植入架构意识:提到DDD和六边形架构,AI在设计代码时会自然考虑领域层、应用层的分离,以及依赖倒置。
- 明确质量标准:“生产级”、“可测试性”这些词会潜移默化地影响AI生成代码的严谨程度。
注意:这个锚定信息通常在对话开始时一次性给出,并在后续复杂任务中偶尔提及或强化。对于非常长的对话,可以在关键节点重新强调角色,以防止AI“忘记”自己的身份。
3.2 构件二:结构化知识注入——提供“参考资料”
这是上下文工程最实质的部分。你需要把AI完成任务所需的关键知识,以结构化的方式“喂”给它。盲目粘贴整个package.json或几十个源文件是低效的。
策略1:关键文件摘要不要直接扔一个500行的nestjs.service.ts文件。而是提取关键信息:
## 现有核心代码结构参考 1. **领域实体 - `User` 示例** (`src/modules/user/domain/user.entity.ts`): - 使用TypeORM装饰器。 - 遵循DDD,实体包含业务逻辑方法(如 `user.hasPermission()`)。 - 所有实体均继承自 `BaseEntity`(包含 `id`, `createdAt`, `updatedAt`)。 2. **仓储接口 - `IUserRepository` 示例** (`src/modules/user/domain/repositories/user.repository.interface.ts`): - 定义于领域层。 - 约定如 `findById(id: string): Promise<User | null>` 等方法。 3. **服务层 - `UserService` 示例** (`src/modules/user/application/services/user.service.ts`): - 位于应用层。 - 构造函数注入仓储接口。 - 方法通常为事务性业务逻辑,如 `registerUser(command: RegisterUserCommand)`。 4. **依赖注入与模块**:所有服务均在对应模块的 `providers` 中声明,通过 `@Inject('USER_REPOSITORY')` 等方式注入。策略2:代码风格与规范提供具体的、可执行的规则,而不是“请写出优雅的代码”。
## 本项目代码风格与硬性约束 - **命名**: - 接口前缀 `I`(如 `IRepository`)。 - 异步方法后缀 `Async` 可选,但我们项目统一不加,通过返回 `Promise` 类型表明。 - 文件名:小写短横线分隔(如 `user-profile.service.ts`)。 - **类型**:必须使用严格模式(`strict: true`)。禁止使用 `any`,除非在极少数泛型工具类型中。使用 `unknown` 替代 `any` 进行类型收窄。 - **错误处理**:使用项目自定义的 `BusinessException` 类抛出业务异常,而非原生 `Error`。 - **导入顺序**:第三方库 -> 项目内部绝对路径导入 -> 相对路径导入。 - **测试**:每个服务必须有对应的 `.spec.ts` 文件,使用 Jest,测试描述使用 `describe` 和 `it`,遵循 Given-When-Then 结构。策略3:通过“最小化可运行示例”定义模式当需要AI遵循一个特定模式时,提供一个精简但完整的例子比描述一百句都管用。
假设你要AI创建一个新的DDD风格模块,你可以提供一个“模块模板”:
## 新功能模块创建模板 创建一个新模块 `feature-name` 的目录结构和文件应遵循以下模式:src/modules/feature-name/ ├── domain/ │ ├── entities/ # 领域实体 │ ├── value-objects/ # 值对象 │ ├── repositories/ # 仓储接口 │ └── exceptions/ # 领域异常 ├── application/ │ ├── commands/ # 命令(CQRS) │ ├── queries/ # 查询(CQRS) │ ├── services/ # 应用服务 │ └── dtos/ # 数据传输对象 ├── infrastructure/ │ ├── persistence/ # 仓储实现(如TypeORM) │ └── external/ # 外部服务适配器 └── presentation/ ├── controllers/ # 控制器(REST/GraphQL) ├── decorators/ # 自定义装饰器 └── filters/ # 异常过滤器
**快速启动示例**:以 `domain/entities` 为例,一个实体文件大致如下: ```typescript // src/modules/task/domain/entities/task.entity.ts import { Entity, Column, ManyToOne } from 'typeorm'; import { BaseEntity } from '../../../common/entities/base.entity'; import { User } from '../../user/domain/entities/user.entity'; @Entity('tasks') export class Task extends BaseEntity { @Column() title: string; @Column({ type: 'text', nullable: true }) description?: string; @Column({ default: false }) isCompleted: boolean; @ManyToOne(() => User, user => user.tasks) assignedTo: User; // 领域方法 markAsCompleted(): void { if (this.isCompleted) { throw new Error('Task is already completed'); } this.isCompleted = true; // 可以在这里触发领域事件 } }通过这种方式,你不仅告诉了AI“做什么”,更清晰地示范了“怎么做”,极大地减少了返工和风格不一致的问题。 ### 3.3 构件三:任务指令的精准表达——下达“清晰工单” 当背景知识就位后,你需要清晰地下达任务指令。Agentic编码要求指令是“目标导向型”而非“步骤导向型”。 **低效指令(步骤导向)**: > “写一个函数,接收用户ID和商品ID,先检查用户是否存在,再检查商品库存,然后创建订单,最后更新库存。” **高效指令(目标导向+约束)**: ```markdown ## 当前任务:实现“创建订单”核心业务逻辑 **业务目标**: 在 `Order` 模块中,实现一个完整的创建订单用例。用户提交选中的商品列表和配送地址后,系统应验证商品库存、计算总价(含税)、创建订单记录、扣减库存,并记录审计日志。 **具体需求与验收标准**: 1. **输入**:`CreateOrderCommand`,应包含 `userId`、`items: Array<{productId: string, quantity: number}>`、`shippingAddress`。 2. **验证**: - 用户必须存在且状态为活跃。 - 所有商品必须存在且库存充足。 - 配送地址格式需有效(可调用现有的 `AddressValidator` 服务)。 3. **业务逻辑**: - 计算商品总价(基于 `Product` 实体的 `price` 字段)。 - 应用税费计算(使用现有的 `TaxCalculator` 服务,税率固定为8%)。 - 生成唯一的订单号(格式:`ORD-{yyyyMMdd}-{6位随机数}`)。 4. **持久化**: - 创建 `Order` 和 `OrderItem` 实体记录。 - 原子化地更新所涉及商品的库存数量。 5. **副作用**: - 发布一个 `OrderCreatedEvent` 领域事件,以便后续发送邮件或通知。 - 通过 `AuditLogger` 记录操作。 6. **输出**:返回包含 `orderId`、`orderNumber`、`totalAmount` 的 `OrderResultDto`。 **请遵循以下要求**: - 在 `application/commands` 下创建 `CreateOrderCommand` 和处理程序 `CreateOrderHandler`。 - 在 `application/services` 下创建 `OrderCreatorService`,封装核心逻辑。 - 确保所有数据库操作在一个事务内完成(参考项目已有的 `TransactionDecorator`)。 - 为 `OrderCreatorService` 编写完整的单元测试,覆盖成功、库存不足、用户不存在等场景。为什么后者更有效?
- 明确“为什么”:AI理解了最终的业务价值,而不仅仅是代码步骤。
- 定义清晰边界:输入、输出、验收标准非常具体,减少了模糊地带。
- 利用现有资产:指明了要复用哪些现有服务(
TaxCalculator,AddressValidator),避免了重复造轮子或创建冲突实现。 - 非功能性需求:提到了事务、事件、审计,引导AI考虑生产环境下的健壮性。
3.4 构件四:交互与迭代策略——建立“验收与反馈循环”
AI生成代码很少能一次完美。你需要建立一个高效的反馈循环机制。
1. 分步提交与审查: 对于复杂任务,不要让它一次性生成所有代码。可以分阶段:
- 第一阶段:“请根据以上上下文,先设计
CreateOrderCommand、OrderResultDto以及Order、OrderItem实体和仓储接口的定义。给出代码即可。” - 你审查生成的领域模型,确认无误后。
- 第二阶段:“好的,领域模型没问题。现在请基于这些模型,实现
OrderCreatorService的核心方法,重点关注库存验证和事务管理逻辑。” - 你审查业务逻辑。
- 第三阶段:“逻辑正确。现在请为这个
OrderCreatorService编写完整的单元测试。”
这种方法降低了单次生成的复杂度,也让你能在早期纠正方向性错误。
2. 提供精准的错误反馈: 当AI生成的代码有问题时,不要只说“这里不对”。提供具体的错误信息、你的分析和期望。
低效反馈:
“这个测试跑不过。”
高效反馈:
“你生成的
OrderCreatorService测试中,模拟(mock)ProductRepository时,findById方法返回的是Promise<Product>,但实际我们的仓储接口定义(之前提供的)中,findById返回的是Promise<Product | null>。这导致测试中productRepository.findById.mockResolvedValue(product)这一行类型不匹配。请修正mock的设置,并确保处理findById返回null(商品不存在)的测试用例。”
3. 引导AI自我诊断与修复: 鼓励AI运行自己的代码或分析问题。你可以说:
“你生成的
calculateTotal方法似乎没有考虑税费。请根据之前提到的需求(使用TaxCalculator服务),检查并修正这个方法。然后,可以一步步描述你的修正思路吗?”
这能促使AI更主动地思考,而不仅仅是等待下一个指令。
4. 高级技巧与避坑指南
掌握了基本构件后,一些高级技巧和常见陷阱能让你和Claude Code的合作更加顺畅。
4.1 技巧一:使用“系统提示词”固化核心上下文
许多支持Claude Code的IDE插件或平台允许设置“系统提示词”。这是一个强大的功能,你可以将最稳定、最通用的上下文(如项目角色、技术栈、核心规范)放在这里。这样,每次新对话都会自动加载这些信息,无需重复输入,为任务特定的上下文腾出宝贵空间。
4.2 技巧二:动态上下文管理——引用而非复制
对于大型项目,完整代码库不可能全部塞进上下文。这时需要“动态加载”策略。
- 让AI引用已知路径:在指令中明确说:“请参考
src/common/interceptors/logging.interceptor.ts中的格式,为新的订单服务创建一个类似的性能监控拦截器。” AI虽然看不到那个文件,但如果你之前提到过这个文件或模式,它能更好地理解你的要求。 - 分文件交互:对于需要多文件协作的任务,可以分次提供。例如,先让AI基于摘要设计接口,你认可后,再把具体的依赖实现文件内容提供给它,让它完成集成。一些高级的AI编程工具(如Cursor、Claude Desktop)能直接读取项目文件,实现了真正的动态上下文。
4.3 技巧三:利用思维链(Chain-of-Thought)提示
对于极其复杂的逻辑,可以要求AI“先思考,再编码”。这能大幅提升生成代码的可靠性。
示例指令:
“在开始编写‘订单库存锁定’的分布式事务补偿逻辑之前,请先一步步分析可能出现的故障场景(如:扣减库存成功但创建订单失败),并为你将要实现的补偿机制(如Saga模式)设计一个步骤流程图。用文字描述清楚每个步骤和回滚策略。完成分析后,再基于此分析编写代码。”
这样,AI会先输出它的思考过程,你可以检查其逻辑是否合理,然后再让它生成代码,相当于多了一层设计评审。
4.4 常见陷阱与解决方案
陷阱:上下文冲突或遗忘
- 现象:对话进行到后期,AI生成的代码违反了早期设定的规范,或者忘记了某个关键业务规则。
- 解决方案:定期“刷新”关键上下文。在开始一个新的大段落任务前,用一两句话重申最重要的约束,例如:“记住,所有数据库操作必须使用我们项目的事务装饰器,并且禁止在循环中进行数据库查询。”
陷阱:过度设计或偏离简单方案
- 现象:AI有时会倾向于使用过于复杂或新颖的设计模式,而忽略了更直接、更易维护的解决方案。
- 解决方案:在指令中明确强调“优先选择简单、直观、与项目现有模式一致的解决方案”。或者,当它提出复杂方案时,直接干预:“这个场景使用简单的服务层方法即可,无需引入事件溯源(Event Sourcing),请简化设计。”
陷阱:幻觉依赖或接口
- 现象:AI可能会“幻想”出项目中不存在的类、方法或服务接口,并基于此生成代码。
- 解决方案:提供尽可能准确的引用。如果它使用了不存在的依赖,立即指出:“项目中并不存在
AdvancedPaymentValidator这个类。支付验证请使用src/modules/payment/application/services/payment.service.ts中的validatePaymentMethod方法。”
陷阱:安全与性能盲点
- 现象:AI生成的代码可能忽略SQL注入、XSS、竞态条件等安全问题,或写出N+1查询等性能低下代码。
- 解决方案:将安全和性能作为明确的验收标准写入指令。例如:“实现用户查询时,必须使用参数化查询以防止SQL注入。分页查询时,确保使用索引优化的
LIMIT/OFFSET或游标分页。”
5. 一个完整的Agentic编码工作流示例
让我们将以上所有内容串联起来,看一个从零开始创建一个小功能的完整工作流。
场景:在已有的NestJS用户模块中,添加一个“用户个人资料更新”的功能,允许用户更新头像和昵称。
第一步:初始化上下文与角色锚定你(开发者)开启与Claude Code的对话,首先粘贴或输入“项目与角色锚定”信息(如3.1所述),确立基本盘。
第二步:注入相关上下文你提供用户模块现有的核心结构摘要(参考3.2策略1):
User实体的当前字段。IUserRepository接口的现有方法。- 现有的
UserService中有哪些方法。
第三步:下达精准任务指令你给出任务说明(参考3.3):
“任务:实现‘更新用户个人资料’功能。 需求:用户可更新
avatarUrl(头像链接)和nickname(昵称)。昵称需唯一(需检查是否被其他用户占用)。 验收标准:
- 创建
UpdateProfileCommand和UpdateProfileHandler。- 在
UserService中新增updateProfile方法,包含唯一性校验。- 头像链接需做基本格式验证(简单的URL正则即可)。
- 编写完整的单元测试和集成测试(测试昵称冲突场景)。 请遵循项目已有的DDD风格和代码规范。”
第四步:分步执行与审查
- AI生成
UpdateProfileCommand和领域模型变更建议。你审查,确认字段和验证逻辑。 - 你反馈:“
Command结构正确。现在请实现UserService中的updateProfile方法。注意,唯一性检查需要调用IUserRepository的新方法findByNickname,请先更新仓储接口再实现。” - AI更新仓储接口并实现服务方法。你审查事务边界和异常处理。
- 你反馈:“业务逻辑正确。现在请为
updateProfile方法编写单元测试。特别注意要模拟(mock)findByNickname返回不同值(存在、不存在)的场景。” - AI生成测试代码。你运行测试,可能发现一个边缘情况未覆盖。
- 你给出精准反馈:“测试覆盖了大部分场景,但缺少当
avatarUrl为空字符串时的处理。根据业务需求,空字符串应被视为清除头像,请更新逻辑和测试。”
第五步:收尾与整合AI根据反馈修正代码和测试。你确认所有代码符合规范、测试通过。最后,你可以让AI生成一个简单的API控制器层,或者直接将其集成到现有控制器中。
在整个过程中,你扮演的是产品经理、架构师和代码评审者的角色,而Claude Code则扮演了一个理解力强、执行力高但需要清晰指引和及时纠偏的高级开发工程师。
6. 总结与个人体会
实践下来,使用Claude Code进行Agentic编码,其效能上限几乎完全取决于“上下文工程”的质量。它不再是一个玩具式的补全工具,而是一个需要认真管理和协作的智能体。
我最深的几点体会是:
第一,投入越多,回报越大。前期花时间精心构建项目锚定、代码摘要和规范文档,看起来麻烦,但在后续无数个开发任务中,这些上下文会被反复复用,节省的沟通和返工成本是巨大的。这就像为团队编写了一份极其详尽、且AI能完美理解的开发手册。
第二,指令的清晰度就是生产力的天花板。模糊的指令得到模糊的结果,甚至是有害的复杂代码。学会用结构化的方式(目标、输入、输出、约束、验收标准)描述需求,不仅AI能更好理解,也倒逼我自己在编码前把业务逻辑想得更清楚,这本身就是一个巨大的提升。
第三,保持主导权,拥抱协作。Agentic编码不是全自动魔法。最有效的模式是“人类决策,AI执行”。由我来把控架构方向、业务规则和关键设计,由AI去完成大量模式固定、逻辑清晰的代码实现、测试编写和文档草拟。它极大地放大了我的能力,但无法替代我的判断。
最后,这是一个迭代进化的过程。你和AI协作的“工作流”会随着项目推进而不断优化。你会发现哪些信息需要放在系统提示词,哪些代码摘要格式最有效,如何分拆任务效率最高。不断反思和调整这个“上下文工程”本身,就是提升人机协作效能的核心。
开始尝试吧。从一个小的、边界清晰的功能模块开始,实践这套上下文工程的方法。你可能会经历一些初期的磨合,但一旦流程跑通,你会发现,编写代码的体验将被彻底改变。你不再是那个在键盘上逐字敲击的“码农”,而更像是一个指挥着智能代码生成团队的“技术总监”。