“如果把初级工程师消灭了,三年后企业去哪里招高级工程师?”

编译 | 王启隆

出品丨奇点折射(ID:rgznai100)

在计算机编程的历史长河中,很少有人能像 Anders Hejlsberg 一样,在长达四十年的时间里连续缔造多个影响整个软件行业的重量级工具。从上世纪 80 年代一人用汇编写出 Turbo Pascal、奠定现代 IDE 雏形的 Delphi,到微软跨世纪的核心基石 C#,再到如今几乎统治了现代 Web 与前端工程的 TypeScript,Anders 一直被誉为“程序员中的程序员”,目前担任微软技术院士(Technical Fellow)。

打开网易新闻 查看精彩图片

这几天,前 Meta 资深工程师、知名技术博主 Ryan Peterman 在其主理的播客《The Peterman Pod》中,与 Anders 展开了一场极具技术深度与行业洞察的对话。这场对话的由头,是近期技术圈最震撼的一项重构:TypeScript 团队正在将编译器核心从 JavaScript 移植到 Go 语言,以换取高达 10 倍的性能暴增。在谈话中,Anders 首次深度拆解了选择 Go 而非 Rust 的底层考量、为何没有全盘依赖大模型写代码、以及用原生代码重塑编译器的核心挑战。

更重要的是,面对当下硅谷 AI 实验室与社交媒体上铺天盖地的“狂热预言”——诸如“AI 将在一年内写完所有代码”、“初级工程师将被彻底消灭”、“今年底不再需要 IDE”等论调,作为一线语言设计宗师的 Anders 给出了一套极其清醒、务实且基于软件工程客观规律的硬核反驳。

打开网易新闻 查看精彩图片

要点速览

  • 推倒重写 vs 代码移植 :“彻底推倒从头重写对生态是有毒的,因为它会破坏向后兼容性。我们做的是语义 1:1 的代码移植,而 Go 是唯一在垃圾回收、内存安全和共享内存并发上完美契合我们假设的语言。”

  • 如何正确用 AI 做大型重构 :“与其把几十万行老代码扔给 LLM 碰运气,不如让 AI 帮你写一个‘转换工具’——把 AI 的随机性(stochasticness)锁在造工具的阶段,而工具的执行必须是 100% 确定性的。”

  • 主流语言的马太效应 :“AI 最擅长的是训练集里出现频率最高的语言。AI 的爆发不仅不会淘汰主流语言,反而会让 TypeScript 和 Python 的统治地位更加巩固。”

  • 警惕‘漂浮在平流层的架构师’ :“在做 C# 时我曾放手得太彻底,成了脱离一线的架构师,后来才发现亲自敲代码才是每天叫醒我的快乐源泉。脱离一线,你就无法真正把握产品的脉搏。”

  • AI 无法消灭初级工程师 :“如果初级工程师都没了,未来三年你上哪去找高级工程师?而且不管 AI 写了多少代码,最终承担法律和道德责任的永远是人类。”

以下是本次访谈的完整内容:

站在生态之内做工具,比自命不凡地强行改造管用得多

主持人:我们今天核心要聊的话题是用原生代码重写的 TypeScript 7。当我看到这个新闻,首先想到的就是:我以前甚至都没意识到 TypeScript 编译器最初是用 JavaScript 写的,因为在我的常规认知里,编译器通常不会用高级动态语言来实现。

Anders Hejlsberg:确实,大家通常不会这么想。

主持人:你最初为什么会选择用 JavaScript 来编写 TypeScript 的编译器呢?

Anders Hejlsberg:这是个好问题。坦白讲,在 TypeScript 项目启动前,如果你对我说:“Anders,你接下来要用 JavaScript 写编译器了”,我肯定会说:“不可能,我绝对不会这么干。”

但实际上,TypeScript 最早的原型(早期叫 Strada 项目)是用 C 语言写的,因为它是基于我们在 Internet Explorer 里的 JavaScript 解析器改造而来的。

后来我们转向了 JavaScript,不过是用偏 C# 的风格来写的。但我们为什么要选 JavaScript 呢?原因在于:如果你能让自己融入并立足于目标生态系统来实现自举(self-host),那绝对比站在生态系统之外去强行适配要好得多。你明白我的意思吗?因为用 TypeScript 编写自身,我们团队每天都是 TypeScript 和所构建工具链的日常重度用户。

只要有什么地方不好用,我们立刻就能察觉;只要有什么地方性能不达标,我们马上就会知道。所以在项目早期,这带来了巨大的优势。再者,JavaScript 无处不在,这意味着我们的编译器天然就能在任何平台、任何环境下运行,甚至能直接在浏览器里跑。如果当时选了原生代码方案,在浏览器中运行是根本不可能实现的。

虽然现在有了 WebAssembly(WASM),但那时候根本没有。所以这是一个非常具有实际意义的选择。而且平心而论,当时大家其实并没有意识到 JavaScript 已经变得有多快了。它以前确实很慢,但后来 Google 凭借 V8 引擎做了极其出色的优化,把性能拉到了接近原生代码 2 到 3 倍的差距以内,这已经相当惊人了。因此用 JavaScript 写编译器完全可行,甚至能做到高性能——因为决定性能的关键往往在于算法,而不必然是运行时环境。当然两者都有影响,但算法起决定性作用。

主持人:TypeScript 编译器有什么独特之处吗?提到编译器,我通常会想到 Clang 或者用来编译 C 语言等静态语言的工具。与这些原生编译器相比,当年用 JavaScript 编写的 TypeScript 编译器有什么特殊之处?

Anders Hejlsberg:我觉得 TypeScript 编译器有几点非常独特。首先,它的目标输出不是机器码,而是 JavaScript,有些人称之为转译器(transpiler)。从某种意义上说,编译阶段的核心工作主要是剥离类型注解,把 TypeScript 还原成纯 JavaScript。

在早期,编译流程中还有一个重要任务就是“降级”(downleveling)JavaScript 代码,因为当时各种运行环境并非都是自动更新的常青浏览器,大家的标准实现往往落后好几年。比如当类(class)被标准化时,大部分 JavaScript 运行时还不支持类语法,但我们可以把它降级转换为构造函数,完成代码变换。所以降级转译是我们当时的核心工作之一。当然,最核心的重头戏始终是类型检查。

