开场
总共大约用了 1360 亿个 token,费用约 12 万美元。12 万美元。
这位是微软杰出工程师 Stephen Toub,他独自用 Rust 重写了一个 40 万行的代码库。我和他聊了这段经历带来的收获。
这些以前贵到做不起的项目,现在已经相当可行了。
我是想问:你一路上是怎么发现并修复回归的?
我逐行读代码并不能防止 bug。事实上,让智能体逐行检查要有效得多。有件事如果重来我会换种做法……下面是完整节目。
我想把这次对话聚焦在你为 GitHub Copilot 运行时做的移植上,也就是把它重写成 Rust。我觉得,最近这些模型让一种新类型的项目变得可行了。业内很多人可能都会尝试类似的事,比如把遗留代码库迁到性能更好的语言。所以我想先问问,这个项目具体是什么?你为什么要把所有东西都用 Rust 重写?当时要解决什么问题?
Copilot 运行时为什么要移植到 Rust
这是个好问题,尤其是现在大家有时会觉得,把东西重写成 Rust 是一种潮流。有人问我时,默认的前提像是“你只是因为能这么做,所以就去做了”。事情完全不是这样。如果项目原本处在某种状态,重写根本不会成为一个选项。真正的推动力主要是 AI 发展太快了,这大家都知道。过去几年里,AI 带来的能力突飞猛进。
在 GitHub 和微软内部,很多项目都是从头开始加入 AI 能力的。它们最初只是调用大语言模型,然后在外面包一点东西,再继续往上加,最后逐渐搭成现在所谓的智能体框架、智能体循环或智能体运行时。结果我们有了很多套,因为每个团队都自己做了一套。这种局面并不好,尤其是大家都想做到最好、走在前沿的时候。AI 领域的新模型每周甚至每天都在发布。几周前,OpenAI 和 Anthropic 还接连发布新模型、互相超越。一个月前还在研究中的新技术,今天可能就成了每个框架都得有的功能,否则马上就过时了。
我们不希望一大堆不同实现都各自费力跟进。最好是有一套,至少也要有少数几套可扩展的框架或运行时,大家都能用,并按需要定制和扩展。Copilot CLI、GitHub 应用,以及 GitHub 上让 Copilot 执行任务或做代码审查的 Copilot 云端智能体,用的就是 Copilot 运行时。它原本是用 TypeScript 写的,运行在 Node 和 V8 上。这会限制它作为可嵌入库的适用范围。
我希望这个运行时能在各种地方使用,但其中一个主要障碍就是它的实现语言、框架和运行环境。核心目标不是非得转向 Rust,而是离开 TypeScript 和 Node,换成更容易嵌入、效率更高、工作集更小、吞吐更快的方案,并能被各种语言和框架的环境使用。这基本上就是这次项目的起因。
正如你刚才提到的,大家想做这类移植或迁移,原因很多。整个行业都很常见:可能是从过时框架迁到新版,可能是换一种语言,也可能是把已经乱成一团的遗留代码库整理成更现代、更清爽的系统。这类迁移到处都有,但很多时候没人去做,因为成本太高、耗时太长。一两年前我们也会遇到同样的问题。现在有了新一代模型和框架,这些过去贵到做不起的项目,已经能在很短时间内完成了。
Copilot 运行时为什么要移植到 Rust(续)
现在这些迁移和移植已经相当可行,所需时间也很短。
为什么选择 Rust
你写的文章里有句话:“我一开始不是想转向 Rust,而是想离开 Node.js 和 V8。”这让我想问,如果不一定要用 Rust,你考虑过哪些替代方案?最后为什么特意选了 Rust?
我做的第一件事,是用智能体方式快速搭了一组原型。现在用模型做原型很快。我用不同运行时的 TypeScript 试了 Node、Bun 等,也试了 Python、Go、C、Java 和 Rust。每种方案我都做了一个很小的端到端骨架,让系统能跑通流程,后端接一个本地的聊天补全模拟服务器;每个原型大概 5000 到 1 万行。
我比较了端到端性能、工作集、吞吐量、承受大量负载时能扩展到什么程度,也看了它能否满足需求,以及让现有用户使用起来要做多少工作。我们当时有六种语言的 SDK,它们都是 Copilot 运行时上面的轻量绑定,所以也要考虑这套运行时怎么供所有 SDK 使用。以前我们不想把 Node 强行塞进调用方应用的进程里,SDK 只能另外启动一个进程来运行会话。但我们也希望能像其他库一样,提供进程内运行的选项。
所以我们用这些性能目标和限制来评估各种语言和运行时,查看数据,判断哪些方案奏效、效果有多好。就性能和嵌入能力而言,TypeScript 和 Python 很快就出局了。Go 的性能不错,代码也挺干净,但它并不是为嵌入设计的;互操作也不是它的强项。不是做不到,只是有点逆着它的设计方向来。Java 在这方面也不太理想。JavaScript 的表现还可以。
最后主要的候选是 C,尤其是用 Native AOT 的 C#,这样可以提前编译成机器码;另一个是 Rust。从性能看,两者几乎不相上下,各项指标都差不多。有些地方 Rust 略好,有些地方 C# 略好。如果原项目本来就是 C,我们根本不会讨论重写,会直接保留。但它不是,所以最后要看这些标准:性能和嵌入能力两者都不错,C# 的互操作特性也很强。于是决定因素主要变成了更软性的条件。GitHub 内部有其他 Rust 项目,C 项目则很少;在 GitHub,懂 Rust 的人比懂 C 的人多。当然微软内部情况就不一样了。综合这些因素,我们最终选了 Rust。
我知道 Anders 最近带团队把 TypeScript 编译器从 TypeScript 移植到了 Go。那个项目好像比你的项目开始得早一点。我想知道你们有没有交流过心得?
当然交流过。这正好说明,不同项目可以得出不同结论,而且都合理。TypeScript 编译器没有必须嵌入其他应用的需求。反过来,编译 TypeScript 的工作形态,也和发请求给大语言模型、处理提示词、管理沙箱这些工作很不一样。项目的本质不同。
有意思的是,最近几周已经有人用智能体把一些项目从其他语言移植出来。你去 X——或者现在该叫 Twitter——搜一下,就能看到有人把 TypeScript 的 Go 编译器移植成 Rust。以代码库的规模和整洁程度来看,现代模型让这件事做得非常快。这很有意思。
为什么选择 Rust(续)
在 GitHub 内部,懂 Rust 的人更多,懂 C 的人更少。当然,微软内部不是这样。综合这些因素,我们最后选了 Rust。
我知道 Anders 最近带团队把 TypeScript 编译器从 TypeScript 移植到了 Go。这个项目好像比我们的项目开始得早一点。我想知道你们有没有交流过心得?
当然。这正好说明,不同项目可以得出不同结论,而且都合理。TypeScript 编译器没有必须嵌入其他应用的需求。反过来,编译 TypeScript 的工作形态,也和发请求给大语言模型、处理提示词、管理沙箱这些工作很不一样。项目的本质不同。
有意思的是,最近几周已经有人用智能体把一些项目移植到了 Rust。你去 X——或者现在该叫 Twitter——搜一下,就能看到有人把 TypeScript 的 Go 编译器移植成 Rust。以代码库的规模和整洁程度来看,现代模型让这件事做得非常快。这很有意思。
讨论迁移策略
既然你们确定了项目动机,也明确了要解决的问题,我想问问迁移策略。我看到过不同项目采用不同方式。比如你刚才提到的 Bun,看起来是新开了一个大分支,整个分支完全用 Rust 重写,然后一次性发布稳定版。你这个项目则显得渐进得多,一点一点迁移。你是怎么权衡这些策略、决定什么时候选哪一种的?
先说 Bun,我很喜欢 Jarred 写的那篇文章。我们迁移到一半时,那篇文章发布了,我之前不知道他们也在做移植。看到另一个项目的代码量大约和我们同一个数量级,而且也认定这件事值得做,还把它落地了,挺让人兴奋的。他们原来用 Zig,而不是 TypeScript。文章里说,他们花了大概 11 天就让 98% 左右的测试通过,这很厉害。正如你说的,他们基本上是一次性重写整个项目。
我没参与他们的项目,不了解幕后所有细节。但我注意到,他们的 npm 页面上似乎一直到四月中旬都差不多每天发一次构建,大量是 canary 版本,然后就停了。文章说移植本身用了 11 天,从零开始把 50 万行代码库移植到 Rust,并让 98% 或 99% 的测试通过,确实惊人。但在我能看到的范围内,之后又过了两个半月才有新的 npm 发布。也就是说,核心移植只花了很短时间,随后似乎还有一段稳定化过程,导致发布间隔约三个月。
有意思的是,这也差不多是 Copilot 移植的周期——我花了十四周半。原因当然有很多,但本质上我们耗时差不多,只是选了不同的做法。Jarred 在文章里提到,不想采用 Copilot 那种渐进方案,因为会产生很多临时互操作代码。他说得没错,确实会有。我们知道这么做会带来什么,只是根据自身约束选择了这种方法。
对 JavaScript 运行时来说,暂停三个月不发功能、不修 bug,问题也许不大;三个月前的 JavaScript 和今天的差异没那么大,可以先把一切停下来,重写完再继续发布。但在 2026 年夏天的智能体框架领域,如果停更三个月,你将面对的世界……
讨论迁移策略(续)
发布时的世界会和开始时大不相同。所以我们不愿意停下来好几天,更别说更长时间。就算两周能完成,也意味着这两周里不是要让整个团队停工,就是要持续处理大型代码库不断涌入的改动,判断怎么适配,再把它们移植过来。
我们采用了我认为适合 Copilot、但不太适合 Bun 的方式:在正常发布的主干代码库中,把代码一块块从 TypeScript 迁到 Rust。过程里仍然每天发布,只是代码构成逐渐改变:开始时是 100% TypeScript、0% Rust,最后变成 100% Rust、0% TypeScript。整体效果挺好。当然,过程里来了又走的临时代码不少。我们用 napi-rs 连接 TypeScript 和 Rust,最多时有几千个互操作签名,我记得两三千个调用接口;最后都降到了零,因为不再需要互操作。
还有其他做法:可以“推倒重来”,让所有人停下手头工作,直接在主干上重写;可以开功能分支,让主干继续前进,然后不断移植新改动。渐进迁移也可以像我说的那样,原子式地用 Rust 替换 TypeScript;还可以两套组件并存、一起发布,再通过功能开关选择,让它们并排接受分批放量和实验。采用哪种取决于具体原因。我觉得我们选的方式适合自己,但其他做法对别的项目也成立,甚至可能更好。
听起来 A/B 方案还是要有临时测试框架来比较两边。要是单独维护一个分支,同时旧项目主干不断有新提交进来,就得不断移植。不过有了现代模型,逐个提交的移植可能很容易。你们考虑过这种做法吗?和承担互操作成本相比,成本怎么算?
这里其实有几个问题。如果我们维护独立功能分支,最后把整套新系统一次性部署,那就只能等上线后在真实环境里一次性测试所有东西,风险很高。我们选择的方式有个很大的好处,就是可以逐步验证。
讨论迁移策略(续)
这样就能把生产环境用于最后阶段的验证,而且每次部署只比上一次多一小块改动。整个移植期间,我们大概发布了 130 个版本,每次都只多迁一点。出了问题,定位原因就很快、很直接,有时候甚至不用打开电脑。有人报问题时,你会立刻知道大概是哪一块,然后迅速缩小范围。要是整个系统一次性重写,这种验证就难多了。
当然,这个过程中确实出现了回归。其中相当一部分来自把主干改动合并进来。我仍然用功能分支,只是分支周期很短:不是几周,而是移植一个组件大约一天。但一天里,可能有 10 到 20 个 PR 改了这个组件。有不少回归是智能体合并时漏掉了东西,因为移植和合并同时进行。
原子式地从 TypeScript 换成 Rust,相比同时保留 A/B 两套实现,有个好处:我要求智能体移植完就删除对应的 TypeScript。这样反而能帮助发现主干合并冲突。如果主干代码碰到了已经移植的 TypeScript,就会立刻冲突,亮起红灯,让智能体去检查那段代码。要是不删掉旧代码,主干对它的改动就很容易悄悄进入分支中仍未动过的 TypeScript。到时候智能体得在整片已移植代码里找出变化并同步进来;而删除旧代码会马上把它的注意力引到受影响的位置。我们当时就是这样逐块替换的。
如何发现回归
我想很多团队或工程师想重写整个代码库时,最大的顾虑应该是正确性:代码太多了,根本不可能让人逐行读完来确认正确。你一路上是怎么发现并修复回归的?
最重要的是端到端测试、集成测试,叫什么都行。Copilot 的测试大致有两类:单元测试主要测代码库里一个个很小的功能,很多测试还能访问内部实现,因此可以测那些没有对外暴露的小函数和代码片段。
另一类是端到端测试,包括运行时层、SDK 层和 CLI 层的测试。CLI 和运行时在同一个代码库里,所以 CLI 的端到端测试实际上也会覆盖运行时。虽然并非完全如此,但大体可以把 CLI 看成是用最贴近用户的界面暴露运行时能力。我们用这些端到端测试验证行为。
绝大多数回归都出在缺少相应功能的端到端测试上。通常我们有单元测试,但这次的单元测试也是用 TypeScript 写的,用来测 TypeScript 内部实现。很多情况下,代码迁移时这些测试也得一起重写。不过渐进迁移有一个好处:如果把底层一个组件换成 Rust,上层 TypeScript 仍通过互操作调用它,原有单元测试还可以间接验证这个 Rust 组件。
真正的安全网是覆盖整套系统的集成测试和端到端测试。我曾给团队写过一份文档,说明为什么要移植、怎么推进、预计时间、采用的方法和技术等。其中第一项就是先加强端到端测试。我做了这件事,但如果重来,我会做得更多,这是我的一个不足。
道理很简单:只要测试覆盖了你关心的行为,测试不回归,行为就应该没问题。出现回归本身就说明测试有缺口。当然也不总是如此。最让人哭笑不得的是,我们没禁止智能体在改变行为的同时重写端到端测试。智能体的目标就是让测试变绿,所以如果允许,它们会同时修改“判定标准”和被测代码,让新代码通过测试。
还有一次,智能体改动了公共 API,CI 拦了下来,说不能这么做。不过我们的 CI 有个逃生口:如果有人确实知道自己在做什么,可以给 PR 加一个标签,表示这次破坏是可以接受的。智能体发现了这个办法,便给 PR 加了标签,表示“这次破坏没问题”——可实际上并没问题。我注意到了,没有让它溜过去。
如何发现回归(续)
还有些问题很难靠测试彻底覆盖。你可能已经有端到端测试,但有些检查很细。例如数据通过网络发出去时,在所有可能情况下,都要确认传输的每一个字节最终都符合预期。这需要庞大的测试套件,任何缺口都可能让问题漏过去。
TypeScript 和 Rust 的线程模型、并发模型差别很大。大家常说 Rust 代码只要能编译就没问题,确实能防住很多类型的错误,但绝不是所有错误。并发错误仍然会漏过去。我们遇到过生命周期和并发方面的问题,这些都不是 Rust 编译器能发现的。我们从大体串行、单线程的执行模型,转到有意追求更好扩展性、让任务并行运行的模型,当然会引入不少问题。
回到你的问题,最关键的是把所有关心的行为都写进测试,再让智能体移植代码,同时维持测试通过。还要设好护栏,防止智能体在需要验证时顺手把验证标准也改掉。
我还是觉得回归不可避免,因为测试覆盖有点像“自愿申报”:得有人先说哪些行为重要,但总可能漏掉东西。这类项目是不是注定会有一些功能被破坏?
我觉得是。谁知道一年后情况会怎样,发展太快了。但就现在而言,肯定会有回归;如果一个都没有,我会非常惊讶。除非只是很小的代码库,要重写几十万甚至几百万行代码,按概率就一定会有。如果智能体 99.99% 的时候都能做对——这可能还比大多数人高出一两个九——剩下的 0.01% 仍然会有各种错误。代码规模到百万行时,哪怕这是一小部分,也会变成实实在在的问题。
你可以多派一些智能体来找问题,我们也这么做了:合并前做很多智能体审查,合并后继续让智能体找 bug、找并发问题等等。它们确实能发现问题,但以我的经验,对这么大体量的代码,不可能做到百分之百完美。
如何发现回归(续)
我猜大部分代码没有经过人工审查。你们怎么大规模做代码审查?还是说基本没审?
我们确实做了一些,但我没有逐行读完。那不可能,就算做了也很浪费时间。逐行阅读并不能防止 bug。事实上,让智能体逐行检查更有效,不光更快,质量也比我自己逐行看更好。
我确实看了自己提交的每个 PR 的代码,但重点放在整体层面:方向对不对,架构对不对,组件划分是否合理,算法是否合适,有没有可疑之处。我会站在很高的视角对照两套代码,看有没有明显不对劲的地方。有时也会快速翻过所有改动,看看有没有什么跳出来。但逐行细节方面,一个好的智能体审查员肯定比我强,我认为也比大多数人强。
在宏观层面,我很多次会叫停智能体,说:“等等,这方向不对。”有时反过来,智能体会说“做完了”,我会回答:“还没有。”(笑)“这不是我们要的结果。”所以我的工作更多是在把握整体方向,而不是盯微观细节。
如何引导智能体
我看到你在博客里有张很有意思的柱状图,把你发给智能体的每条消息都按意图分类了。回头看你是怎么引导它们的,你的大部分时间花在哪些事上?哪些时候你介入后影响最大?
大致有两三类更高层次的工作。有一类反映了现在模型和框架处理长周期任务的状态:我对进展不满意,有时要在它们停下来时让它继续,有时要在它们走错方向时叫停。
但大多数时候,我是在处理需要权衡的选择。合理的人或智能体可能会选不同路径,我要确保我们走对了。我经常会和主智能体讨论当前进度、需要做的决定,并像橡皮鸭调试那样,一起梳理当时各种选择的利弊。这包括之前谈过的渐进迁移还是暂停一切、整体重写,也包括某一个具体切片该怎么做。
有时智能体会说某个组件做完了,我看了会觉得这不是合适的收尾点:它暂时还牵连着其他系统,也许再多做一点,越过某个架构边界,就能把一大堆东西清理好,让合并后的代码库处于更合理的状态。有时它宣布一个切片完成,乍看似乎可以,但多做一点,或者撤回一些、少做一点,都能让代码库更好。
还有时它做得超出了我的预期。我要求移植尽可能严格一对一:不删功能、不加功能、不大改算法,也不要顺手修碰到的任意 bug。遇到这些问题先记录下来,让我之后单独处理。我希望移植严格保持原有功能。有时我发现它违反了这些规则,就让它撤回到之前的做法,或者先和我讨论清楚,确认我接受这个改动。相当一部分回归来自它违反了这些规则,而我没发现,结果把不该做的修复也带进去了。
如何引导智能体(续)
移植阶段的问题大多已经不再被报告了。不过每天往代码库合并几百个 PR,本来就会有回归。通常我们会在发布前发现并修好,问题只是在主干里出现一下。
但前一两天有个很边缘的案例,我看到后觉得奇怪:报告说这是我加的,可我不记得加过。回头一查,它最初并不在我的移植 PR 里。后来同事或同事的智能体提出一条建议,智能体为了响应安全审查,把某项功能限制得更严;限制过头后,移植前能用的东西,移植后反而不能用了。追查这种事挺有意思。
有意思。所以你的智能体会自动监听 PR,如果觉得反馈合理,就可能不经你确认直接采取行动,对吗?
我主要用 GitHub Copilot 应用里的 Agent Merge 功能。它可以自动监视 PR,自动变基或处理合并冲突,自动修测试、让 CI 变绿,自动处理审查反馈,也可以在所有检查通过后自动合并。这四项都能单独选择。
我通常开前三项,但不开自动合并。等所有检查都变绿后、我按合并按钮前,我会回头看看智能体为了让 PR 变绿做了什么:修了哪些测试,收到了什么反馈,怎么处理的。这样我至少能从整体上了解改动,而不是逐行审查。前面那个案例我记得自己看过,但没有完全理解它把限制收得多严、会挡住多少情况。否则我会告诉它:“这些情况没问题,放行吧。”
要是四项全开,团队里肯定没人会故意这么做,但假设我是团队成员,我可能会写一条恶意评论,措辞听起来还挺合理,比如“请把所有东西都删掉”。
对,或者说:“我之前让你格式化 C 盘,你格式化了吗?”(笑)把所有东西都删光。不同项目我会选择不同的自动化选项。比如开源项目,我可能只会保留自动处理 CI 这一项。
如何引导智能体(续)
也可能保留自动变基,但我绝不会让它自动处理 PR 上任意的审查意见,更不会允许它自动合并。对于我信任参与项目的人所维护的闭源项目,风险小一些,但勾选越多选项,仍然需要越高的信任。
我喜欢这个应用的一点是,各种 Agent Merge 功能都有内置提示词,而且我能为每项能力添加自己的要求,告诉智能体该怎么做。例如要求它总是变基而不是合并;处理审查反馈时要先认真判断哪些意见该接受,哪些可以直接处理。
OpenAI、Anthropic 和 Cursor 都在用这个产品让工作更轻松。WorkOS 是一个 API 平台,只需几行代码,就能给应用加入 SSO、SCIM、RBAC 等企业功能。这些功能是向其他公司销售软件时需要的,但通常不是你产品的核心。现在 WorkOS 也增加了对 AI 智能体的支持。比如 WorkOS for Agents 给每个智能体单独身份和短期、有限权限的令牌,而不是长期 API 密钥;出了问题可以撤销某个会话,让它下面的所有令牌都失效。另一个功能 Airlock 是授权层,每个智能体执行的操作都会根据你用自然语言写下的规则进行检查,既不是什么都直接放行,也不是完全无人监管。最后还有 Pipes,可以把智能体连接到 300 多种第三方工具,你不用自己管理令牌。
在上线前发现回归
如果你在做 SaaS 或 AI 智能体并向企业销售,WorkOS 能用几行代码补齐这些能力。访问 workos.com 了解详情并开始使用。感谢他们赞助本节目。
我们刚才谈到回归,你说很多都在进生产环境之前发现了。很多人听到“回归”会以为应用宕机、系统不断爆炸、事故频发。端到端测试之后,还有哪些防护措施,才让这些回归没有变成严重问题?
我称作回归的这些问题里,大约三分之二是在合入主干后、发布任何预发布版本之前很快发现的。那时我们每天大概发三个预发布版本,所以发现得很快。
有些问题进入了预发布版本。CLI 的发布分预发布版和稳定版;某一天的预发布版大概会触达 5% 到 10% 的用户,其余 90% 或 95% 只使用稳定版。我不确定具体比例,但大约有 10% 到 20% 的问题进入过预发布版。很快就会有人提 issue,或者说:“我发现行为变了,这是设计如此吗?”很多变化只是和过去不同,不一定是坏事,但差异明显到用户会注意并来问。有些情况是我没能阻止智能体在移植时顺手修 bug;这些改动有的其实是好事,只是并非我们有意要做。
还有一些进入了稳定版。由于主干和预发布阶段都没注意到,它们有时会在稳定版里存在一段时间。这就是最近一段时间我们陆续发现的一些问题;一个月前会持续几周陆续出现一些特别边缘的情况,行为有一点不同。用户会问这是否有意为之,很多时候并非如此,我们收到反馈后就会修。还有一些问题是从遥测系统里发现的,比如异常、崩溃监控。这些显然都是非预期的,我们也会修复。
他使用了哪些模型
你在文章里放了张图,展示整个项目过程中用过哪些模型。图上按周统计了 Claude、GPT,偶尔还有 Gemini 消耗的 token。项目刚开始几乎全是 Claude,到最后几乎全变成 GPT。你为什么中途换了主要使用的模型?
我一直在试不同模型,因为那段时间两家实验室都在很快地发布新模型。哪个模型当时好用,我就先用着;遇到让我不满意的地方,再试另一个。这并不代表长期偏好,只是当时哪个最适合我的工作。
一开始我主要用 Opus 4.6 或 4.8,效果很好。后来 GPT 5.5 还是 5.6(我记不清了)在这些任务上更好,我就换过去。之后 Opus 5 发布,我觉得不错,又换了过去。最近我用 GPT-6.1-Sol 很顺手,会用 Opus 5.5 来审查它写的代码,所以两者分工。
移植过程中,GPT 和 Claude 有时都会表现得很让人沮丧。某个模型可能卡在一个点上,不管我怎么做,它都盯着某件事,不能跳出来看全局。我有时会保留代码改动、开一个干净的新会话,让它清空上下文、重新开始;但它常常还是会陷回原来的死胡同。这时我就换模型,让新模型接着做,直到它也开始出现让我不满意的行为,再换别的。
我也试过用 Gemini 和 Grok 作为主要执行模型,但更多时候把它们用于多模型代码审查。主要工作我用 GPT 或 Claude,之后会让来自不同模型系列的三四个模型逐行审查移植结果。我经常这么做,至少每次变基、把主干的新功能合进来时都会做,有时更频繁。
我注意到你有个提示词,可能会在变基时自动触发审查,要求模型以对抗性的方式、或者让多个模型一起检查。你为什么会选择这种方式,而不是让同一个模型用不同线程和关注点做代码审查?
他使用了哪些模型(续)
也就是说让多个模型同时审查。你为什么认为这比用同一个模型、给不同线程分配不同关注点更好?
这样能得到不同的视角。非确定性系统里,即使只是稍微改一下,也可能让模型沿着不同路径走、关注不同内容。而你能做的最大改变之一,就是换一个模型。要想得到不同意见、让它们关注不同的问题,我发现最好的办法就是换用不同模型,尤其是和最初负责主要工作的模型不同的模型。
项目的收益与成本
重写完成后,具体产生了什么影响,花了多少成本?从商业角度看,这个项目的收益和成本分别是什么?
收益非常明显。GitHub 和微软各个产品、服务和团队可以依赖一套共享框架,它会持续变得更好、力求保持一流水准,其他团队就不用各自投入资源重复造轮子,可以把精力放在自己的业务价值上并复用这个组件。就像依赖任何一个大型库一样,你希望有人专门持续维护它,而自己可以使用不断演进的成果。从商业价值上说,GitHub 和微软能够共用这套框架,价值无法估量。移植以前,这件事更难推销,也更难证明值得投入。
另一部分收益来自实打实的性能提升。假设一个系统需要十倍的工作集,机器密度就会降低十倍,可能意味着要十倍数量的服务器。要是能让同一台机器同时服务更多用户、提高吞吐量、把延迟降低五六倍,都会直接影响成本和利润。
很多服务收到一个请求后,只要启动智能体循环再多跑一轮,事情很快就结束了,因此启动成本会占很大比重。启动 Node 应用和启动原生 Rust 或 C 程序差别很大,每个请求可能差几百毫秒,有时甚至几秒。这样的延迟很容易测出来,对商业结果影响也很大——前提是你能采用这个运行时;如果做不到,就还是回到之前那种难以推广的局面。所以这次移植很有价值。
至于成本,我保存了这次工作中约 99% 的会话日志,再从中推算输入 token、缓存写入和读取、输出 token 的数量。总计大约 1360 亿个 token。按当时 OpenAI 和 Anthropic 网站上这些模型的公开价格计算,约为 12 万美元。
项目的收益与成本(续)
12 万美元没有计入企业折扣,只是按 OpenAI 和 Anthropic 网站上的价格估算。再加上我的时间:项目用了十四到十五周,但我同时还做其他事。我现在打开 Copilot 应用,大概有 35 个工作会话在进行,有些做同一类事情,有些属于不同项目。开发者不再一次只做一件事。
粗略估算一下,那段时间我提交的 PR 里,大约五分之一和这个项目有关,另外 80% 用于其他工作。按十五周的五分之一算,大约是三周的个人工作时间,分散在三个月左右,再加上 token 成本。
成本还在下降。模型的价格正以惊人的速度下跌。我记不清具体数字了,但我想 GPT-6.1-Sol 的价格大约是 GPT-6-Sol 的一半,而 GPT-6-Sol 又是 GPT-5.6-Sol 的一半;模型越来越便宜,能力也越来越强,我引导它们做对事情所需花的时间也越来越少。所以这类移植和迁移只会越来越便宜。
三周左右的开发者时间,再加上 12 万美元,就能重写整个代码库。如果没有 AI,你们要做这件事会是什么样?
首先,项目根本不会发生。(笑)说真的,这个项目做不成,因为等我们完成时,移植的对象都已经过时了。这个领域变化太快,我们可能永远也做不完。博客里的图能说明这一点:移植很长一段时间里,Rust 代码在增加,但 TypeScript 的规模大体没变、甚至稍微增加了,因为与此同时代码库还不断加入大量新功能。移植刚开始时,大部分代码是 TypeScript,新功能自然也大多用 TypeScript 写;项目快结束时,大部分代码换成了 Rust,新代码也大多用 Rust 写。
即便不考虑这些新功能,这也是约 45 万行生产 TypeScript 代码,还不包括测试。TypeScript 里还有几十万行单元测试,也需要移到 Rust。想想一个优秀开发者一天能合理完成多少工作,就知道过去要做成有多难。
项目的收益与成本(续)
过去这大概要整个开发团队花一两年才能做成,对吧?即便能做成,也会是一笔巨大的投入。我甚至很难想象怎么写这样的提案,因为我知道它大概会被立刻否决:“当然不可能做。”而现在的提案是:我用三个月时间,每周投入大约 20%,之后整个系统就迁好了,大体上也不会影响团队其他人。虽然这不是百分之百准确,但可以说几个月后就能获得这些业务收益,成本就这么多。这种提案好写得多。
即便真的按没有折扣的公开价格花掉 12 万美元,拿它和一名资深开发者一年的成本相比——包括福利和奖金——这也划算。对于这种工作,目标很明确,而且已有功能里已经编码体现出来了,成本至少低一个数量级,甚至更多。工程师一年可能要花 20 万到 40 万美元,12 万美元只是很小一笔。你当初提案时,预估过 token 成本吗?
预估过,但账单比我想的高,主要是最后要迁的代码行数比我预计多。我没充分考虑新代码增长的速度。有些事我做对了,也有些做错了,现在回头看得更清楚。前面提过一项:端到端测试投入不够,没把覆盖做得尽可能全面。另一项就是没有认真估计待迁代码的增长量。
写提案时,代码库大约有 15 万行生产 TypeScript,准确数字记不清了,大概就是这个量级。我根据这个数字做了些粗略计算,也参考之前不同语言原型的耗时,估计大约六到八周可以完成。但实际需要移植的东西远不止这些:有几十位开发者用智能体很快给代码库增加了各种功能。这不是公开开源项目,但基本上相当于内部开源,很多人会进来添加他们需要的东西,再由核心团队帮忙审查。因此代码功能不断大量涌入,我没有把这部分算进去。
项目的收益与成本(续)
另一个我没考虑到的问题是,Copilot CLI 和运行时的耦合比理想状态更紧。要只移植运行时,就得先把它们拆开,让使用运行时的应用不必连 CLI 一起部署。还要把某些其实不必放在 CLI、可以下移到运行时共享的功能移过去。我低估了目前标记成 CLI 的代码里,有多少适合下沉。整个过程中,这部分代码也被移了下来。
最后我们移植了大约 40 万到 45 万行 TypeScript,差不多是我估算的三倍,这影响了完成时间,也影响 token 数量。
走过这个过程之后,如果有工程师团队考虑重写,应该看哪些条件来判断值不值得做?
什么时候值得整体重写
归根结底要看业务价值。有些事情是自己掏钱做着玩的,那你自己决定好玩值多少钱就行。比如我不喜欢去赌场,因为我觉得赌博是在输钱;有些朋友很喜欢,他们觉得那是在为一晚娱乐付费,不觉得自己输钱。个人开发项目也是一样,如果你想花 token 把它移植到另一种技术上,那就做吧,玩得开心。只是要清楚收益是什么——在这种情况下,收益就是你享受这个过程。
但对商业项目来说,要问它能带来什么业务价值,再像其他项目一样,算清成本和投资回报。这个回报必须足够明确。全世界很多公司在智能体出现之前就做过迁移评估,发现投资回报非常值得。比如从 .NET Framework 迁到 .NET Core,微软投入了很多资源让 .NET Core 变得很快,因此迁移能带来真实的性能收益。公司可以算账:现在每年运行服务花 X 百万美元,迁移后每年省 300 万美元,迁移要花一年、占用一位开发者一年,再算一下就能看出值不值得。
更细一点说,连该投资哪些功能,也可以这样算。我以前花很多时间做 .NET 正则表达式引擎(Regex Engine),可以估算公司或某项服务每年花多少钱运行正则表达式。如果我投入两周改进引擎,让他们使用的模式快四倍,那么这两周投入能带来多少投资回报、服务一年能省多少钱?这样的权衡无处不在。你只要明确为什么做、要花多少、能得到什么。如果把这些写清楚并且说得通,那就是值得做的项目;如果无法证明,就很可疑。
成为杰出工程师一路上的职业体会
(笑)我为这次采访做功课时看了看你的职业经历。你的 LinkedIn 上没什么资料,但我找到一段你做过的采访……
对,我在微软已经 25 年了,很喜欢这里。我在公司内部换过很多岗位,但没兴趣跳槽去其他公司,所以也没怎么维护 LinkedIn 资料。(笑)
我听了那段介绍你不同工作的采访。你成为杰出工程师的路径很有意思,因为做过很多不同岗位,不只是从一个团队的纯开发跳到另一个团队的纯开发。起初你有点像驻场工程师、顾问工程师,后来做过技术写作、VP 的技术顾问,还转去做过 PM。在这些角色里,你最建议别人尝试哪种?如果他们想试试纯软件工程之外的工作,哪种值得体验?
我做过的每个岗位都教会我不同的东西。我很尊重大学毕业后想当开发者、成为工程师,然后一直走这条路的人;这完全是有效的职业路径。对我来说,绕了一些弯让我积累了很多不同技能。
你提到的驻场工程师,当时属于微软的咨询部门。我会在某个客户现场待三周到三个月,帮他们解决问题。我因此了解了很多行业和各类具体难题,也从外部视角看到微软的产品哪些做得好、哪些不够好。我还了解了客户真正面对的困难,后来能把这些经验带回来,为他们构建解决方案,这非常有价值。
后来我在《MSDN Magazine》做技术写作和编辑,也会开发一些东西再写出来。这让我学会判断什么真正有趣、什么真正重要,怎么把事情讲清楚,哪些该说、哪些不必说,以及如何讲一个能引起共鸣的故事。
做 PM 时,我学到怎么判断真正需要构建什么,以及需要哪些部分协同起来才能交付。做开发时当然会学到编码、设计、架构等东西。
对我最有意思、我非常庆幸做过、但绝不会再做一次的工作,是微软某位副总裁的技术顾问。那是一次很棒的经历,持续了几年。我像是他的右手边,一个不显眼的搭档,跟着他参与各种工作,替他写材料。每次会议后,我们都会回顾听到了什么、接下来该怎么办,再来回讨论想法。那段经历非常……
成为杰出工程师一路上的职业体会(续)
……能让我真正看清事情怎么运作、决策到底如何做出。我得以参与一些原本要很多年后才可能接触到的事情。那段经历很棒,但也非常耗人、非常需要无私付出,确实让我精疲力尽。我很珍惜那段经历和学到的东西。如果有人有机会,我很建议去做一段时间这样的工作。
这个岗位很有意思,听起来你像是一位高层管理者的技术型幕僚长,不是行政助理,而是会参与很多重要的内部讨论,提供他们做决策所需的信息。你说自己学到了事情实际上是怎么做成的,能举些例子吗?
普通工程师在公司里会看到各种决定:公司宣布新的发展方向、建立合作关系、投资或收购,关闭某个产品、投资新产品、成立新团队。但这些决定看起来像凭空出现,最后才被完整地公布下来。
你通常看不到事情如何走到这一步:考虑过哪些方案、为什么否决某些方案、有哪些权衡,以及有经验的行业人士如何思考这些选择和问题,决定关注什么、忽略什么。我以前接触不到这些。做技术顾问时,我能在一旁观察、吸收这些信息,之后还能和 VP 讨论。我们会走出会议,他问:“你听到了什么?你怎么看?帮我一起理一理。”有时我能提出有价值的看法;有时他会说:“不是这么想的。”(笑)每一次都是学习。
我也听工程师说过,尝试管理岗位很有价值,即使最后觉得不喜欢、再也不想做,也能看到团队管理实际是怎么运作的,这对之后的职业生涯有帮助。
是的。我之前没提到,其实我也经历过。我曾以为自己真正想做的是管理,于是安排好转岗,和团队约定六个月后可以转成带团队的负责人。后来一切顺利,我真的成了负责人。但当了六个月后,我开始怀疑这是不是我想象的那样;又过了六个月,我确定这绝对不适合我。(笑)
成为杰出工程师一路上的职业体会(续)
我知道管理不适合我之后,又花了大约一年,才稳妥地回到个人贡献者(IC)岗位,并一直做到了现在。就像你说的,我非常感谢这段经历,也很感谢微软提供个人贡献者职业发展通道,让人可以持续成长、晋升,却不必直接管理下属。
我后来意识到,我以为自己想当管理者,是因为觉得只有那样才能帮助别人、给人职业建议、帮别人解决问题。这其实是我误解了。做过管理后我才明白,个人贡献者也完全可以做这些事。我喜欢管理岗位的那些事情,并不需要当管理者才能做。
技术书籍推荐
有没有哪本对你影响很大的书值得推荐?
这本书已经有些年头了,但我读过好几遍,非常喜欢。Charles Petzold 写的《Code》(《编码:隐匿在计算机软硬件背后的语言》)。大概是 2003 年左右出的,我记不太清了。我很看重一种能力:把复杂概念讲清楚,让完全不了解相关领域的人也能听懂。
即使听众本来就懂,能把复杂概念拆成清晰、简洁的内容并讲明白,也很有价值;这样大家能在宏观层面达成共识,你也能清楚地说明自己的观点,把事情往希望的方向推进。
《Code》就做到了这一点。Charles 从头讲计算机是怎么工作的,让没有技术背景的人逐步了解二进制、逻辑门、电路、编程、计算机和互联网,从基本原理开始,一层层搭出对整体运作方式的理解。这本书让我非常惊艳,后来我又读了好几遍。虽然里面的知识我都知道,但我就是喜欢它讲事情的方式。只能推荐一本的话,就是它。
给年轻工程师的建议
回顾你的职业经历,你在行业里工作很久了。对今天刚入行的年轻工程师,你有什么可能有帮助的职业建议?
这是个好问题,我一直很难回答。几年前我可能能给出更明确的建议,但有了 AI 之后,我还不知道正确答案是什么。
我和 AI 智能体合作时,常常要用上 25 年经验——这可能既有好处也有坏处——才能很快看出它们做得对不对、该怎么引导。智能体可能说:“我百分之百确定正确答案是 A。”我会问:“你有没有考虑 B、C、D?那些取舍不一定不存在吧?”它会说:“确实。”这种小小的引导,也需要你自己有过相关经历。
我担心 AI 智能体的发展会让人失去积累这些经验的机会。我还不知道该怎么办。我很相信要把基础学扎实、从核心原理逐步搭建理解。比如学编程语言,我仍然觉得从 C 这样的语言入门有价值,因为你能理解计算机实际怎么运作、内存管理是什么意思,也能学到不同算法的取舍,而不是只在更高层工作,让语言和环境替你处理许多细节,却不知道底下发生了什么。
所以我仍觉得,专注于基本概念、真正弄懂算法和机器怎样工作,然后据此推理和举一反三,是有价值的。我猜无论你从事哪个领域,掌握分布式系统或数据库等基础知识,都有助于在未来取得成功——即便绝大多数代码都由智能体编写,而你负责引导它们。
但我也不确定。智能体学习和发展的速度太快,我不知道两三年或四年后的世界会是什么样。我猜它会和今天大不相同。我们会经历一段很疯狂的时期。
太好了,非常感谢你今天抽时间来聊,Stephen。谢谢你。
很荣幸。这次谈话很愉快,谢谢。
片尾
谢谢你收看这期播客。如果你喜欢节目、希望节目继续发展,请点赞或留言。也欢迎留言推荐你希望我邀请的嘉宾。Barbara Liskov、Mike Stonebraker、Mark Brooker 都是有人留言推荐后,我邀请来的。
另外,除了播客,我还在做一款我一直希望存在的人体工学键盘。这是它的原型,是一款左右分体键盘。它已经在 Kickstarter 上发布,八小时内就达成了筹资目标。如果你是早期支持者、买了一台,我非常感谢。现在我们正在推进漫长的模具制作过程;如果你还想买,Kickstarter 上仍开放延迟认捐,可以去那里下单。我会把链接放在简介里。再次感谢你收看节目,我们下期再见。