一个流行的说法
最近听到一种观点:
设计模式、OOP、语言选择——这些都是前 Vibe Coding 时代的抽象,为了降低人类的实施复杂度而存在。现在有 AI 了,这些东西无所谓了。以前性能敏感的程序要上 C++,现在 Python 先跑起来,热点分析完直接让 AI 写 Cython 搞定。
这个说法的方向有一定道理,但尺度上被过度放大了。它模糊了”能用 AI 做”和”AI 能做好”之间的距离,而这个距离的大小,取决于一个比语法深层得多的东西:语言的执行模型。
两件事必须分开
跨语言迁移的难度不在一个水平线上。有一类迁移是语法翻译,另一类是架构移植。
语法翻译是 AI 今天做得最好的事情之一。把一段 JSON 解析逻辑从 JavaScript 翻成 Python,把一个 CRUD 接口从 Java 翻成 Go,甚至把一套 HTTP SDK 的请求构造和响应反序列化部分从任意语言翻成任意语言——这些是模式化的、可机械推导的转换。API spec 到代码的映射是确定的,认证 header 的拼接是确定的,分页逻辑的光标管理是确定的。这部分确实是”模板代码”,AI 一次性生成效果很好。
但架构移植是另一回事。
执行模型:语言真正的”底层”
什么叫执行模型?不是语法,不是标准库,甚至不是类型系统——而是这门语言如何组织计算在时间轴上的展开方式。
- Python 的
async/await建立在单线程事件循环 + 协程之上。asyncio.sleep出让事件循环给其他协程,整个并发模型是协作式的。 - Go 的 goroutine 是用户态轻量线程,调度器在函数调用边界自动切换,
time.Sleep只阻塞当前 goroutine。 - C++ 的并发是 OS 线程 + 共享内存。同步原语(
std::mutex、std::semaphore)和内存序(std::memory_order)暴露给程序员直接管理。 - Rust 的
tokio::spawn是协作式调度 + work-stealing 线程池,所有权系统在编译期保证无数据竞争。 - Erlang 的 actor 模型里根本没有”共享内存”这个概念,并发通过消息传递实现,失败通过 supervision tree 隔离。
这些都是执行模型——不是语法,不是库,是一门语言对”程序如何运行”这个问题的根本回答。
为什么有些百万行迁移能成功
近几年确实有大规模语言迁移的成功案例:Firefox 的 Stylo 引擎从 C++ 换 Rust、Linux 内核引入 Rust 驱动、Android 蓝牙栈重写、大量项目从 C 迁移到 Rust。
这些案例能成功,关键不是 AI,而是执行模型的同构。
C 和 Rust 都是系统级语言:无运行时/GC,编译到 native code,用 OS 线程做并发,直接管理内存布局。Rust 的 ownership 本质上是对 C 程序员已经在脑子里维护的规则的形式化——谁分配谁释放、谁借用谁拥有。翻译的鸿沟在语法和安全性层面,不在执行哲学层面。
更重要的是,Rust 的 C FFI 是一等公民。你可以一个模块一个模块地换,旧代码和新代码通过 ABI 直接互调。机械转译工具(如 c2rust)的第一步就是把 C 变成 unsafe Rust——之所以这第一步能跑起来,是因为 C 的内存模型和 Rust 的 unsafe 子集高度对应。
如果你选的两种语言执行模型不同构,这一切都不成立。
Python → C++:为什么不是同一件事
回到开头的例子。假设你按 Python 的协程思维设计了一套架构——大量 async def、依赖 asyncio.gather 做并发、用 asyncio.Queue 做协程间通信——然后发现某几个模块是热点,想”让 AI 写成 Cython”。
问题从第一步就开始了:
- Cython 不直接支持
async/await的 native 化。cythonized 的协程仍然要走 Python 的事件循环,你跳不出 GIL。 - 如果你想跳出 GIL 用真正的多线程,那整个调用链的同步模型都要重设计——原本依赖协程协作式并发的代码,要在 C++ 里手动管理锁和内存序。
- 数据结构的选择也不同。Python 里随手
dict/list,C++ 里需要连续内存、cache-friendly 的 layout。这不是”翻译一个函数”能解决的问题。
“先 Python 后 Cython”这条路,只适用于计算密集但调用模式简单的热点——一个纯数值计算函数、一个字符串处理函数、一个图像滤波。对于并发模型、内存布局、IO 模式这些架构级的决策,一开始的语言选择就锁定了设计空间,事后局部重写是改不动的。
这也是为什么你见不到 Python → C++ 的”百万行自动化迁移”案例——不是 AI 不够好,是这两种语言从根本上就没有对等的运行时行为。
SDK 迁移的两层
跨语言迁移中,SDK 的迁移是另一个常被讨论的场景。有人觉得”SDK 本质上是模板代码 + 对底层执行模型的包装,所以可以重写”。
这个理解基本正确,但”所以”需要拆开看。
SDK 里确实有大量机械可翻译的部分:请求构造、响应反序列化、分页管理、错误码映射。给你一个 OpenAPI spec,AI 能一口气生成 6 种语言的 SDK,效果很好——因为这部分确实是纯模式转换。
但 SDK 里的资源管理策略不是模板:
- 连接池在 Go 里用
channel+ goroutine,在 Python 异步 SDK 里用aiohttp的connector,在同步 SDK 里用urllib3的pool。同样的”HTTP 连接池”概念,三种不同的执行模型,三种完全不同的实现。 - 重试与退避在 asyncio 里是
async for+asyncio.sleep(出让事件循环),在 Go 里是time.Sleep+ goroutine(调度器自动切换)。如果 SDK 在重试期间还要处理其他事件流,行为就不同了。 - 流式传输和背压,gRPC streaming 在 Go 里是 channel,在 Python 里是 async iterator,在 Java 里是 Reactive Streams。同一个 proto 定义,流的消费方式完全由语言的并发原语决定。
所以 SDK 迁移是两层的:80% 模板,AI 可以一次性翻;20% 执行模型适配,需要理解两个语言的运行时语义才能做好。好消息是这 20% 是可解的——不像业务应用代码那样还缠着领域逻辑。但”可解”不等于”无所谓”。
一个有用的框架
把跨语言迁移放到一个简单的坐标系里:
| 语法层 | 执行模型层 | 架构层 | |
|---|---|---|---|
| C → Rust | 低难度(模式匹配) | 低难度(同构) | 低-中(FFI + 渐进迁移) |
| Java → Kotlin | 极低 | 极低(同 JVM) | 极低 |
| Python → C++/Cython | 中 | 高(协程 vs 线程) | 高(无渐进路径) |
| Ruby → Go | 中 | 高(GIL + 纤程 vs goroutine) | 高 |
| JS → Rust (Wasm) | 中 | 高(事件循环 vs 系统线程) | 高 |
AI 在”语法层”这一列上的进步是巨大的,它让所有人都能用更少的成本跨过语法鸿沟。但”执行模型层”和”架构层”的难度,不因为 AI 的存在而消失——它是由语言本身的运行时语义差异决定的。
所以
“Vibe Coding 时代语言选择无所谓”这个说法,正确的部分是:AI 确实降低了尝试不同语言的成本,也大幅降低了模板代码的翻译成本。过去你可能因为”不会写 C++“而不敢碰需要性能的模块,现在你可以让 AI 帮你写。这是真正的进步。
但它错误地将所有跨语言迁移问题都归约为了语法翻译问题。
执行模型的不同不是 bug,是语言设计的选择。Python 的协程不是”还没实现线程的能力”,Go 的 goroutine 不是”忘记加 await 了”,C++ 的显式内存管理不是”设计失误”。它们各自回答了不同的问题,选择了不同的权衡。当你说”先 Python 后 C++“的时候,你隐含地假设了这两个问题域存在平滑的渐进路径——而这个假设本身,比”AI 能不能写 C++ 代码”更需要被审视。
AI 能翻译语法,但翻译不了架构。语言的选择,依然是有代价的。