而且 TypeScript 的类型检查器也与其他编译器截然不同。通常情况下,编译器中的类型检查器是为了辅助代码生成器——需要搞清楚这里处理的是浮点数、整数还是字符串,以便为对应数据类型生成正确的机器指令等等。但在 TypeScript 中完全不是这样。事实上,由于我们最终会擦除所有类型,这些类型对代码的运行时行为没有任何影响。它们纯粹是为了开发者和工具链服务的,用来支持代码补全、重构、代码导航等功能,这完全是另一套逻辑。而且代码里甚至不需要处处声明类型,TypeScript 拥有渐进式类型系统(gradual type system),你完全可以让一半代码有类型,另一半全是 any 且不做检查。很少有语言具备这种特性。

所以在这种意义上,它是一个非常不同的编译器,也是一门非常独特的语言。这也让开发工作变得极其迷人,因为我们在解决前人从未解决过的问题。

主持人:据我所知,许多编译器在处理高级代码时通常会进行优化,比如展开循环、消除重复指令、简化逻辑等。TypeScript 到 JavaScript 的编译过程中会有类似的优化吗?

Anders Hejlsberg:很少有这样的优化。正如我刚才所说,在做降级转换时确实有一些复杂的 AST 变换,但这对于现在的 TypeScript 来说已经越来越不重要了。事实上,很多人现在仅仅把 TypeScript 当作类型检查器来用,然后配合 esbuild 或 SWC 等打包工具来打包应用、擦除类型,并完成在浏览器中运行所需的一切准备。所以我们的目标并不是在运行时去优化你的代码,而是为了提高你作为开发者的生产力,或者说提高 AI 作为开发者的生产力。

彻底推倒重写往往是有毒的,选 Go 是为了最少折腾换最大收益

主持人:那么对于这个最初完全用 JavaScript 编写的编译器,这次用原生语言重写究竟是为了解决什么问题?又为什么最终决定改用原生代码?

Anders Hejlsberg:我们要解决的问题非常单纯:性能与可扩展性。JavaScript 最初并不是为编译器这种计算密集型负载而设计的。它的初衷是在浏览器中构建 UI,最初甚至只打算写个百来行代码,虽然现在承载的规模大得多了,但它依然不是为我们这种计算负载做深度优化的环境。

所以在 JavaScript 中,首先相比原生代码你要承担 2 到 3 倍的性能损耗。当然这取决于具体负载,但如果是纯计算密集型任务,基本就是这个差距。其次,JavaScript 在并发使用上有极其严格的限制。JavaScript 在设计之初本质上就是一门单线程语言,这也是为什么我们会有回调函数、async/await 等机制。你无法直接创建系统线程,而这在绝大多数时候其实是件好事——因为在包含可变数据的语言中处理并发极其困难,会遇到竞态条件、死锁等各种棘手问题。这也是为什么函数式编程语言更容易处理并发,因为所有数据都是不可变的,推理起来容易得多。但在 JavaScript 中,除了 Web Worker 之外,你无法使用真正的并发机制。

然而 Web Worker 之间除了跨进程通信外无法共享数据。你可以让一个 Web Worker 计算某些内容,但如果要传给另一个 Web Worker,就必须序列化成 JSON,而不能直接传递内存对象。这意味着你无法让一个 worker 构建好某个数据结构后直接供其他 worker 使用。换句话说,你无法使用“共享内存并发”(shared memory concurrency)。

而这正是我们迫切需要的——如果不利用当今每个人电脑上都标配的多核 CPU,我们就白白浪费了巨大的潜在性能。摩尔定律已经不再为我们提供单核性能更高的 CPU,而是提供核心数量更多的 CPU。在计算密集型负载中,我们必须找到利用多核的方法,否则就是暴殄天物。所以我们在很多方面都在平白损失性能。这就是为什么我们要寻找一门能同时解开这两个束缚的语言:它既必须是原生语言,又必须具备共享内存并发能力。

主持人:我注意到你们最终选了 Go。提到系统级语言,现在大家更热衷讨论的往往是 Rust 或 Zig,为什么你们为这次原生重写选择了 Go?

Anders Hejlsberg:整个决策过程其实非常系统严密。我们做的第一个决定是:我们不是推倒重写(rewrite),而是代码移植(port)。因为只有通过代码移植,我们才能完整保留现有编译器的语义、算法和精确行为,而这正是所有人保持向后兼容所依赖的基石。如果我们彻底推倒从头重写,最终造出来的其实会是另一门语言——在某些面临多种可能性的决断场景下,它会给出不同的报错或表现出不同的行为。

既然确定要移植,就意味着我们的代码依赖于某些核心假设。例如,我们的代码假设存在垃圾回收(GC),假设函数是一等公民(支持函数嵌套定义、闭包捕获外部状态等)。当我们按这些标准评估各种语言时,Go 是满足条件最多的一个。它在所有主流平台上都能生成非常成熟稳健的原生机器码,具备垃圾回收机制,并且对共享内存并发有着极其出色的支持。这些正是我们最看重的核心诉求,而其他语言或多或少都有某些特性与这些目标相冲突。因此针对这一特定工作负载,Go 是我们的绝佳选择,实际效果也非常棒。

主持人:如果复盘一下 Rust,它缺少了什么关键特性?如果具备什么条件你们才会选它?

Anders Hejlsberg:我觉得主要有两点:首先它没有垃圾回收,内存管理要么手动处理,要么通过借用检查器(borrow checker)管理。但借用检查器不允许存在循环引用的数据结构,而我们的编译器里充斥着大量的循环引用数据结构——包含父节点指针的语法树、递归类型、互相引用的符号表等等,到处都是。正如我所说的,如果我们选择从零重写,大概可以绕过并解决所有这些问题;但那样就不仅仅是移植代码了,我们还得去解决因为选择这条路线而给自己招惹的一大堆全新难题。我们尝试过,但根本不可行。我们希望尽可能用最少的工作量拿到收益。而且平心而论,在最终生成的机器码质量和并发收益方面,Rust 相比 Go 并没有实质优势。Go 已经足够优秀,选择 Go 让我们以更少的工作量拿到了所有好处。

主持人:我知道有些语言是内置了垃圾回收机制,但也有一些语言可以通过第三方独立库来实现 GC,对吧?

Anders Hejlsberg:确实有,但这往往伴随着大量限制条件,而且未必能提供我们所需要的安全保障。Go 兼具类型安全和内存安全,意味着你不会莫名其妙搞出悬垂指针。在 Rust 中,你可以用引用计数或者各种各样的策略,但为了让代码跑通,你总会不得不引入一些 unsafe 块。而如果垃圾回收是直接内置在语言设计中的,它就真正成了一个正交的独立关注点,你不需要做任何特殊操作。当然,你依然不能一直持有不再需要的数据,但引用计数同样也有这个问题,不是吗?而内置 GC 让这一切成为语言的原生能力。

主持人:在把代码从 JavaScript 移植到 Go 的过程中,我很想知道你们多大程度上使用了 LLM?因为一些知名大项目在重写整个代码库时都在用 AI。比如 JavaScript 生态里的 Bun 就把很多东西重写移植,这看起来是个很管用的策略。你们在这次移植中利用了 LLM 吗?

Anders Hejlsberg:其实用得并不多,但这很大程度上也是时机问题。我们这个项目是两年前启动的,两年前的 LLM 远没有现在这么强大,整个领域发展得实在太快了。不过,我们在移植过程中确实编写了大量辅助工具。最初的原型、词法扫描器(scanner)和语法解析器(parser)都是我们纯手写的。到了那个阶段,我们已经能够确信:天哪,这真的能带来 10 倍的性能提升!

那接下来怎么处理剩下的庞大代码库呢?我们写了一个工具,在语法层面把 TypeScript 机械转换为 Go 代码。转换出来的代码没有任何语法错误,但也根本无法编译——因为它依然把类型当成 TypeScript 的类型在使用,而 Go 里根本不是这么回事。因此所有这些类型定义都必须重构,我们必须重做所有数据结构等等。但至少我们得到了结构完全对齐的代码库,然后我们可以在这个基础上逐个击破,并在局部偶尔借助 AI 来帮我们做些转换。

所以整个过程是手动编写、自动化脚本加少量 AI 辅助的结合。如果今天重新启动这个项目,我们会换种方式吗?有可能。但我依然要说,如果直接把老编译器里那几十万行(在 50 万到 100 万行之间)代码一股脑扔给 AI 让它全盘翻译,这并不意味着你就能省事——你仍然必须逐行去仔细审查 AI 生成的每一行代码,确保没有任何幻觉。除非你的测试覆盖率达到 100% 的完美状态,否则你依然必须人工全量核对。

坦率地讲,我认为更好的做法是:让 AI 帮你写一个“将 TypeScript 转换为 Go 的转译程序”。因为这样一来,你把 AI 固有的随机性(stochasticness)留在了编写工具的阶段,而转换程序一旦生成,它的运行行为就是完全确定性的(deterministic)——每次运行都会给出完全一致的转换结果。人们往往忽视了 AI 的一个核心特性:它是非确定性的,你根本无法指望它在两次执行中给出完全相同的结果。如果放在今天,我们可能会采用这种方式,但当时 AI 的能力还达不到这个水平。当然,这并不意味着我们现在后续的流程中不用 AI。正如所有人一样,我们在围绕编译器的内部流程中大量使用 AI,比如辅助编写测试用例、推进 PR 等等。现在每当有新的 Issue 提交进来,我们的第一反应也是交给 Copilot 看看它能不能直接修好。所以在这个层面上我们用得很深。

主持人:也就是让 LLM 生成确定性的工具,然后你只需要验证那个工具即可。

Anders Hejlsberg:没错,因为你需要验证的表面积要小得多。说实话,我在和朋友结伴出游时就亲身经历过类似的事情。我们一起出去旅行,需要结清账目:他付了酒店房费,我们付了其他开销,需要算清楚谁欠谁多少钱。我们把所有账目明细列出来,直接让 AI 帮我们算。结果它极其自信地给出了一个完全错误的答案,因为中途漏掉了一些结算项。但如果我们让它写一个电子表格公式(本质上就是一段程序),我们就能拿到完全正确的结果,因为 AI 极其擅长编写程序。这非常有意思:有时你不应该直接向 AI 要最终答案,而是应该让它写一个能计算出答案的程序。

主持人:选择代码移植而不是彻底推倒重写,你们是否有错失什么潜在收益?

Anders Hejlsberg:老实说,我觉得没有任何损失,而且我们也不会考虑推倒重写。我们对现有代码库的运行方式非常满意,并不觉得错失了什么。正如我之前所言,对生态系统来说,彻底重写往往是有毒的,因为它会牺牲兼容性——推倒重写后,你永远无法完全还原以前的一切。开发人员常常打着“让代码更优雅、更漂亮”的旗号去改动各种细节,结果受苦的全是广大用户。如果你想维系一个健康繁荣的生态系统,向后兼容性至关重要,而这正是我们坚决要保障的。

AI 不会杀死主流语言,反而会把 TypeScript 推向绝对统治

主持人:在为这次访谈做准备时,我不断读到关于 JavaScript 各种奇怪语言语义的讨论。在我的职业生涯中,大家也总会因为各种原因对 JavaScript 颇有微词。我的问题是:既然如此,为什么它还能如此流行?

Anders Hejlsberg:首先,我觉得 JavaScript 并没有某些人说的那么不堪。不得不说,在 90 年代中期 Brendan Eich 匆忙赶工创造这门语言的那短短三四周时间里,他其实做对了很多至关重要的决策。谢天谢地,他受过系统的函数式编程训练,把“函数是一等公民”做对了——你可以定义函数嵌套,内部函数可以访问外部状态,函数可以作为一等值随意传递。这一特性极其强大,JavaScript 在这一点上做得非常漂亮。

当然,这门语言确实有一些怪异的设计。所有的隐式类型转换,以及双等号 == 和全等号 === 之间匪夷所思的差异,常常把所有人逼疯。但这恰恰是类型检查器最擅长解决的问题。类型检查器可以把所有这些细微差异逐一揪出来,因为它对语言底层的深层语义了如指掌,而人类大脑根本不可能时刻记住所有这些坑。

我觉得这也是 TypeScript 如此受欢迎的原因之一:它能真正激发出 JavaScript 优秀的一面,同时将糟糕的部分隔绝开来。而且别忘了,JavaScript 是真正的跨平台语言。哪怕是当年专为跨平台应用而设计的 Java,也算不上真正的全平台——它跑不进浏览器,无法做到无处不在。而 JavaScript 真正做到了无处不在。所以你有很多理由喜欢 JavaScript。而通过 TypeScript,我们成功地把所有的糟粕都关进了笼子里。

