去年冬天接了个私活,给一套跑了七八年的订单系统加一个"对账状态"字段。功能本身半小时的活,我却在里面耗了整整两天。真正让我改变工作方式的,是第三天早上我换了个思路:新功能那部分继续在 Cursor 里写,老代码那部分交给 Claude Code 去啃。结果当天下午就提了 PR。从那以后,我的机器上就固定跑着这么一套双工具的工作流——Cursor 负责从零到一的产出,Claude Code 负责把历史包袱翻译成人话,中间靠一份手写的交接文档串起来。IDE 装两个不稀奇,稀奇的是你要清楚什么活派给谁,以及怎么让它们互相不打架。
1. 我为什么把"写新功能"和"读懂老代码"拆成两条线
1.1 一个真实的下午:给十年前的订单模块加个字段
需求很简单:订单表加一个reconcile_status字段,下单时默认PENDING,支付回调时改成MATCHED。听起来是三个文件的事。
实际打开项目之后是这样的:Controller 层有一层拦截器做参数脱敏,Service 层继承了一个泛型基类,基类里又调了一层模板方法,最后落到一个 Dao,Dao 里的 SQL 不是写在注解里,而是由 XML 加上一堆<if>拼出来的,而且同一个字段名在两个不同的 XML 里都出现过,其中一个已经被废弃但没删。我盯着这个链路看了四十分钟,脑子里全是问号:这个字段到底该在模板方法的哪一步塞进去?改了基类会不会影响另外十几个子类?
Cursor 在这种场景下给我的体验是分裂的。我敲下第一行代码,它的 Tab 补全跟读心术一样准;但当我问它"这个基类的模板方法被哪些子类重写过",它给的答案里有明显编造的成分——因为它能看到的上下文只有我当前打开的几个文件,剩下的靠"猜"。这不是它不行,是任务类型不对。
1.2 两类任务对工具的诉求几乎是相反的
我把这两类活儿的差别列了个表,列完就明白为什么一个工具干不了全部:
| 维度 | 写新功能 | 读懂老代码 |
|---|---|---|
| 输入规模 | 局部,几个文件足够 | 全局,要扫全仓 |
| 起点 | 一张白纸 | 一堆历史决策 |
| 核心诉求 | 速度、补全、即时预览 | 检索、溯源、证据链 |
| 输出的东西 | 能跑的代码 | 能信服的结论 |
| 最容易出的错 | 代码风格不一致 | 幻觉出的"事实" |
| 人该干的活 | 审 diff | 定边界 |
写新功能是"发射"动作,你要的是快的反馈循环:敲一行,看到补全,Tab 接受,跑起来看效果。这个时候任何需要你去终端里敲命令、等它检索、再回头切窗口的动作都是打断心流。
读老代码是"考古"动作,你要的是广度和耐心:一次把所有相关的文件都读进来,顺着调用关系往上往下捋,找不到证据就继续找。这个时候编辑器里那点补全能力毫无用处,你需要的是一只能替你跑 grep、能一次吞下几万 token 的"机械臂"。
1.3 分工线画在哪里:一张判断表
具体怎么分?我自己用的是下面这张表,干了一段时间基本没有犹豫过:
| 任务 | 交给谁 | 原因 |
|---|---|---|
| 从零写一个新模块/新页面 | Cursor | 上下文干净,补全效率高 |
| 补单元测试 | Cursor | 有现成的被测代码做参照 |
| 改样式、调布局 | Cursor | 需要即时视觉反馈 |
| "这个函数是谁调的" | Claude Code | 全仓检索 + 调用链 |
| "为什么这里要加个 if" | Claude Code | 需要看提交历史和相邻代码 |
| 老逻辑的重构方案 | Claude Code | 先出方案,人来拍板 |
| 两套老代码的行为差异 | Claude Code | 需要同时读两边 |
| 修一个已知的 bug | 看情况 | 定位用后者,改动用前者 |
注意:这张表里最容易被忽略的是最后一行。很多人修 bug 时用一个工具从头干到尾,结果定位阶段嫌它慢、改动阶段嫌它不准。拆开之后,定位的活儿交给 Claude Code,它在终端里跑几条检索命令就能把嫌疑范围缩到两三个文件;改动交回 Cursor,你在熟悉的编辑器里几秒钟改完。
2. 环境落地:让两个工具在同一台机器上各就各位
2.1 Cursor 侧的中文界面与基础设置
Cursor 本身就是从 VS Code 分支出来的,所以扩展生态和快捷键体系基本通用,这一点让上手成本几乎为零。新装完第一件事是切中文:按Ctrl+Shift+P打开命令面板,输入Configure Display Language,选简体中文;如果列表里没有,就去扩展市场搜 "Chinese",装那个简体中文语言包,重启即可。也可以直接在设置里搜language,在 Display Language 那一栏改。
我个人的偏好是代码区保持英文界面,只有菜单和设置用中文——因为大量的技术名词翻译过来反而增加理解成本。这一点不影响使用,语言包是整体的,装了就都变了,看个人习惯。
比语言设置更重要的是三件事:
- 补全的开关和强度。Cursor 的 Tab 补全是它的核心能力,但如果你在写老代码,它会顺着老代码的坏习惯给你补出同样的坏味道。我的做法是改老代码时把
Cursor Tab临时关掉,或者只在写新文件时开。 - 模型选择。简单补全用小模型,跨文件重构用大模型,这个差别在响应速度上非常明显。
- 隐私相关的选项。设置里有是否允许把代码用于训练的开关,涉及公司项目的机器上,这个选项我建议在入职第一天就确认清楚,别等到出问题才想起来看。
2.2 Claude Code 的安装与终端接入
Claude Code 是一个命令行工具,走的是"在终端里跟它对话、它自己读写文件、自己跑命令"的模式。安装方式我当时用的是 npm 全局安装:
npm install -g @anthropic-ai/claude-code claude --version装完之后最容易卡住的地方不是安装本身,而是PATH。如果你用的是 nvm 管理 Node 版本,npm 的全局 bin 目录不在系统默认路径里,终端会提示找不到命令。解决办法是把它加进 shell 配置:
# 先看看全局 bin 在哪 npm config get prefix # 假设输出是 /Users/you/.nvm/versions/node/v20.x.x # 把它加进 ~/.zshrc 或 ~/.bashrc export PATH="$HOME/.nvm/versions/node/v20.x.x/bin:$PATH"然后在项目根目录直接敲claude就能起来。它跑在终端里,所以你在 VS Code 或者 Cursor 的内置终端里用都行——我更喜欢在 Cursor 的内置终端里跑它,这样两个工具在同一个窗口,切窗口的成本降到最低。用Esc可以打断它正在做的事,Ctrl+C两次退出。
提示:如果你的项目在 Windows 上,建议在 WSL 里跑这个命令行工具,文件路径和权限问题会少很多,Linux 那一套命令也能直接用。
2.3 一份两边都要读的"项目约定"文件
这是我认为整套工作流里最值钱的一步。两个工具各自都支持项目级的上下文文件——Cursor 侧是.cursor/rules目录下的规则文件,Claude Code 侧是根目录的CLAUDE.md。我的做法是写一份主文档,两边都指向它,而不是维护两份内容。
这份文档里我固定放四类东西:
# 项目约定 ## 1. 技术栈与版本 - 后端 Java 17 + Spring Boot 3.x - 前端 Vue 3 + Vite - 数据库 PostgreSQL 15 ## 2. 目录职责(新人/新工具看这里) - src/main/java/.../controller 只做参数校验和转发,不写业务 - src/main/java/.../service 业务逻辑都在这里 - src/main/resources/mapper 所有 SQL 都在 XML,不要用注解写 SQL ## 3. 禁止事项 - 不要改动 legacy/ 目录下的任何文件,除非明确要求 - 不要引入新的第三方依赖 - 不要重命名已有的 public 方法 ## 4. 命名与风格 - 新字段一律加 biz_ 前缀 - 时间字段统一用 timestamptz为什么要写这份东西?因为两个工具最大的共同问题不是能力不够,而是不知道你的规矩。它默认会用"通用最佳实践"来给你建议,而你的项目可能有一堆反常识的约定。把约定写下来,相当于给两个新来的同事发了一本员工手册,后面每次对话都省掉一大段解释。
2.4 手不能离开键盘:切换动作的肌肉记忆
双工具流最大的敌人不是技术问题,是切换成本。如果每次交接都要鼠标点三下、等窗口加载,用不了两天你就会退回单工具。
我固化了几个动作:
- Cursor 里
Ctrl+L打开对话面板、Ctrl+K做内联改写、Ctrl+I打开跨文件的 Agent 模式; - 终端里跑
claude之后,所有提问都是纯文字,不需要记额外快捷键; - 用 Cursor 的内置终端跑 Claude Code,这样"编辑器里改代码"和"终端里问老代码"在同一个窗口,
Ctrl+~就能切焦点。
这三个动作练熟之后,整个工作流就变成了:左边写新代码,右边问老逻辑,中间靠Ctrl+~来回跳。
3. Cursor 那一侧:从一句话需求到可提交的代码
3.1 先让它复述需求,再让它动手
这一步是我踩了坑之后加上的。以前我直接把需求丢进去,它噼里啪啦生成一堆代码,我看着挺像那么回事,合进去之后发现它理解的需求和我要的不是一个东西——比如我说"下单时初始化对账状态",它理解成"每次查询订单时都重新算一遍对账状态",逻辑完全跑偏。
现在的固定流程是:第一句话不要求写代码,只要求它复述。
先不要写代码。 请用你自己的话复述一遍下面这个需求,并且列出你认为有歧义的地方: 需求:订单新增对账状态字段,下单时初始化为 PENDING, 支付成功回调时改为 MATCHED,退款时改为 REFUNDED。它复述完,我扫一眼就能发现理解偏差。有歧义的地方当场拍掉,后面生成的代码质量会高一个台阶。这个动作只花三十秒,能省掉半小时的返工。
3.2 Tab 补全和 Agent 模式不是一回事
很多人把这两个能力混着用,结果两边都没用好。它们的适用范围差别很大:
| 能力 | 适用场景 | 不适用场景 |
|---|---|---|
| Tab 补全 | 你已经开始写了,它在后面接 | 从零生成整个模块 |
| 内联改写(Ctrl+K) | 选中一段代码局部修改 | 跨多个文件的改动 |
| Agent 模式(Ctrl+I) | 新建多个文件、跨文件重构 | 精细的逐行调整 |
我自己的习惯是:写新功能时 80% 的时间在 Tab 补全上。因为我对代码结构很清楚,就是手速跟不上思路,Tab 正好把中间那段机械化的工作接过去了。只有当我需要一次性生成"实体类 + DTO + Service + Controller + 测试"这一整套的时候,才会切到 Agent 模式。
Agent 模式有个必须知道的特性:它会在你没明确授权的情况下,顺手修改相邻的文件。我遇到过一次,让它加个校验方法,它顺手把同一个类里另外三个方法的命名风格统一了——从它角度看这叫"顺手优化",但对我意味着 review 成本翻倍。应对办法是在指令里写死边界:
只允许修改 OrderService.java 这一个文件。 不要重命名任何已有方法,不要调整任何已有代码的格式, 不要修改 import 顺序。新增内容只允许追加。3.3 写新功能时我的提示词骨架
用了几个月之后,我的提示词基本固定成五个部分。这个骨架对任何语言都通用:
[目标] 我要实现什么,一句话说清楚。 [约束] 必须遵守的项目约定(引用项目约定文档里的条目)。 [边界] 允许改哪些文件,明确不允许碰哪些。 [验收] 什么情况下算完成:能编译、测试通过、某个接口返回什么。 [参照] 项目里已有的同类实现是哪个文件,照着它的风格来。其中"参照"这一条最容易被忽略,但效果最明显。老项目里往往已经有一个写得很标准的同类功能,你把它指出来当模板,生成出来的代码风格就自动对齐了,不需要你一行行去改格式。
3.4 它为什么会"顺手改"别的文件
这个问题值得单独说,因为它是最容易让你在 code review 时翻车的点。
原因是模型的工作方式决定的:它不是"只改你指的那一行",而是"重写它认为相关的一段内容"。如果它读到了同一个文件里的其他代码,觉得那里"有点问题",就会一并处理。这不是 bug,是能力边界。
我的防御手段有三个,按成本从低到高:
- 写完立刻看 diff,在 Cursor 的源代码管理面板里逐块 review,看到不该动的地方直接丢弃那个 hunk;
- 提交前用
git diff --stat扫一眼,被改动的文件数量和你预期不符就停下来查; - 把大改动拆成多次小指令,一次只让它动一个文件,虽然慢一点但可控。
提示:如果你的项目有严格的 lint 或者格式化配置,接完 AI 生成的代码之后跑一遍
prettier --write或者spotless:apply,能一次性消掉大量格式噪音,让 review 时真正需要看的内容浮出来。
4. Claude Code 那一侧:在几万行老代码里定位那一句判断
4.1 老代码最耗时间的三类陷阱
啃老代码的难度不在代码量,在于它总会用三种方式骗你:
第一类是命名撒谎。方法叫getOrderStatus(),实际返回的是订单的支付渠道编码。这种代码在经历过多次需求变更的项目里到处都是。你在编辑器里搜getOrderStatus,跳过去发现返回的东西跟名字完全不搭。
第二类是多层包装。一个简单的校验,从 Controller 到 Service 到 Manager 到 Helper 到 Util,中间每层都只加了一点点逻辑。你在任意一层停下都看不明白它在干什么。
第三类是动态拼接。SQL 是字符串拼的、分支是配置驱动的、字段名是反射取的。这类代码你没法靠"找引用"来定位,因为编译器根本看不到这一层关系。
这三类问题,靠编辑器里的"跳转定义"和"查找引用"基本瘫痪。而 Claude Code 的价值就在这儿:它能一次读进几十个文件,然后顺着你的问题地毯式搜索。
4.2 用检索式提问替代"通读一遍"
新手最容易犯的错是把整个模块丢给它,然后问"这个模块是干什么的"。这种问法得到的是一篇通顺的废话——它会把类名和注释串起来编一段看起来很像但你没发验证的描述。
有效的问法是把问题变成一个可验证的检索任务:
| 无效问法 | 有效问法 |
|---|---|
| 这个模块干什么的 | reconcile_status这个字段在哪些文件里被读、在哪些文件里被写,全部列出来 |
| 为什么这么设计 | 找出所有给status赋值为MATCHED的位置,逐个说明触发条件 |
| 帮我重构一下 | 列出调用OrderHelper.buildSql()的所有位置,标注每个位置的入参差异 |
| 有 bug 吗 | 找出所有对amount做乘法的地方,检查有没有漏掉精度处理 |
差别在哪?前者得到的是一段话,后者得到的是一份清单。清单可以逐条验证,验证不了的当场标出来继续追。整段话你只能选择信或者不信,而"信"在改造老代码这件事上是奢侈品。
4.3 一次完整的溯源排查链路
举个我实际遇到的问题。现象是:某个订单在对账页显示为MATCHED,但数据库里明明是PENDING。
我交给 Claude Code 的问题是:"页面显示的reconcile_status和数据库里的值不一致,列出所有可能影响这个字段展示结果的代码路径。"
它给出的过程大致是四步:
第一步,定位读路径。它在全仓里搜reconcile_status和对应的驼峰命名,找到了三个地方:一个 Mapper 的 resultMap、一个 VO 的字段、前端的一个格式化函数。
第二步,检查中间有没有转换。它发现 VO 里那个字段的 setter 被重写过,里面做了一层映射:把PENDING映射成了MATCHED。原因是有个历史遗留的状态码表,两套状态值对应的枚举不同。
第三步,验证影响面。它搜出了所有使用这个 VO 的接口,确认只有对账页这一个地方受影响。
第四步,给出结论和证据。结论是"读路径上的 setter 做了状态映射,展示层用的是另一套枚举",并且附上了具体的文件路径和行号。
整个过程大概两分钟。如果我自己手动查,光是搜那些命名变体就得反复试十几次。这四步里最关键的是它每一步都给了可验证的落点——我只需要打开那几个文件对照一眼,就能确认结论真假。
4.4 让它交方案,别让它直接改
这是我在被坑过之后立下的规矩:在老代码上,Claude Code 只出方案,不直接改文件。
原因很直白。老代码里到处是隐式约定,模型看到的只是代码文本,看不到那些"当年客户提的这个需求"、"这个分支线上跑着"之类的背景。它动手改,改出来的东西可能逻辑上没毛病,但会破坏你根本不知道存在的约束。
所以我的指令里会明确写:
先不要修改任何文件。 请输出一份改造方案,包含四部分: 1. 需要改动的文件和具体位置(文件路径 + 方法名) 2. 每一处改动的理由 3. 这次改动可能影响的调用方清单 4. 你不能确定的地方,单独列出来让它输出方案之后,我拿这份方案去对比自己的理解,把不确定的条目挑出来继续追问。确认无误之后,方案的执行环节我再切回 Cursor——因为在编辑器里改代码、看 diff、跑测试,手感比在终端里舒服得多。
5. 交接环节:上下文在两个工具之间怎么不丢
5.1 用一份中间产物当交接单
两个工具之间没有共享上下文,这是个硬约束。Cursor 不知道你刚才在 Claude Code 里问出了什么,Claude Code 也不知道你在编辑器里改到哪一步了。所以中间的传递必须靠人来完成,形式就是一份文件。
我在项目根目录建了个notes/目录,每次改动开一个 markdown,结构固定:
# 任务:订单对账状态字段 ## 一、老代码事实(来自 Claude Code 的结论,已人工核实) - 状态字段存在两套枚举:DB 用 order_reconcile,展示层用 display_reconcile - 映射发生在 OrderVO 的 setReconcileStatus 里 - 影响接口:/api/order/detail、/api/order/list ## 二、改造边界 - 允许改动:OrderService、OrderVO、order_mapper.xml - 不允许改动:legacy/ 目录、BaseService 模板方法 ## 三、接入点 - 下单入口在 OrderService.create(),第 87 行附近 - 支付回调入口在 PayCallbackHandler.handle(),第 42 行附近 ## 四、待验证 - display_reconcile 的枚举在哪儿初始化?尚未确认这份东西看着朴素,但它是整套流程的枢纽。写的时候你被迫把思路理一遍,很多模糊的地方在落笔时就暴露出来了。
5.2 术语与命名对齐
有个细节很容易被忽略:同一件事在两个工具里的叫法可能不一样。Claude Code 在描述老代码时用的是旧的术语,Cursor 生成新代码时用的是新术语,两边对不上,后面就会出问题。
我的办法是在项目约定文件里加一个术语对照表:
| 旧称呼 | 新称呼 | 说明 |
|---|---|---|
| status | reconcile_status | 老代码里的 status 专指对账,不要跟订单状态混 |
| check() | verify() | 同名不同义,新代码一律用 verify |
| OrderHelper | 已废弃 | 新代码不要调用 |
这张表在两边对话时都会被读到,能消掉一大半的沟通偏差。
5.3 分支与提交节奏
老代码改造和新功能开发,我强制走两条分支。原因不是洁癖,是回滚粒度。老代码的改动一旦出问题,影响面往往大得多,你需要能干净地回退掉它而不影响新功能的进度。
提交节奏上我用的是"一步一提交":Claude Code 给的方案确认之后,我把它拆成若干个独立的改动点,每完成一个就提交一次,提交信息写清楚"做了什么 + 为什么"。这个习惯在后期排查时价值巨大——你可以直接git log看你当时是怎么想的。
5.4 模型互相"圆谎"时的打断方法
最后说一个很隐蔽的坑。当你把 Claude Code 的结论转述给 Cursor,Cursor 又基于这个结论生成代码时,如果那个结论本身是错的,Cursor 不会质疑,它会顺着错误结论编出一套自洽的实现。两个工具互相"圆谎",最后交付的东西看起来逻辑完整,实际上建在沙子上。
打断链路的办法只有一个:回到可执行的事实。具体就是写一个最小的复现——一个单元测试、一段 SQL、一个 curl 请求。能跑通的才是事实,跑不通的结论一律打回重查。我在交接单的"待验证"那一栏里强迫自己至少写一条,就是为了防止这种连锁错误。
6. 坑单与成本账:我把踩过的问题列了一遍
6.1 上下文不是塞得越满越好
很多人有个直觉:给模型的信息越多越好。实际用下来完全不是这样。当你一股脑塞进去十几个文件,模型对中间部分的关注度会明显下降,问它一个具体问题,它可能给你答一个文件开头的内容。
我现在的做法是按问题范围控制上下文:问一个字段的读写路径,就只让它读读写这个字段相关的文件;问一个调用链,就顺着链子读。真要全仓扫描的时候,用检索式提问让它自己去找,而不是你手动把文件全贴进去。
6.2 大仓库的索引与响应速度
仓库一大,各种隐式成本就冒出来了。编辑器侧的代码索引在几万个文件上会明显变慢,命令行工具每次启动都要重新扫一遍目录结构。几个具体的应对:
- 把构建产物、依赖目录、日志目录全部写进忽略规则,别让工具去扫它们。抓到一个几万行的
dist/目录,响应速度直接砍半; - 把历史遗留的、已经不维护的目录单独圈出来,在约定文件里写明"这些目录不要读";
- 别在仓库根目录直接跑工具命令,尽量在子模块目录里跑,扫描范围小一个数量级。
6.3 "看起来很对"的代码最危险
AI 生成的代码有个共同特征:语法对、风格对、逻辑自洽,但调用的 API 可能不存在。它会根据命名规律编出一个看起来非常合理的方法名,比如orderService.fetchReconcileByOrderNo(),实际上你项目里叫queryByNo()。
防御手段没有捷径,就是编译和测试兜底。我现在的流程里有一条硬规定:AI 生成的代码在提交前必须过一次完整的编译,不能靠肉眼扫。测试能补的尽量补,哪怕只补一个最粗糙的冒烟测试,也比没有强。
6.4 代码外发的三条基本纪律
用这类工具绕不开一个问题:你的代码要送到哪里去。我的做法是三条:
第一,密钥、配置里的密码、证书、生产环境地址,绝不粘进任何对话框。要问就先把这些值替换成占位符再问。
第二,跟公司确认规则再动手。不同公司对代码外发的要求差别很大,入职的时候问清楚一句话的事,别自己猜。
第三,优先用工具提供的隐私相关设置,能关掉数据用于训练的开关就关掉。涉及核心算法的部分,宁可不问也不外发。
7. 这套工作流不该用的几种场合
7.1 三五行的改动
如果改动范围就是三五行的函数体,用这套流程是自找麻烦。你写交接单的时间比改代码的时间还长,而且模型看完还要问你一堆确认问题。这种活直接编辑器里手敲,两分钟搞定。
7.2 性能敏感的热点路径
热点路径上的代码,模型给的方案往往是"能跑但不快"。它对常数因子、缓存友好性、内存分配次数这些东西没有直觉,生成的循环里可能藏着一个不易察觉的重复分配。这类代码我坚持自己写,最多用它帮忙生成测试用例和压测脚本。
7.3 你完全不懂的领域
这是最重要的一条。如果你对一个领域没有判断力,你就无法验证它给的答案对不对。而这类工具的输出永远是自信的——错误的答案和正确的答案,语气一模一样。在没有判断力的领域用它,等于闭着眼睛开车,速度越快越危险。
我的判断标准很简单:如果我不能在三分钟内看出它给的东西哪里不对,那这个任务我就不该交给它。判断力是你自己的,工具替不了。
用了大半年这套工作流,我最大的体会是:这两个工具解决的是完全不同的问题,把它们硬凑成一个反而互相拖后腿。Cursor 是手,负责产出;Claude Code 是眼睛,负责看清。中间那份手写的交接单看着笨,但它是唯一能让两边不跑偏的东西。
另外分享一个小习惯:每次用 Claude Code 啃完一块老代码,我都会顺手把结论写进那份交接单,哪怕这次不改它。攒上半年,你就有了一个自己项目的"考古笔记",下次谁再问起这段逻辑,你不用再重新查一遍。这个副产品,可能比省下来的那点时间更值钱。