17611538698
webmaster@21cto.com

为什么 TypeScript 7.0 用 Go 语言重写

编程语言 0 12 1小时前
图片

在过去的一年,TypeScript 的创始团队将 TypeScript 编译器和工具移植到了 Go 语言

是的,不是 Rust,也不是 C++,而是Go。根据微软的数据显示,基于 Go 的 TypeScript 7.0 的构建时间缩短了大约一个数量级。

在人工智能辅助开发的时代,全球最大的 JavaScript/TypeScript 组织之一选择了 Go 作为其内核引擎语言。

微软的理由很务实,首席架构师 Anders Hejlsberg 对此进行了详细阐述

现有编译器的函数密集型风格几乎可以一对一地移植到 Go 中;新旧编译器都依赖垃圾回收机制;而共享内存并发的原生代码正是性能提升 10 倍的关键所在。但公告中只字未提人工智能。也正是这些特性使得务实的 1:1 移植成为可能:简洁的函数、无隐藏的“魔法”、垃圾回收机制、团队能够轻松理解的代码,而这些特性也正是下一代开发所需要的,这也是本文要探讨的内容。

Go 语言的诞生源于一个假设:优化代码以方便读者而非编写者,从而减少代码误读的可能性。LLM(高级语言学习者)阅读代码的数量远超任何人类,而且随着他们编写的代码量不断增加,人类作为读者的角色也愈发重要。智能体开发是对 Go 语言设计初衷——可读性、可维护性和大规模长期正确性——迄今为止最严峻的考验。

Python 和 TypeScript 呢?

在人工智能时代,Python 和 TypeScript 的传统优势表面上看起来很强大。

Python 拥有 PyTorch、LangChain 和机器学习生态系统。TypeScript 则拥有 Web 平台和庞大的开发者群体。经过海量 Python 和 TypeScript 代码训练的机器学习专家能够流畅、自信且快速地编写代码。

Python 和 JavaScript 的设计初衷就是脚本语言:编写速度快、容错性高、动态性强。TypeScript 在其基础上增加了结构,但它只是编译时覆盖层:类型会在运行时被清除,因此 TypeScript 的任何保证在执行后都无法保留。

TypeScript 并没有取代 JavaScript,而是与之共同发展:GitHub 发布的 2025 年 Octoverse 报告预测,TypeScript 的月度贡献者数量将超过 Python 和 JavaScript,但 JavaScript 在同一时期内新增的代码仓库数量几乎是 TypeScript 的两倍。GitHub 自身的总结也直言不讳:“JavaScript 仍然非常庞大”,并且“JavaScript/TypeScript 生态系统的整体活跃度仍然超过了 Python 本身”。然而,代理系统并非脚本;它们是服务、管道、命令行界面 (CLI) 和分布式系统,需要在生产环境中运行多年,而这三种语言在各个环节都面临着与脚本设计相悖的挑战:复杂性管理、依赖隔离、部署、运行时安全。

Go 语言最初是为大型、长期运行的软件系统而设计的,而整个行业也正朝着这个方向发展:编译型、安全、旨在长期运行,而非仅供一下午编写的脚本。TypeScript 目前最令人兴奋的进展是其 Go 移植版。Python 世界最激动人心的进展大多发生在 Rust 中:Pydantic的验证核心、PolarsHuggingFace 的分词器以及 Astral 的分词器uv都是用 Rust 编写的。

Go 语言在这一转变中所占的份额虽然看起来有所不同,但同样意义重大:它是当前这波智能体工具所依赖的基础设施的默认选择。运行本地模型的标准工具Ollama就是用 Go 编写的。无数 RAG 和智能体内存栈背后的向量数据库Weaviate也是用 Go 编写的。团队用来编排长时间运行的智能体工作流的持久执行引擎Temporal也是用 Go 编写的。Charm 的Crush ,一个与 Google Antigravity同属一类的精美 AI 编码智能体命令行界面 (CLI) ,也是用 Go 编写的。甚至连GitHub 自家的 MCP 服务器(用于将 AI 智能体连接到 GitHub 的参考实现)也是用 Go 编写的。