主持人:我记得以前也有过一些项目,不知道你是否了解 Dart 或 CoffeeScript,它们似乎都曾试图替代或改进 JavaScript,但如今都不太流行了。为什么会这样?

Anders Hejlsberg:我觉得 CoffeeScript 本质上只是给 JavaScript 换了套语法外壳,并没有改变语义,也没有提供什么额外的工具链支持。而 Dart 在某种意义上也是试图去修复 JavaScript。但如果你能立足于原本的事物去修缮它,而不是试图用一套全新的东西取代它,你其实是在给整个生态帮一个大得多的忙。这正是我们在 TypeScript 上所坚持的初衷:我们不想颠覆 JavaScript,我们只想改良 JavaScript。我认为这正是我们最终取得成功的根本原因。

主持人:如今进入了新时代,AI 已经完全具备了重写代码库的潜力。我想人们可能会逐渐转向更理想的语言,而不是继续沿用既有的老语言。你觉得 JavaScript 未来的使用率会下降吗?

Anders Hejlsberg:我不这么看。我的观察是:AI 最擅长的语言,恰恰是其训练集中出现频率最高的语言。AI 是基于全世界现存的所有代码进行训练的,这意味着训练集里包含海量的 JavaScript、海量的 TypeScript 和海量的 Python,因此 AI 自然极其擅长写这三门语言。如果换成某个人刚刚自创的小众语言,AI 就会笨拙得多——因为训练集里压根没有,在向 AI 提问前,你得在 prompt 里白白浪费天知道多少 token 先去教它这门语言的语法。从这个角度来看,我认为既有的主流语言非但不会被淘汰,反而会因为 AI 的普及而变得更加繁荣。我们在 TypeScript 上也清晰地看到了这一点:随着 AI 的爆发,TypeScript 的普及曲线上出现了一个非常明显的加速拐点。我认为主流语言的地位反而在进一步巩固。

主持人:现在所有 JavaScript 代码中有多少比例是用 TypeScript 写的?

Anders Hejlsberg:去年 TypeScript 已经跃居 GitHub 上使用率第一的语言,使用量甚至超过了原生 JavaScript。至少在 GitHub 上,超过 50% 的代码是用 TypeScript 写的。所以我们现在的规模已经超过了我们最初想要改进的那门语言自身。

主持人:太厉害了。

Anders Hejlsberg:是的,而且规模也超过了 Python。

主持人:如果顺着这个趋势往后看,你觉得 TypeScript 最终会成为绝对默认的标准,让几乎 100% 的 JavaScript 都带上类型吗?

Anders Hejlsberg:必然会这样。如果你让 AI 去写代码,你甚至很难找到一个还在写纯 JavaScript 的 AI 工具。它们全都默认输出 TypeScript,因为类型注解能帮助 AI 写出更高质量的代码、犯更少的错误,而且我们可以在代码运行前对其进行静态校验。这就是本质区别所在:验证 JavaScript 的唯一方法是把它实际运行起来,这意味着必须搭建运行时环境,而处于沙箱环境中的 AI 未必有条件运行;但 AI 完全可以通过工具链调用编译器来静态检查刚刚生成的代码。编译器一旦报错提示“你写错了”,AI 就可以在通知人类之前自己在后台把错误修好。

主持人:了解了用原生语言重写带来的这 10 倍性能提升之后,我不禁好奇:为什么还会有人在后端使用 JavaScript?为什么不直接在后端全盘使用原生语言呢?

Anders Hejlsberg:这取决于你具体要解决什么问题。因为这种观点忽略了一个事实:你在后端究竟要写什么业务?比如生态里有大量极其出色的 Web 服务端框架都是基于 TypeScript 或 JavaScript 构建的。如果生态中现成的框架能帮你更快交付解决方案,何必因为原生语言根本不契合该领域的生态而从头造轮子呢?所以这很大程度上取决于你的问题领域与什么生态最契合。

而且在很多场景下,纯代码执行性能根本不是系统的瓶颈。看看 Python 就明白了:全世界所有的 LLM 训练几乎都是用 Python 写的脚本。其中确实有大量对时间极度敏感的计算,但那些底层算子是用原生代码写的,Python 只充当编排语言。在 Web 服务端场景下,TypeScript 本质上也就是一个用来与数据库交互、协调业务流的编排者。决定这类应用整体性能的关键,往往根本不在于内部那个 for 循环是不是跑在原生机器码上——更何况在 JIT 即时编译器的加持下,它实际上也是原生执行的。所以一定要先做性能基准测试再做决策,实际测量出的数据往往会出乎你的意料。

主持人:我立刻想到的一个问题是:为什么没有更早进行原生重写呢?

Anders Hejlsberg:在刚启动 TypeScript 项目时,我们根本无法预料到人们今天会用 TypeScript 写出如此庞大规模的项目。比如 VS Code 已经有大约 230 万行代码;微软内部甚至有些项目超过了 1000 万行代码,这在当年看来简直不可思议。放在当时我们肯定会觉得:“怎么可能会有人写这么大?”但那已经是十多年前的事了。我们在 2012 年启动 TypeScript 时,整个行业格局完全不同。

随着时间推移,人们构建的项目规模越来越庞大,与此同时我们的编译器也变得越来越聪明——我们加入了联合类型、可辨识联合、控制流分析等一系列强大的功能。这些特性大幅提升了类型检查的能力,但每增加一个复杂特性,编译速度就会不可避免地慢上一点。实际上,TypeScript 6 的运行速度大概只有 TypeScript 1.5 的 50%,但 1.5 版本能做的事情也少得多。项目体积在暴增,编译器处理的逻辑在剧增,这两者共同侵蚀了开发者的实际效率。再加上前面提到的摩尔定律停滞,单核性能不再提升,而 JavaScript 又无法利用多核。在十几年前我们不可能预见到所有这些变量的汇聚,但事态的发展就是如此。

主持人:我刚成为软件工程师去 Facebook 工作时,他们内部有一个非常流行的框架叫 Flow,它负责在 JavaScript 之上做类型推导。那会儿它检查一次要花很长时间,而现在已经几乎听不到它的声音了。如果说当年有一场关于 JavaScript 类型化的较量,看起来是 TypeScript 赢了。

