Skip to content

第 10 章:看懂第九门语言

本书的真正目标

如果读完前面 9 章,你只记住了 "Python 用缩进,Rust 用所有权,Go 没有 filter/map"——那这本书没有完成使命。

这本书真正想给你的是:

当一门陌生语言出现在你面前时,你不会从零开始恐慌。你会有一套问题清单、一个观察框架、一种本能:先看懂它把复杂性放在了哪里。

一旦你有了这个,第九门语言就不是一门新的外语。它是一座新城市——街道名不同,交通规则不同,但你已经学会先看地铁图、再看老城区、再看金融区。


10.1 九问法:接触任何新语言时的第一轮侦察

当一门新语言出现在你面前——无论是 Zig、Odin、Carbon、Mojo 还是某门 10 年后才会诞生的语言——先别问 "怎么写 Hello World"。

按这个顺序问九个问题:

第一问:它如何让你声明?

  • 变量声明默认可变还是不可变?
  • 类型标注是可选还是强制?类型推断的边界在哪里?
  • const/final/val/let 的语义落在第几层(只锁引用 / 锁内容 / 编译期常量)?

这一问告诉你:这门语言对 "说清楚" 的耐心有多大。

第二问:它如何让数据成型?

  • 基本类型有哪些?整数有几个宽度?
  • 怎么定义一个复合类型?struct / record / data class / class?
  • 可空性怎么表示?Option<T> / T? / T | null / 指针?

这一问告诉你:这门语言如何让现实世界进入代码。

第三问:它如何组织多个值?

  • 核心容器有哪些?List/Array/Vec/Slice?
  • 容器的默认可变性?
  • 有推导式、函数式链式操作吗?

这一问告诉你:这门语言鼓励怎样的数据流。

第四问:它如何表达逻辑?

  • if/else 是语句还是表达式?
  • 有模式匹配吗?有多穷尽性检查?
  • 循环有几种?有 break 标签吗?有 enumerate 吗?

这一问告诉你:这门语言在多大程度上替你追踪逻辑完整性。

第五问:它如何让函数带上下文?

  • 闭包捕获的是引用还是值?能不能显式控制?
  • this/self 是隐式还是显式?
  • 有高阶函数吗?有尾随闭包语法吗?

这一问告诉你:这门语言如何对待 "函数和环境" 的关系。

第六问:它如何画边界?

  • 接口/协议/trait 是名义类型还是结构类型?
  • 可以有默认实现吗?
  • 有扩展函数/扩展方法吗?能给别人类型加自己的方法吗?
  • 泛型是怎么实现的?擦除 / 单态化 / 模板?

这一问告诉你:这门语言给了你多大的权力约束别人。

第七问:它如何管理生与死?

  • 内存是 GC、ARC、RAII 还是所有权?
  • 外部资源(文件、连接、锁)怎么释放?确定性还是非确定性?
  • 有析构函数吗?有 defer 吗?

这一问告诉你:这门语言在多大程度上要求你正视资源的终结。

第八问:它如何处理同时发生的事?

  • 并发模型是什么?线程?协程?事件循环?goroutine?
  • 任务是结构化的还是野生的?
  • 取消怎么传播?
  • 数据竞争靠约定、靠运行时检查、还是靠编译期阻止?

这一问告诉你:这门语言对 "混乱" 的容忍度有多大。

第九问:它如何面对失败?

  • 错误用异常、返回值、还是类型(Result/Either)?
  • 调用方能忽略错误吗?编译器会管吗?
  • 错误链/原因链怎么保留?

这一问告诉你:这门语言的人性假设——它认为你会记得失败,还是会忘?


10.2 复杂度图谱:把这 9 问串起来

当你用这 9 个问题审阅完一门新语言,你会得到一张心理地图。

不是所有维度都同等重要。对于不同类型的语言,有不同的问题最重要:

  • 系统编程语言:先看第七问(生死)和第八问(并行)。这些是它存在的理由。
  • 应用/业务语言:先看第二问(成型)和第九问(失败)。这些决定了团队维护成本。
  • 脚本/胶水语言:先看第一问(声明)和第三问(组织)。速度和灵活度最重要。

但无论什么语言——你总能在 "编译器替你做" 和 "你自己注意" 这条光谱上定位每一个维度。

有些语言把所有维度都偏向 "编译器替你做"(Rust)。有些语言把所有维度都偏向 "你自己注意"(C++)。大多数语言在某些维度上偏向一边,在其他维度上偏向另一边——这构成了它们的个性。


10.3 一眼看穿:从 8 门语言到任何语言

下面这张表不是结论。它是一种看问题的方式。当你面对第九门语言时,填上它的每一格,你就知道了它的性格。

维度靠编译器/运行时严格治理 ← → 靠程序员自律和约定
声明(类型)Rust > Swift/Kotlin > Java > TypeScript > Go > Python > JavaScript
声明(可变性)Rust(默认不可变)> Swift/Kotlin(val/let)> Java(final 可选)> C++(const 可选)> 其他
成型(空值)Rust Option > Swift/Kotlin T? > TypeScript T|null > C++ optional > Java @Nullable > Go *T > Python None > JS null/undefined
组织(不可变容器)Rust/Swift > Kotlin > Java > Python > C++ > Go/JS
流转(穷尽性)Rust match > Swift switch > Kotlin when > Java switch(exp) > Python match > C++ switch > Go/JS
边界(扩展)Kotlin/Swift > Rust trait > Go 结构类型 > TypeScript 结构类型 > Java/C++/Python
生死(确定性)Rust/C++ > Swift ARC > Go defer > Python with > Java try-with > JS finally
并行(结构化)Swift TaskGroup > Kotlin coroutineScope > Rust tokio::join > Python asyncio.gather > JS Promise.all > Java CompletableFuture > Go goroutine
失败(类型化)Rust Result > Swift throws > Java checked > Go error > Kotlin 异常 > Python/C++/JS 异常

