先设定一个具体的改造对象,后面所有操作都围绕它。项目形态很常见:Express 4 + React,src/routes/index.js 1800 行,路由、SQL、参数校验混在一个文件里;package.json 里有 jest 但没有一个测试文件;.env 三年没人动过;上线方式是 SSH 到服务器上 git pull && pm2 restart。
这次只做三件事,其他一律不碰:
- 把 1800 行的路由文件按资源拆成目录
- 让下单、查库存、退款这三条核心路径有能跑的测试
- 打成镜像部署到一台云服务器,出问题能在三分钟内回到上一版
工具选型:先分清哪类活归哪个工具
AI 编程工具到 2026 年的差异,主要不在补全速度,而在两件事:能不能自己跑命令看结果,以及你能不能看懂它改了什么。
| 工具 | 形态 | 公开计费档位 | 更适合的活 |
|---|---|---|---|
| Cursor | 独立 IDE(VS Code 分支) | Pro 约 20 美元/月,另有更高额度档 | 跨文件改动、Tab 补全、边看 diff 边改 |
| Claude Code | 终端 CLI | 含在 Claude Pro(20 美元/月)与 Max 档内,也可换成 API key 按量付费 | 长链路重构、自己跑测试、批量重命名 |
| 通义灵码 | VS Code / JetBrains 插件 | 个人版免费,企业版单独报价 | 国内网络环境、中文注释补全、单测生成 |
| GitHub Copilot | 编辑器插件 | 个人版 10 美元/月 | 行内补全,跟 GitHub PR 流程配合 |
实际组合方式:Cursor 开项目,左边看代码,底部终端跑 claude。让 Claude Code 去改文件、跑 npm test、把报错吞掉重试;你在这边盯着 diff 决定收不收。查框架文档的时候,通义灵码在弱网下更省事,Claude Code 则适合把文档页内容直接粘进上下文里问。
需求拆解:把重构切成能被两条命令验证的块
一次会话只解决一个能被命令验证的问题。判断标准很简单:这次改动做完,我能不能用两条命令(一条测试、一条类型检查)判断它对不对。
拆的时候按文件边界切,不要按功能重要性切。功能相关的代码往往耦合,一次动三个模块,测试红了都不知道是哪个引起的。
那 1800 行文件拆成任务卡,大概是这个样子:
- 抽出
GET /books、GET /books/:id到src/routes/books/list.js - 抽出订单相关 6 个接口到
src/routes/orders/ - 把散落在路由里的 SQL 抽到
src/repositories/ - 调整
src/routes/index.js的挂载顺序,保证 404 处理还在最后
npm test 全绿,npx tsc --noEmit 无输出。达不到就停,不往下走。
上下文工程:决定 AI 每次能看到什么
上下文给多了,模型被无关代码带偏;给少了,它自己猜,猜错还得你收尾。我把它分三层。
第一层,项目级常驻规则。 Cursor 放在 .cursor/rules/ 下的 .mdc 文件里,带 frontmatter;Claude Code 放在项目根目录的 CLAUDE.md 里,第一次可以用 /init 生成初稿再手改。写规则时只写能被检查的约束,别写「注意代码质量」这种没法验证的话。
---
description: 项目通用约束
alwaysApply: true
- 运行时 Node 22,包管理用 npm,不要执行任何 yarn / pnpm 命令
- 数据库连接统一从 src/db/index.js 导出的 pool 取,禁止在路由里 new Client
- 新增接口必须写 zod schema,放在同目录的 schema.ts
- 禁止修改 src/routes/legacy/ 下的任何文件
- 每次改动结束必须跑通:npx tsc --noEmit 和 npm test
**第二层,单次任务的说明。** 包括改哪个文件、不改什么、验收命令是什么。这一段写清楚,能省掉一半来回。
第三层,这一轮对话里 @ 的具体文件。 别指望它自己找,文件名有歧义的时候尤其容易找错。
一次拆路由的提示词,实际长这样:
任务:把 src/routes/index.js 里 /books 相关的 2 个接口拆到 src/routes/books/list.js。
约束:
- 只允许新建 src/routes/books/list.js,并修改 src/routes/index.js 的挂载部分
- 不要动 src/db/、src/repositories/ 下的任何文件
- URL、状态码、响应字段全部保持原样,前端不改
- 用 express.Router(),在 index.js 里按原来的位置挂载
- 不要顺手格式化其他行
验收:npx tsc --noEmit 无输出,npm test 全绿。
先告诉我你打算怎么改,等我确认再动手。
最后那句很重要。让它先给方案,你花三十秒看一眼,比它改完 200 行你再回滚便宜得多。
代码审查与测试:AI 说通过不算通过
重构最大的风险是行为悄悄变了。测试是唯一的防线,而存量项目大概率没测试,所以第一步是补特征测试——把现在的行为固定下来,哪怕这个行为看起来不合理。
对 AI 的指令可以这样写:不要改实现代码,先读 src/routes/index.js 里的 POST /orders,写一个测试把它当前的响应结构和状态码固定下来,然后运行 npm test 确认通过。
写完要验一下测试有没有真的在测东西。方法很土但有效:手动把实现里的 res.status(200) 改成 res.status(201),再跑测试。如果还是绿的,这个测试就是摆设。
常见的 AI 测试放水有三种形态:
- 断言写成
expect(res.body).toBeDefined(),什么都没验证 - 把被测函数 mock 掉,最后测的是 mock
- 测试跑不过时,改的是测试而不是实现
审查 diff 的时候别点 Accept all。按这个清单逐块看:
| 看什么 | 具体怎么判断 |
|---|---|
| 删除的逻辑 | 有没有把错误处理、边界判断一起删掉 |
await | 异步调用有没有漏 await,forEach 里有没有用 async |
| 错误分支 | try/catch 是不是被改成了静默吞掉 |
| SQL | WHERE 条件有没有在搬移过程中变松 |
| 导出 | 有没有文件被改了但没被任何地方引用 |
整个循环长这样:
部署到云:让 AI 写文件,让人按发布键
镜像相关的文件(Dockerfile、compose、CI 配置)是 AI 写得又快又好的部分,但生产环境的操作必须由人执行。理由很直接——它看不到你的账单、安全组和证书到期时间。
多阶段 Dockerfile,可以让它按这个骨架生成:
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD node dist/server.js
生成之后逐条检查四件事:基础镜像有没有 pin 到具体 tag;有没有用非 root 用户跑;.dockerignore 里有没有 .env 和 node_modules;有没有把源码一起复制进运行阶段。
国内服务器上,镜像推到阿里云容器镜像服务(个人版免费),ECS 上用 compose 起:
services:
api:
image: registry.cn-hangzhou.aliyuncs.com/your-ns/bookshop:2026-03-01
env_file: .env
restart: unless-stopped
ports:
- '127.0.0.1:3000:3000'
nginx:
image: nginx:1.27-alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
ports:
- '80:80'
- '443:443'
depends_on:
- api
两个细节值得留意。端口绑 `127.0.0.1` 而不是 `0.0.0.0`,应用容器不直接对公网暴露;镜像 tag 用日期或 commit sha,不用 `latest`——回滚就是把 tag 换回上一个再 `docker compose up -d`,跟重新构建没有任何关系。
数据库迁移别交给 AI 在生产上跑。让它生成 migration 文件,你在预发环境先执行一遍,确认能回滚,再手动上生产。
效率提升与坑点
省时间的做法,本质上都是减少上下文来回搬的次数:
- 同一类改动批量做。10 个接口的校验逻辑是同一个模式,让 AI 一次改完再统一 review,比改一个聊一次快得多
- 把验证命令固化到
package.json的 scripts 里,提示词里直接写npm run check,不用每次解释要跑什么 - commit 前挂 pre-commit hook,跑类型检查和 lint,AI 生成的问题在本地就拦掉
- 一个任务一张卡,做完就
/clear或开新会话。跨任务的上下文是负资产
| 现象 | 真正的原因 | 处理方式 |
|---|---|---|
| 改了 A 文件,B 功能挂了 | B 不在上下文里,AI 不知道有依赖 | 把 B 也写进规则或 @ 进去 |
| 它说测试通过了,但你没看到输出 | 没真跑,或者跑的是旧结果 | 要求贴出完整命令和原始输出 |
| 调用的库函数不存在 | 模型记的是旧版本 API | 让它先读 node_modules 里的类型定义 |
| 会话越聊越糊涂 | 无关内容把上下文挤满了 | 换任务就清空,别硬撑 |
.env 进了 git | 生成的配置里硬编码了连接串 | 提交前跑一遍 gitleaks |
参考
- Cursor 官方文档:https://cursor.com/docs
- Claude Code 官方文档:https://docs.claude.com/en/docs/claude-code/overview
- 通义灵码官方文档:https://help.aliyun.com/zh/lingma/
- GitHub Copilot 官方文档:https://docs.github.com/en/copilot
先做这一件事
打开项目根目录,新建 .cursor/rules/project.mdc(Claude Code 用户写 CLAUDE.md),把三条不能被违反的约束写进去,比如「禁止修改 legacy 目录」「接口响应结构不变」「改完必须过 tsc」。然后挑最小的那个路由文件,让 AI 拆一次。
如果拆完的 diff 你花十分钟还看不明白,说明任务切得太大,退回去再切一刀,别急着合并。