Anders Hejlsberg:当时确实有些竞争。在早期,我认为让我们脱颖而出的核心优势在于“自举”——因为编译器本身就是用 TypeScript 写的,开源社区贡献代码的门槛极低。据我回忆,Flow 当时是用 OCaml 编写的,如果想给 Flow 贡献代码,你必须去学一门完全不同的编程语言和思维方式。

此外,Flow 当时并没有把重点放在 IDE 工具链体验上。而我们在立项之初就想得非常清楚:我们写编译器不是为了生成机器码,而是为了打造一流的工具链。这也是为什么我们从第一天起就将语言服务(Language Service)与编译器紧密绑定,通过开放协议深度集成到 Visual Studio、VS Code 以及各种编辑器中。因为那才是真正亟待解决的痛点。拥有类型检查器固然很好,但开发者真正想要的远不止这些——大家需要自动补全、代码红波浪线报错、实时的交互式反馈体验。

别做漂在平流层的架构师,写代码才是每天叫醒我的动力

主持人:在你的职业生涯中打造过多门编程语言。创造一门编程语言究竟需要付出什么?

Anders Hejlsberg:首先我想说的是:这个世界对新编程语言的需求,就像人脑门上需要再钻个窟窿一样多余。也就是说,你得是某种特定类型的人,才会在一开始就踏上这条路。这是计算机科学中一个极具魅力的领域,自计算机诞生以来就一直存在。实现每一门语言、每一个编译器,你都需要掌握很多机制层面的东西,比如语法解析器、词法分析器、代码生成器等等;但那只是语言实现的机械工程,除此以外还有语言的设计——如何让它用起来感觉恰到好处,这更像是一门艺术。我认为这两方面你都必须精通。

其次你必须认识到:任何一门新语言,其实只有 10% 是全新的创新,剩下 90% 都是其他所有语言都必须去处理的枯燥苦活。所以你必须做好准备去面对大量可能并不那么有趣的繁重工作。

最后永远不要忘记:你站在巨人的肩膀上。要打造一门优秀的编程语言,你必须先深入理解大量的其他编程语言,理解过程式、面向对象、函数式等各种不同编程范式之间的差异。

最重要的一点在于,这是一场漫长的持久战,可能比软件领域的任何其他事情都要持久。我参与过的每一个语言项目,每一个我都至少投入了 10 年以上,发布过多个大版本。说实话,通常直到发布到 3.0 版本,事情才真正开始走上正轨。这需要投入难以想象的执着与专注。如果你是一个很容易对问题感到厌倦并迅速转向新事物的人,那你注定不适合做语言设计。你看行业里的那些编程语言设计师,大家全都在这个领域深耕了极其漫长的岁月。

主持人:你刚才提到了两个部分:客观的工程实现与设计语言人体工学的艺术。抛开你亲自参与过的语言不谈,在其他编程语言中,是否有你特别欣赏的?

Anders Hejlsberg:学习并充分领略函数式编程之美对我来说极具启发性。因为它代表了一种截然不同的编程思维方式——一种比起机器更贴近数学的思维方式。我认为现代编程从中汲取了无数精华。老实讲,即使在构建 TypeScript 项目时,TypeScript 编译器的很大一部分也是以高度函数式的风格编写的,基于不可变数据结构运行。现在得益于共享内存,并发线程或进程由于不会修改数据,可以安全地共享同一个数据结构,这带来了极其强大的威力。但在命令式程序中构建纯函数式编程的“孤岛”,并将其与并发机制融合在一起,这非常复杂。我认为函数式编程为世界做出了巨大的贡献。当然面向对象编程也是如此,我不会单挑某一个具体的语言,因为你接触到的每一门语言都会让你学到新的东西。

主持人:你提到世界其实不需要更多编程语言。展望 10 年后,你觉得到时候的编程语言会比现在更少吗?

Anders Hejlsberg:这很难说。但正如我之前所言,AI 往往更有利于既有的主流语言。而且我认为——哪怕在 AI 出现之前也是如此——成功实现一门编程语言并围绕它建立起生态系统的门槛正在不断抬高。过去你可能只需要写一个编译器就够了;而现在你必须配备整套工具链:你需要语言服务、调试器、性能分析器、框架与基础库,以及能针对各种平台的代码生成器……要把这一切全部配齐,难度正变得越来越高。

主持人:仔细想想,所有这些工具和设计本质上都是服务于人类的。IDE 和类型系统虽然计算机也会处理,但主要是为了让人类能读懂源码。也许未来某一天,LLM 会直接生成机器码?

Anders Hejlsberg:我对此表示怀疑。我认为 LLM 在不需要做大量冗余重复时表现最好。当一个程序以文本形式表达时,它最接近其终极凝练的语义与表征。如果直接翻译成机器码,里面会充斥着大量的噪声——比如机器指令中有一半都是内存地址,这些地址与程序核心逻辑毫无关系,这就意味着一半的数据本身就是纯噪声。再比如具体分配哪个寄存器,在代码生成器看来也无关紧要,随时都可以换。因此从这种巨大的噪声中提炼出真实意图,远比处理一个变量名叫 i 的文本程序要困难得多;而且文本程序还能与用户刚刚输入的自然语言提示词直接产生语义映射。

别忘了,AI 在某种意义上只是对人类心智的模拟器。人类之所以创造高级编程语言,就是因为我们的大脑极其不擅长直接脑补写出机器码。AI 和我们并无本质区别,所以这个逻辑对它同样适用。

主持人:回顾你在 C#、TypeScript 等语言项目上的漫长历程,有哪些经验是如果你在刚起步时就知道,会让你走得更顺畅的?

Anders Hejlsberg:天哪,每一段历程都各不相同。我参与的第一个项目 Turbo Pascal 诞生于 8 位微机和 64KB 内存的远古时代,当时一个人就可以搞定一切,掌控所有细节。所以我当时完全是单打独斗的“一人作坊”。但这种模式很快就难以为继了。计算机的硬件容量从 64KB 跃升到 640KB,随后内存天花板被彻底打破,你可以拥有任意多的内存。单凭一个人再也无法完成这样庞大的系统,我必须学会成为一个团队协作者。这对我来说是一次巨大的心路历程——学会放手。如果你是一个完美主义者,放手会非常艰难,但这是我在那个阶段学到的最重要的一课。

