三种交互模式
> **时效说明**:本文内容以 2026 年 3 月为基准。
Claude Code:三种交互模式
时效说明:本文内容以 2026 年 3 月为基准。
6.1 Ask模式——只问不改
Claude Code有三种使用姿势,分别对应不同的场景。很多人只用其中一种,其实三种模式组合起来才是最高效的工作流。
Ask模式是纯只读的。Claude Code在这个模式下绝对不会修改任何文件,也不会执行任何写操作。它的任务就是帮你理解代码、定位问题、解释逻辑。
什么时候用Ask模式?
你接手了一个老项目,看到一段复杂的SQL,不知道它在干什么——用Ask模式让Claude解释。你发现一个NullPointerException,但堆栈太深,不知道根本原因在哪——用Ask模式让Claude帮你推理。你想知道某个接口的完整调用链——用Ask模式让Claude追踪。
示例对话:
你:这段代码里的 synchronized 锁的粒度怎么样?有没有性能问题?
Claude:这里锁的是整个方法,每次调用都会阻塞。根据我看到的调用频率和你
的业务场景,建议改成ConcurrentHashMap + computeIfAbsent,
可以把锁粒度降到key级别...(继续分析)
Ask模式不是Claude Code的默认状态,但你可以在对话里明确说"不要修改任何文件,只帮我分析",Claude会遵守这个约束。
6.1.1 Ask模式的实战示例:代码审查
代码审查是Ask模式最典型的用途之一。假设你在Code Review时发现了一段可疑的代码,想快速评估风险:
你:帮我审查这个登录方法,重点看安全性问题:
@PostMapping("/login")
public Result<String> login(@RequestParam String username,
@RequestParam String password) {
String sql = "SELECT * FROM users WHERE username='"
+ username + "' AND password='" + password + "'";
User user = jdbcTemplate.queryForObject(sql, User.class);
return Result.success(generateToken(user));
}
Claude:这段代码有严重的安全问题,我从高到低列一下:
P0(必须立即修复):
1. SQL注入漏洞。username和password直接拼接进SQL,攻击者
输入 admin' OR '1'='1 就能绕过密码验证,登录任意账户。
修复:使用PreparedStatement或MyBatis的#{}参数绑定。
2. 密码明文存储/比对。密码应该用BCrypt等算法加密存储,
不应该在SQL里直接比对明文。
P1(近期需要修复):
3. 没有登录失败计数。暴力破解没有任何防护。
4. 没有验证码或其他二次验证。
P2(建议优化):
5. queryForObject在查不到时会抛EmptyResultDataAccessException,
异常处理逻辑缺失,会导致500错误暴露给用户。
这种Review,Claude几秒钟就能完成,比你一行行看要全面很多。而且在Ask模式下,它只给分析,不会动你的代码。
6.1.2 Ask模式的实战示例:性能分析
你:这个查询为什么慢?执行计划我粘给你看:
EXPLAIN SELECT o.*, u.name, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 1
AND o.create_time > '2024-01-01'
ORDER BY o.create_time DESC
LIMIT 20;
执行计划:type=ALL, rows=2400000, Extra=Using filesort
Claude:问题很明显,有两个:
1. type=ALL 表示全表扫描——orders表240万行全部扫了一遍。
create_time和status字段上没有索引,或者索引没有被利用。
建议:创建联合索引 INDEX idx_status_time (status, create_time)
把status放前面,因为它的区分度低(只有几个值),
但配合时间范围查询效果很好。
2. Using filesort 表示结果在内存/磁盘里做了额外排序,
说明ORDER BY字段不在索引里,或者索引没有覆盖排序。
如果联合索引包含(status, create_time),排序也能走索引。
预估效果:加索引后,这个查询从全表扫240万行降到扫索引匹配行,
大约是几百到几千行,执行时间从秒级降到毫秒级。
6.1.3 Ask模式的实战示例:调用链追踪
当你面对一个陌生的大型项目,最难的是理解"一个请求进来,到底经过了哪些地方"。Ask模式可以帮你快速建立这个认知:
你:我想了解 POST /api/order/create 这个接口的完整处理链路,
从Controller到数据库,帮我梳理一下
Claude:我来追踪一下...
(读取 OrderController.java)
(读取 OrderService.java)
(读取 OrderMapper.java)
链路如下:
1. OrderController.createOrder()
- 参数校验(@Valid + OrderCreateRequest)
- 调用 orderService.createOrder(request, userId)
2. OrderService.createOrder()
- 查库存:inventoryService.checkAndDeduct(items)
- 计算价格:priceService.calculate(items, couponId)
- 创建订单主记录:orderMapper.insert(order)
- 创建订单明细:orderItemMapper.batchInsert(items)
- 发MQ消息:rocketMQTemplate.send("order-created", event)
注意:步骤2-5在同一个@Transactional事务里,MQ发送在事务外
3. OrderMapper.insert()
→ 写 orders 表
4. OrderItemMapper.batchInsert()
→ 写 order_items 表(批量插入)
关键风险点:我注意到库存扣减和订单创建不在同一个分布式事务里,
如果订单写成功但消息发送失败,库存会扣了但订单消息丢失,
可能导致下游服务认为没有新订单。你们有做消息补偿吗?
Ask模式给你的不只是代码层面的答案,还有业务层面的风险提示——而且它一行代码都没动。
6.2 Plan模式——先规划再执行
Plan模式下,Claude会先给你一份详细的执行计划,等你确认之后才开始真正动手。
这个模式在以下情况特别有价值:多个文件需要联动修改时;你不确定改动范围有多大时;涉及数据库Schema变更等高风险操作时;架构调整前需要对齐思路时。
6.2.1 Plan模式的完整工作流
Plan模式的核心价值是在执行前充分沟通。下面是一个完整的工作流示例。
场景:你接到需求,要在现有的电商系统里加一个"限时秒杀"功能。
第一轮:让Claude规划
你:我需要给订单服务加一个限时秒杀功能。秒杀商品有特殊的库存扣减逻辑
(需要用Redis做预减库存),支付超时后需要自动释放库存。先给我一个
改动计划,不要动代码。
Claude:好,我来分析改动范围:
需要新增的文件(5个):
- SeckillController.java:秒杀入口接口
- SeckillService.java:核心业务逻辑
- SeckillInventoryService.java:Redis库存管理
- SeckillOrderConsumer.java:处理超时释放
- SeckillConfig.java:秒杀相关配置
需要修改的文件(3个):
- OrderService.java:增加秒杀订单的特殊分支逻辑
- pom.xml:增加 redisson 依赖(分布式锁)
- application.yml:增加秒杀相关配置项
需要新增的数据库表(1个):
- seckill_activity:秒杀活动表
预估改动量:约400-600行新代码,修改约80行现有代码
第二轮:讨论方案细节
你:库存用Redis的话,如果Redis宕机怎么办?你用什么方案保证可用性?
Claude:这是个好问题。主要有两种方案:
方案A(降级到数据库):Redis宕机时,自动降级到直接扣数据库库存,
用数据库行锁保证不超卖。缺点是秒杀期间并发高,数据库可能扛不住。
方案B(Redis高可用 + 本地缓存兜底):用Redis Cluster保证可用性,
同时维护一个本地原子计数器,Redis不可用时用本地计数,
请求量打到数据库之前先用本地计数过滤大部分请求。
我建议方案B。你们的Redis是单机还是集群部署?
你:现在是单机,短期不会改集群
Claude:了解。那建议用方案A加一个简单的熔断:
Redis宕机超过5秒,自动切换到数据库模式,同时限流,
让QPS从10000降到500,数据库可以扛住。
熔断恢复后再切回Redis模式。这样既保证可用性,又不改动部署架构。
要用这个方案吗?确认后我再给出具体的代码改动计划。
第三轮:确认后执行
你:好,就用这个方案,开始改代码吧
Claude:明白,开始执行...
这个过程可以来回好几轮。Plan模式的价值就在于,你们把分歧和风险在"动手之前"就解决掉了,而不是改了一半发现方向不对。
6.2.2 切换到Plan模式的方式
两种方式可以进入Plan模式:
方式一:按 Shift+Tab 循环切换,直到右上角显示 Plan 模式
方式二:在消息里直接说明:
先给我一个改动计划,不要动任何代码,我确认后再执行
Claude会把这当作一个软约束遵守。注意:Plan模式下,Claude仍然会读取文件(这是只读操作),但不会写文件或执行命令。
6.2.3 Plan模式的常见误用
误用1:简单任务也用Plan模式
如果你要做的是"把这个方法的返回值从String改成Integer",根本不需要Plan模式。Plan模式的价值在于复杂任务,简单任务用它只是浪费时间。
误用2:确认计划后不检查执行过程
Plan模式只是确保开始方向是对的,并不保证执行过程完全符合预期。确认计划后切回执行模式,还是要观察每一步的diff。
6.3 Edit模式——直接执行
Edit模式是最直接的,你说改什么,Claude就改什么。在它真正写入文件之前,会展示一个diff让你确认。
你:把登录接口的成功响应码从200改为201
Claude:我将修改 UserController.java:
- return ResponseEntity.ok(result);
+ return ResponseEntity.status(HttpStatus.CREATED).body(result);
确认修改吗?[y/N]
你看到diff没问题,按y确认,文件才会真正被改。如果改得不对,直接回复"不对,xxx地方有问题",Claude会重新给出修改方案。
6.3.1 diff界面详解:每个选项的含义
在Edit模式下,Claude展示diff时,你有几个操作选项:
| 选项 | 操作 | 说明 |
|---|---|---|
y |
接受并应用 | 确认这个改动,写入文件 |
n |
拒绝 | 跳过这个改动,文件不变 |
a |
全部接受 | 接受后续所有改动,不再逐个确认 |
e |
手动编辑 | 打开编辑器自己修改这段代码 |
d |
查看diff详情 | 展示更多上下文 |
s |
跳过 | 暂时跳过,后面再决定 |
实际使用中,y和a用得最多。e在你觉得Claude的改法不完全对、但你知道应该怎么改时很有用——不用让Claude重来,直接自己动手微调。
6.3.2 多文件修改的处理方式
当一个任务涉及多个文件时,Claude会按顺序展示每个文件的diff。你可以对每个文件单独决定:
Claude:需要修改以下文件:
1/3 - OrderService.java
[diff展示]
接受吗?[y/N/a]
> y
2/3 - OrderMapper.java
[diff展示]
接受吗?[y/N/a]
> n(这个我不想动)
3/3 - OrderServiceTest.java
[diff展示]
接受吗?[y/N/a]
> y
第2个文件你拒绝了,Claude会重新评估——因为Mapper没改,Service里对Mapper的调用方式可能也需要调整。这时候Claude会提示你,或者重新出一个不依赖Mapper改动的方案。
6.3.3 Edit模式的最佳使用姿势
指令要具体,不要模糊:
# 模糊(Claude可能改很多地方)
帮我优化这个服务
# 具体(Claude知道边界在哪里)
帮我把 OrderService 里的 getOrdersByStatus 方法
从循环查询改成一次批量查询,只改这一个方法
告诉Claude不要动哪些地方:
帮我添加缓存到 getUserById 方法,
但不要修改方法签名,也不要改其他任何方法
先问再改:对于复杂的修改,可以先在Ask模式里确认Claude的理解,再切到Edit模式执行。
6.4 如何在对话中切换模式
6.4.1 通过Shift+Tab切换
这是最直接的方式。每按一次 Shift+Tab,在三种模式间循环切换:
Ask → Plan → Edit(默认确认) → Ask → ...
右上角会显示当前是哪个模式。
6.4.2 通过对话语言软切换
你不需要手动切模式,在消息里用自然语言描述你想要的行为,Claude会理解:
| 你说的 | Claude的行为 |
|---|---|
| "只分析,不要改" | Ask模式行为 |
| "先给我一个计划" | Plan模式行为 |
| "直接改,别问我了" | Edit模式 + 减少确认 |
| "每个文件改之前都告诉我" | Edit模式 + 逐步确认 |
这种方式比切模式更灵活——你可以在一条消息里同时指定:"先给我分析,如果没问题再帮我改,改之前每个文件都让我确认"。
6.5 不同任务类型选哪种模式
这张决策表帮你快速判断:
| 任务类型 | 推荐模式 | 原因 |
|---|---|---|
| 接手陌生代码、理解逻辑 | Ask | 只需要信息,不需要改动 |
| Code Review | Ask | 审查不需要修改 |
| 性能分析、找瓶颈 | Ask | 分析阶段,不动代码 |
| 安全漏洞检查 | Ask | 先找漏洞,另起对话再修 |
| 新功能开发(>3个文件) | Plan → Edit | 复杂任务先规划 |
| 架构重构 | Plan → Edit | 风险高,必须先对齐方案 |
| 数据库Schema变更 | Plan → Edit | 不可逆操作 |
| 小范围Bug修复 | Edit | 明确知道要改什么 |
| 单文件改动 | Edit | 范围小,直接改 |
| 批量重命名、格式化 | Edit + 自动接受 | 低风险、高频操作 |
6.6 模式组合的高效工作流:从发现Bug到修复验证
下面是一个完整的案例,展示如何把三种模式串联起来处理一个真实问题。
场景:线上报告,用户反馈下单后偶尔会出现"订单金额与支付金额不一致",但概率很低。
第一阶段:Ask模式定位问题
你:线上有个概率性问题:用户下单后订单金额和支付金额不一致,
概率大约千分之三。帮我分析可能的原因。
Claude:(读取 OrderService.java、PaymentService.java、
OrderController.java)
这个概率性不一致通常有几个来源:
1. 并发问题:我注意到 calculateTotalAmount() 方法里,
先查优惠券再计算金额,中间没有加锁。如果用户在这个窗口期
修改了订单(加购、换优惠券),金额就会不一致。
2. 浮点数精度:我看到 discountAmount 是 double 类型,
参与多次乘除后有精度误差。应该用 BigDecimal。
3. 缓存问题:价格是从缓存读的,TTL是5分钟。如果商品价格
刚刚改了,有5分钟内的订单可能用的还是旧价格。
千分之三的概率指向并发问题,因为浮点精度误差通常不会
这么低频,缓存问题应该会更集中在价格变更后的短时间内。
建议先排查并发场景——问题最可能在这里。
你:并发那个场景,你能重现条件吗?
Claude:可以模拟。下单流程是:
1. 用户加入购物车(此时快照价格A)
2. 打开结算页,calculateTotalAmount()计算出金额X(用了优惠券C)
3. 如果用户在2秒内换了一张优惠券,再次触发计算,
优惠券C失效,新优惠券D生效,金额变成Y
4. 但用户提交的是金额X,实际扣款用的是Y
这个时间窗口只有前端渲染结算页到用户点支付的这段时间,
所以概率不高,但存在。
此时我们已经定位到根本原因,但一行代码都没动。
第二阶段:Plan模式制定修复方案
你:好,并发那个问题确认了。给我一个修复方案,
不要改代码,我们先讨论。
Claude:修复方案有两个思路:
方案A(服务端二次校验):
在支付前,服务端重新计算一次当前金额,
如果与用户提交的金额误差超过0.01元,拒绝支付,提示用户重新结算。
改动量:OrderService + PaymentService,约50行
方案B(版本号锁):
结算页生成时带一个版本号,提交时校验版本号是否仍然有效。
如果期间有任何价格/优惠券变化,版本号失效,要求重新结算。
改动量:OrderService + OrderController + 前端,约120行
方案A改动小,但用户体验略差(支付时才报错)。
方案B体验好(结算时就能发现问题),但需要前端配合。
建议方案A,因为:1.改动范围可控;2.支付拦截是更安全的兜底。
你:方案A可以,但校验阈值不一定是0.01,金额大的订单误差也会大,
用百分比更合适,比如误差超过0.1%就拒绝
Claude:完全正确,用相对误差更严谨。
修改:校验逻辑改为 |实际金额 - 提交金额| / 提交金额 > 0.001
这样不论订单大小,误差容忍度都是0.1%。
确认这个方案吗?
你:确认,开始改
第三阶段:Edit模式执行修改
Claude:我将修改以下文件:
1/2 - PaymentService.java
在 processPayment() 方法里增加金额校验:
[diff展示]
+ // 二次校验金额,防止并发导致的金额不一致
+ BigDecimal submittedAmount = order.getSubmittedAmount();
+ BigDecimal actualAmount = orderService.recalculateAmount(order);
+ BigDecimal diff = submittedAmount.subtract(actualAmount).abs();
+ if (diff.divide(submittedAmount, 4, HALF_UP).compareTo(THRESHOLD) > 0) {
+ throw new AmountMismatchException("订单金额已变化,请重新结算");
+ }
接受吗?[y/N]
> y
2/2 - OrderService.java
新增 recalculateAmount() 方法:
[diff展示]
接受吗?[y/N]
> y
Claude:修改完成。建议运行以下测试验证:
mvn test -Dtest=PaymentServiceTest -pl order-service
你:测试通过了,帮我补充一个并发测试用例,覆盖刚才修复的场景
Claude:(继续在Edit模式里补测试)
这个完整流程大约花了20分钟,从问题定位到修复加测试,三种模式各司其职,没有一步是多余的。
6.7 常见问题
Q:我在Edit模式里说"帮我改",Claude改了但方向不对,怎么办?
按 Ctrl+Z 不能撤销Claude的文件修改(Claude Code不提供内置的撤销功能)。在开始大范围修改前,先用git提交或者做备份:
git stash # 把当前改动暂存,方便回滚
如果Claude改错了,你可以:
git checkout -- 文件名恢复单个文件git stash pop恢复改动前的状态- 直接告诉Claude"你刚才改错了,帮我恢复到修改前",它能生成一个反向diff
Q:Plan模式里讨论了很久,上下文快满了怎么办?
在切到执行模式前,先在CLAUDE.md里记录已确认的方案要点。这样就算 /compact 压缩了历史,Claude在执行时仍然能参考方案文档。
Q:Edit模式下Claude改了20个文件,我来不及每个都仔细看怎么办?
按 a(全部接受)先让它改完,然后用 git diff 完整审查所有改动,发现问题再告诉Claude修正。这比在Claude每改一个文件时盯着看更高效——因为你可以对着完整的改动做系统性评估。