我用 Claude Code 重构了一个 3 年老项目,终端 AI 编码到底能不能打?

31 阅读10分钟

三天前我接到一个活:把一个 3 年前写的 Flask 项目迁移到 FastAPI,同时把 SQLAlchemy 1.4 升到 2.0。代码量大概 8000 行,20 多个接口,测试覆盖率 0%。

原本准备硬啃一周。结果用 Claude Code 在终端里搞了两天半就上线了——没有 GUI 编辑器,全程 SSH + 终端。

这不是一篇"Claude Code 多好用"的吹文。说实话,过程中踩了不少坑,有些甚至让我差点放弃切回 Cursor。但最终能在两天半完成,确实说明终端 AI 编码在某些场景下有独特优势。

这篇就把整个过程拆开来说,哪些地方让我惊喜,哪些地方让我骂街,以及我沉淀下来的几个实战工作流。

为什么选终端而不是 GUI?

先说背景。这个项目跑在一台海外的 Linux 服务器上,我本地是 Mac。

之前用 Cursor 做远程开发,走的是 Remote SSH 插件。体验嘛... 说多了都是泪:

  • 大文件一打开 Cursor 就卡,CPU 直接拉满
  • Remote SSH 时不时断连,重新加载整个项目
  • Cursor 的 AI 功能在 Remote 模式下经常抽风,要么不读上下文,要么读错文件
  • 改完代码还要在终端里跑测试、看日志,不停切窗口

后来想了想,反正都在终端里跑命令,干脆直接 SSH 上去用 Claude Code 算了。这个项目不涉及前端,纯后端 API,也不需要 GUI 看效果。

事实证明这个决定是对的。

第一步:给 AI 写"入职手册"

上手第一件事不是急着重构,而是在项目根目录建了一个 CLAUDE.md

这个文件很多人知道但不重视,觉得可有可无。我之前也这么想,直到在这个项目上吃了亏。

第一次没写 CLAUDE.md,直接让 Claude Code 改代码。结果它把 SQLAlchemy 的 session 管理搞成了全局单例——项目用的是 request-scoped session,多请求并发直接炸了。它根本不知道这个项目的 session 策略。

痛定思痛,花了 20 分钟写了一份:

# CLAUDE.md

## 项目架构
Flask → FastAPI 迁移项目。原框架 Flask + SQLAlchemy 1.4 + Celery。
目标:FastAPI + SQLAlchemy 2.0 + 保留 Celery。

## 数据库
- PostgreSQL 14,连接池用 asyncpg
- Session 管理:request-scoped,通过 FastAPI Depends 注入
- ⚠️ 绝对不要用全局 session 或 scoped_session

## 迁移规则
- Flask Blueprint → FastAPI APIRouter,一一对应
- Flask g/request → FastAPI Depends 注入
- 所有 DB 操作改 async,用 select() 语法替代 query()
- 错误处理统一用 HTTPException,不要自定义异常类

## 目录结构
src/
├── routes/     # API 路由(原 views/)
├── services/   # 业务逻辑
├── models/     # SQLAlchemy 模型
├── schemas/    # Pydantic 模型
└── deps.py     # FastAPI 依赖注入

效果立竿见影。有了这份文件后,Claude Code 生成的代码在 session 管理、错误处理、代码组织上基本都符合项目规范了。偶尔有小偏差,提醒一次它就记住。

**经验:CLAUDE.md 不是可选的,是必须的。**特别是对已有项目,AI 不了解你的架构决策和约定,写 5 分钟的文档能省 5 小时的返工。

进阶用法:我在 tests/ 子目录也放了一份 CLAUDE.md,专门说明测试规范(用 pytest-asyncio、fixture 命名、mock 策略),AI 写测试的时候会额外加载。

重构的正确姿势:先读后写

这是我踩的第二个坑。

一开始我的指令是:"把 src/views/user.py 从 Flask 迁移到 FastAPI"。

Claude Code 直接动手了,生成了一版 FastAPI 代码。格式漂亮,结构清晰——但它把好几个业务校验逻辑"优化"掉了。比如原来有一段判断用户是否属于某个组织的代码,它觉得"冗余"就删了。

AI 非常擅长"优化"代码,但它不理解哪些"冗余"是有意为之的。

后来我改了策略:

第一步:先读 src/views/user.py 和 src/services/user_service.py,
       列出所有接口和核心业务逻辑,不要遗漏任何校验。
       
(等它读完列出来,我确认没遗漏)

第二步:现在按照 CLAUDE.md 的规范,逐个接口迁移到 FastAPI,
       所有业务逻辑原样保留,只改框架相关的部分。

这个两步走的方式,让迁移准确率从大概 70% 提升到了 95% 以上。少数问题也都是 SQLAlchemy 2.0 语法差异,不是业务逻辑错误。

上下文窗口是命脉,必须主动管理

重构到第二天,我发现 Claude Code 的输出质量开始下降。问它改一个接口,它开始"忘记" CLAUDE.md 里的规范,又搞出了一些之前纠正过的问题。

原因很简单:对话太长了,上下文窗口被塞满了。

Claude Code 的 /compact 命令就是为这个设计的。它会把之前的对话压缩成摘要,释放上下文空间。

但我发现光 /compact 还不够。更好的策略是按模块开新会话

会话 1:迁移 routes/user.py + routes/auth.py(用户和认证模块关联紧密)
会话 2:迁移 routes/order.py + routes/payment.py(订单和支付关联紧密)
会话 3:迁移 routes/admin.py + routes/stats.py(管理后台)
会话 4:写集成测试

每个会话都是干净的上下文,只需要读 CLAUDE.md + 当前模块的代码。效果比一个超长会话好太多了。

我的经验法则:

  • 对话超过 15 轮 → /compact
  • 切换到不相关的模块 → 开新会话
  • 发现输出开始跑偏 → 先 /compact,还不行就新会话

Git 操作:终端 AI 的杀手锏

这是让我真正觉得"终端 AI 比 GUI 强"的部分。

Claude Code 不只是帮你写代码,它直接操作 Git,而且理解你的 commit 历史和 diff。

场景一:智能 commit message

每迁移完一个模块,我会说:

> 看看当前改了什么,帮我生成 commit message,用 conventional commits 格式

它会 git diff --stat,分析每个文件的变更,然后给出结构化的 message:

refactor(routes): migrate user routes from Flask to FastAPI

- Convert Flask Blueprint to FastAPI APIRouter
- Replace g.db_session with Depends(get_db)
- Rewrite SQLAlchemy queries to 2.0 select() syntax
- Add Pydantic response models for type safety

Affects: routes/user.py, schemas/user.py, deps.py

比我自己写的 refactor: migrate user routes 详细太多了。而且因为它真的读了 diff,内容都是准确的。

场景二:回滚辅助

有一次我迁移支付模块后跑测试,发现金额计算对不上。但改了好几个文件,不确定是哪里引入的 bug。

> git diff HEAD~1 -- src/services/payment_service.py 帮我逐行分析,
> 哪些改动可能影响金额计算

它直接定位到了问题:原来 Flask 版本用的是 Decimal,我迁移时 Pydantic v2 的 schema 默认把它转成了 float,精度丢了。

如果在 GUI 编辑器里,我大概得自己肉眼看 diff,一个文件一个文件翻。在终端里 AI 帮你看 diff + 定位问题,整个流程丝滑得多。

场景三:冲突解决

迁移过程中我开了多个分支并行改不同模块,最后 merge 时遇到了一些冲突(主要是 deps.pymodels/__init__.py 被多个分支修改过)。

> 这个文件有 merge conflict,帮我解决。
> 两个分支都在 deps.py 里添加了新的 dependency,我需要保留所有的。

Claude Code 理解了两个分支的意图,正确地合并了代码,没有丢失任何一方的修改。这种"理解意图再合并"的能力,纯机械的 merge 工具做不到。

真正的效率差异在哪

整个重构下来,我记录了一下时间分配:

阶段用时备注
写 CLAUDE.md + 了解项目2h这步省不得
迁移 20+ 个接口12h平均每个接口 30-40 分钟
写测试6h让 AI 写初版,我重点 review 边界
修 bug + 联调4h主要是 SQLAlchemy 2.0 的异步坑
Git 整理 + 上线1h
总计~25h两天半