主持人:换个角度问:在你看来,人们在着手构建一门编程语言时,最常犯的错误是什么?

Anders Hejlsberg:我认为人们往往会过度执着于脑海中的某一个灵感,总想着:“要是我的语言能实现某某特性,那该多酷啊!”但他们却严重低估了所有编程语言都必须去处理的那些琐碎基建工作。结果往往是:他们热衷的那一个特性可能做得确实不错,但在其他所有方面都表现平平甚至很糟糕,综合体验反而是负分。在实现一门编程语言时,有太多繁重的工作本质上与你想解决的核心问题并没有直接关系,这一点往往很难向外界传达清楚。

主持人:在主导 C# 期间,你会密切关注其他语言的动向吗?关注它们在哪些方面做得更好、C# 可以借鉴什么?

Anders Hejlsberg:当然,你必须时刻掌握行业动态。正如我所说,我们都站在巨人的肩膀上,没有哪门编程语言是在绝对孤立的环境中诞生的。大家互相学习:某个人提出了一个好点子,随后就会在其他语言中看到它的身影。比如我们在 C# 中就借鉴了许多函数式编程的思想;而其他语言也采纳了 C# 的优秀设计,例如如今 JavaScript 和其他多门语言中广泛使用的 async/await 异步机制,最早就是在 C# 中开创的。整个行业就是通过这种互相借鉴与吸收来共同向前发展的。

主持人:你刚才提到了“学会放手”。随着职业生涯的进阶,很多人都是从一线独立贡献者(IC)做起,随后逐步开始授权和分工。你是如何掌握这项技能的?有没有什么具体的故事或项目让你体会到了这种转变?

Anders Hejlsberg:关于放手,我这里其实有一个相反方向的故事。在 C# 项目中,我的主要职责并不是去写具体的 C# 编译器实现代码,而是作为语言首席设计师,编写语言规范并设计所有新的语言特性。在此之前开发 Turbo Pascal 时,我每天都在核心代码上一线奋战。在 C# 时期我放手了,甚至放手得有些过了头——我整个人漂浮到了平流层,变成了一个“空中楼阁式的架构师”(architecture astronaut)。我不再亲自挽起袖子去触碰一线底层代码了。我虽然也在写框架和库的代码,但那些并不是底层最硬核、最棘手的问题。

当时我自己并没有立刻察觉,但我对工作的满意度其实在悄然下滑。我依然热爱工作,但总觉得心里空落落的。所以当 TypeScript 的契机出现时,我决定亲自跳入一线,全力参与到编译器的代码编写中。这给我的生活带来了巨大的快乐!我终于认清了自己:我本质上就是一个快乐的程序员。我也喜欢写设计规范,但写代码才是每天早上叫醒我的动力——泡上一杯咖啡,写上一段代码,那一刻我才真正进入了最纯粹的幸福状态。

主持人:当工程师达到你这样的极高资历时,通常都会背负着更高维度的业务影响力期望。作为如此高层级的顶尖工程师,你是如何保持一线写代码的状态的?

Anders Hejlsberg:我亲手写下的某几行具体代码本身未必承载着多么巨大的影响力,关键在于我同时作为团队一员提供架构指导。项目里有足够多的工作分给大家,但你必须亲自坚守在项目的某个具体角落,因为只有这样你才能看清代码库里正在发生什么。当你提交代码时,你会注意到:“那边那个模块是怎么回事?这里为什么这么写?”或者你会敏锐地意识到:“这部分需要重构了,感觉已经不对劲了。”如果你完全脱离了一线代码,你就无法真正谈论人们每天实际使用的那个工具——你充其量只能谈论语法的纸面设计,却无法把握产品实体的脉搏。我认为两者都至关重要,我也很庆幸自己能兼顾两者。

主持人:很多科技公司都有所谓的工程师定位画像(archetypes)。你是在表达你个人的工作风格偏好,还是认为所有高层级工程师都应该在某种程度上保持一线实操?

Anders Hejlsberg:不,我并不是这个意思。这是我个人的自主选择——我明确选择留在一线做独立贡献者(IC),而不是转向管理岗。我曾有无数次转型管理的机会,但那并不是能让我发挥最大价值的地方。每个人都必须自己搞清楚:究竟是什么在激励你、让你感到快乐、让你愿意倾注一生去从事这项事业?人生只有一次,要想做出最出色的成果,你就必须做那件能让你感到最快乐的事。这就是我在一路走来中所领悟到的:写代码就是我的快乐之源,占据着至关重要的分量。

主持人:所以在主导 C# 期间、在 TypeScript 启动前,你是怎么察觉到自己心里缺少了什么的?

Anders Hejlsberg:就是一种直觉,我也说不清具体为什么。只做纯设计工作时,我总觉得缺乏成就感。我无比怀念写代码的日子,每当有机会写上一小段代码,我的心情就会好上一大截。我当时心想:“这挺耐人寻味的。”但转过头,我又不得不继续去写更多的语言规范。

主持人:所以你走向 TypeScript,在一定程度上是因为你想写更多代码吗?

Anders Hejlsberg:原因有很多,最初倒不完全是因为这个。我只是觉得那个问题本身极其迷人。TypeScript 的诞生其实与 C# 间接相关。当时 Outlook.com 团队找到 C# 团队,恳求我们能不能把一个叫 Script#(Script Sharp)的项目做成正式产品。Script# 的功能是把 C# 代码转译成 JavaScript,以便在浏览器中运行。

我当时的第一反应是:“什么?为什么要这么干?你们为什么不直接写 JavaScript?”

对方回答说:“因为这样我们才能获得优秀的开发工具支持啊!我们才能用上 Visual Studio,才能管理大型工程,拥有类型检查、代码重构、代码导航,能定义接口让大家看懂系统架构。”

我当时心想:天哪,JavaScript 的工具生态真的糟糕到了这种地步吗?你们解决这个问题的办法,居然是彻底抛弃 JavaScript 本身,把它当成底层指令集,然后用代码生成器去编译它?我们难道就不能直接去修复 JavaScript 自身吗?这样难道不是更好吗?

TypeScript 就是这样立项诞生的。我们意识到 JavaScript 工具链领域存在着严重破损且亟待修复的痛点,而这正是我们全力以赴的目标。

如果初级工程师都没了,未来三年你上哪去找高级工程师?