注意:这张表是在比较 "编译器/运行时替你管了多少",不是在说 "哪个更好"。每一项维度都有自己的代价和收益。


10.4 迁移时最常见的三种幻觉

幻觉一:"语法很像,心智模型就像"

人类对熟悉感的本能反应是放松警惕。

Kotlin 和 Java 语法高度相似,但空安全系统和协程改变了你的边界设计。Swift 和 Kotlin 都很现代,但 ARC 和 GC 的差异意味着你在 Swift 里必须持续关注引用图,在 Kotlin 里可以少想一点。

语法相似是最危险的跨语言陷阱——它让你以为你懂了,而你只是看得懂而已。

幻觉二:"行数更少 = 更简单"

有时更少,只是因为语言把复杂性移到了你不在的地方——运行时、编译器魔法、约定的灰色地带。

Rust 的 ? 操作符让代码比 Go 的 if err != nil 更短,但 ? 内部做了类型转换、错误传播、From trait 调用——如果你不理解这些,你写出来的代码只是 "看起来短"。

真正该问的是:复杂性消失了,还是转移了?转移到我能承受的地方了吗?

幻觉三:"生态能补齐语言的一切差异"

优秀的库和框架当然重要。但它们通常只能顺着语言的底层哲学扩展,不能完全改写它。

给 JavaScript 加上 TypeScript 能大幅提升类型安全,但运行时仍然是 JavaScript——any 类型的存在意味着类型系统永远有后门。给 Python 加上 mypy 能在 CI 里做类型检查,但运行时 Python 解释器不执行这些检查——类型标注永远不会变成 Rust 级别的契约。

语言底层的制度选择,是生态叠加层无法完全复制的。


10.5 一个 30 天迁移计划

假设你下周要开始一门新语言。下面这个节奏比 "每天背 20 个语法点" 更有效,因为它不是强迫你成为语法词典——它是在帮你建立判断力。

第一周:建立地图

用同一个业务题(我们一直用的订单系统就不错),快速回答本章的 9 个问题。

  • 第一天:声明和成型(问题 1、2)
  • 第二天:组织和流转(问题 3、4)
  • 第三天:捕获和边界(问题 5、6)
  • 第四天:生死和并行(问题 7、8)
  • 第五天:失败(问题 9)
  • 周末:画出这门语言的复杂度图谱

目标不是写漂亮代码——是迅速知道这门语言的 "习性"

第二周:专门练复杂路径

不要再写 CRUD Happy Path。改练这些:

  • 资源释放(文件、连接、锁——写的时候故意中途抛异常,看善后机制是否可靠)
  • 并发边界(两个 goroutine/coroutine/线程 同时操作数据,会发生什么)
  • 错误包装(底层错误传到顶层,中间包装了三次,还能不能判断错误类型)
  • 取消和超时(启动一个长任务,中途取消它——子资源释放了吗)

因为一门语言最真实的性格,不在简单路径里,在复杂路径里。

第三周:放进真实约束

开始加上:

  • 测试怎么写。断言风格、mocking 难易、测试文件组织。
  • 构建、依赖管理、工程目录结构。是否直觉。是否像其他语言一样 "理所当然"。
  • 日志、监控、性能剖析。这些工具的存在与否,反映了这门语言生态的成熟度。

这是为了让你从 "会写单文件" 走向 "能判断它是否适合真实团队"。

第四周:写一份迁移复盘

不要写 "学会了哪些语法"。写:

  1. 这门语言最擅长处理哪类复杂性(并发?内存安全?原型速度?)
  2. 它把哪类代价后移了(性能优化?错误处理纪律?类型建模?)
  3. 我在哪些旧习惯上最容易犯错(闭包?空值?并发?错误处理?)
  4. 如果团队要用它,治理重点应该放在哪(代码评审模板?lint 规则?架构约束?)

这份复盘,才是你真正带得走的东西。 语法可以被忘记,但你对自己学习过程的元认知不会。


10.6 这本书结束的地方,你的迁移能力开始

编程世界会继续产出新语言、新框架、新范式。

如果你每次都从零开始恐慌——"又是一门新语言,又是一个新生态,又要重新学怎么写 Hello World"——那职业生涯会非常辛苦。

但如果你每次看到一门新语言时,能先问自己:

  • 它的声明制度是什么?默认对程序员有多信任?
  • 它的空值制度是什么?"缺席" 是不是一个正式概念?
  • 它的生命周期制度是什么?谁为资源的善后负责?
  • 它的并发制度是什么?当多件事同时发生,秩序从哪里来?
  • 它的失败制度是什么?它相信你记得失败,还是相信你会忘?

然后你会发现一个很奇妙的事:

你之前的那 8 门语言,不再是你简历上的一行行技能。它们变成了你认知坐标系里的 8 个锚点——让你面对任何新语言时,不是从零开始重新建坐标系,而是在已有的锚点之间定位它。


最后一段话

本书从 8 门语言的各自一行声明代码开始,到这里收束。

你从 "orderId = 'A1001'" 走到这里,一路上看见的不只是语法差异——你在看语言对程序员的信任程度、对混乱的容忍度、对失败的态度、对时间的假设。

语法只是皮肤。制度才是骨架。

当你下次面对一门新语言时,我希望你不会从 "变量怎么声明" 开始恐慌。我希望你在打开文档的第一页时,脑子里已经有了那 9 个问题。

如果你只能记住本书的一件事,记住这个:

无论眼前是哪门语言,先看懂它把复杂性放在了哪里——放在语言里、放在运行时里、还是放在你手里。

这,才是横向学习真正给你的自由。