Go语言的甜蜜十六岁

就在上周一,11月10日,我们庆祝了Go语言开源发布的16周年纪念日!

就在上周一,11月10日,我们庆祝了Go语言开源发布的16周年纪念日!

我们遵循现已成熟可靠的发布节奏,于今年2月发布Go 1.24,并于8月发布Go 1.25。为践行打造最高效生产系统语言平台的使命,这些版本新增了构建稳健可靠软件的API,显著提升了Go构建安全软件的成功记录,并进行了多项重大底层优化。与此同时,生成式人工智能给行业带来的颠覆性变革已不容忽视。Go团队正以审慎而坚定的态度应对这个动态领域中的挑战与机遇,致力于将Go语言生产就绪的理念应用于构建稳健的人工智能集成方案、产品、智能体及基础设施。

元素周期表

核心语言与库改进

最初作为实验性功能在Go 1.24版发布,随后在Go 1.25版正式推出的全新testing/synctest包,显著简化了并发异步代码的测试编写。此类代码在网络服务中尤为常见,传统上难以进行有效测试。synctest包通过虚拟化时间实现其功能。它能将原本缓慢、不稳定或兼具两者特性的测试,轻松重写为可靠且近乎瞬时的测试,通常仅需添加几行代码。它同时彰显了Go语言对软件开发的集成化理念:在看似简单的API背后,synctest包实现了与Go运行时及标准库其他组件的深度集成。

过去一年中,testing包获得的增强远不止于此。新增的testing.B.Loop API不仅比原生testing.B.N更易用,更解决了Go基准测试编写中诸多传统(且常被忽视!)的陷阱testing包还新增了简化清理操作的API,适用于使用Context的测试;以及便捷写入写入测试日志。

Go语言与容器化技术共同成长,二者配合默契。Go 1.25 推出的容器感知调度进一步强化了这种协同。该机制无需开发者干预,即可透明调整容器内 Go 工作负载的并行度,防止影响尾部延迟的 CPU 限流,提升 Go 语言开箱即用的生产就绪性。

Go 1.25 的全新飞行记录器在强大的执行追踪器基础上,实现了对生产系统动态行为的深度洞察。传统执行追踪器因收集信息过多而难以应用于长期运行的生产服务,而飞行记录器如同微型时光机,能在故障发生后精确还原服务近期事件的详细快照。

安全软件开发

Go持续强化对安全软件开发的承诺,在原生密码学包领域取得重大进展,并通过标准库迭代提升安全性。

Go 标准库内置完整的原生加密套件,过去一年达成两大里程碑:独立安全公司 Trail of Bits 的安全审计获得优异结果,仅发现一项低严重性漏洞。此外,通过Go安全团队与Geomys的协作,这些包已获得CAVP认证,为全面FIPS 140-3认证铺平道路。这对特定监管环境中的Go用户具有重大意义。此前因需采用不受支持的解决方案而引发摩擦的FIPS 140合规性问题,现已实现无缝集成,有效解决了安全保障、开发体验、功能实现、发布速度及合规性等方面的顾虑。

Go标准库持续演进,致力于实现 默认安全设计安全 。例如,Go 1.24 引入的 os.Root API 实现了 抗遍历文件系统访问,有效抵御了攻击者诱导程序访问本应不可访问文件的漏洞类型。此类漏洞在缺乏底层平台和操作系统支持时极难解决,而新版os.Root API提供了简洁、一致且可移植的解决方案。

底层改进

除用户可见的变更外,Go语言过去一年在底层进行了重大优化。

在 Go 1.24 中,我们基于哈希表设计的最新理念,彻底重构了 map 实现。该变更完全透明,显著提升了 map 性能,降低了 map 操作的尾部延迟,某些情况下甚至实现了显著的内存节省。

Go 1.25 引入了名为Green Tea的实验性垃圾回收器重大革新。该机制能为多数应用降低至少10%、最高达40%的垃圾回收开销,其创新算法专为当代硬件能力与限制设计,开辟了我们正积极探索的新设计空间。例如在即将发布的Go 1.26版本中,Green Tea将在支持AVX-512向量指令的硬件上实现垃圾收集开销额外降低10%——这是旧算法几乎无法实现的突破。Green Tea将在Go 1.26中默认启用,用户只需升级Go版本即可受益。

深化软件开发栈

Go的价值远不止于语言本身和标准库。它是一个完整的软件开发平台。过去一年中,我们定期发布了四版gopls语言服务器,并建立合作伙伴关系以支持新兴的智能体应用框架。

Gopls为VS Code及其他基于LSP的编辑器和IDE提供Go语言支持。每次发布都带来大量功能更新与体验优化,显著提升Go代码的阅读与编写效率(详见v0.17.0v0.18.0v0.19.0v0.20.0 的发布说明获取完整细节,或查阅我们全新的gopls特性文档!)。部分亮点包括: 支持变量提取、变量内联及 JSON 结构体标签的重构功能;以及面向模型上下文协议(MCP)的实验性内置服务器,该服务器通过 MCP 工具形式向 AI 助手开放 gopls 部分功能。