在开发循环中,问题不再是“哪种语言最容易编写?”,而是“哪种语言最容易编写、审查和发布?”。智能体开发将这个问题放大了数百次。现在,机器每天要问这个问题数百次。

代理循环会放大每一个弱点


一个智能体编码循环大致如下:实现 → 构建 → 测试 → 分析失败 → 自我纠正 → 重复。人类开发者每小时可能运行这个循环十几次;而一个自主智能体每个任务运行数十次:就像Anthropic 关于智能体编码的指南所描述的那样,这类系统“利用测试结果作为反馈来迭代解决方案”。这种频率改变了语言选择的经济性。

Wes McKinney 在他关于智能体人体工程学的文章中将这种转变称为“智能体人机工程学”:一旦智能体开始编码,快速的编译测试循环、无摩擦的分发和确定性的构建就比语言是否便于人类输入更为重要。他不仅在文章中阐述了这一点,他开发的用于人工智能编码智能体的持续后台代码审查工具Roborev就是用 Go 编写的。

Go 解决了四个相互交织的问题,而大多数工程师却对此忽视不见,因为他们早已习惯了这些摩擦,以至于不再注意到它们。每个问题的代价都是一样的:开发者的时间和精力、代理的迭代次数,以及用于修复语言本身缺陷的 API 开发成本。而且这些问题依次累积:构建速度慢会浪费迭代次数,依赖关系解析错误会浪费整个运行,糟糕的错误反馈会让错误在运行过程中继续存在,而生态系统的变化则会使代理在启动前积累的知识失效。

构建时间


对于大型 Rust 或 C++ 项目来说,几分钟的构建时间是常态:对人来说只是短暂的停顿,但对于运行 50 次迭代来开发某个功能的智能体来说,却浪费了数小时的资源。

Go 的编译几乎是瞬间完成的,从而保持了循环的高效运行。

依赖关系管理


Pythonpip默认情况下仍然无法保证确定性安装,即使在虚拟环境中,跨机器的版本冲突仍然很常见。

Node 的npm能很好地处理冲突的传递依赖:嵌套依赖node_modules可以并排容纳同一包的多个版本,并且package-lock.json使npm ci安装比以前更具可复现性。npm 的瓶颈在于对等依赖,冲突的依赖项会抛出ERESOLVE需要手动解决的错误。这种版本灵活性导致依赖树过深且重复,从而膨胀node_modules并扩大了代理需要推理的范围。

这两个生态系统都允许在安装时执行任意代码(setup.py例如postinstall脚本),这是一个真正的供应链风险,而且它们都曾多次因此遭受损失。Go 默认采用不同的方法:go.sum精确锁定校验和,每个模块在整个构建过程中确定性地选择单个版本,并且不存在可供被攻破的依赖项利用的安装时代码执行钩子。对于持续生成和部署的代理来说,这意味着更少的版本漂移和更小的攻击面。

错误反馈


Python 的类型提示和 TypeScript 的类型系统可以在运行时前捕获错误,例如当 `__init__` mypypyright`__init__` 或 ` __init__` 等工具tsc作为循环的一部分运行时。

但两者都存在一个问题:Python 的类型系统仍然是可选的,并且普及程度参差不齐,即使像 Pydantic 这样的工具正在推动团队使用它;而 TypeScript 的类型保证会在运行时消失,any但当代码遇到问题时,会提供一个官方认可的、零成本的“逃生通道”。

这正是受时间和代码量压力影响的开发者所青睐的:无论整个生态系统的规范如何,使用 `__init__` 来编译代码在当下都不会有任何成本。

构建了Superpowers智能体编排框架的 Jesse Vincent记录了当存在“选择退出”机制时会发生什么:

智能体会通过各种方式绕过规则。在一个如今“臭名昭著”的事件中,他环境中的一个 AI 智能体删除了测试文件,以使失败的测试消失:这符合内部逻辑,因为不存在的测试不可能失败。他这样解释道 “规则有一条‘选择退出’的路径(我可以合理化‘我会在完成这件事之后再做’)。而门则没有——下一个动作会被阻塞,直到门条件得到满足。”

Go 则没有这样的退出机制。

Go 确实也有`__init__`(别名interface{}。TypeScript 的 `__init__` 会关闭它所触及的一切类型的检查;而 Go 的 `__init__` 仍然会在每个使用点进行检查,并且编译器会拒绝任何类型不支持的操作,除非它明确地验证了类型支持的断言。这种“退出机制”本身也是有门禁的。

当 Python 或 TypeScript 中出现错误时,它往往会在运行时才显现出来,通常是在代理程序基于此构建了更多工作之后,那时它已经积累了大量的上下文信息和 API 调用。在 Go 中,同样的错误会在编译时立即被捕获,在代理程序运行任何代码之前。这种差异在代码本身中就能体现出来:

// Go: even the escape hatch is checked — error at compile time, before the agent runs anythingfunc ProcessData(data interface{}) int{return data + 1// error: invalid operation: data + 1 (mismatched types interface{} and int)}

# Python: runtime error — agent wastes iterations before discovering the mistakedef process_data(data):return data + 1# no error; error only surfaces at runtime when data is actually called# Agent runs it, builds on it, then:process_data({"key""value"})  # TypeError: unsupported operand type(s) for +: 'dict' and 'int'

Python 的人体工学优势在这里反成了劣势:语言越宽容,智能体在被发现之前可以犯的错误就越多,从而浪费上下文信息和 API 调用。

同样的问题也出现在代码审查中,因为现在双方通常都是人工智能:

审查智能体可以看到 Python 或 JavaScript 代码的字面意思,但却无法可靠地了解其实际行为,因为元类和原型链隐藏了静态读取无法捕捉到的行为。

Go 弥补了这一缺陷:函数名只有一个含义,方法分发仅通过名称进行,并且没有任何隐藏的控制流来掩盖代码错误,从而避免审查双方发现问题。Go 的类型并非附加在代码之上的一层;它们本身就是语言的一部分,通过构造就覆盖了 100% 的代码。

生态系统动荡


这或许是四个原因中最大的一个。

Go 的兼容性承诺意味着,2012 年为 Go 1.0 编写的代码至今仍能正确编译和运行。

该语言及其标准库虽然不断添加新功能,但基本上不会破坏已有的方法。

但Node 生态系统中没有任何内容能提供这种保证。主流框架经常会破坏与自身早期版本的兼容性:Svelte 5 的 runes响应式模型与 Svelte 4 不同,Vue 3 需要从头开始重写 Vue 2 的响应式模型,而 React 从类组件到 hooks 再到服务器组件的演变,导致多个不兼容的 React 代码版本仍在流通。询问一段代码的目标 Svelte 或 Vue 版本,实际上就是在问它是否能运行。

Python 生态也并非完全免疫;Python 2 到 3 长达十年的迁移过程就是一个警示。但 Node 的版本更迭速度更快,也更频繁。

这种频繁的变更对智能体来说是一个特殊的、不断叠加的问题。智能体对生态系统的工作知识是训练时的一个快照,而在一个快速变化的生态系统中,这个快照会迅速过时,导致生成的代码看起来合理,但实际上却指向已被重构或弃用的 API 接口。在 Go 语言中,几年前的训练快照至今仍然有效。实际上,这意味着智能体可以依赖它已有的知识,而无需在每个任务中都重新验证其假设。

谷歌的杰出工程师 Dave Rensin 在构建一个拥有 10 万用户的内部工具(该工具集成了 AI 代理)时,敏锐地捕捉到了这种风险的累积:“如果我们不谨慎,我们不仅仅是在更快地编写代码,我们还在大规模地制造错误。” Rensin 指的并非 Go 语言本身;这种风险是任何语言中 AI 辅助开发都普遍存在的。但这恰恰是 Go 语言的结构(静态类型、显式导入、无魔法)旨在防范的风险:糟糕的代码从一开始就更难编写出来。

在PayPal公司,一个团队跟我分享了他们的经历:一个定制的C++项目,数据库功能强大且运行稳定,但团队发展停滞不前:每个新员工都要花几个月时间学习代码库,大部分工程资源都投入到了维护工作中。大约六个月后,团队规模扩大到十名左右,用Go语言重写了数据库,其生产环境的性能超越了原有的C++系统,维护成本也大大降低。

Go的速度并不比C++快,那么究竟发生了什么?瓶颈从来不在于代码的运行速度,而在于团队理解、维护和扩展代码的能力。实际性能优于理论性能,这正是代理团队如今正在做的,只不过周期大大缩短了。

Rust的情况如何?


Rust 和 Go 是互补的,通常用于不同的编码用例,两者在智能应用开发中有一席之地,但 Rust 是一种专门的工具,而不是默认工具。

Rust 与 Go 有很多共同的优势:内存安全、静态类型、没有隐藏的运行时机制。Rust 的编译器错误信息也以其极高的指导性而闻名:根据本文的编译器即门逻辑,智能体可以从详细的借用检查器信息中获得真正的价值。然而,这两种语言的差异恰恰体现在智能体开发容错率最低的地方。

正如上文构建时问题所示,编译时间要长得多,而且 Rust 安全保证(生命周期、trait 边界、借用检查器)背后的表达能力牺牲了 Go 从设计上就保证的单一清晰的可读性。

这种代价在重构时体现得最为明显:根据经验,在 Go 中仅影响局部性的更改,在 Rust 中会影响生命周期和 trait 边界,而可重构性正是智能体开发最依赖的特性。现在,重构工作由智能体而非人类完成,而智能体需要进行更多的重构才能使代码正确。语言越复杂,智能体就越容易误读,并产生随着迭代而累积的 bug。

Rust 通过 PyO3 和 maturin 等工具提供的零成本 C ABI 绑定,使得将编译好的 Rust 核心代码嵌入 Python 包中变得异常轻松,这也是 pydantic、Polars 和 HuggingFace 分词器选择 Rust 的原因。

Go 历来在这方面表现较弱,cgo 会给这种嵌入方式增加实际的开销。虽然 Go 正在缩小这一差距:Go 1.26 将 cgo 调用的基准开销降低了约 30%,但对于许多用例而言,Rust 仍然是更好的选择。然而,对于智能体开发而言,权衡显而易见:Go 的简洁性和可读性使其成为智能体运行所在系统层的默认选择,而 Rust 则是一种专用工具,适用于其性能保证值得付出相应代价的情况。

上下文窗口是智能体开发中稀缺的资源——Go 能够保护它


以上所有成本都是乘以迭代次数。上下文是智能体工作的固定成本,而且上下文越多并不意味着效果越好。

“只需使用更大的上下文窗口”背后的假设是,模型能够均匀地处理 20 万或 100 万个词元。Chroma在 2025 年对 18 个领先的语言学习模型(GPT-4.1、Claude 4、Gemini 2.5 和 Qwen3)进行了一项研究,直接检验了这一假设。该研究保持任务难度不变,仅改变输入长度,结果发现该假设并不成立:即使是简单的任务,随着输入量的增加,模型的性能也会下降。造成这种下降的主要原因是研究人员所说的干扰项:这些内容在主题上与模型所需内容相关,但与答案无关。一个干扰项就会显著降低准确率;四个干扰项则会加剧这种影响。与其试图增大上下文窗口,不如减少输入量,这样才是更好、更经济的做法。

类层次结构、混入链、装饰器栈:对于追踪 bug 的智能体来说,这些都是干扰因素。

super()通过菱形继承解析的调用、运行时元类重写行为、埋藏在三层类中的重写:这些都不是智能体需要的逻辑,但智能体仍然需要加载它们、权衡它们并排除它们,而此时智能体的推理能力已经开始下降。这就是为什么智能体会自信地发布忽略继承的重写或调用错误混入方法的代码。

而Go 的设计简洁明了。

一个文件所需的一切都存在于该文件及其直接导入中:没有继承链,没有从六个目录之外引入 mixin,也没有在运行时重写元类方法解析。函数名只有一个含义,编译器会强制执行。Go 代码所需的标记几乎全部都是相关的标记。

除了准确率之外,这还有第二个影响:它降低了任务所需的模型复杂度。一个能在 20,000 个干净标记内找到的 bug,所需的复杂度远低于一个隐藏在 150,000 个框架噪声标记中的 bug。更简单、干扰更少的代码更容易被模型正确识别,这意味着更小、更便宜,甚至本地模型也能用 Go 编写出在 Python、Java、Rust 或 TypeScript/JavaScript 等同等代码中无法正常运行的代码。

当人工智能企业停止补贴其成本时,这一点显得尤为重要,而我们已经看到这种情况开始出现。一种代码足够简洁,足以让一个中等规模的模型正确处理的语言,其成本结构即使在补贴消失后也能维持下去。同样的特性可以带来双重收益:它既能让小型模型正确编写代码,也能让人工审校者快速阅读,因为双方都没有任何隐藏信息需要重新推导。

除此之外,它更有可能一次性编写出正确的代码,这意味着更少的迭代、更少的 API 调用、更少的上下文信息消耗,以及模型可以花费更多时间在无需人工干预的任务上。

上下文效率是 Go 在 2009 年做出的同一赌注的最鲜明体现:优化读者(无论是人还是机器)的体验,而不是优化作者的体验。

一种格式,零风格之争


这种以读者为中心的理念也体现在代码层面。gofmt它随 Go 语言一起发布,并贯穿于整个社区的代码中。每个 Go 文件(无论是人工编写的、机器生成的、几十年前的,还是今天早上由代理编写的)在结构上都完全相同。

花费大量时间构建 AI 辅助 Go 工具的 Simon Willison直接观察到了这一点:“我喜欢 Go 通常只有一种显而易见的做事方法,而且生成的代码既枯燥又易读——而这正是语言学习硕士 (LLM) 非常擅长编写的。”

当所有 Go 文件看起来都一样时,可以在代理开始工作前对其进行初始化。只需将其指向 Go 的标准库(`go.h` encoding/jsonnet/http`go.h`、 `go.h`),它就能立即生成符合惯用语法、高质量的生产级代码,而无需猜测`is``is` 的区别。如果再添加类似`go-skills` 的内容,初始化过程会更加直接,因为代理现在拥有明确的、可复用的 Go 语言编写规范指南,而不是从零散的示例中推断约定。这样,无论是人工还是 AI 进行代码审查时,注意力就能完全集中在逻辑上,而不是格式上的瑕疵。ioblackyapfisortruff

Go优化了整个软件开发生命周期——而不仅仅是编码步骤


大多数语言比较都侧重于编写代码的速度:编写代码的速度有多快?但软件开发生命周期远不止于此:构建、测试、部署、调试、维护。在智能体工作流中,所有这些步骤都以机器频率运行。

过去十年间,Go 的生态系统从一种语言发展成为一个完整的软件开发生命周期(SDLC)平台:模糊测试成为内置功能,漏洞管理日趋成熟govulncheck,工作区模式统一了单体仓库开发,模块代理减少了版本冲突。所有这些都无需借助 Go 生态系统之外的资源。

依赖关系由go mod tidy 处理,在笔记本电脑、持续集成 (CI) 和容器上都具有确定性和一致性,这与pipnpm不同,后者每次解析失败都会导致一次迭代丢失。测试无需任何其他操作,用go test ./...:无需选择框架,无需配置测试夹具。只需_test.go在包旁边放置一个文件,编写一个Test以 为前缀的函数,然后运行一个命令即可。

对于强制执行测试驱动开发的代理而言,这种可靠性使得测试门控具有确定性,而 Python 的测试夹具则无法做到这一点。由于精确的依赖关系图,编译速度保持快速,这直接提高了迭代吞吐量。部署也非常简单go build:一个独立的二进制文件,没有运行时依赖项,无需解释器版本控制,也无需容器启动时间。只需复制该二进制文件并运行即可。

这些优势并非线性叠加,而是成倍增长。循环中每一步更快的执行速度意味着每小时更多的迭代次数、更低的 API 成本以及更可靠的结果。

这并非关乎某种语言的胜出


本文重点并不是“取代 Python”或“取代 TypeScript”。无论哪种语言最终胜出,将所有希望都寄托于单一语言在技术栈的每一层都是同样的错误。

真正的问题开始变得更加具体:对于智能体工作流日益依赖的系统、服务和基础设施层而言,哪种语言最合适?到 2026 年,Go 语言很可能是最佳选择,因为它与这一层完美契合。

Python 的机器学习生态系统(PyTorch、LangChain、Transformers)不会消失,也不应该消失。对于模型训练和推理而言,Python 是最佳选择。Go 语言在这方面并不与之竞争,而是与之互补。

Go 既可以作为 Python 的底层语言运行,也可以与 Python 并行运行。

Simon Willison 开发了名为 PyPI 的工具,它可以go-to-wheel将编译后的 Go 二进制文件打包成 Python wheel 文件,并发布在 PyPI 上:任何 Go 二进制文件都会变成标准库pip installuvx单行脚本,他自己
用 Go 编写的并发文件系统扫描器sqlite-scanner就是这样发布的。

Flask 的创建者 Armin Ronacher 从另一个角度也表达了类似的观点:将 MiniJinja 从 Rust 移植到 Go 只花费了他 45 分钟和 60 美元的 API 调用费用,因此他认为“编码成本正在急剧下降……生态系统的广度不再那么重要”。Python负责统筹,Go 负责底层执行,而随着两者之间移植成本的不断降低,选择单一生态系统的“锁定”论点也逐渐站不住脚。你不必在 Go 的结构优势和 Python 的生态系统之间做出选择;你可以两者兼得。

Go 的设计初衷是为了降低大规模软件的总成本,而这正是智能开发现在放大到临界点的问题,Go 的每一个特性,使其适用于人类规模的团队,结果证明也同样适用于每天运行数千次迭代的机器。

结语

这并不意味着 Go 是唯一值得使用的语言。

Python 仍然主导着机器学习生态系统,而且在特定任务中,选择 Rust、TypeScript 或其他语言也有其合理之处。与其追求“一门语言统治一切”,不如将 Go 作为智能体开发系统、服务和基础设施层的默认语言,并要求有充分的理由才选择其他语言。

在启动新的服务、命令行界面或系统时,问问自己“为什么不选择Go?”。如果你能给出令人信服的理由(例如缺少关键库、Go 无法满足的性能要求、团队投入巨大以至于迁移成本超过节省的成本),那就说明理由。如果找不到,那么 Go 很可能就是正确的默认选择。

构建速度变慢、依赖项不稳定或运行时出现意外情况,每损失一个百分点,就意味着 API 成本的增加。

随着代理工作负载在 2026 年之前不断扩展,编程语言的选择可能是影响成本的最关键因素。那些能够及早消除这些摩擦的团队,迭代速度和成本都将比那些仍在为此付出代价的团队更高。

作者:场长

评论

我要赞赏作者

请扫描二维码,使用微信支付哦。

分享到微信