主持人:你在之前的一场演讲中提到,因为 AI 的出现,开发工具链的运行速度在当下变得更加重要了。能谈谈为什么会这样吗?

Anders Hejlsberg:因为我们的工作流正越来越多地被在后台持续运行的 agent 所主导。它们在为你编写代码,而且会像人类一样犯错。但与人类不同的是,你可以同时启动任意多个 agent。这意味着生态中突然涌现出了海量的虚拟工人,正在以惊人的速度生成远超以往的代码量。而在整个工作流中,LLM 本身就已经存在延迟瓶颈了;如果你在一个大型项目中写代码,每当 LLM 生成一段代码,类型检查器都要卡上两分钟,那体验简直是灾难性的。因此大幅提升工具链的速度具有巨大的价值。

主持人:确实如此,因为 agent 正在不知疲倦地持续高频调用工具。

Anders Hejlsberg:没错。我们都知道 LLM 喜欢用 grep 等文本搜索工具,但现在的模型也已经足够聪明,能够意识到:如果你想修改一个叫 version 的属性,而系统中几乎每个对象都有一个 version 属性,你绝不能简单地全工程 grep 替换,否则就会把所有其他无关对象的 version 接口全改坏。所以必须进行语义级搜索(semantic search),而全天下唯一能做到语义搜索的工具就是编译器。

因此 AI 必须调用编译器——通常是通过 LSP(语言服务协议)、语言服务或我们的命令行工具来完成。AI 越来越频繁地进行这类操作,这意味着编译器正在背后极高频地默默运转,替你分担大量工作。

主持人:你对 AI 的看法非常务实,深植于实际的软件工程实践中。我想把互联网上经常听到的一些关于 AI 的热门论点抛给你,看看你怎么看。第一个论点是:AI 将在一年内写出 90% 以上的代码。你怎么看?

Anders Hejlsberg:对于某些特定类型的应用来说可能确实如此,比如某些完全由 AI 编写的应用,AI 写的代码占比就是 100%,而且这类代码的生成量非常庞大。但它们能写出互联网上 90% 的“高质量代码”吗?我深表怀疑。现在的现状是,单纯从代码总“体量”来看,数字确实在飞速飙升,因为 AI 生成代码的速度太快了。所以在纯体积维度上,这甚至是一个自我实现的预言。但如果谈到那些前所未有的、极其硬核的高质量代码,我不认为 AI 能写出其中的 90%。

主持人:能举例说说那些 AI 无法全盘包办的超高质量代码是指什么吗?

Anders Hejlsberg:比如前人从未见过的全新算法,或者特定领域的专有解决方案。我很确定 AI 根本写不出我们的 TypeScript 编译器,它就是做不到。我们尝试过,它确实无能为力,我也压根没指望它能做到。当然,我们的编译器并不是常规的平庸代码,正因为在训练集里几乎没有类似的代码范例,所以 AI 才不擅长编写。虽然训练集里有一些常规编译器,但绝不是我们这种架构设计、这种概念模型以及这种实现手法的编译器。因此在这种场景下它并不适用。

我们绝不能忘记:AI 的本质是一台记住了全网数据的庞大随机概率机,它具备在记忆内容之上进行一定程度外推的能力;但如果它以前从未见过某种模式,它就很难凭空做好。它确实在不断进步,能力也极其令人惊叹,但我认为距离它能包揽一切还有很长的路要走。我甚至不知道我们是否真能达到那一天,而且我希望永远不要达到——因为如果真到了那一步,人类存在的意义又在哪里呢?

主持人:网上还有一个很常见的论点:一年之内你将不再需要使用 IDE,甚至不再需要阅读代码了。

Anders Hejlsberg:那我只能祝你好运了。如果你打算彻底把钥匙交给 AI,那一切后果都得自负。一旦 AI 无法按你的意图修好某个 Bug,或者做不出你想要的新功能,而你对底层代码又一无所知,你该怎么办?这是第一个问题。第二个问题是:假设有用户找上门来,怒斥你的应用盗刷了他的银行账户,他们会起诉你,而不是去起诉 AI。归根结底,必须由具体的人来为 AI 的行为承担最终责任。如果你全权放手把控制权交给它,所有法律与道德责任都在你身上。反正换作是我,如果我根本搞不懂自己背书的代码底层到底干了什么,我晚上绝对睡不着觉。

主持人:为什么你的看法与 Anthropic 等 AI 实验室成员的观点会有如此巨大的反差?

Anders Hejlsberg:区别在于:我从来没有否定 AI 能创造惊人的奇迹,它确实能,而且每天都在展现这种能力。但有些人总是喜欢走向极端,把推论直接拉满到 100%。而在这一点上我恕难苟同。我认为 AI 完成工作的比例确实会越来越高,但依然必须有人真正理解底层到底发生了什么、这些技术如何契合我们的业务场景、以及如何与整个组织的运转相协调。有太多的现实维度需要被串联起来,如果简单地认为“AI 已经搞定了一切,人类完全多余”,那绝对是极其错误的看法。

主持人:三年后,AI 会取代初级软件工程师,或者取代初级工程师今天所做的工作吗?

Anders Hejlsberg:如果真把初级工程师取代了,我们未来又从哪里获得高级工程师?所以我根本不认同这种说法。除非你天真地相信,再过三年你的企业甚至连高级工程师都不需要了。高级工程师不可能凭空产生,你必须去培养他们。但在我看来,现实中确实在一定程度上发生了一种变化:对于某些开发群体而言,金字塔的底部正在收窄。涌入行业的新人变少了,而我们希望他们能更快地成长进阶,尽早胜任更具统筹监督性质的岗位。

软件工程这门手艺的本质确实在发生改变——从过去一行行亲手敲代码,转变为指挥 agent 去敲代码,而你负责去审查(review)agent 的产出。也就是说,你花在代码审查上的时间变多了,花在机械敲代码上的时间变少了。有些人非常喜欢并且极度擅长这种工作流。但这改变的是程序员日常工作的形态,是你工具箱里新工具带来的工作模式迭代。它终归只是一个工具,而人类程序员依然是整个流程的核心主体,对此我深信不疑。

主持人:听起来你非常享受亲自敲代码的乐趣。如果五年后你不需要怎么敲代码了,你的快乐会减少吗?