在 gopls v0.18.0 中,我们开始探索 自动代码现代化工具 。随着 Go 语言的演进,每次发布都带来新特性与新惯用法——这些更优的实现方式,正是 Go 程序员长期寻求的替代方案。Go始终恪守其兼容性承诺——旧有方式将永久有效——但这不可避免地导致了旧式与新式编程范式的分化。现代化工具作为静态分析工具,能识别旧式表达并自动建议更快速、更易读、更安全、更 现代 的替代方案,其可靠性堪比一键操作。正如gofmt实现了风格统一,我们期待现代化工具能达成表达方式的统一。我们已将现代化工具集成到IDE建议中,它们不仅能帮助开发者维持更一致的编码标准,更将助力开发者发现新特性并紧跟技术前沿。我们相信现代化工具还能帮助AI编码助手保持技术前沿性,避免其强化过时Go语言知识、API及惯用法倾向。即将发布的Go 1.26版本将彻底改造长期闲置的go fix命令,使其能批量应用整套现代化工具,回归其Go 1.0之前的本源

九月底,我们与Anthropic及Go社区合作,发布了v1.0.0版本版本的官方Go SDK,用于支持模型上下文协议(MCP)。该SDK同时支持MCP客户端与服务器端,并为gopls中新增的MCP功能提供技术支撑。开源贡献此项工作有助于赋能围绕Go构建的日益壮大的开源智能体生态系统,例如近期由谷歌发布的Go语言智能体开发工具包(ADK)。ADK Go基于Go MCP SDK构建,为开发模块化多智能体应用与系统提供了符合Go语言特性的框架。Go MCP SDK与ADK Go共同展现了Go在并发性、性能和可靠性方面的独特优势,这些优势使其在生产级AI开发中脱颖而出。我们预计未来几年将有更多AI工作负载采用Go语言编写。

前瞻展望

Go语言即将迎来激动人心的一年。

我们正通过全新推出的go fix命令、对AI编码助手的深度支持,以及持续改进gopls和VS Code Go来提升开发者生产力。绿茶垃圾回收器的全面可用、对单指令多数据(SIMD)硬件功能的原生支持,以及运行时和标准库对大规模多核硬件的优化支持,将使Go持续适配现代硬件并提升生产效率。我们正着力完善Go的“生产级堆栈”库与诊断工具,包括由Joe Tsai及Go社区成员推动的encoding/json重大升级goroutine性能分析功能Uber编程系统团队贡献;同时对net/httpunicode等基础包实施多项优化。我们致力于为Go与AI的融合开辟清晰路径,精心演进语言平台以满足当代开发者的动态需求,并构建能同时赋能人类开发者与AI助手及系统的工具与能力。

值此 Go 开源发布 16 周年之际,我们亦展望 Go 开源项目本身的未来。从最初的微小起步,Go 已发展出蓬勃的贡献者社区。为持续满足日益扩大的用户群体需求——尤其在软件行业剧变的当下——我们正探索如何在保持Go核心理念不变的前提下,优化开发流程的扩展性,并更深入地凝聚我们卓越的贡献者社区。

若没有非凡的用户与贡献者社区,Go不可能取得今日的成就。谨祝各位在新的一年里一切顺遂!

元素周期表抱枕

本文由 TecHug 分享,英文原文及文中图片来自 Go’s Sweet 16