同样的工作量,如果纯手写我估计至少一周。如果用 Cursor 通过 Remote SSH,考虑到卡顿和上下文丢失,大概 4 天。

效率差异主要来自两点:

  1. 终端里"改-测-调"是零切换的。改完代码直接在同一个终端跑 pytest,看到报错直接让 AI 修。不用在编辑器、终端、浏览器之间切来切去。

  2. Git 操作集成在工作流里。commit、diff、回滚、分支管理都在同一个对话上下文中,AI 知道你前面改了什么,分析问题更精准。

几个反直觉的教训

1. 别让 AI 一次改太多

"把这个模块从 Flask 迁移到 FastAPI"(整个文件 500 行)
✅ "先迁移这个模块的前 3 个接口:GET /users, POST /users, GET /users/{id}"

拆小任务,每次改完验证。一次让 AI 动太多代码,出了问题你都不知道从哪查起。

2. 报错信息是最好的 prompt

遇到 bug 别自己描述,直接贴完整 traceback:

> 跑 pytest tests/test_user.py::test_create_user 报错了:
> [粘贴完整 traceback]
> 帮我分析原因并修复

Claude Code 处理 traceback 的能力非常强。比你花 5 分钟描述"好像是数据库事务的问题"有效得多。

3. SQLAlchemy 2.0 迁移有个大坑

这个和 Claude Code 关系不大,但值得说。session.execute(select(...)) 返回的是 Result 对象,不是直接的 model 实例。很多人(包括 AI)会忘记调 .scalars().scalar_one_or_none()

# 这样写拿到的是 Row 对象,不是 User 实例
result = await session.execute(select(User).where(User.id == user_id))
user = result.first()  # 这是一个 Row,不是 User!

# 正确写法
result = await session.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()  # 这才是 User 实例

Claude Code 在我 CLAUDE.md 里特别标注后就没再犯这个错,但第一次迁移它搞混了好几处。所以踩过的坑一定要更新到 CLAUDE.md,不然后面会反复踩。

4. 终端 AI 不适合所有场景

说了这么多优点,也说说不合适的场景:

  • 前端开发:需要实时看页面效果的,终端里搞不了
  • 需要频繁跳转阅读代码:如果你需要同时打开 5 个文件对照看,GUI 编辑器更方便
  • 小修小补:改个配置文件、调个 CSS——杀鸡别用牛刀

我现在的组合是:日常开发 + 前端 → Cursor,服务器端重构 / 跨文件改动 / Git 操作 → Claude Code。

开发中的模型选择

重构过程中有一个细节:不同阶段对模型的要求不一样。

写简单的样板代码(比如把 Flask route 转成 FastAPI route,结构基本固定),用轻量级模型就够了,快且便宜。但遇到复杂的业务逻辑迁移、并发问题排查,就需要更强的模型来保证准确性。

现在大部分 AI 编码工具都支持多模型切换。Claude Code 默认用 Sonnet,需要深度分析的时候可以手动切到更强的模型。日常开发养成这个意识,长期能省不少成本。

总结

Claude Code(或者说终端 AI 编码)不是万能的。但在服务器端开发、跨文件重构、Git 密集操作这几个场景下,它比 GUI AI 编码工具更流畅。

核心工作流:

  1. 先写 CLAUDE.md——给 AI 足够的项目上下文,这步绝对不能省
  2. 先读后写——让 AI 先理解现有代码再动手,避免它"好心"删掉你的业务逻辑
  3. 管好上下文窗口——按模块拆会话,定期 /compact
  4. 善用 Git 集成——commit、diff、blame、冲突解决,终端 AI 的原生优势

两天半搞定一个老项目迁移,过程不完美但结果还不错。如果你手头也有类似的后端重构任务,值得试一下终端 AI 的方式。


最近在频繁折腾各种 AI 编码工具,后续打算写写 SQLAlchemy 2.0 异步迁移的详细踩坑记录。感兴趣可以关注一下。