Anders Hejlsberg:在过去四五十年里,我一直沉浸在亲自敲代码的乐趣中。所以对于特定核心部分的代码,我依然会选择亲手去写;但对于另一些繁琐的代码,我其实并不在乎是不是自己敲的。比如编写单元测试、严格遵循特定测试框架的各种模板代码,我才不想亲手写。如果能把这些杂活全部甩给 AI,我求之不得!

对我个人而言,审查代码一直比亲手写代码更费劲,花时间看别人的代码对我来说向来挺折磨的。但我相信随着时间的推移,代码审查流程的人体工学体验会比今天好得多。如今的代码审查通常只是扔给你一堆变更文件列表和 diff 差异让你自己去琢磨;而在未来,AI 完全可以通过为你梳理和解释具体改动来提供更大的辅助。所以随着工具链的演进,这门手艺会变得更加有趣,但它确实正在重塑软件开发的本质形态。

别让别人用他们的极限,来定义你能不能做成一件事

主持人:你说你写代码已经写了四五十年。你写过的难度最高的一段代码是什么?或者说你做过的最具技术挑战性的事情是什么?

Anders Hejlsberg:很多事情在各自的时代都极具挑战性。就拿我们最近做的这个项目来说,把代码迁移到另一种语言以生成原生机器码只是最轻松的部分;真正困难的是驯服“共享内存并发”这头猛兽,让编译器能够榨干机器上的每一个 CPU 核心。在编译器里解决这个问题绝非易事,因为我们从小受到的训练都是写顺序执行的代码——先做第一步,再做第二步。但如何让所有任务并发执行,事后还能安全地共享数据结构,确保没有任何一个线程意外修改其他线程的数据?在必须修改的场景下,又该如何做好同步并彻底规避死锁和竞态条件?这极其棘手。我们在项目中攻坚克难解决了一批硬核的技术难题,对此我们深感自豪。

主持人:在你早年的职业生涯中,当硬件资源极其匮乏时,你是否做过什么匪夷所思的硬核操作来让程序跑起来?

Anders Hejlsberg:那是肯定的。在我刚入行时,做程序员和现在完全是两码事。我最早接触的是 8 位微机,主板 ROM 里内置了微软的 BASIC 解释器。你可以买一些刊登代码的书,照着把《星球大战》之类的游戏一行行敲进去。全都是纯文本交互,屏幕不断滚屏提示你“克林贡人的飞船出现在某个象限”等等。但我当时深深着迷于探究底层是如何协同运转的,于是很快自学了汇编语言。

我主导的第一个商业产品 Turbo Pascal 完全是用 Z80 汇编语言写出来的——编译器、代码编辑器、运行时基础库,所有东西全部是纯汇编。用汇编语言去写一个结构化的编译器需要进行无数极其微观的精雕细琢。你必须时刻精打细算:“好,我们必须把代码塞进这块 EEPROM 芯片里,总共只有 12KB 空间,而我现在超标了 20 字节。等等,我可以用短跳转替换掉这里的长跳转,串联几个跳转后能省下 1 字节……”就这么一字节一字节地扣。

你坐在那里一遍遍地反复打磨,那真是一门手艺活,就像传统木工在精雕细琢一样。那与我们今天的开发模式有着天壤之别——如今硬件拥有深不见底的海量容量,而用户也拥有深不见底的无限期望。

主持人:对软件工程师而言,你最推荐的经典技术书籍是哪一本?

Anders Hejlsberg:我通常推荐的都是同一本。在我年轻时,对我影响最深的一本“启蒙圣经”是尼克劳斯·维尔特(Niklaus Wirth,Pascal、Modula 和 Oberon 语言的发明者,图灵奖得主)写的《算法 + 数据结构 = 程序》(Algorithms + Data Structures = Programs)。这本书没有堆砌晦涩的数学公式和符号推导,而是包含了大量极具启发性的实例,深入浅出地讲解了数据结构的运行原理。

我当年就是通过这本书学会哈希表的。当你自学编程时,你根本不知道什么是哈希表,你只知道链表。在最初版本的 Turbo Pascal 中,所有的符号表全是用单向链表实现的;当局部变量很多时,查找耗时就会呈指数级暴增。后来我读到了哈希表章节,整个人顿悟:“天哪,原来还能这么做?!”这样查找时间基本就变成了常数级线性开销。我把它实装之后,编译速度直接翻了一倍!那本书简直太有价值了。书中还深入讲解了编译器构建中如何优雅地实现错误恢复机制。对我来说那是一本伟大的神作。虽然它已经绝版了三四十年,但现在网上到处都可以下载到完整的 PDF。

主持人:最后一个问题:结合你如今积累的所有经验,如果能穿越回刚入行时的自己面前,你会给自己一条什么建议?

Anders Hejlsberg:主要有两点:第一,正如前面聊到的,早点学会成为一个团队协作者,如果一开始就能在这方面多有些自觉就更好了;第二,回想起来,千万不要让别人打击你说“某件事根本做不到”。

当别人对你说某件事做不到时,往往只是因为他们自己做不到,并不代表你做不到。所以永远不要因此而气馁。

主持人:有哪一个项目是别人断言做不到,但你最终做成了的吗?

Anders Hejlsberg:比如我做的第一个产品 Turbo Pascal。每当我们向别人展示成果时,大家都说:“这绝对不可能实现,你们别吹牛了,这根本做不到。”但我们最终做到了——或许正因为我当时“不知道它不可能”,我才把它做成了。

主持人:太精彩了!非常感谢你能抽出时间做客,真的非常感谢你,Anders。

Anders Hejlsberg:不客气,聊得非常开心!

打开网易新闻 查看精彩图片

最后,说一件事:

2026 奇点智能大会 ,终于要和大家见面了。

11 月 20-21 日·北京,奇点智能研究院联合 CSDN,把两场技术大会放在了同一个时空里:

  • 奇点智能技术大会(始于 2016)——聊大模型、AI Native、企业级 AI 落地、多模态与世界模型;

  • C++ 及系统软件技术大会(始于 2005)——聊现代 C++ 演进、AI 算力与推理优化、高性能低时延系统。

为什么要放在一起?因为我们越来越相信——上层 AI 应用的爆发,离不开底层系统软件的支撑;而底层技术的演进方向,也正在被 AI 重新定义。

这次大会汇聚 70+ 位技术专家、18 个主题、1000+ 同行到场。

如果你也在这些方向上做研究、做产品、做工程,别错过。

打开网易新闻 查看精彩图片