三天前我接到一个活:把一个 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.py 和 models/__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 天。
效率差异主要来自两点:
-
终端里"改-测-调"是零切换的。改完代码直接在同一个终端跑
pytest,看到报错直接让 AI 修。不用在编辑器、终端、浏览器之间切来切去。 -
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 编码工具更流畅。
核心工作流:
- 先写
CLAUDE.md——给 AI 足够的项目上下文,这步绝对不能省 - 先读后写——让 AI 先理解现有代码再动手,避免它"好心"删掉你的业务逻辑
- 管好上下文窗口——按模块拆会话,定期
/compact - 善用 Git 集成——commit、diff、blame、冲突解决,终端 AI 的原生优势
两天半搞定一个老项目迁移,过程不完美但结果还不错。如果你手头也有类似的后端重构任务,值得试一下终端 AI 的方式。
最近在频繁折腾各种 AI 编码工具,后续打算写写 SQLAlchemy 2.0 异步迁移的详细踩坑记录。感兴趣可以关注一下。