TypeScript 之父:TypeScript 提速 10 倍,为什么 AI 不会取代软件工程师
- 核心观点
- 访谈实录
- 1. 当初为什么用 JavaScript 编写编译器(00:48)
- 2. 为什么要用 Go 重写编译器(07:29)
- 3. 用大语言模型做大型代码迁移(14:49)
- 4. JavaScript 为何如此流行(20:12)
- 5. 为什么会在后端使用 JavaScript(26:32)
- 6. 打造一门编程语言需要什么(32:59)
- 7. 十年后编程语言会变少吗(37:06)
- 8. 亲力亲为的工程实践 vs 委派他人(42:57)
- 9. 为什么如今快速工具链比以往更重要(49:14)
- 10. 对 AI 软件工程的预测(51:16)
- 11. 最具技术挑战性的工作(58:52)
- 12. 最推荐的一本书(01:02:04)
- 13. 给年轻时的自己的建议(01:03:50)
- 附:对文中 AI 观点的评析
原文链接:[Creator of TypeScript: 10x Faster Typescript, Why AI Won’t Replace SWEs Anders Hejlsberg](https://www.developing.dev/p/creator-of-typescript-10x-faster)
与 TypeScript 和 C# 的创造者 Anders Hejlsberg 聊用 Go 重写 TypeScript 编译器背后的技术细节,是一次很有趣的经历。我有很多问题想问:为什么他们选择了 Go(而不是 Rust)?重写过程中是否用到了大语言模型(LLM)?事实证明,他们在重写中使用 LLM 的程度远低于预期,他在对话中解释了原因。
Anders 对 AI 将如何影响软件工程有非常鲜明的观点,所以我特意把在 Twitter 上看到的各种著名论断都拿去向他求证,比如:
- AI 会在 1~2 年内写出所有代码吗?
- AI 会在几年内取代初级工程师吗?
- 到今年年底,我们还会需要使用 IDE 吗?
我还问了他,为什么他的观点似乎与那些对 AI 更为狂热的公司(比如各大 AI 实验室)如此不同。希望你喜欢这一期节目。和往常一样,如果你想快速浏览,下面是完整的文字实录。
你可以在任何播客平台收听本期节目:YouTube、Spotify、Apple Podcasts。
对谈人物
- Ryan:主持人
- Anders:Anders Hejlsberg,TypeScript 和 C# 的创造者
为提升长文阅读体验,下文按主题分节;对话顺序与内容保持不变,小节标题与时间轴一一对应。文末”附:对文中 AI 观点的评析”一章为编者补充,不属于访谈内容。
时间轴:
- 00:48 - 当初为什么用 JavaScript 编写编译器
- 07:29 - 为什么要用 Go 重写编译器
- 14:49 - 用大语言模型做大型代码迁移
- 20:12 - JavaScript 为何如此流行
- 26:32 - 为什么会在后端使用 JavaScript
- 32:59 - 打造一门编程语言需要什么
- 37:06 - 十年后编程语言会变少吗
- 42:57 - 亲力亲为的工程实践 vs 委派他人
- 49:14 - 为什么如今快速工具链比以往更重要
- 51:16 - 对 AI 软件工程的预测
- 58:52 - 最具技术挑战性的工作
- 01:02:04 - 最推荐的一本书
- 01:03:50 - 给年轻时的自己的建议
核心观点
- 移植而非重写: 选择把现有编译器移植到 Go,完整保留语义与向后兼容;重写对生态系统往往是有毒的。
- 为什么选 Go 而不是 Rust: Go 具备成熟的原生代码生成、垃圾回收和共享内存并发;Rust 的借用检查器不允许编译器中无处不在的循环引用数据结构。
- 大模型在迁移中用得不多: 更可靠的做法是让 AI 写一个确定性的翻译程序,而不是直接让 AI 翻译几十万行代码——AI 的输出不是确定性的。
- AI 偏爱既有语言: 训练数据中最多的是 JavaScript、TypeScript 和 Python,既有语言会因 AI 而更强;TypeScript 已超过 JavaScript 成为 GitHub 上使用量第一的语言。
- AI 不会包办一切: AI 能写出海量代码,但写不出训练集中不存在的超高质量代码;把钥匙完全交给 AI,意味着你要为自己不理解的东西负责。
- 程序员的手艺正在改变: 从自己敲代码转向审查智能体的工作,但整个过程中始终有程序员把关;更快的编译器工具链因此比以往更重要。
访谈实录
1. 当初为什么用 JavaScript 编写编译器(00:48)
Ryan: [00:48] 我们今天要聊的重头戏,显然是 TypeScript 7 的原生代码重写。看到这个消息时,我第一反应是:我甚至都没意识到编译器一开始是用 JavaScript 写的——因为我通常不会想到编译器会用高级动态语言来编写。
Anders: [01:09] 是的,确实如此。
Ryan: [01:11] 那么 TypeScript 最初为什么——编译器为什么是用 JavaScript 写的?
Anders: [01:17] 这个问题问得好。说实话,如果在 TypeScript 项目启动之前有人告诉我:”Anders,你将要用 JavaScript 编写编译器”,我会说:”不,不,我才不会。”但 TypeScript 最早的原型——也就是极早期被称为 Strada 的项目——实际上是用 C 写的,因为它是对我们在 Internet Explorer 里的 JavaScript 解析器的改造。
[01:49] 后来我们迁移到了 JavaScript,但写法上有点像是 C# 的风格。至于为什么选 JavaScript?这么说吧,如果你能在你想要融入的生态系统中实现自举(self-host),那要比把自己置于生态之外、再试图去适配那个生态好得多。你懂我的意思吧?因为用 TypeScript 来编写编译器,我们自己也就成了 TypeScript 的日常用户,也成了我们正在构建的工具的日常用户。
[02:21] 每当有什么东西不能正常工作时,我们立刻就能知道;每当有什么性能不对时,我们也能立刻知道。所以在早期,这是巨大的优势。当然,JavaScript 还能到处运行,对吧?这意味着我们的编译器自动就能在每个平台上运行,任何地方都行,甚至在浏览器里。如果当时我们选择了原生代码方案,就不可能在浏览器里运行了。
[02:49] 现在我们有了 WebAssembly(WASM),但当时它还不存在。所以那是一个有意义的选择。而且说实话,那时候人们也没有真正意识到 JavaScript 已经变得多快了。它过去非常慢,后来 Google 在 V8 上做了出色的工作,把与原生代码的性能差距缩小到了 2 到 3 倍以内,这相当了不起。用 JavaScript 写编译器完全是可行的,甚至还能做出不错的性能。[03:33] 因为往往决定性能的是算法,而不一定是运行时环境。当然,两者是结合的。但就是这样。
Ryan: [03:38] TypeScript 编译器有什么独特之处吗?我印象中的编译器是 Clang 这类——用于 C 语言之类的静态语言的编译器。与这些原生编译器相比,用 JavaScript 编写的 TypeScript 编译器有什么独特之处?
Anders: [04:00] 我认为 TypeScript 编译器有几个相当独特的地方。首先,它不面向机器码,而是面向 JavaScript——有些人把这称为”转译器”(transpiler)。从某种意义上说,编译阶段主要是移除类型注解,把你的 TypeScript 还原成 JavaScript。对了,在早期,编译过程中还有一个重要部分是把你的 JavaScript 降级(downlevel)——因为当时所有的运行时环境都不是”常青”的(即不会自动跟进最新标准)。[04:36] 有时候用户在标准实现方面会落后好几年。比如,当类(class)被标准化时,大多数 JavaScript 运行时还没有实现类,但事实证明你可以把它降级转换为构造函数。所以我们做的一部分工作就是这种代码的转换、降级转译。当然,类型检查才是我们做的重头戏。
[05:06] TypeScript 的类型检查器与其他编译器里的类型检查器也大不相同。通常,类型检查器存在于编译器中是为了指导代码生成器——你想知道:这里处理的是浮点数、整数还是字符串,以便为这种数据类型生成正确的机器指令,等等等等。但 TypeScript 不是这样的。事实上,由于我们抹除了类型,类型对代码的运行时行为没有任何影响。
[05:40] 类型纯粹是为工具服务、为开发者服务的,用来支持语句补全、重构、代码导航这类功能——这完全是另一回事。而且类型也不是必须存在的。
TypeScript 采用的是渐进式类型系统(gradual type system)。你可以让一半代码带类型,另一半则是我们不检查的 any。现在几乎没有别的语言有这样的设计。
[06:13] 所以从这种意义上说,它是一个非常不同的编译器,也是一门非常不同的语言。这实际上让它非常迷人——因为我们在解决以前没有人解决过的问题。
Ryan: [06:25] 据我所知,很多编译器在把高级代码转换后还会做优化,比如展开循环、消除重复指令、简化代码之类。TypeScript 到 JavaScript 的编译过程中会发生这类事情吗?
Anders: [06:44] 非常少。就像我说的,我们做降级转换时确实有一些复杂的变换,但那越来越不是 TypeScript 的重点了。事实上,很多人只用 TypeScript 的类型检查器,然后用 esbuild 或 SWC 之类的打包工具来打包应用、抹除类型、做一切让代码能在浏览器中运行所需的事情。
[07:15] 所以我们并不是为了优化你的代码运行时性能而存在的,我们是为了让你作为开发者更高效——或者说,让 AI 作为开发者更高效。
2. 为什么要用 Go 重写编译器(07:29)
Ryan: [07:29] 关于把这个原本纯 JavaScript 的编译器重写为原生代码这件事——你们试图通过重写解决什么问题?为什么最终选择了用原生代码重写?
Anders: [07:43] 我们想解决的问题其实很简单:性能和可扩展性。JavaScript 从来都不是为编译器这种计算密集型工作负载而优化的。JavaScript 更多是用于在浏览器中创建 UI。它最初的设计目标可能只是写个 100 行代码,当然现在已经远远不止了。但它不是一个会为我们这种工作负载做优化的环境。
[08:16] 在 JavaScript 中,首先你要付出性能代价:比原生代码慢一半到三分之二。具体取决于工作负载,但如果是计算密集型任务,大致就是这个水平。其次,JavaScript 在并发使用方面有很多限制。JavaScript 在设计上就是一门单线程语言,所以我们才有回调、async 之类的机制。
[08:44] 你无法启动线程,而这其实是件好事。主要原因在于:在一门数据可变的语言里做并发非常非常难,会有竞态、死锁等等一堆麻烦。这就是为什么函数式编程语言做并发更容易——因为所有数据都是不可变的,推理起来容易得多。JavaScript 除了 Web Worker 之外,不给你任何并发手段。
[09:15] 但 Web Worker 之间除了通过消息传递,无法共享数据。你可以让一个 Web Worker 计算某些东西,但如果它想把结果交给另一个 Web Worker,就必须先把结果序列化成 JSON——对象不能直接传递。这意味着你无法让一个 Worker 计算出一个数据结构,然后让别人直接使用。换句话说,你无法拥有共享内存并发。
[09:41] 而共享内存并发正是我们想要的——只有这样才能拿到那些被白白浪费掉的性能,因为如今人人都有多核 CPU,而我们没有利用它们。摩尔定律已经不再给我们更快的 CPU 了,它给我们的是更多的核心,我们得想办法在计算密集型工作负载中用上它们,否则就是把白捡的性能拱手丢掉。所以当时我们在多个维度上都在浪费性能。
[10:08] 所以我们需要原生代码——这就是为什么我们要找一门能同时帮我们摆脱这两个束缚的语言:它必须能编译为原生代码,还必须能使用共享内存并发。
Ryan: [10:21] 我看到你们在语言选择上选了 Go。我想到系统级语言时,大家热议的可能是 Rust 或 Zig,为什么你们为这次原生重写选择了 Go?
Anders: [10:35] 我认为我们的决策过程其实相当有条理。我们做的第一个决定是:我们不重写(rewrite),而是移植(port)。因为只有通过移植,我们才能保留现有编译器的语义、算法和精确行为——而所有人都依赖这些行为来保证向后兼容。当然,我们也可以推倒重来、完全从零开始,但那样做出来的会是一门不同的语言——它会给出不同的错误提示,或者在一些需要在多种可能性中做出选择的情形下表现得不一样。
[11:15] 既然要移植,就意味着我们的代码带有一些既有假设。比如,我们的代码假设存在垃圾回收(GC),假设函数是一等公民——你可以在函数里定义函数,可以闭包捕获外部状态,等等。在评估了所有这些因素之后,Go 是最符合这些条件的语言。它在所有主流平台上都有非常稳健、成熟的原生代码生成能力。[11:53] 它有垃圾回收,还有出色的共享内存并发支持。这些是我们最看重的几个关键点。而其他每门语言都有一些与这些目标相悖的地方。所以对于这个特定的工作负载,Go 对我们来说是正确的选择。而且实际效果也很好。
Ryan: [12:16] 如果考虑 Rust 的话——它缺了什么?如果它有了什么,你们就会选它?
Anders: [12:22] 我认为主要有两点。它没有垃圾回收——要么手动管理内存,要么通过借用检查器(borrow checker)。但借用检查器不允许循环引用的数据结构。而我们的编译器里满是循环数据结构:带父指针的树、递归的类型、相互引用的符号……到处都是。就像我说的,如果我们当初选择重写,这些问题大概都能解决。[12:54] 但那就不仅仅是移植代码了,还得解决一大堆因为走那条路而自己招惹来的新问题。我们尝试过,但确实不可行。所以我们只是想尽量少干活、尽快拿到收益。而且说实话,在生成代码的质量或者我们能获得的并发收益方面,Rust 也并不比 Go 更好。
[13:27] Go 就是很棒。我的意思是,走这条路,我们用更少的工作量拿到了所有的好处。
Ryan: [13:35] 据我所知,有些语言说自己是垃圾回收的,是语言本身内置了 GC;但也有一些独立的库可以提供垃圾回收,对吧?
Anders: [13:52] 确实有,但那些库往往带着一大堆限制,而且不一定能提供我们想要的安全保证。我的意思是,Go 是类型安全且内存安全的——你不会遇到野指针之类的问题。用 Rust 的话,你可以用引用计数,也可以用各种不同的策略,但总有那么一点点 unsafe——你必须那么做才能让整个东西跑起来。
[14:26] 而当垃圾回收被设计进语言本身时,它真的是一个完全独立的关注点——你不需要做任何特殊处理。当然,你还是得注意不要一直抓着不再需要的数据不放,但引用计数也同样如此。GC 是语言内建的。
3. 用大语言模型做大型代码迁移(14:49)
Ryan: [14:49] 在从 JavaScript 移植到 Go 的过程中,我很好奇你们用了多少 LLM。我知道有几个著名的大项目在做整个代码库的重写,比如有个叫 Bun 的 JavaScript 项目把所有东西从 Zig 重写成了 Rust……看起来这是个挺有用的手段。我很好奇你们在这个过程中是否利用了它。
Anders: [15:16] 用得不多,但这和时间点有关。我们是两年前开始这个项目的,而两年前的 LLM 远不如现在这么好。你看这一切发展得多快。不过我们当时确实有一堆辅助移植的工具。扫描器(scanner)和解析器(parser)的原型是我们手工写的。到那个时候,我们已经能让自己确信:哇,我们真的能从中获得 10 倍的提升。
[15:51] 那么剩下的代码库怎么办?我们的做法是写了一个在语法层面把 TypeScript 翻译为 Go 的工具。翻译出来的代码没有语法错误,但也编译不过——它把类型当作 TypeScript 的类型来用,但 Go 里并不是那样的。所以所有这些都必须重构,我们必须重做所有数据结构之类的。但我们由此得到了一个对应的代码库,然后就可以在这个基础上逐步打磨,偶尔以更局部的方式用 AI 来帮我们做一些转换。
[16:29] 所以整个过程是手工、自动化加一点点 AI 的结合。如果今天重新开始这个项目,我们会换种做法吗?可能会。不过我要说,如果我们直接把 AI 放开,让它翻译我们旧编译器那大约 50 万行——或者说 50 万到 100 万行之间的代码——我不认为这样就能免除我们逐行仔细检查产出物的责任。[17:04] 我们必须确认其中没有幻觉(hallucination)。除非你有 100% 完美的测试覆盖率,否则你可能还是得去检查所有东西。坦率地说,我认为更好的方法是让 AI 帮你写一个程序,一个帮你把 TypeScript 翻译成 Go 的程序。因为这样你就可以把随机性”封存”在那个程序里,之后每次运行都能得到确定性的行为——每次运行都会得到相同的转换结果。
[17:35] 人们总是忘记 AI 的这个特点:它不是确定性的。你无法相信它两次会做出同样的事情。我们本来可能会那样做,但当时 AI 还没准备好。这并不意味着我们现在不在后续工作中使用 AI。我们在编译器的内部流程中大量使用 AI,就像所有人一样。比如帮忙写测试、处理 pull request 之类的。
[18:05] 每当有人提交 issue,我们做的第一件事就是让 Copilot 上,看看它能不能帮我们修复。就是这种方式。
Ryan: [18:13] 也就是你用 LLM 来生成工具,然后工具本身是确定性的,但你还是得验证那个东西本身。
Anders: [18:23] 是的,但那需要验证的表面积要小得多,对吧?说实话,我在朋友身上也见过同样的情况。我们一起旅行,回来后要算账——他付了酒店房费,我们付了这个那个的,需要把账目理清楚。于是我们说:好吧,这是所有的费用明细,让 AI 帮我们算清楚——谁该给谁多少钱。
[18:46] 结果它非常自信地给出了错误答案。因为它忘掉了其中几笔结算。但如果我们让它写一个电子表格——那其实就是一个程序——我们就能得到正确答案,因为它非常擅长写程序。这挺有意思的:有时候你不该直接问 AI 要答案,而应该让它写一个能算出答案的程序。
Ryan: [19:17] 选择移植而不是完全重写,你们放弃了哪些潜在的好处?
Anders: [19:23] 说实话,我不认为放弃了什么,而且我们也不会考虑重写。我们对这个代码库的运作方式相当满意。我认为目前我们没有遗漏任何收益。就像我之前说的,重写对生态系统来说往往是有毒的,因为它牺牲兼容性——重写之后你永远无法完全回到原来的样子,对吧?
[19:54] 总会有人打着”让它更好、更漂亮”之类的旗号,改这改那。然后所有用户都得跟着遭罪。所以,如果你想让生态系统保持满意,向后兼容是超级重要的。而我们确实希望如此。
4. JavaScript 为何如此流行(20:12)
Ryan: [20:12] 在为这次访谈做准备时,我一直在读关于 JavaScript 及其奇怪语义的资料。在我的职业生涯中,大家也总是因为这样那样的原因对 JavaScript 不太满意。我的问题是:尽管如此,它为什么还这么流行?
Anders: [20:36] 首先,我不认为 JavaScript 像某些人说的那么差。我得说,Brendan Eich 当年在 90 年代中期只有——多少来着——三四周的时间来创造这门语言,但他做对了很多事情。谢天谢地,他受过函数式编程的教育,把一等公民的函数做对了,对吧?[21:09] 你可以在函数里定义函数,内部函数可以访问外部函数的状态,可以把函数当作一等值传递。这非常强大。JavaScript 在这一点上做得很对。当然,这门语言也确实有些怪癖——各种自动类型转换、双等号和三等号之间匪夷所思的区别,把所有人都逼疯了。但这恰好是类型检查器乐意干的事:它们能找出所有细微差别,因为它们理解语言的所有深层语义——那些没人能全部记在脑子里的东西。
[21:39] 所以我认为这也是 TypeScript 如此流行的部分原因。它真正能发挥 JavaScript 最好的部分,同时把糟糕的部分抛在身后。而且你无法否认一点:JavaScript 是真正的跨平台语言。就连 Java——那门专为跨平台应用而设计的语言——其实也不算真正的跨平台。[22:06] 它不能在浏览器里运行,不能到处运行。而 JavaScript 哪里都能跑。所以你有很多理由喜欢 JavaScript。而通过 TypeScript,我认为我们设法把所有糟糕的部分捕获并”圈”了起来。
Ryan: [22:27] 我看到过一些项目——不知道你熟不熟悉 Dart 或 CoffeeScript——它们似乎是想取代或改进 JavaScript 的尝试,但如今都不流行了。我很好奇发生了什么。
Anders: [22:44] 我认为 CoffeeScript 大体上只是 JavaScript 的另一种语法,并没有真正改变语义,也没有提供任何额外的工具之类的。而 Dart 从某种意义上说也是在”修复”JavaScript。但问题是:如果你能通过”修复”来修好一样东西,而不是试图用别的东西取代它,那么我认为你才是给生态系统帮了大忙,对吧?[23:16] 这正是我们用 TypeScript 所做的事情。我们不是要改变 JavaScript,而是要改进 JavaScript。我认为这最终就是我们成功的原因。
Ryan: [23:28] 在这个 AI 很可能有能力重写代码库的新时代,我想人们会开始转向他们”想要”的语言,而不是那些”既有”的语言。我很好奇你是否认为 JavaScript 未来可能会被用得越来越少。
Anders: [23:51] 我不这么认为。我观察到的是:AI 最擅长的是它在训练集中见得最多的语言。AI 是用世界上存在的所有代码训练的,这意味着有海量的 JavaScript、海量的 TypeScript、海量的 Python,因此 AI 擅长 JavaScript、TypeScript 和 Python。而我刚发明的那门小众语言,它就不太擅长了。[24:22] 事实上训练集里一行都没有,这意味着你必须在提示词里牺牲掉不知道多少个 token 来教 AI 这门语言,然后才能开始问它关于这门语言的问题。所以从这种意义上说,我认为既有语言反而会因为 AI 而更加繁荣。我们现在在 TypeScript 身上也看到了这一点,对吧?[24:47] TypeScript 的采用率曲线上有一个明显的拐点,恰好与 AI 的出现同步。我确实认为既有语言实际上正在变得更强,而不是更弱。
Ryan: [24:58] 现在所有 JavaScript 中有多少百分比是用 TypeScript 写的?
Anders: [25:04] 去年,TypeScript 成为 GitHub 上使用量第一的语言。也就是说 TypeScript 已经超过了 JavaScript——在 GitHub 上至少超过 50% 的代码是用 TypeScript 写的。所以,我们实际上已经比我们当初想要改进的那门语言更大了。
Ryan: [25:28] 哇。
Anders: [25:29] 是的,也超过了 Python。
Ryan: [25:31] 如果把这个趋势延伸下去,你认为最终 TypeScript 会成为默认选择、几乎所有 JavaScript 都会带类型吗?
Anders: [25:41] 当然会。我的意思是,如果你让 AI 来写代码,我敢说你几乎找不到一个还在写 JavaScript 的工具——它们全都在写 TypeScript。因为类型注解能引导 LLM 写出更好的代码、犯更少的错误;而且我们能在代码运行之前对它做静态验证。这就是关键区别,对吧?验证 JavaScript 的唯一方法是运行它,这意味着你必须搭一个运行时环境——而 AI 未必做得到,因为 AI 生活在沙箱里。但 AI 可以通过工具运行编译器,检查它刚生成的代码,然后被告知:”哦,你犯了个错误。”
[26:27] 然后它就会说:”好吧,那我在告诉任何人之前先把这个错误改掉。”
5. 为什么会在后端使用 JavaScript(26:32)
Ryan: [26:32] 了解到原生重写带来的 10 倍性能提升之后,我不禁想问:为什么还有人要在后端用 JavaScript?为什么不干脆在后端永远只用原生语言?
Anders: [26:48] 嗯,我认为这取决于你的问题是什么,因为这里被忽略的一点是:你到底要在后端写什么?有非常多非常优秀的 Web 服务器框架就是用 TypeScript 或 JavaScript 写的。所以如果你所在的生态系统里有能让你更快得到解决方案的框架,那就比用原生语言从头写要划算——因为原生语言本来就不是为那个特定社区准备的。
[27:21] 所以我认为这在很大程度上取决于你的问题更适合什么。而且很多时候,你的代码性能并不是瓶颈。看看 Python。世界上几乎所有的 LLM 训练基本上都是用它来编排的。那些代码里对时间极度敏感的部分都是用原生代码写的。然后你用 Python 作为编排语言。同样,在 Web 服务器里,TypeScript 只是一个编排者——和数据库对话、干这干那。最终决定这类应用性能的,并不是那个内层 for 循环是否以原生代码运行。[28:12] 何况由于 JIT 的存在,它实际上就是以原生代码运行的。所以,先测量,再做决定——因为测量之后你总会感到惊讶。
Ryan: [28:24] 我脑海里立刻浮现的一个问题是:这件事为什么没有更早做?
Anders: [28:33] 在我们启动 TypeScript 的时候,我不认为我们曾预见到人们会用 TypeScript 写出如今这么大规模的项目。Visual Studio Code 大约有 230 万行代码,我们内部还有超过 1000 万行代码的项目,这太疯狂了。要是放在当时,我们只会说:”好吧,显然永远不会有人这么干。”
[29:01] 但那是十年前、十多年前了。我们是 2012 年启动 TypeScript 项目的,那时候的世界完全是另一个样子。渐渐地,人们开始写越来越大、越来越大的项目。与此同时,我们的编译器也变得越来越聪明——我们加入了各种很酷的特性,比如联合类型、可辨识联合、控制流分析,等等等等。
[29:26] 所有这些让我们的类型检查变得更好,但也让编译器每加一个特性就变慢一点。事实上,我们的 TypeScript 6 的运行速度大约只有 TypeScript 1.5 的 50%。当然,TypeScript 1.5 做的事情也少得多。总之,项目变大了,编译器做的事情变多了,这些都拖累了你的有效产出。而且正如我之前提到的,不幸的是摩尔定律也停止给我们更快的 CPU 了,而 JavaScript 又让你无法利用那些多出来的核。
[30:02] 我们当初未必能预见到这一切,对吧?但事情就是这样发展的。
(以下为赞助商口播)
Ryan: [30:10] OpenAI、Anthropic、Cursor 和 Vercel 都在用这款产品来让自己的工作更轻松。它解决的问题是:当你在构建 SaaS 或 AI 产品、想卖给其他公司时,总有一堆必须满足的要求。[30:40] SSO、SCIM、RBAC、审计日志,这些东西集成起来都很花时间,却又不是你应用的核心。WorkOS 是一个 API 层,让你只用几行代码就能满足所有这些要求。假设你有一个新的 SaaS 产品想卖给其他公司,WorkOS 会帮你补齐所有这些关键的功能缺口。你可以访问 workos.com 了解更多并开始使用。感谢他们支持我的工作、赞助本期播客。Atlassian 出品的 Jira 如今不再只是用来追踪工作了。现在你可以挑选自己喜欢的 AI 智能体并给它分配任务,它们可以直接利用 Jira 中已有的丰富上下文。[31:07] 当智能体完成工作时,它会产出一个 pull request。这样你就能在一个平台上借助喜爱的智能体完成更多工作。访问 jira.dev 了解更多——J-I-R-A 点 D-E-V。感谢他们赞助本期播客。现在回到节目。
我最初成为软件工程师时去了 Facebook 工作。当时他们有一个很流行的框架叫 Flow,它在 JavaScript 之上做类型推断。
[31:37] 这东西当时要花很长时间才能跑完,而如今我再也听不到有人提它了。所以听起来,如果当年存在一场”给 JavaScript 加类型”的竞争,是 TypeScript 赢了。
Anders: [31:46] 确实有过那么一段竞争。在早期,我认为对我们有利的一点恰恰是我们是自举的——这让社区为项目做贡献变得容易得多。据我所知,Flow 是用 OCaml 写的,所以想给 Flow 做贡献,你必须先学会用另一门语言以完全不同的方式编程。而且 Flow 也没怎么专注于基于 IDE 的工具,而我们从一开始就非常清楚:[32:22] 我们在写一个编译器,但我们写编译器不是为了生成机器码,而是为了打造更好的工具。这就是为什么我们在写编译器的同时还写了语言服务(language service),两者齐头并进。语言服务被深度集成进 Visual Studio、Visual Studio Code,还通过一个开放协议集成进各种其他编辑器——那才是需要被解决的问题所在。
Ryan: [32:47] 我是说,有个类型检查器当然好,但你还会想要语句补全,想要红色波浪线,想要交互式的体验,对吧?
6. 打造一门编程语言需要什么(32:59)
Ryan: [32:59] 我知道在你的职业生涯中,你做过好几门编程语言。打造一门编程语言到底需要什么?
Anders: [33:07] 首先我得说,这个世界对”再来一门编程语言”的需求,就跟头上需要再多一个洞差不多。我是说,你得是某种特定类型的人才会踏上这条路,对吧?这是计算机科学中超级迷人的领域,从计算机诞生之初就存在了。每门语言、每个编译器都有一大堆你需要理解的东西——解析器、扫描器、词法分析器、代码生成器,等等等等。
[33:43] 但那些算是实现语言的”机械部分”,然后还有语言的设计——一门让它”感觉对”的艺术。我认为这两样你都得掌握。然后你还得明白:每门新语言其实只有 10% 是新的,剩下 90% 都是每门语言都得做的那些枯燥活儿。所以要做好准备,会有大量可能没那么有趣的工作。最后我还要说:永远不要忘记你站在巨人的肩膀上。
[34:24] 要做出一门伟大的编程语言,你得先理解一大堆其他编程语言,理解各种编程风格之间的区别——过程式、面向对象、函数式等等。最后但同样重要的是:这是一场持久战,可能比任何事情都更持久。我参与过的每一门语言,每一门我都至少做了 10 年,发布了多个版本。
[34:58] 而且通常要到第三个版本,它才真正开始变得像样。这需要难以置信的投入。所以你真的不能是那种很快厌倦一个问题、然后转身就走的人——那种人不适合做语言设计。你和业界做语言设计的人聊聊也会发现这一点:他们都干了很久很久。
Ryan: [35:23] 你提到了两个部分:需要实现的客观部分,以及设计的艺术——我想,也就是让语言对开发者来说更顺手。说到第二部分,那个”艺术”——再看看其他编程语言,在你没参与过的语言里,有没有你欣赏的?
Anders: [35:45] 当然,学习并真正领会函数式编程之美让我受益匪浅。因为它真的是一种完全不同的思考编程的方式——一种更接近数学而不是机器的思考方式。我认为其中诞生了非常多了不起的好东西。而且说实话,即使在构建 TypeScript 项目时,TypeScript 编译器的大部分代码也是用高度函数式的风格写的,操作的是不可变数据结构,然后……[36:25] 现在因为有了共享内存并发,这些数据结构可以在不同的线程或进程之间共享——只要它们不修改数据,就能共享同一个数据结构,这极其强大。但要掌握这种技艺——在命令式程序中写出一座座纯函数式的”孤岛”,再和并发结合起来——我的意思是,这很复杂,但我认为函数式编程给世界带来了很多东西。
[36:55] 我对面向对象编程也一样了解,但我不会单独点名哪一门语言。你接触到的每一门语言,都能让你学到一些东西。
7. 十年后编程语言会变少吗(37:06)
Ryan: [37:06] 你提到世界其实不需要更多编程语言了。如果放眼 10 年后,你认为编程语言会比今天少吗?
Anders: [37:19] 这很难说。但就像我说的,AI 倾向于偏爱既有语言,对吧?而且我认为——其实在 AI 出现之前就已经如此——成功实现一门编程语言并围绕它建立生态系统的门槛一直在提高。过去是”哦,你只需要一个编译器”,现在你还得有工具链:你需要语言服务、调试器、性能分析器、框架和库,还需要能面向各种目标的代码生成器……
[37:59] 要把所有这些做到位,就是变得越来越难。
Ryan: [38:08] 想想这些组成部分,其实它们都是为人服务的——IDE、类型……当然机器也会看这些,但那是为了让我们能阅读源代码。也许有一天 LLM 会直接生成机器码。
Anders: [38:28] 我倒想看看。我认为 LLM 在不需要大量重复自己输出的情况下表现更好。当程序以文本形式表达时,它最接近其凝练后的最终含义,对吧?如果你把它翻译成机器码,机器码里有大量的噪音——指令里差不多一半的内容是内存地址,跟真正在发生的事情毫无关系。
[39:09] 所以一半的数据已经是噪音了。然后代码生成器选了哪个寄存器,其实也无所谓——说实话,你随时可以改变主意。所以从那种噪音中还原出真相,要比直接读一段”变量叫 i“这样的源代码难得多。顺便说一句,那样的程序你还能和用户刚给你的文字指令对应起来。
[39:34] 要知道,AI 从某种意义上说就是人类的模拟器。神经网络嘛。我们之所以有编程语言,是因为我们不擅长凭空直接写机器码。AI 和我们并没有那么大的不同,所以同样的结论大概也成立。
Ryan: [39:56] 回顾你做 C#、TypeScript 这些语言项目的历程,它们都是漫长的旅程。我想大家可能想知道的是:在这些旅程中你学到了什么——如果你在一开始就知道,会做得更好?
Anders: [40:19] 天啊,每段旅程都不一样。比如我参与的第一个项目 Turbo Pascal——
Ryan: [40:30] 那个项目——
Anders: [40:30] 起步于很早的年代——8 位微型计算机、64K 内存,一个人可以把所有事情都自己干完,对一切有绝对的掌控。所以当时我是一个人包揽一切的。但那很快就无法扩展了。机器容量越来越大,从 64K 到 640K,然后”砰”的一声,天花板没了。[40:59] 你想要多少内存就有多少。于是一个人再也干不完了。我不得不学着成为一个团队合作者,对吧?那对我来说是一段重要的成长历程——学会放手。如果你是完美主义者,这可能会很难,但这就是我在那段旅程中学到的东西之一。
Ryan: [41:14] 这个问题还有一种问法:你看到人们在开始构建一门编程语言时最常犯的错误是什么?
Anders: [41:24] 我认为人们往往会过度专注于自己脑袋里的某一个点子——”哦,如果我的语言能做什么什么,多酷啊”——然后对每门编程语言都必须做的所有平凡琐事投入不足。结果是,它也许在一件他们热爱的事情上做得更好,但其他所有事情都做得不如别人,总体上反而更差。所以很难向别人传达清楚:实现一门编程语言时,你要做的那么多事情其实跟你真正想解决的问题本身毫无关系。
Ryan: [42:06] 当年你在做 C# 的时候,你会关注其他语言吗——看它们哪里做得更好、C# 可以在哪里做得更好?
Anders: [42:15] 当然,你必须让自己保持信息灵通,因为就像我说的,我们都站在巨人的肩膀上。没有哪门编程语言是在完全与世隔绝中被创造出来的。每个人都向别人学习,有人想出一个好点子,然后你就会看到它被别处采纳。比如我们在 C# 中采纳了很多函数式编程的思想;然后其他语言也采纳了我们在 C# 中创造的一些好点子,比如 async——现在 JavaScript 和其他一些编程语言里都有了。[42:46] 而它最早是在 C# 中开创的。我们都在互相学习,行业就是这样向前发展的,真的。
8. 亲力亲为的工程实践 vs 委派他人(42:57)
Ryan: [42:57] 你提到了”学会放手”。我想很多人的职业发展路径都是这样:先做个人贡献者的工作,然后开始把事情委派给别人。你是怎么学会放手的?或者有没有什么故事、某个具体的项目,让你在那上面锻炼出了这种能力?
Anders: [43:21] 有意思的是,关于”放手”我几乎有个相反的故事。在 C# 项目中,我的主要职责并不是实现 C# 编译器,而是担任语言设计师——写规范、设计所有新的语言特性。在那之前,我做 Turbo Pascal 的时候,一直在亲自写大量代码。但后来我放手了,而且放得有点太过了——我飘到了平流层,变成了一个”架构宇航员”,你懂我的意思吧?
[43:59] 我不再动手写代码了。我在框架和库上写一些东西,但那些都不是真正需要解决的、最深最硬的问题。当时我并没有意识到,但我渐渐对自己做的工作越来越不快乐。我仍然享受它,但总觉得缺了点什么。所以当 TypeScript 这个机会出现时,我决定跳进去,真正参与编译器的编写。
[44:30] 这给我的生活带来了巨大的快乐。我终于意识到:我就是一个快乐的码农。我是说,我也喜欢写规范那些事,但写代码——伙计,那才是让我早起的动力。冲杯咖啡,写点代码,那就是我的快乐空间。
Ryan: [44:48] 你知道,当人们处在非常非常高的级别时,大家对他们的影响力会有某种期待。作为这么高级别的工程师,你是怎么坚持继续写代码的?
Anders: [45:02] 嗯,我写的那部分具体代码本身未必有多大影响力,但关键是我同时也做架构指导——作为项目组的一员,每个人都有干不完的活。你要让自己始终参与项目的某个角落,因为这样你才能看到代码里实际发生了什么。当你提交自己的代码时,你会注意到:”咦,那边那个是什么?[45:31] 让我看看这里发生了什么。”或者你会意识到:”哦,我们得重构这块了,这感觉已经不对了。”但如果你放了手,你就没法真正谈论人们每天实际使用的那个东西了——你可以谈论它的语法,但你没法谈论那个东西本身。所以我认为两者都很重要,我也很高兴自己能两者兼顾。
Ryan: [45:57] 我知道很多公司都有”工程师原型”(archetype)的说法——高级工程师通过什么方式完成工作。你刚才提到了架构师,很多公司对架构师有各种不同的叫法。我好奇的是,你是说这仅仅是你个人的工作风格,还是说所有高级工程师都应该在某种程度上保持亲自动手?
Anders: [46:22] 不,我不是说所有人都应该这样,因为我不觉得……我自己的情况是:我是有意识地选择继续做一名个人贡献者,而不是转做管理者。我是说,我有大把的机会可以走管理路线,但那不是我干得最好的地方,对吧?所以我认为你得自己决定:是什么激励着你、让你快乐、让你愿意做一辈子?人生只有一次。[46:54] 如果你要做出自己最好的工作,那就去做让你最快乐的事。我想这就是我一路走来的收获——写代码就是其中很大的一部分。
Ryan: [47:10] 那么,当年你在做 C#、还没开始做 TypeScript 的时候,你是怎么知道”缺了点什么”的?
Anders: [47:16] 我就是知道,说不清怎么知道的。我是说,只做设计那部分让我感觉没那么充实。我想念写代码。每当我有机会写一点代码,我都会快乐很多。我会想:”这挺有意思的。”但接下来我又得回去写更多的规范了。
Ryan: [47:41] 那么 TypeScript 是你因为想写更多代码而主动奔向的方向吗?
Anders: [47:46] 我奔向它有很多原因,一开始并不是专门为了写代码。我只是觉得那个问题很迷人。TypeScript 的诞生其实是间接通过 C# 而来的。当时是 Outlook.com 团队——如果我没记错的话——找到 C# 团队,问我们能不能行行好,把一个叫 Script Sharp 的东西产品化。Script Sharp 是把 C# 转译成 JavaScript、从而能在浏览器里运行的工具。
[48:19] 我当时说:”什么?你们为什么想这么干?为什么不直接写 JavaScript?”他们说:”因为这样我们就能拥有很棒的工具链——可以用 Visual Studio,可以有项目、类型检查、代码重构和导航,可以写接口,人们能理解这一切是怎么运作的。”我说:”哇,JavaScript 真的破到这种程度了吗?而你们解决问题的办法居然是抛弃 JavaScript、把它当作一种底层指令语言来对待,对吧?[48:51] 然后再写一个面向它的代码生成器。”我说:”我们难道不能修好 JavaScript 吗?那样不是更好吗?”TypeScript 就是这样起步的。就是感觉到:这里有个真的坏掉了的东西。[49:00] 需要有人来修——就在 JavaScript 工具链这件事上。而这正是我们着手去做的。
9. 为什么如今快速工具链比以往更重要(49:14)
Ryan: [49:14] 我觉得很有意思的一点是:在你的一次演讲中你说过,因为 AI 的存在,工具链的速度如今更加重要了。我想听听你为什么这么认为。
Anders: [49:30] 是这样的:我们的工作流越来越多地被在后台狂奔的智能体(agent)主导——它们在替你写代码。它们和我们一样会犯错,但你可以想要多少个就有多少个。所以生态系统里多了许许多多”小工人”,在写多得多的代码。而整个工作流对 LLM 来说本来就已经有性能挑战了。
[50:00] 如果你在一个大项目里写代码,每次 LLM 写点东西你都得做类型检查、一检查就是两分钟。那太可怕了。所以让这件事变快是极大的收益。
Ryan: [50:14] 是的,绝对的。我猜是因为它们在一刻不停地调用这些工具。
Anders: [50:17] 是的。我们都知道 LLM 喜欢用 grep 之类的工具。但它们现在也够聪明,会意识到:如果你想改一个叫 version 的属性,而差不多每个对象都有 version 属性,你不能直接全局搜索 version 然后重命名成别的东西——那样会把其他所有带 version 的接口都弄坏,对吧?
[50:45] 所以你得做语义搜索,而唯一能做语义搜索的就是编译器。所以你得通过编译器来做——通常是通过 LSP(语言服务协议),或者通过我们的命令行工具,它能给你等价的能力。越来越多的 AI 正在这么做。这意味着编译器一直在跑——你甚至可能看不到它们在跑,但它们在跑,在替你干活。
Ryan: [51:15] 是啊。
10. 对 AI 软件工程的预测(51:16)
Ryan: [51:16] 你谈 AI 的时候非常现实、非常扎根于软件工程实践。所以我想把我在网上听到的一些常见 AI 论断拿出来,听听你觉得它们有多准确、你认为它们何时或者是否会成为现实。第一个论断——这在去年年底特别流行——是:AI 将在一年内写出 90% 以上的代码。
[51:49] 我很好奇你怎么看。
Anders: [51:51] 嗯,对于某些类别的应用,可能确实如此——比如所有”凭感觉写代码”(vibe coding)的应用,100% 的代码都是 AI 写的。而且确实有大量这样的东西在被写出来。但它们会写出互联网上 90% 的高质量代码吗?我对此表示怀疑。事情是这样的:如今代码总量的暴涨,九成都是这么来的。因为 AI 写了太多代码。所以从某种意义上说,这是一个自我实现的预言,对吧?[52:22] 但说到那种从来没有人写过的超高质量代码——我看不出 AI 能写出其中的 90%。
Ryan: [52:35] 那么想到 AI 无法包办所有代码的场景时,你能举些”超高质量代码”的例子吗?那是什么样的?
Anders: [52:44] 比如说,从来没人见过的算法,或者某些特定的解决方案。我是说,我知道 AI 写不出我们的编译器——TypeScript 编译器,它就是写不出来。我们试过,它做不到。我也不是非要它做到——我并不指望它做到。但我们的编译器也不是一段典型的代码。这正是 AI 写不好的原因:训练集里没有这样的代码。[53:16] 它见过一些编译器,但没见过这样的——没有这些概念,没有用我们这种方式做的。所以它在那个方向上就是不合适。我们不能忘记:AI 本质上是一台巨大的随机性机器,它记住了整个互联网。[53:49] 并且能在记忆的基础上做一定程度的外推。但如果它以前没见过,它就未必能做得多好。它在变好——我不是说它不令人印象深刻,它能做的事简直惊人——但我只是觉得,离”包办一切”还有很长的路要走。而且我不知道我们是否真会到那一步,我倒希望不要。因为到了那一步,人类还有什么意义呢?
Ryan: [54:09] 还有一个我常看到的论断:一年之内你就不再需要用 IDE 了,或者说你再也不需要读代码了。
Anders: [54:19] 那就祝你好运吧。我是说,如果你要把钥匙都交给 AI,那一切就不好说了,对吧?等到 AI 修不好这个 issue、或者实现不了你想要的新功能的那一刻,你打算怎么办?如果你根本不理解底层是什么,你怎么办?
[54:46] 这是问题之一。另一个问题是:假设一个用户跑来找你说”你的应用偷走了我银行账户里的钱”——好吧,他们要起诉的是你。他们会起诉你,而不是 AI。我的意思是,归根结底,总得有人为 AI 的所作所为负责。如果你把钥匙交给了它,那责任就在你身上。但我不觉得……就我个人而言,如果我对自己担保的东西一无所知,我会睡不着觉的。
Ryan: [55:13] 为什么你的这种看法和我从 Anthropic 之类公司的人那里听到的观点差别这么大?
Anders: [55:22] 区别在于:我在这里说的并不是 AI 不能做惊人的事情——它能,它每天都在做。但人们就喜欢把事情一路推到 100%,对吧?而我恰恰在这一点上要打住了。我觉得:哦,是的,好吧,比例当然可以越提越高,但总得有个人理解正在发生什么、理解它与我们的业务问题有何关联。[55:53] 还得理解它与我们组织正在做的事情有何关系。还有那么多别的事情需要被连接起来……所以我认为,把事情看成”AI 包办了所有工作,而我们完全没有必要存在”,这种观点是错的。
Ryan: [56:04] 是的。我接下来想问的是:三年后,AI 会取代初级软件工程师、或者取代今天初级软件工程师所做的工作吗?
Anders: [56:13] 嗯,如果真会,那我们从哪儿得到高级软件工程师呢,对吧?我的意思是……我不这么认为。除非你也相信,再过三年你也可以在没有任何高级软件工程师的情况下运营你的业务。
Ryan: [56:30] 也是。
Anders: [56:30] 因为他们从哪里来呢?总得有人培养他们,对吧。但对我来说,我认为某种程度上已经发生的一个变化是:对于某些类别的程序员,金字塔的底部变窄了。新进来的人变少了,而我们希望他们更快地向上走,好让他们能尽早进入那种更具监督性质的角色。我认为软件工程师这门手艺确实正在改变:从你自己敲一行行代码,变成你让智能体敲一行行代码,而你来审查智能体的工作。
[57:14] 所以你会做更多审查、更少敲键盘。有些人热爱这种方式,并且非常擅长这样的工作流。但作为程序员,这份工作的性质确实在改变,而你工具箱里新添的这件工具正在产生这种影响。但它终究只是一件工具,过程中仍然有程序员参与——这一点我深信不疑。
Ryan: [57:42] 听起来你很享受亲手敲代码的过程。如果五年后……你会因此变得不那么快乐吗?
Anders: [57:48] 我这 40、50 年来一直很享受亲手敲代码,实际上……所以我会继续亲手写某些部分的代码,但也有些部分的代码我并不特别想自己敲。我不爱敲测试代码,也不爱应付某个测试框架的那套繁文缛节。当然不爱。如果能把那部分外包给 AI,那我就开心了。我确实觉得,对我来说审查一直都更难——花时间审查别人的代码,比自己写代码更难。
[58:24] 但我也认为,随着时间推移,我们能让审查代码的过程比今天更顺手,对吧?如今你往往只拿到一个清单:这是改动的文件列表,这是 diff,你自己琢磨去吧。AI 可以在这方面帮我们更多,比如解释这些改动是什么、为什么这么做。所以我认为我们会在这方面做得更好,会变得更有意思,但它会改变这门手艺的性质。
11. 最具技术挑战性的工作(58:52)
Ryan: [58:52] 你说你已经写了 40 年、可能 50 年的代码。你写过最难的一段代码是什么?或者说,你做过最具技术挑战性的事情是什么?
Anders: [59:06] 有太多事情在当时那个年代都是挑战。就说我们最近做的这个项目吧:我们知道简单的部分是把代码变成原生代码——只要把它搬到另一门语言就行。但接下来要驯服”共享内存并发”这头猛兽,让编译器能用上你机器上所有的 CPU——这在编译器里不是一个简单的问题,因为我们所受的教育都是写顺序执行的东西。
[59:48] 先做这,再做那。但如何让所有事情同时发生,然后还能在事后共享数据结构、又不让任何人不小心改了别人的数据?而在那些不得不修改的场景里,你又该如何做同步、避免死锁和竞态。这很棘手。我们攻克了一些很棒的技术难题,我认为我们可以为此自豪。
Ryan: [01:00:13] 那你职业生涯早期硬件更受限制的时候呢?有没有为了把东西做出来而不得不干的什么疯狂的事?
Anders: [01:00:23] 哦,当然有。我刚开始那个年代……当程序员是一件非常非常不同的事。我从 8 位微型计算机起步,ROM 里固化了 Microsoft Basic,就是一个 BASIC 解释器。你可以买那种书,照着敲入《星球大战》游戏之类的程序——全是文本的,屏幕滚动着告诉你”那个象限有克林贡人”,你就按 K4 还是什么键攻击。
[01:00:55] 但我迷上了去理解这一切是如何构造的,很快学会了写汇编。Turbo Pascal——我参与的第一个产品——整个都是用 Z80 汇编写的:编译器、编辑器、运行时库,一切都是汇编。用汇编写一个结构化的编译器,就意味着无穷无尽的折腾。你一直在数着字节:”好,我们得把这个塞进这块 EEPROM。[01:01:30] 可它只有 12K,而我超预算 20 个字节。哦,不过我可以把这个跳转改成长跳转,让它跳到一个短跳转、再跳到那个跳转,这样就能省出一个字节。好,省出一个字节。”你就这样坐在那里不停地摆弄。那是一门手艺,几乎像木工活一样。我是说,那和我们现在做的事情太不一样了,对吧?
[01:01:57] 如今一切都有着近乎无限的容量,还有用户近乎无限的期待。
12. 最推荐的一本书(01:02:04)
Ryan: [01:02:04] 说得太对了。你有没有一本最想推荐给软件工程师的技术书?
Anders: [01:02:09] 我一般推荐的都是同一本。在我年轻时,对我来说”就是它了”的那本书是《算法 + 数据结构 = 程序》(Algorithms + Data Structures = Programs),作者是 Niklaus Wirth——Pascal 的发明者,后来又发明了 Modula 和 Oberon 等等。这本书很少用数学符号,却充满了有教学意义的例子,把数据结构的工作原理讲得清清楚楚。我就是从这本书里学会哈希表的,对吧?[01:02:43] 当你是自学成才时,你不知道哈希表这种东西——你只知道链表,就这样。所以在 Turbo Pascal 最初的版本里,所有的符号表都只是……好吧,都只是链表,懂吧?然后随着局部变量越来越多,查找时间就成指数级增长。后来我读到了哈希表,惊了:”哇,你是说我可以这样做?然后查找时间基本上就是线性的?”[01:03:11] 我实现了它,编译速度直接翻倍。我当时想:”我的天,这书读得太值了。”我一直就是那种工程师。他还讲到了做编译器时如何做错误恢复……总之对我来说那是一本伟大的书。现在它其实就是网上一个可以下载的大 PDF。[01:03:46] 已经绝版三四十年了。是的。
13. 给年轻时的自己的建议(01:03:50)
Ryan: [01:03:50] 最后一个问题:以你如今的全部经验,如果回到刚入行的时候,给自己一些建议,你会说什么?
Anders: [01:04:01] 嗯,有几点吧。比如我们聊过的”成为团队合作者”这件事——我可能会从一开始就努力多教育自己一些。另外一件回头看很重要的事是:不要让别人告诉你”这做不到”。当他们告诉你做不到时,那是因为他们自己做不到,这并不意味着你也不可能做到。你懂我的意思吧?
[01:04:29] 所以,不要让那种话使你气馁。
Ryan: [01:04:31] 有没有哪个项目,是有人说你做不到、然后你做出来了,于是你就……
Anders: [01:04:38] 嗯,我参与的第一个项目——Turbo Pascal 就是这样。每当我们告诉别人”我们做出了这个东西”,他们先说”那不可能”;等真看到了,又改口说”伙计们,你们可真行”。反正一开始谁都不信,懂吧?但它就是可能的——而我当时根本不知道那”不可能”。
Ryan: [01:04:55] 太棒了。感谢你抽出时间,非常感谢你,Anders。
Anders: [01:04:58] 不客气。这次聊得非常开心。
附:对文中 AI 观点的评析
本章为编者补充,不属于访谈内容。以下从大语言模型(LLM)的底层原理出发,分析文中关于 AI 的表述哪些成立、哪些不准确。
一、先厘清 AI 的底层原理
评价这些观点之前,需要明确当前大语言模型的几个基本事实:
- 自回归预测: LLM 的训练目标是预测下一个 token,本质上是学习训练数据分布中的统计规律,而不是在执行符号推理或查询数据库。
- 采样随机性: 输出是逐 token 采样生成的。温度(temperature)大于 0 时输出天然带有随机性;即使设为 0,受 GPU 浮点运算、并行批处理等工程因素影响,也无法保证逐字复现。
- 无置信度校准: 模型没有”知道自己不知道”的内置机制,总是生成”看似合理”的延续——幻觉(hallucination)由此而来。
- 能力由分布决定: 对训练集中常见的模式泛化良好;对从未见过的领域,能力随”分布外距离”快速衰减,但并非存在一条硬边界——模型确实具备一定的外推与组合能力。
- 上下文学习有代价: 可以在提示词中临时”教”模型新知识(in-context learning),但要占用上下文窗口,且稳定性不如在训练中内化的知识。
- 依赖外部验证: 模型自身无法执行或验证代码,编译器、测试等外部反馈回路(可验证信号)是提升其产出可靠性的核心手段。
二、这些表述基本正确
- “AI 不是确定性的,不能相信它两次做出同样的事”(17:35) —— 符合原理。采样机制决定了输出的随机性,即使在低温设置下,工程层面的非确定性也依然存在。Anders 由此得出”让 AI 写一个确定性程序,而不是直接让 AI 翻译几十万行代码”的结论,正是业界应对这一限制的标准做法(程序辅助推理、工具调用)。
- “AI 会自信地算错账”(18:46) —— 符合原理。模型没有置信度校准机制,多实体、多步骤的上下文处理容易丢项,”算账忘了几笔”是典型表现。而把同样的任务改成”让 AI 写一个电子表格程序”就能得到正确答案,说明模型擅长的是生成可执行的程序,而不是在上下文里直接做多步运算。
- “AI 最擅长训练集中见得最多的语言”(23:51) —— 符合原理。模型能力与训练数据的规模和分布强相关,JavaScript、TypeScript、Python 正是语料最丰富的语言;对一门全新语言,只能靠在提示词里消耗 token 临时教学,能力与稳定性都会打折扣。
- “类型注解引导 LLM 写出更好的代码,编译器能给 AI 反馈”(25:41) —— 符合原理。类型标注相当于给生成过程施加约束、给结果提供可验证的规约;”生成—编译—反馈—修正”的回路正是当前提升 AI 代码可靠性的核心机制,与可验证奖励强化学习(RLVR)同理。
- “机器码一半是噪音,LLM 直接生成机器码不会更好”(38:28) —— 结论符合原理。模型以文本为主要训练分布,机器码中内存地址、寄存器分配等信息密度低且近乎随机,对分词与学习都不友好。不过他用”AI 是人类的模拟器,所以和人一样不擅长机器码”来论证,是松散的拟人化类比——真正的原因是训练分布与表示密度,而非”像人”。
- “90% 的代码由 AI 写出是自我实现的预言”(51:51) —— 数量与质量的区分符合实际:凭感觉生成(vibe coding)的应用确实可以 100% 由 AI 完成,但这类代码的暴涨拉高了分母,推不出”高质量代码也能被包办”。
三、这些表述不准确或过于绝对
严格地说,文中没有原则性的硬错误——Anders 对 AI 的理解明显高于行业平均水平——但有三处字面表述或论证方式不准确:
- “AI 写不出我们的编译器,它就是写不出来”(52:44) —— 方向正确,表述绝对。原理上更准确的说法是:模型能力随任务的分布外距离与时间跨度衰减,超长、超新颖的工程目前确实会失败,但这是当前能力的梯度限制,而非永久性的硬边界。配合测试与反馈回路的智能体,已在若干窄领域产出训练集中不存在的新成果。
- “AI 是一台记住了整个互联网的随机性机器”(53:16) —— 作为心智模型可用,但字面理解有偏差。模型是对训练数据的有损压缩加泛化,而非逐字存储:它有时能近乎复述训练内容(记忆),更多时候是在重组统计模式(泛化);它既做不到像数据库一样精确检索,也不是纯粹的”随机鹦鹉”。
- “TypeScript 采用率的拐点与 AI 同步,既有语言会更强”(24:47) —— 趋势有数据支持:GitHub Octoverse 2025 显示 TypeScript 已于 2025 年 8 月超过 Python 和 JavaScript,成为 GitHub 上使用最多的语言;原理上”AI 偏爱语料丰富的语言”也确实会形成正反馈。但”拐点与 AI 同步”是相关性观察,生态成熟、编辑器普及等混杂因素同样能解释它,不能直接当作因果。
四、这些属于价值判断,原理无法判定对错
- “不需要 IDE、不需要读代码——祝你好运”(54:19) —— Anders 反驳的核心是责任归属:出问题时被起诉的是你,而不是 AI。这是法律与组织问题。技术原理只能支持其中一个前提:只要模型仍会幻觉且没有置信度校准,完全放弃人工验证,就等于把责任交给一个无法报告自身可靠性的系统。
- “AI 取代了初级工程师,高级工程师从哪里来”(56:13) —— 人才管道论证属于组织与经济学范畴。原理层面只能说:当前 LLM 在边界清晰的小任务上接近初级水平,在长程自主任务上仍不可靠——这与 Anders”金字塔底部变窄、但过程中仍有程序员参与”的观察一致,但无法据此推出”永远不会取代”。
Enjoy Reading This Article?
Here are some more articles you might like to read next: