为什么 Go 是 AI 辅助软件工程的理想语言
文章摘要
这是 Google Developers Blog 在 2026 年 8 月 11 日发的一篇官方立场文,作者是 Go 的产品经理组长 Cameron Balahan 和 Google Cloud 首席布道师 Richard Seroter。
论证起点是瓶颈的迁移。 过去衡量一门语言的生产力主要看「写起来有多容易」;但当 agent 能在几秒内生成几百行语法合法的代码时,人写代码的速率就不再重要了,重要的是审阅、验证和维护已经写好的代码。他们把 AI 定位成「你的队友——有点不守规矩,但仍是队友」,于是关键变成「我们作为一个团队如何协作」。
第二步是把这归到 Go 的设计初衷上。 他们说 Rob Pike、Robert Griesemer 和 Ken Thompson 二十多年前在 Google 创造 Go 时考虑的就是团队驱动开发:当别的语言快速堆特性、扩大表达程序逻辑的方式时,Go 追求的是「服务于软件工程的语言设计」。他们特意区分编程(写代码并运行它来解决问题)与软件工程(与他人协作设计并实现一个随时间演化的耐久系统)——编程只是软件工程的一部分。而服务于软件工程需要:贯穿整个 SDLC 的端到端平台化工具;有主张的简洁(opinionated simplicity),让整个团队用同样方式组织、格式化和测试代码;强兼容性保证(今天写的代码十年后不仅还能跑,而且十年后依然是好代码);能随团队扩张的全局依赖管理生态;以及贯穿其中的合理稳健的安全考量与工具。
「Go 是一个平台」。 开箱即带格式化器、测试框架、依赖管理和高级安全工具,全部从标准工具链直达;配上一套全面的标准库,免去复杂外部框架,提供了「无可比拟的一致性基线」。他们的关键论断是「AI 和人的需求出奇地相似」:当 agent 被要求在没有外部验证的情况下反复重构时,表现会迅速退化——第一遍可能 95% 正确,后续每一遍都在累积错误率、污染上下文窗口,准确率下降而 token 成本上升;而在 Go 里 AI 可以借用平台的端到端工具链,更快、更便宜、更可靠地操作 Go 代码。这套整合工具链还有第二个不那么明显的好处:生态级一致性——因为绝大多数 Go 开发者用同一套核心工具,整个社区在运行时、IDE、包生态上同步吞下重大语言增强;标准库进一步降低了程序逻辑的方差、促成可预测的重复惯用法,这不仅帮人类团队维护大代码库,也为 LLM 生成了更干净、更标准化的训练数据。
「Go 可读」。 Go 优先可读性而非可写性,因为开发者读代码的时间远多于敲代码;在纯人类世界这体现为一种「简洁优于聪明」、明确拒绝其他语言所推崇的语法魔法的文化——Go 程序员常说他们喜欢「永远分不清某段代码是团队里谁写的,全都长一个样」。在 AI 驱动的时代,这种读优先的哲学变成了力量倍增器:个人开发者过去偏好语法简短、隐式类型和加速原型的聪明捷径,而 agent 人机工学(以及配套的人类验证循环)要求的恰恰相反:可预测、显式、刚性结构。他们指出瓶颈已完全从生成移到验证——如果一门语言有十几种方式表达同一逻辑,模型必然生成风格杂乱的大杂烩,对人类审阅者而言,验证那样的代码就变成一场耗尽心力的「破译意图」练习。Go 用 gofmt 强制单一标准格式,并有意限制复杂抽象,于是资深工程师、初级贡献者和 LLM 写出的代码都长一个样;语法完全可预测时,人类能更快发现幻觉的 API 调用、逻辑缺陷或安全漏洞。而且因为这种标准化延伸到整个开源 Go 生态,模型是在标准化数据上训练的,能以更少的尝试次数生成正确、惯用的 Go。
「Go 可靠」。 第一道防线是静态类型系统,作为 agentic 代码的自动安全网:LLM 经常在跨文件的结构边界和类型一致性上出问题,导致幻觉属性和沉默的定时炸弹;在 Python 这类动态语言里这些幻觉常常溜过基本语法检查、只在特定生产负载下运行时崩溃,而在 Go 里编译器立刻拒绝——用不存在的方法、传错类型、留下未初始化的变量,代码就是编译不过。配上 Go 标志性的编译速度(他们说比 Java、C#、Rust 等生产级编译语言快几个数量级),agent 能在一个高效的自我纠正循环里迭代修掉自己的语法和类型错误,在人类队友审阅前就交出语法正确的代码。第二是「电池内置」哲学解决 AI 生成代码固有的供应链风险:LLM 依赖训练数据,常建议陈旧、无人维护甚至恶意的第三方依赖,而 Go 全面的标准库会自然引导模型使用优化过、安全、官方维护的包。需要外部依赖时,Go 的平台基础设施保证完整性:每一个曾被任何 Go 程序 import 过的模块,其校验和与缓存副本都记录在 Go checksum database 和 module mirror 里,防中间人攻击,也消除依赖消失或被悄悄篡改的风险;再加上 Go 漏洞数据库和 govulncheck——它跨依赖追踪已知漏洞,并只标记真正调用了脆弱符号的代码,提供低噪声、高可操作性的反馈。最后是内置测试框架和原生 fuzz 测试提供标准化的持续验证沙箱,AI 可以跑 fuzz 暴露隐藏的边界情况 bug,迭代地强化自己的逻辑。
「Go 可维护」。 他们指出当自主 agent 能一时兴起生成几百个 PR、重构整个服务时,代码库演化速率和架构漂移的可能性都急剧加速。Go 的第一个答案是著名的兼容性承诺:十五年前为 Go 1.0 写的代码不改一行就能在最新工具链上编译运行;而且因为 Go 承诺永不破坏向后兼容(永远不会有 Go 2.0),Go 代码永不会坏——编译器和运行时变好,你的代码也跟着变好,只需升级、重编译、坐收好处。第二是运维可移植性:Go 编译成单个零系统依赖的静态二进制;当自主 agent 越来越多地扮演系统管理员(拉起微服务、执行脚本、通过 CLI 与环境交互)时,这种自包含设计愈发重要,加上交叉编译能力,agent 能不靠复杂构建系统就为所有目标平台出二进制。第三是对抗架构漂移的确定性重构工具:官方语言服务器 gopls,以及新重写的 go fix 及其 modernizers 概念——它们确定性地把旧代码模式更新到最新惯用法和语言特性,在规模上不只推进你的代码,还推进整个 Go 生态,让库、开源项目和其他第三方代码库保持统一;由于这些工具标准化且直接内建在平台里,agent 可以放心用它们重组包、管理依赖、清理技术债而不弄坏代码库。第四是把可维护性延伸进生产环境的内置可观测性:运行时自带 profiling 和执行追踪,编译器原生支持 profile-guided optimization,能用真实生产 profile 编出高度优化的二进制——配合 AI 编排的部署流水线,就形成一个闭环优化周期,生产数据自动喂回编译器重建并优化系统。
结论是一句反直觉的话:开发者写的代码越少,语言选择反而更重要;那些历史上偏向松散原型和聪明隐式捷径的语言,在碎片化的 agentic 输出的重量下难以保持稳定。文末的 Get Started 建议里有一条值得注意——如果你在用基于 VS Code 的 IDE 比如 Antigravity,记得装官方 Go 扩展;并且要「明确地、或者通过预加载的 skill」指示你的 agent 使用 Go 工具链,还链了一个「热门社区仓库」提供这类 skill。
HN 评论精华
这条帖子 444 分、544 条评论,是本组里评论最多的一篇。讨论几乎没有接受文章的框架,而是立刻变成 Go vs Rust(以及 C#、TypeScript、Zig)的语言之争;第二条主线是质疑这篇文章的性质——是 Google 的广告,还是在给 LLM 灌输「Go 是理想语言」的 GEO/AEO 操作;第三条主线是最激烈的,从「Go 的并发模型是否更容易出 bug」一路打到 etcd 的 Raft 实现到底正确不正确,甚至有 Antithesis 员工下场。
- 开场第一条 mbrumlow 就是「Rust 更好。就是更好。Go 不差,但作为长期 Go 拥护者,我团队用 Rust 的门槛已经消失,所以现在全都是 Rust」。threethirtytwo 立刻要求论证:「说 Rust 更好因为『它就是』,说服不了任何人。」这条支线里最被认可的论点来自 greenavocado:Rust 编译器是暴君,类型系统严格,借用检查器不留情,LLM 想 slop 也会被编译器打回去。但 threethirtytwo 提出这需要数据:也可能 LLM 觉得 Go 更容易,于是写出的代码本来就更好、很少像人一样撞上静态错误;「在有人做出这门科学之前,都只是人们说更多静态检查更好」。vorticalbox 提出更实际的反驳:如果审阅者对 Rust 没有深入理解,「不能 slop」和「不可读的 slop」没有区别;而 Go 简单、没有宏和元编程魔法,任何有一点编程经验的人都能看懂它在干什么。greenavocado 举反例说自己写过 5 万行、非平凡、健壮、100% LLM 生成的 Rust 软件,四个人用了两人月只有一两个小 bug;vorticalbox 追问关键区别:你是审阅了代码,还是只测试它能工作?以及一个没有深厚 Rust 经验的工程师能同样有效地审阅它吗?
- simonw 给出了最贴合文章论点的一条:他个人觉得 Rust 比 Go 难读得多,「如果你要让 agent 写掉大部分代码,可读性非常重要」。这引出了一条重要的分歧:threethirtytwo 说他也让 agent 读大部分代码、由 agent 用英文向他解释,「人类还该读代码是默认情绪,放弃它是一种信仰之跃」,但看过去两年的进展,这个差距在收窄。simonw 划出自己的界线:他也不再读 agent 产出的全部代码,但保留在遇到特别诡异的 bug 时、以及任何与安全相关的代码上去读的能力。mbrumlow 更进一步:agent 写代码的量已超出人类在任何有意义时间内能读完的程度,让 agent 生成代码再要求人类慢慢消化,抵消了 AI 带来的大部分速度收益;他和团队正在越来越少读代码,转而要求 agent 用别的方式证明东西按预期工作。
- 也有反向意见。dralley 说 Go 让他反感——主要是指针和
if err != nil {}的错误处理噪音,读起来像「对 Python 和 C 的高度妥协的模仿」;他喜欢 Rust 的?和match。kev009 的评价很尖锐:Go 是一种「蒸汽朋克设计」,创造者集体忽略了几十年的编程语言研究进展,结果是一个「没有锋利边缘的 C,但又不能用在 C 能用的地方」。odo1242 举了一个具体的类型系统坑:把一个 nil 的*bytes.Buffer赋给io.Writer接口变量后,out != nil为真但调用会崩——agonux 回应这是经典陷阱、每个 Go 开发者都知道接口是「类型+数据」两个字段的结构,赋了带类型的 nil 指针后接口就不等于 nil 了。orangecat 给了本帖流传最广的一句类比:Go 好读,就像「只用最常见的一千个英文词写作」那种好读(引 xkcd 1133);而这种性质对 LLM 是帮助还是伤害是个有意思的问题。ndriscoll 认为答案很显然是有害的,理由和对人有害的方式完全相同:对 LLM 用上真正的行话,它立刻变成专家——LLM 用宏、用 monad 这些「人们害怕的东西」毫无障碍,能写出更简洁、直接表达业务逻辑、完全类型安全、高性能且编译器可内省的代码,而 Go 有意设计成什么都不让你做、只能永远写「初学者代码」。 - 质疑文章动机的支线由 amiune 开启:「虽然我某种程度上同意,但我分不清这是 Google 的广告,还是一种诱导 LLM 认为 Go 是理想语言的手段。」radicalriddler 附议说读起来像 AEO/GEO(生成引擎优化)的软文,「对大多数人来说啰嗦到不会去读」。frollogaston 补了个相关观察:Gemini 过度索引 Reddit 答案,会因为 Reddit 上一个人的说法给出明显错误的信息,「人们很可能已经在为这个目的刷 Reddit 了」。
- bensyverson 提供了本帖最实用的一条支持性论证:他过去六个月用 Go 做了大量 agentic 开发,没让他失望;理由是五条具体的——外面 Go 代码多所以模型会写;标准库出色,做个简单 web app 不用拖进 100 个依赖;增量构建极快,这在 agent 不停构建和跑测试时非常关键;性能与安全的黄金配比,不用让模型花周期去修 Rust 生命周期或 Swift 并发问题换取边际收益;Go 相对稳定,所以 LLM 记住的知识还算新鲜(对比 SwiftUI 这种 API 变化很快的东西)。dralley 逐条反驳(Rust 也有大量训练数据、
cargo check能不完整构建就抓问题、愿意接受 Go 级性能就用 clone 不用 borrow、主要依赖类型都有明确的社区赢家),但 codexon 和 bensyverson 都坚持编译时间这一条:bensyverson 的论证是「好的错误信息」换不来「快的编译」——LLM 抓到一个错误、可能又撞一个、再试一次,导致更多 token 和延迟,最后还要付更长的编译时间。nylonstrung 的反驳角度不同:Rust 的价值不只是错误信息「好」,而是更多问题在编译期被抓到,很多还能被cargo fix或编译器直接指出该改什么;「没有什么比 LLM 去调试运行时错误更烧 token,那种东西和它的『智能』映射得很差」。 - kstenerud 给出了另一条具体的工具链论证:Go 对 LLM 开发的杀手级特性是工具——
forbidigo让他能把环境配置挡在应用外、把文件访问限制到少数路径;覆盖率工具有nocover,可以保证每条现实路径至少被跑到一次;linting 也很好。唯一缺的是强制错误处理,「Rust 在错误路径上更好,因为你不被允许忽略它们」。jerf 指出errcheck(通常通过 golangci-lint)正是干这个的,并提出一个概念上的澄清:「故意丢弃这个错误」是一种合法的错误处理方式,所以「强制错误处理」不可能等于禁止它——这不是 Go 特有的,而是普遍成立。kstenerud 的规则是「要么遵守规则,要么标注一个带正当理由的例外」,并批评 Go 犯了「默认可变」和「静默丢弃错误」两个错误默认(但认为禁止循环导入是好决定)。xavdid 提了个反例:他一直惊讶于 Go 的 linting 有多慢——golangci-lint 在他们代码库上任何改动后要跑近 5 分钟,所以他干脆不在本地或编辑器里跑;对比 Python 的 Ruff(瞬时)和 Rust 的 Clippy,甚至用 JS 写的 ESLint 全量扫也只要 21 秒,「很意外,因为 Go 那么多开发工具都设计得那么好」。 - 关于 gofmt 的支线相当长且有意思。有人指出「很多语言现在都有主张明确的格式化器(比如 Black)」,overfeed 抓住了原论点的核心:在 Go 里没有争论,因为
go fmt是唯一算数的那个;Black 很好,但有人偏好 Ruff,于是就有了「团队该用哪个」的争论。讽刺的是这条评论线本身就上演了这一幕——有人推荐 Black,立刻有人说「现在你大概该用 Ruff」。the_sleaze_ 认为这是最低级的 bikeshedding,「第一个选的人赢,到此为止;如果没到此为止,你有的是人才问题」;overfeed 反手把这句变成了对 Go 的辩护:「你说这是人才问题,但言下之意是 Go 对那些有『人才问题』的人效果更好。」Cthulhu_ 承认这确实为真且是刻意设计的,引 Go 团队 2012 年 SPLASH 那篇文章的原话——在 Google 工作的程序员多处于职业生涯早期、最熟悉 C 系过程式语言,要让他们在新语言里快速产出,语言就不能太激进;他补充说人才很重要但不可规模化,在工业规模(几百个应用、几千万行代码、几千名开发者、几十年工作)下再顶尖的开发者也无关紧要。frollogaston 给出集中化 linter 的实际理由:迟早你要和另一个做了不同选择的团队或项目打交道,而你选的东西可能失宠、失去支持——Python 类型 linter 已经有一片墓地,包括 Google 自己的 pytype。 - jeanbza(Go 团队成员)在被追问文章里「AI 生成的 Go 代码更好」的证据来自哪里时,坦率地承认那是「用户的反馈」而非系统性分析,也不是同类对比。jdw64 追问是否有内部报告可以分享,jatins 猜「大概就是同事之间一些非正式的 Slack 消息被描述成了『用户反馈』,这里没有报告」;zero_shift 说这条被踩但他认为是对的——「我很怀疑 Netflix 真的在做哪种语言产出更好的 AI PR 的双盲 RCT,那怎么做?每个服务用两种语言各写一版?」jeanbza 直接确认了:「是的,就是这样。我一直和公司里的 Go 用户聊,他们的反馈一段时间以来都是这个方向。」switchbak 提出了明显的方法论问题:「喜欢某语言的用户不都会这么说吗?Rust 用户不也会这么回答?」PrimalPower 提炼出他称之为「Go 悖论」的东西:他同时相信 80% 的时候我们该拿 Go 来解决常见的协作问题、而成为一门更差的语言在这些场合恰恰是资产;但这样做的同时,我们对更有表达力、也许更好的语言的流畅度会生疏。
- mbreese 给出了三条对「为什么 Go 适合 LLM 写」的解释,被广泛认可:静态类型加快速编译器(变量声明后类型不能变,与 Python 不同,你几乎总知道变量类型;快编译加硬停错误意味着 LLM 每轮都拿到扎实信号);语法上高度有主张,Go 代码本该只有一种样子,没有运算符/方法重载让它容易推理;标准库和浅依赖树加上默认静态链接,让你基本可以确信写出来的东西能跑。
- 最激烈的支线由 yosefk 点火:Uber 报告过他们的 Go 代码在并发 bug 的数量上比其他语言的代码更多,而且这有实际数据支撑;「有任何定量数据支持『Go 在 LLM 工作流里比另一门流行语言更好』这个说法吗?」vips7L 一句话回答:「你知道没有定量数据。这是自上而下的 vibes。」rubiquity 附议并加码:Go 粉把「能轻松让东西并发」和「把并发做得好」混为一谈,Go 的并发原语几乎永远不该被直接使用;他还断言「Go 至今没有产出一个正确的 Raft 或 Paxos 实现,而 Java、C++、Rust 里有几十个」,并引 Antithesis 最近在 HashiCorp Raft 里找到更多 bug 的博文。这引发了长达数十条的缠斗:ramoz 指出那篇博文作者说的是「我们测过的每一个 Raft 实现都找到了 bug,包括 HashiCorp Raft、Aeron Cluster、OpenRaft 和 MicroRaft」,因此这个引用不支持「只有 Go 不行」;jibal 和 Dylan16807 进一步指出这个证据若只看它本身,说明的是没有人做出过正确实现,因此关于 Go 什么也没学到,「『Go 至今没有产出一个正确的』是一种极具误导性的呈现方式,因为同一份证据对每门语言都说了同样的话」;rubiquity 辩称他见过 C++、Java、Rust 里多个在生产跑了十年、每秒数千万请求的私有实现,但 joshuamorton 反问「你如何确信那些未经完整测试的私有实现没有在实践中不显形的微妙 bug?」而 0x696C6961 用一句「我发誓我有女朋友,她只是在别的学校」把这条线钉住。有趣的是 dain 表明自己在 Antithesis 工作,「我们很乐意测试任何至今还没被我们注意到的 Raft 实现 :)」,rubiquity 回答自己没有可以提交的,「和共识算法打交道久了,你学会除非真的必要就避开它」。
- etcd 的具体评价也分裂。Thaxll 指出「世界跑在 Kubernetes 上」而它用的正是
go.etcd.io/etcd/raft,自 2016 年起是生产中使用最广的 Raft 库,支撑 etcd、Kubernetes、Docker Swarm、Cloud Foundry Diego、CockroachDB、TiDB、Project Calico、Flannel、Hyperledger 等。gyesxnuibh 提供了最有分量的技术辩护:他运维过数百个 etcd 集群、每秒上万请求,指出上游也跑 Antithesis 类似的测试并已与 Antithesis 正式合作,Jepsen 很早也测过,近几年还有人给它做过 TLA+ 证明——「etcd 的 raft 算法没问题;是的,几年前有过一次正确性问题,但除此之外你回复的那个人不知道自己在说什么,而且那些 bug 与 raft 实现无关,而是建在它之上的状态机」。反方 iscoelho 说「有过一次正确性问题」是轻描淡写,etcd 的损坏和失去 quorum 在实践中极为常见、GitHub issue 挂几年,「设计简单、性能一般,却从未可靠过」;fl0ki 更狠,说 etcd 是他见过最业余的代码之一,goBGP 甚至更糟。rubiquity 补了一个有力的事实:全球最大的托管 Kubernetes 服务 AWS EKS 在大规模集群上已经把 etcd 换成了自研的共识服务。另一边 preisschild 和 Manfrednotfunny 都以多年自管 Kubernetes 的经验反驳「etcd 有问题」的说法——前者说跨欧洲大陆光纤都工作得不错,「是整套栈里问题最少的部分之一」。camkego 在这条线里贴了个反面教材:他建议别人「去问 Gemini flash」某段历史,Cthulhu_ 直接怼回去:「为什么打一个 prompt 而不是给一个权威来源的链接?Gemini 不是来源。」 - 最贴合文章主题的一条批评来自 mrsilencedogood:LLM 生成/编辑 Go 代码「还行」,至少和它们写大多数主流语言一样还行;「但天啊,一旦涉及任何与并发有关的东西,它就彻底失去理智」——尽管让 agent 在代码库某些部分放手已经勉强可以,它们连真实生活中那种程度的并发都做不到基本水准。rubiquity 说这也是他的经验,并给出他认为的关键差异:LLM 在 Rust 的众多抽象上也吃力,但你可以知道「只要它编译通过就没有数据竞争」,并随时间与 LLM 一起改用更好的抽象。
- 关于「Go 为什么没有 Java/JCTools 那样健壮的并发容器」,Groxx 给出了本帖最深入的一段技术解释:Go 没有可以从「外部」做 fork/join 的 goroutine handle,加上成型年代里长期没有泛型(以及之后明显更弱的泛型),共同导致大多数并发代码非常「侵入式」——你创建裸线程,在被并发化的代码内部手工加裸同步原语,
errgroup已经是很多代码在复杂度上走到的尽头。他还解释了select的结构性后果:select非常有用(用过就会怀念),但只能用于 channel,这意味着你常常被迫在 mutex 明显更简单更自然的地方使用 channel;也意味着大量 API 变成以 channel 为中心,因为那几乎是唯一的非阻塞选项;而一旦走上 channel 中心路线,你又撞上泛型的历史限制,且 channel 性能通常明显不如 mutex,加上包装 channel 会改变语义所以你基本被要求手工直接对 channel 用 select。他也提到 Go 已经在向 Java 风格移动,「1.27 会让更多这类东西成为可能」。 - 其他语言的横向对比里,joseda-hg 提出 C# 是最接近的直接对照(稳定、文档多、模式成熟、性能好且相对安全),bensyverson 同意并说自己更喜欢 Go 直接产出单个二进制;keithnz 给了具体经验:他用 Go 写的项目大体顺利但有诡异 bug 且 AI 难以解决,用 C# 重写后明显更健壮,「AI 很少在语言/框架上出问题」。skybrian 说 coding agent 配 TypeScript 也挺好用,radicalriddler 指出 TypeScript 的具体麻烦:LLM 喜欢在微观层面找最省事的路,除非你处处设护栏,它们会用
as any或as unknown从编译器问题里「强转」出去、之后再转回来,结果要么是可读性问题、要么是运行时问题漏出来;skybrian 说他的护栏是用 Deno 并把deno lint放进构建,它不允许any。jhawk28 提到 Zig 可能更贴近 Go 开发者的偏好,jdw64 反对:Go 因为 GC 没有内存安全问题,而 Zig 有 UB 问题,「性能也许略好,但选一门没有内存安全的语言我不觉得是好主意」。