共有{104}精彩评论

  1. 虽然都说编程语言本身不是瓶颈,但我记得当年作为新手开发者,当我接触Go语言时,曾因无法用当时使用的语言更快地解析代码而沮丧不已。

    又过了几年我才真正开始学习Go,不得不说这是我掌握最快的语言(这很合理,毕竟它的语言规范是所有语言中最精简的)。

    虽然这种说法肯定有诸多谬误,但Go确实让我用20%的努力就达到了Rust的80%效果。

    1. Go语言的妙处在于能在合理时间内掌握“全部精髓”:陷阱、并发机制、所有细节。通晓一门语言的完整规范令人倍感安心。

      我确信真正理解C#或C++全部精髓的人屈指可数。你总会遇到晦涩概念,被迫中断代码阅读去查证“部分方法”或“泛型委托”这类玩意儿究竟是什么鬼,然后若还有余力才继续研读代码库。

      1. > Go语言的妙处在于能在合理时间内掌握“全部精髓”

        这种说法总让我觉得是程序员个人偏好的“口味”问题,几乎找不到证据证明它比其他语言更能带来实际成功。

        毕竟人们每天都在用C#和C++完成大规模实际工作。Java、Ruby、Rust、JavaScript亦然——所有被程序员斥为臃肿庞大的语言都在高效运转。

        我并非否定偏好精简语言的合理性,只是职业生涯中从未见到精简语言在交付速度或减少缺陷方面表现更优。

        顺带一提,我甚至认为C++的核心问题不在于特性数量庞大,而在于这些特性之间存在不可预测的交互关系。换言之,关键不在于图中节点数量,而在于边数量及其连接方式。

        1. 仅举一例,虽未必具普适性:

          我在学术界从事机器学习研究,团队几乎完全使用Python。当时处理一个庞大的CSV数据集,需要排序、过滤等数据转换操作。简而言之,每周新增数据时我们都得重跑整个流程。即便用尽各种技巧加速Python代码,仍需耗时约3天。

          对此深感困扰的我决定用编译型语言重写程序。由于距上次编写C/C++代码已有数年——本科时仅为一门课程学过且记忆模糊——我最终选择了Go语言。

          短短数小时内,我便掌握了足够的语言知识编写出简易数据处理程序,将耗时从三天以上压缩至两小时内。

          遗憾的是此后再无机会或需求使用Go语言。其他编译型垃圾回收语言(如Nim)想必同样高效,但若改用C/C++,不仅耗时更长,对团队中仅熟悉Python的同事而言代码可读性也会大幅降低。我相当确定,若团队成员需要扩充程序功能,他们最多耗费一天就能完成。

          1. 在选择Go之前,你尝试过scipy/numpy或其他采用编译实现的Python库吗?

            1. 当然尝试过,但数据集主要由字符串构成,需要与GIS数据交叉比对。我试遍了所有库。最快的方案是用polars处理以字符串为主的CSV文件,但提升有限。不过话说回来,当时polars刚发布不久,现在肯定有了不少性能优化。

        2. > 我并非否定偏好小型语言的合理性,只是职业生涯中从未见到小型语言在交付速度或减少缺陷方面表现更优的实例。

          我能想象自己陷入某个晦涩语言特性的纠缠,最终导致注意力分散。当然,世间存在诸多晦涩事物,但Go语言绝非其一,它对整个开发环境都产生了深远影响。

          又或者,在选择合适的语言特性时,我可能陷入过度权衡众多选项的困境,最终仍无法从语言正确性角度(确保代码可扩展性、美观性、统一性、与其他特性良好兼容等)做出正确抉择。

          一个与Go无关的例子:bash和rc [1]。理解16页的Duff rc手册就足以让我比用bash时更快地编写脚本。这确实促使我放宽了对程序正确性的担忧,而我对此表示欢迎。整个过程变得更愉快,不再被bash特有的语法束缚。

          或许难以精确衡量具体收益,但这种优势确实存在。

          1: https://9p.io/sys/doc/rc.html

      2. 我从事Go语言开发已有十年,常在编写Go代码时感叹“这实在太枯燥了”,紧接着又补充“但这恰是好事”——因为我确信即便遭遇车祸或因无聊而死,自己编写的Go程序也不会给团队成员带来麻烦。

        相比之下,编写C++就像在解无穷无尽的谜题,总忍不住想搞些“绝妙创意”。

        1. Go还行。我不讨厌它,但也谈不上喜爱。

          它的打包机制确实优于C++或Python,但这没什么好夸耀的。私有仓库的管理简直令人抓狂,更别提最初必须把所有代码塞进特定目录的强制要求,模块化设计更是事后补丁——这些都充分暴露了设计者缺乏批判性思维。

          另外我怀念能用异常处理的日子。

          1. Go刚问世时,其优于Python和C++的包管理确实令人瞩目。它未必首创,但当时主流语言中,很少有不强制用户学习命令式DSL才能添加依赖的。

            1. 没错,但那些语言可没有像Go那样在前几版强制所有代码都必须存放在$GOPATH下的疯狂设计。

              我并非说它糟糕,只是它终究是个相当中规中矩的语言罢了。

              1. 此后该语言已发生巨大变化。不妨抽空重新审视它。

      3. > 我确信完全掌握C#或C++的人类不超过寥寥数人

        那么完全掌握Rust的人类比例又如何呢?

        1. 若比较Rust与C++,我认为完全掌握Rust要容易得多。C++是极其复杂的语言,存在大量特性交互关系。

          C#其实相当复杂。虽不确定是否与Rust处于同一复杂度级别,但要完全掌握其难度应该相差无几。

        2. Rust真正的难点在于 实现 而非语言本身。但不确定其实现复杂度与C++相比如何。

        3. 若具备非GC语言背景,Rust其实并不复杂。

          1. 楼主说的是全部内容,不是日常使用的子集。

      4. 这也是我喜欢JS的原因,它比Go更简单。而Python却有大量令人意外的随机特性。

        1. 从几乎所有维度衡量——语言规范长度、解析难度、上下文敏感关键词与运算符数量等——ECMAScript的复杂度都比Go高出一个数量级。

          1. 没错,我敢说那些宣称JS简单的家伙根本不懂它的原型式面向对象机制。

        2. 恕我难以认同。若能彻底理解JS中this的完整含义,你就会发现这绝非简单语言。它更令人费解,正因如此才有了《JavaScript精粹》这本书。

        3. 我认为JS的复杂性早已臭名昭著:“精粹”这个说法在程序员群体中广为人知。

        4. 不过JavaScript世界将复杂性隐藏在核心语言之外。JS本身并不算太奇怪(尽管永远有“Wat?”视频可看),但实际编写和阅读代码所需的咒语式语法实在离谱。

          等你搞懂TypeScript、选定的模板引擎,尤其是npm世界日益玄奥的构建流程时,投入的时间绝对不亚于学习C#或Java(可能还更多)。不过比起C++或Rust还是简单些。

          1. ……你可知道直接写JavaScript在浏览器运行就行?根本不需要TypeScript、NPM或构建工具。

        5. 别用那句台词来告诉我你根本不懂自己在说什么。

    2. 这倒不错,毕竟Go语言就是专为初学者设计的。

      罗布·派克本人曾表示:“它必须足够熟悉,大致类似C语言。谷歌的程序员们正处于职业生涯早期,最熟悉的是过程化语言,尤其是C语言家族的语言。要让程序员快速掌握新语言并高效工作,意味着该语言不能过于激进。”

      然而,主要的设计目标是缩短谷歌的构建时间。正因如此,未使用依赖项会被视为编译时错误。

      https://go.dev/talks/2012/splash.article#TOC_6

      1. > 这就是为何未使用依赖项会被视为编译时错误。

        https://go.dev/doc/faq?utm_source=chatgpt.com#unused_variabl

        > 不产生警告有两个原因。首先,值得抱怨的问题就值得在代码中修复(反之,不值得修复的问题就不值得提及)。其次,让编译器生成警告会诱使实现对弱案例发出警告,导致编译噪声增加,掩盖了真正需要修复的错误。

        我认为这是个错误(遗憾的是Zig也沿袭了这种做法)。实际开发中存在太多不该被视为编译错误的情况,因此必须运行代码检查工具。当需要临时注释或删除某些代码时,程序甚至无法构建,此时不得不逐层移除未使用的变量/导入项才能通过检查,实在令人烦躁。

        与此同时,未经检查的Go程序充斥着小错误,例如未检查的错误或错误变量滥用导致的漏洞。要是能有警告就好了…

        1. 没错,但单纯回归警告机制反而会导致功能退化。

          我认为正确方案是提供两种构建模式:发布版和调试版。

          调试模式编译极快且允许未使用变量等,但生成的二进制文件运行极慢,可能还会额外添加安全检查(如竞争条件检测器)。

          发布模式为默认模式,严格且运行快速。

          这样开发时可以随心所欲地折腾,但发布前必须清理代码。这也能减轻发布构建速度的压力,允许进行更多优化迭代。

          1. 这毫无意义,发布版仍需运行代码检查工具。为何放弃检测“未用变量”却放任实际有害的代码?

        1. 设计目标与实际成效是两回事 😉

          这次引自Russ Cox:"问:你认为Go试图取代哪种语言?…令我意外的是,新晋Go程序员的原语言背景如此多元。发布初期我们试图向C++程序员推介Go,但实际吸引的开发者大多来自Python、Ruby等动态语言阵营。"

          https://research.swtch.com/gotour

          1. 有趣的是,参与Rust项目的人也提到过类似现象。他们原本预期C++程序员会更感兴趣,却惊讶于Ruby/Python程序员的参与热度。

            我猜想可能是Ruby/Python程序员本就对这类语言感兴趣,却被C/C++排挤了。

    3. 遗憾的是,Rust 剩余的 20% 特性提供了 80% 的实用价值。

    4. 当语言缺乏关键特性时就会成为瓶颈,比如早期多数语言都不支持协同式多任务处理,或是需要编译环境(或反之),又或是垃圾回收机制的缺失。Go语言最初就具备强大的绿线程机制,而据我所知当时没有其他主流语言/运行时能媲美(据说Java现在实现了)。

      人们往往过度重视语法细节差异,比如Scala试图成为更优雅的Java,甚至ObjC与Swift在后者获得async/await特性前的对比。

    5. 我不理解你将Rust视为语言能力渐近线的论述框架。它并非如此,而是自成一套权衡体系。2025年用Go编写浏览器显然不合时宜。但同样存在大量网络服务不适合用Rust实现:为规避GC和Goroutine,你将付出巨大代价(如放弃彩色函数和借用检查器)。

      Rust确实优秀。现代编程实践中最愚蠢的现象,莫过于这两大语言社区间的无谓争斗。

    6. > 虽然这观点可能有诸多谬误,但Go确实让我以20%的付出实现了80%的Rust体验。

      我不认同。能否具体说明你所获得的80%体验?

      类型系统感觉完全不同,我猜语法上的相似性在于Go是分号语言,而Rust虽然本质上是ML却刻意伪装成分号语言——除此之外其实差别很大。它们都算比较现代的语言,所以开箱就能用上不错的工具链。

      但这感觉就像有人告诉我,新开的披萨店做的芝士披萨和那家贴近俗气学生酒吧的小店里的鸭肉河粉有80%相似。鸭肉河粉和芝士披萨并非毫无共通点——在我看来两者都适合用大火快速烹制——但相似之处实在有限。

      1. > 我不认同。能否具体说明你感受到的80%体现在哪些方面?

        我理解为“达到Rust可靠性与性能水平的80%”。这并非指类型系统或语法相似,而是指能获得部分同等效益。

        我可能会说:“C语言能让你用20%的努力达到汇编语言80%的效果。”根据上下文,你完全可以合理推测我在谈论性能。

        1. 是的,我始终在探索语言能实现的内存与CPU使用极限。自然语言处理、文本转换、视频编码、图像渲染等领域皆是如此。

          Rust在性能上胜过Go…但远不及Java、C#或脚本语言(Python、Ruby、TypeScript等)在我实际应用中落后的程度。使用Go时,我几乎无需额外努力就能获得接近Rust的性能,同时拥有完整的标准库/测试套件/包管理器/格式化工具等生态支持。

          1. Go的性能表现与Java和C#处于同一水平,大量基准测试数据佐证了这一点。

          2. Rust是我有幸接触过最无缺陷的语言。它几乎能确保:只要编译通过且编写了测试,就不会出现运行时错误。

            今年我用Rust编写的代码中,仅出现过两个生产环境错误,且都是小问题。而我日常大量使用Rust开发。

            该语言在错误处理方面有着精心设计:Result<T,E>Option<T>match语句、if let结构、函数式谓词、映射操作、?运算符等。

            反观Go语言,它既存在空值问题,又需要极其繁琐的错误检查模板。

            坦白说,在引入错误方面,Go是我除Python、Ruby和JavaScript外最糟糕的语言之一。处理错误和异常行为简直令人抓狂。这导致频繁出错和陷入愚蠢的陷阱。

            欣喜地看到新兴语言正效仿Rust的设计理念——从诞生之初就摒弃空值和异常机制,这简直是天赐之福。

            我渴望一种介于Rust与Go之间的脚本语言:兼具Rust的安全设计理念和Go的快速编译特性,同时实现内存管理与静态类型系统。它将用于我的小型任务和脚本编写。Swift虽不错,但过于苹果中心化,在非苹果平台难以使用。

            坦白说,我完全满意在各种问题领域继续使用Rust。它属于顶级语言。

            1. 我认同你说的很多观点。希望随着Rust技能提升,我会更喜欢它。我讨厌nil/null。

              > Go… 极其耗时的冗余错误检查

              这种说法其实并不准确。因为Go是唯一要求开发者在每个步骤都考虑错误处理的语言。若像处理异常那样直接忽略错误,本质上只是用“整体通过/失败”的假设替代了真正的错误处理。

              若在Rust(或Java等语言)中像Go那样实现真正的错误检查,Go反而往往更简洁。

              这本质上是两种截然不同的错误处理理念,开发者社区对此存在分歧。以下是Rust开发者给出的精辟解释:https://www.youtube.com/watch?v=YZhwOWvoR3I

              1. Go语言中常见的做法是直接传递错误,因为该层级无法处理错误。

                Rust同样强制开发者深入思考错误处理,但在常见的错误传递场景中,其设计更符合人体工学。

      2. 我猜80%的优势在于编译后的二进制文件性能合理且依赖项易于管理?而剩余20%则是Rust编译器严格性带来的额外性能提升和安心保障。

      3. 单二进制部署在Go语言早期曾是重大突破;这或许值几个百分点的优势。此外:语言级设计选择与特性能自动规避整类潜在漏洞。不过编译时间除外 😉

      4. 大胆猜测:在当前JS/Python主导的局面下,这或许只是(现代)编译型语言的天然优势。

    7. 我的情况类似。我一直在寻找一种“不添乱”的语言——不需要为添加几个依赖就得死记硬背专有DSL,还能轻松生成可共享的程序文件,无需担心目标机器是否安装了所有依赖项。

    8. 我主要用Go编程,偶尔写些Rust,最近Zig也在慢慢渗透进来。

      补充前文观点:Go语言诸多设计都致力于提升可读性…虽然某些机制(如错误处理)偶尔显得刻板,但这些看似繁琐的文化规范与编程风格,反而让代码更易阅读。

      可移植二进制文件堪称福音,编译速度迅捷,第三方库管理与vendoring机制的选择更是锦上添花。

      这种80%的满意感不仅源于语言本身,更包含其生态体系带来的种种便利…

      1. 如今借助AI自动补全功能,错误处理编写已不再痛苦——据我经验,其准确率超过95%。

        1. 你说的没错,但…Go社区中确实存在相当一部分人对任何层级的AI/ML/LLM生成的代码反应强烈。

          我常举这个比喻:这些工具对办公室职员而言就像钉枪,但有些人就是固执守旧。

          1. > Go社区中存在相当大一部分群体,对任何层级的AI/ML/LLM生成代码都持强烈抵触态度。

            但你所说的这个Go群体,本身并不介意亲手编写模板代码。对其他人而言,LLM正是为此而生。

  2. 参与新Go代码库的贡献很轻松。

    所有Go代码库都大同小异。不仅语言本身原语极少,标准库、gofmt和golangci-lint强制执行的代码规范,更使得代码库结构高度相似。

    许多语言社区连构建工具都难以达成共识。

    1. 我仍在努力说服合作的科学家们规范代码格式或使用代码检查工具。在Go中强制执行这些规范是个明智之举。

      1. > 我仍在努力说服合作的科学家们规范代码格式或使用代码检查工具。

        若条件允许,建议添加提交前检查钩子。

        1. 我们团队的仓库执行严格规范,但他们的没有。

    2. 刚开始学习Go语言,特别欣赏这种统一性——统一的实现方式,统一的格式规范。不过百分比运算符对负数有点困惑,为此钻了点小研,才明白余数概念与日常认知存在差异。

  3. 对我而言,Go就像被过度简化到荒谬程度的Rust。它会在未经请求时修改代码,删除你刚写好的内容;它缺乏迭代器——每次都必须编写冗长的循环替代。它连在映射中检查键是否存在这样基础的功能都不具备。

    拥护者宣称它内部毫无复杂机制。但我每次都能目睹底层魔法在运作。

    1. 数组追加就是一例。尝试从数组删除元素——你必须依赖某种魔法般的笨拙语法,且完全无法理解底层实际发生了什么(所有文档只告诉你切片是向量片段的指针)。

    2. 枚举创建简直荒谬

    3. 更糟的是,公司代码检查工具强制要求:a) 每个分支都需添加 if err != nil 判断 b) if/else 语句的 for & 条件数不得超过20行。这迫使开发者将函数拆解成无数碎片,让代码沦为企业级Java的翻版。

    实现相同功能时,Go的开发效率感觉只有Rust的一半。

    积极方面在于:

    * 接口更简单,没有Rust某些严格的限制;唯一的问题是在使用代码中,你无法区分接口和结构体

    * 学习起来非常快,我只需几天时间看些示例就能开始编写代码。

    我认为Go语言若具备以下特性会更出色:

    * 完善的枚举类型(即使不包含封装数据也无妨)
    * 合理的数组与切片,去除魔法特性和蹩脚语法
    * 迭代器
    * 结果解包简写语法

    1. 我开发了一种玩具编程语言(编译为Go语言),它基于Go的词法分析器/语法分析器分支,但修改了函数只能返回单一值的规则,从而支持Result[T]/Option[T]类型及错误传播运算符!?

      该语言支持枚举(和类型)、元组、内置集合 Set[T] 及完善的迭代器方法。其类型推导的lambda函数设计精妙(深受Swift语法启发)…诸多亮点!

      https://github.com/alaingilbert/agl

    2. > 它缺少迭代器——每次都得写大段循环替代

      确实 有迭代器——https://pkg.go.dev/iter

      > 缺少基础功能如检查键是否存在于映射中。

      什么?value, keyExists := myMap[someKey]

      > 尝试从数组移除元素——必须依赖某些魔术和笨拙的语法,且底层机制毫无清晰说明(所有文档只告诉你切片是向量片段的指针)。

      首先,若需删除数组中间元素,99%情况下说明你选错了数据结构。若在循环中操作,性能必将严重退化。

      其次,https://pkg.go.dev/slices#Delete

    3. > 规范枚举

      它具备规范枚举。诚然缺少枚举关键字,这似乎让许多人困惑。

      或许你真正需要的是合集类型?鉴于你提到了Rust——该语言奇怪地[1]用enum关键字表示合集类型——这种可能性很大。Go确实缺乏这种特性。但合集类型并非枚举。

      > 合理的数组与切片,没有魔法和笨拙的语法

      其数组和切片与C语言完全一致。确实会让习惯了魔法包装的语言用户感到困惑,但你指出的问题恰恰是魔法的 缺失 。要帮助习惯魔法的用户,需要添加魔法而非去除。

      > 迭代器

      你认为它们存在哪些不足?在我看来它们与其他语言的迭代器并无本质差异,尽管承认匿名函数模式确实稍显非传统。但这完全没问题。

      > 结果展开简写

      Go语言多年来一直想添加这个功能,但始终无人能提出合理的实现方案。各种表面解决方案只能解决50%的问题,却无人愿意攻克剩余50%。我想,强迫别人卷起袖子干活终究行不通。

      [1] Rust通过枚举生成合集类型的标签作为实现细节,因此其设计初衷虽不似表面那般怪异,但仍令人费解——为何要基于实质隐藏的实现细节命名,而非依据用户实际需求命名?很可能最初采用标准枚举,后来发现合集类型更优却未同步更新关键字命名。

      不过话说回来 Swift 也干过同样的事,谁知道呢?公平地说,Swift 的 “enums” 为了兼容 Objective-C 还能降级为标准枚举,虽然理由不充分,但至少能理解这种设计思路。至于 Rust…

      1. > 它确实有标准枚举。

        但它们看起来很别扭,总让人觉得像语法滥用。

        > 它的数组和切片完全遵循C语言的写法。虽然确实会让习惯了魔法封装的语言用户感到困惑,但你指出的问题本质上恰恰是魔法的缺失。

        在Rust中,我能清晰看到操作对象的本质——真正的向量、实体数据,或是作为向量视图的切片。此外Rust的切片始终是连续的,从元素a开始到元素b结束。我能随意移除向量中间的元素,但切片是只读的,根本无法操作。向量仅支持尾部追加。若需插入中间元素,文档会明确警告:后续所有元素都将向前位移。这里完全没有魔法。

        而在 Go 中,如何在数组中间插入元素?我看到这样的建议:myarray[:123] + []MyType{my_element} + myarray[123:](删除操作类似:myarray[:123] + myarray[124:])。

        我在这段代码中处理的是什么,之后又会得到什么?这是否是一个复杂的切片,同时维护着3个视图——2个指向myarray,1个指向匿名切片?

        网上文档指出Go语言中的切片与Rust完全相同,都是数组元素的连续序列。若果真如此,在我插入元素的示例中(删除时亦然),底层必然存在大量复杂操作。

        1. > 这样看起来很别扭,感觉像是语法滥用。

          所以无需担心?

          > 如何在数组中间插入元素?

          与C语言相同。若数组分配空间充足,可将右侧元素移至相邻内存位置,再替换中间值。

          示例如下:

              replaceWith := 3
              replaceAt := 2
              array := [5]int{1, 2, 4, 5}
              size := 4
              for i := size; i > replaceAt; i-- {
                  array[i] = array[i-1]
              }
              array[replaceAt] = replaceWith
              fmt.Println(array) // 输出:[1 2 3 4 5]
          

          若数组容量不足,则无法实现替换。如同C语言,Go数组必须在编译时定义固定大小。

          > 网络文档指出Go语言的切片与Rust完全一致,都是数组元素的连续序列。

          其实现方式恰似C语言的切片:

              struct slice {
                  void *ptr;
                  size_t len;
                  size_t cap;
              };
          

          Go语言真正新增的特性(除将切片设为内置类型外)仅有[:]语法,这在C语言中并不存在。

          但它与Rust的实现也并非完全一致。严格来说,Rust的切片结构类似于:

              struct slice {
                  void *ptr;
                  size_t len;
              };
          

          当然存在明显重叠之处——毕竟最终仍需在同一台计算机上运行。但Rust内置的魔术特性足以隐藏底层细节,我认为上述描述未能体现这种微妙差异。而Go则完全沿用了C语言的模式,因此理解C语言实现方式的人自然也能掌握Go的用法。

          当然,这意味着操作层级比某些开发者习惯的更低。Go 倾向于让高成本操作显而易见,这是它愿意做出的权衡。但无论如何,若要让来自“魔法世界”的开发者更熟悉它,反而需要更多魔法,而非更少。

    4. Go 拥有迭代器,且已存在相当长一段时间。要从切片中删除元素,可使用slices.Delete

      >3) 更糟的是,公司代码检查工具禁止合并分支,除非满足:a) 每个分支都包含if err != nil判断 b) if/else语句的for &条件数不超过20。这迫使你将函数拆解成无数碎片,让代码沦为企业级Java的翻版。

      这并非Go语言的问题。

  4. 初学Go时我曾持怀疑态度,但它很快成为我最爱的语言。我欣赏它简单却强大的特性。

    若能施展魔法,我仅会添加三项改进:更完善的空值检查、默认附带错误堆栈跟踪,以及对合集类型的穷举检查。除此之外,它已满足我所有需求。

  5. 若Go能增加几项函数式编程特性,它很可能成为我最爱的语言。特别是不可变性、空值处理,以及可能的穷举式switch语句。这样它或许就能臻于完美。

    工作中我们使用Uber的NillAway工具,多少能缓解些问题。https://github.com/uber-go/nilaway 不过若能由类型系统直接处理会更理想。

    1. 若Go能支持总和类型且消除空指针,那简直太棒了!这梦想是否太过奢侈?Gleam似乎接近这个目标,但它又偏离到其他方向去了。

  6. 我喜欢Go。作为Python转投者,我欣赏它将多数特性显式化而非依赖魔法,更欣赏并发机制不像拼凑的噩梦般令人头疼。

    在$DAYJOB写微服务时,即使需要更多前期代码,也感觉轻松得多且减少了猜测——因为每个组件的功能和存在理由都清晰可见。

    1. 今年终于抽空学习Go,体验与上述描述高度契合。

      相比Python,它确实是更简洁的语言和生态系统。更重要的是,性能表现远超预期!

  7. 工作中我每天都在用Go,完成个人项目时它仍是我的首选。这门语言年年进步,Go团队继续加油!

  8. Go是我最爱的编程语言。记得初遇Go时,我正用Java学习Akka并行框架,发现Go的代码量远少于Java,且能轻松理解。此后我便频繁使用它,虽仍觉自己掌握得不够精深,但它确实助我高效完成工作。值此Go语言诞生16周年之际,谨此致敬。

  9. 欣喜看到保龄球开发团队正专注于确定性工具链建设,例如通过gopls实现语言服务器协议,并利用静态分析技术配合go fix自动修复代码。

    近期我也同样强调了Go在大型语言模型/AI编排领域的优势。

    https://news.ycombinator.com/item?id=45895897

    考虑到谷歌作为巨型服务公司的规模,其内部下达禁令要求团队不得使用Python工具链开发生产级代理或工具链,转而采用Go语言,我丝毫不感到意外。

    1. 记得大约15年前Guido还在任时,就曾被告知"我们不该用Python编写新服务。起初看似简单,后来却变得混乱不堪,最终不得不重写。"我记得主要问题在于性能和工具支持,还有类型系统缺失——虽然后来这些都改进了,或许他们现在更包容了。

  10. 我很庆幸Go语言的存在。至少它证明了工具链的重要性不亚于语言本身。

  11. 六七年前我在项目中尝试使用Go时,曾因需要直接从源代码库获取包且缺乏内置版本控制而震惊。这让我彻底放弃并回归Python。听说现在有了Go模块的新系统,或许该重新审视它了。

  12. 自2020年起它便成为我的首选语言。当时主管要求我一周内完成任务,只让我浏览Go playground写代码(涉及SNMP收发功能)。令我惊讶的是它如此易学、易写,更重要的是易于测试。近期唯一未掌握的是泛型,希望尽快攻克。作为Java背景的开发者,Go的诸多设计让我惊叹其巧妙,简直难以置信

  13. 我刚完成首个Go语言副业项目——一个协调多方业务流程的Web平台。不仅获得报酬,还收到更多功能需求。前端采用Caddy,整个系统在5美元的VPS上运行完美。我爱Go。

  14. 感谢Go语言让我轻松接触静态类型系统。

    记得开发小程序时,结构体遗漏的类型错误会神奇地出现在所有正确位置。这段经历彻底改变了我。

  15. 我虽不甚熟悉Go语言,但不禁思考:若没有谷歌的支持与维护,它会走向何方?毋庸置疑,这是一种扎实的语言,背后凝聚着语言设计领域顶尖人才的智慧。发布编程语言易如反掌,但长期维护与精进却难如登天。

  16. 自2014年起便愉快地使用Go开发。职业生涯中接触过C、Python、C#、Ruby等多门语言,但始终对Go情有独钟。

  17. 热爱Go!它温柔地引领我踏入系统编程的世界。

  18. 天啊,谷歌碾压Go!语言竟已16年。那个早于Go十年问世、本应拥有同名权的语言,在谷歌仍假装“不作恶”是其品牌标识的年代遭遇了不公。

    盗亦有道,语言开发者更应恪守操守。当谷歌对Go!使出卑劣手段时,便昭示了其真实面目。

    状态改为不幸

    https://github.com/golang/go/issues/9#issuecomment-66047478

    1. 还有人用那个语言吗?没有!

      1. 重点不在这儿,99%的语言都没人用,按这标准岂不是大乱斗?编程语言圈子小,规范才重要。

        编程语言命名规则如下:

        1. 先使用名称者拥有命名权。优先权判定依据:规范发布时间或首个仓库提交时间。

        2. 仅当原作者放弃原项目时,名称方可被复用。通常设有宽限期,时长取决于项目开发周期。但若项目已弃置,则无人能阻止他人接手该名称。

        3. 任何情况下,PL开发者均不得使用当前活跃的PL项目名称。若发生此类情况,应由最新命名的项目更改名称,而非要求历史更悠久的项目更名——即便新项目拥有资金支持。

        4. 具有历史争议性的语言名称本质上已被“退役”,尽管它们并未停止开发。

        这一切都合理,因为编程语言命名空间仍存在大量未被占用的空间*。有大量单音节英语单词可供选用——各类动物、动词、名词以及常见的人名都可成为语言名称。仅因与公司品牌存在关联就践踏他人成果,实属毫无道理。

        因此,像肯·汤普森和罗布·派克这样的行业先驱竟联手支持谷歌滥用权势打压其他开发者的成果,在被要求停止时还摆出“你敢来试试”的姿态,这种行为堪称卑劣至极。

        * 当然这不适用于单字母语言,但即便如此,该命名空间也仅有不到25种语言处于活跃开发中。

  19. 我实在无法忍受这种愚蠢的语法。

    与其写“int x”

    却要写成“var x int”

    这种写法模糊了类型,降低了代码可读性。唯一理由是16年前某位仁兄自以为聪明。但对99.99%的代码而言,这语法更糟糕——日常编程中谁会用八层指针重定向?

    1. 16年这个说法还算保守。我认为最早采用这种声明形式的流行语言是Pascal。

          var
              foo: char;
      

      Go语言由众多C语言核心开发者参与设计,惯性思维本应使他们采用C式声明。虽不知他们是否公开过选择Pascal式声明的原因,但我敢打赌:Pascal式声明对计算机而言解析起来更简单高效。这种风格不仅提升编译速度,还能显著增强语法高亮的可靠性并加速工具开发。

      当然,若习惯了C语言“类型在前,标识符在后”的风格,初始阶段可能会感到不适,但很快就能适应。随着时间推移,这种风格已被众多现代语言所采用。我随口就能列举出TypeScript、Python类型提示、Go、Rust、Nim、Zig和Odin都采用这种风格。我向Claude求证后得知,Kotlin、Swift以及各种变体的ML和Haskell也使用此法。

      不过嘛,如果你依然钟爱变量前置类型声明,PHP永远是你的后盾。

          class User {
              public int $id;
              public ?string $name;
      
              public function __construct(int $id, ?string $name) {
                  $this->id = $id;
                  $this->name = $name;
              }
          }
      
      1. > 不确定他们是否解释过采用Pascal风格的原因

        虽不确定是否为此,但三位创始人之一的Robert Griesemer确实有Pascal/Modula背景。

    2. 类型推断会如何实现?

      你可以写

      var x = 5

      但若强制类型前置会怎样?后期添加推断的语言往往用“auto”表示类型,看起来很糟糕。

  20. 编程语言设计史仍有太多值得学习的教训。

    或许到18岁或21岁时,成熟度终将稳定下来。

  21. 对我而言,Golang是优秀的运行时环境,却是糟糕的语言。或许能习惯类似C语言指针的语法,也能忍受半数代码都在检查err != nil,但缺乏类的设计实在过分。Go语言的典型做法是构建庞大的微服务集群,通过网络相互通信来管理复杂性,而非采用类结构。这种模式适用于系统代理(如K8)等场景,但对多数应用而言毫无意义——它徒增开发复杂性,而单体架构反而更易于调试。

    我不会用Go语言处理包含大量业务逻辑的大型代码库。Go语言并未撼动大型企业中Java的使用地位,没有大型企业会尝试用Go语言替换Java代码库——因为毫无优势可言。Java运行速度几乎与Go相当,且拥有类系统和更丰富的并发原语集。

    1. 我认为缺乏类系统反而极具价值。太多企业代码充斥着拙劣的抽象设计。

      Go语言需要增强函数式特性,例如迭代器、结果类型和模式匹配。

        1. 感谢提醒!之前没注意到这个特性,期待实际应用

  22. > Go 始终恪守兼容性承诺——旧版功能将永久有效…

    在 Go 1.22 版本对三子句 for 循环进行语义变更后,他们竟仍坚持此承诺,实在令人费解。

    当 Go 模块从 1.21 升级至 1.22+ 时,存在某些难以及时检测的潜在破坏性案例。https://go101.org/blog/2024-03-01-for-loop-semantic-changes-

    Go工具链1.22确实破坏了兼容性。就连核心团队也承认了这一点。https://go101.org/bugs/go-build-directive-not-work.html

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: