Go 是一种 80/20 语言

Go 是最令人讨厌的编程语言。与其他语言相比,它以 20% 的复杂度提供了 80% 的实用性。这种讨厌情绪来自那些希望获得 81% 实用性、85% 或 97% 实用性的人。

正如 Rob Pike 所说,没有人否认 87% 的实用性比 80% 更高。问题在于,额外的 7% 实用性需要多出 36% 的工作量。

以下是一些例子。

有人在 HN 上抱怨 结构体标签 (struct tags)没有注释或宏那么强大。我 解释 这是 80/20 设计。

Go 标准库中的 测试支持 仅有几百行代码,多年来变化不大,却提供了你可能需要的所有基本测试功能。它没有提供你可能想到的所有便利功能。这就是 Java 的 jUnit 库所做的,代价是数万行代码和数年无休止的开发。Go 是 80⁄20 设计。

元素周期表与 C# 或 Rust 中的异步相比,Goroutines 是并发性的 80⁄20 设计。它没有那么多的功能和旋钮,但复杂度却只有一小部分(对于用户和实施者而言)。

Go 推出时,它没有用户定义的泛型,但需要它的内置类型是 泛型:数组/切片、映射(maps)、通道(channels)。这种 80⁄20 设计在十多年里为 Go 提供了很好的服务。

大多数语言都难以抗拒以 400% 的成本追求 100% 设计。C#、Swift、Rust——它们似乎都陷入了不断添加功能的无休止循环中。甚至 JavaScript,它最初是一个 70⁄15 语言,但后来也被那些以添加更多功能为工作的人所占据。

如果80/20是好的,那么70/15会更好吗?不,不会。Go已经证明,没有枚举类型也能拥有流行的语言。我认为没有结构体就无法拥有流行的语言。存在一条底线,低于这条底线,语言就不再足够实用。

最后,什么是“工作”?

这里指的是语言的用户的工作量。语言的每一项新增功能都要求程序员去学习它。这比表面看起来要复杂得多。如果你将函数作为第一类概念,那么学习的不仅仅是语法和功能。你还需要学习新的编程模式,比如返回函数的函数。你还需要学习柯里化、将函数作为参数传递等概念。你不仅需要学习“如何”使用,还需要学习“何时”使用:何时应该使用这种强大的功能,何时不应该使用。

你无法跳过这种复杂性。即使你决定不学习如何将函数作为第一类概念使用,你的同事可能会使用,而你必须能够理解他的代码。或者某个有用的库使用了它,或者某个教程提到了它。

这就是为什么80%以上的编程语言需要编码规范。谷歌为C++制定了规范,因为如果没有对个人程序员可使用功能的限制,数百名程序员将无法有效协作开发共享的C++代码库。谷歌的C++风格指南旨在将C++的复杂度从95%降低到90%。

另一项工作是由语言的实现者完成的。Swift 就是一个警示案例。尽管在苹果公司的优先项目上,由一群非常聪明的人花了超过 10 年的时间开发,且预算几乎无限,但 Swift 编译器仍然运行缓慢、容易崩溃,且在跨平台方面没有实质性改进。

他们设计了一种无法正确实现的语言。相比之下:Go 是一种简单但功能强大的语言,从 1.0 版本开始就具备快速、跨平台和健壮的特性。

384 Responses to Go 是一种 80/20 语言

  1. zjm555 says:

    Go 是最令人讨厌的编程语言

    哦,你一定没听说过 JavaScript

    • sambeau says:
      • 或者 PHP
      • 或者 Perl
      • 或者 Objective-C
      • 或者 Java
      • 或者 C++
      • 或者 COBOL
      • :
      • moger777 says:

        对bash没有爱(我是说恨)?

        • syklemil says:

          这也不是人们特别会为之辩护的东西。你听到的厌恶通常是某种来回争论的一部分。bash在某种程度上作为粘合剂存在于各个地方,介于那些更愿意使用POSIX shell(#!/bin/sh而不是#!/ bin/bash,可能由dash或其他非GNU Bash提供),以及像我这样的人,更愿意将shell仅用于某种配置好的程序调用,即使如此,也会使用set -euo pipefail尝试严格模式(它仍然没有你想象的那么严格),并使用shellcheck来捕捉更多“哦,该死,Bash”

          • butt_fun says:

            Bash是最奇怪的东西。大约十年前我被告知的一条经验法则是:除非脚本少于十行、没有比单个if语句更复杂的控制流、且不需要任何数据结构(否则直接用Python)

            在我看来,这一原则在实际应用中大多成立。总体而言,Bash 脚本(从广义上说)已显过时。

            • PurpleYoshiEgg says:

              我已掌握足够的 Bash 技能,可使用哈希表、理解字符串分割的细节,并能通过完整的 GNU 风格长短选项实现预期功能。

              我只能说:除非你想接受挑战,否则最好不要使用。

              如果我需要一个看起来像命令行程序的东西,我就使用Perl。如果我需要哈希表,同样使用Perl。如果我需要超过几屏的内容或更复杂的逻辑,同样使用Perl。

              Perl 在 Unix 世界中的定位非常精准,尽管它不像 Bash 那样支持管道(坦白说,这是任何语言中都非常直观且强大的功能,以至于我在其他语言中都感到缺乏),但变量行为的一致性与 Bash 相比是一个强大的优势。

            • shevy-java says:

              如果我需要一个看起来像命令行程序的东西,我就会使用 Perl。

              对我来说也是如此,不过我使用的是 Ruby。

              Perl 在 Unix 世界中的定位非常精准,尽管它不支持像 Bash 那样使用管道

              为什么不支持?我有点困惑。在 Ruby 中,我们也可以通过 ARGF 从文件中接受输入。而且管道本质上就是一个方法调用,所以方法链在这里是自然而然的。

            • PurpleYoshiEgg says:

              我特指管道作为从左到右的函数应用,比如command_a | command_b | command_c(这在大部分语言中可以轻松重写为类似从右到左的函数应用command_c(command_b(command_a(())))。

              对我来说,管道是一个很棒的功能,因为它意味着你按照读取顺序应用函数,因此更容易扩展以提供增量功能(特别是在你试图将数据增量转换为你需要的形式时,通常通过 sed 和 awk 实现)。

              Bash 的额外优势在于支持并行处理,因此第一个命令的行缓冲输出可以在第二个命令读取并生成输出时,为另一行生成输出。遗憾的是,这在 Perl 中并不简单(可能是因为其支持的操作系统可能不支持正确的进程 fork,例如 Windows 或其他非 Unix 类操作系统)。

            • ktoks says:

              试试 Nushell。它拥有所有实用功能且极易阅读。

              创建类似 pandas 的高效数据管道非常简单。

              确保获取内置 Polaris 的版本。

            • equisetopsida says:

              Perl 是大多数 Linux 发行版的默认安装包

            • syklemil says:

              是的,这也是我的基本态度。其中一部分原因是,如果脚本很可能继续增长,那么最好早点退出,而不是先编写复杂的 Bash 脚本,然后在发现这是一个糟糕的主意时再将其移植。

            • shevy-java says:

              除非脚本少于十行

              我将这条规则应用于每次需要编写 shell 脚本的情况,无论其行数多少。

              Ruby 取代了所有这些逻辑。Ruby 就像我与计算机之间使用的粘合剂。它实际上是 C 的封装层。

              我认为唯一能认同的批评是 Ruby 的速度不如 C。对于小型脚本这无关紧要,但在处理更多任务的大型脚本中,例如加载并处理一个巨大的 .yml 文件时,我能明显感受到速度差异。(或许应该转用 SQL,但 .yml 文件修改起来相当方便。)

          • apadin1 says:

            Python 已经基本上取代了 Bash 成为首选的脚本语言。只有那些已经熟悉 Bash 的人仍然使用它来编写脚本

            • syklemil says:

              是的,我已经淘汰了一些复杂的 Bash 脚本,通常使用 Python 来完成这些任务。

              Python 在分发方面也可能有点麻烦。如果你知道目标操作系统并能通过包管理器安装软件,或者你将软件打包在容器中,那就没问题。uv 可以帮助你。在 Python 中确保有正确的库可用,与在 Bash 中确保有正确的可执行文件可用,基本上是同一个问题,所以它们在这一点上其实有点相似。但 Python 允许你在 pyproject.tomlrequirements.txt 等文件中声明依赖项,而 Bash 没有类似的约定,而且你不需要以 root 身份运行 uvpip

              但 Go 也抢占了部分市场。许多 Go 用户来自 Python,而发布一个静态二进制文件确实有其优势。我们有一些 Go 代码,长度在 20-30 行左右,如果是在十多年前,这些代码肯定会用 Python 或 Perl 实现。

              不幸的是,尽管 Go 很容易上手,但似乎很多人没有意识到,普通的 git 对于二进制文件来说相当糟糕,因此将二进制构建结果提交到仓库并不是最佳的分发策略。

        • beyphy says:

          讨厌PowerShell的人比讨厌bash的人多。

          • wvenable says:

            我讨厌PowerShell并非因为它比Bash差(它并不差)。而是因为至少Bash有充分的理由解释其怪异之处——它是在几十年前为性能仅相当于我腕表的机器拼凑而成的。PowerShell则没有这样的借口。

          • no_brains101 says:

            PowerShell还可以。Bash更快,管道的异步处理很棒,但PowerShell的哈希表更好,也有一个糟糕的结构体之类的东西。

            PowerShell在简单任务上更差,但在复杂任务上更具表达力,但速度更慢,而且到那时你应该使用另一种语言了

          • Coffee_Ops says:

            认真使用过两者的用户大概都讨厌 Bash。

            • IDENTITETEN says:

              我对两者都算专家,但同样讨厌两者……

              它们都有一些非常奇怪的特性,最终会让你陷入困境。

              我认为两者都不应用于除胶水代码以外的任何场景。

              我见过太多用这两种语言构建的过于复杂的系统,维护起来噩梦般困难,本应使用具备完善开发工具的正规编程语言。

            • Coffee_Ops says:

              在这些语言中构建复杂系统的理由是它们能在任何环境中运行。

              我见过很多环境对随机可执行文件或运行时环境(如Python)感到不安。

              大多数环境都允许运行Bash或PowerShell。

              是的,权衡在于维护噩梦,但至少环境中的每个人都应该能够阅读代码。

            • beyphy says:

              我在两者方面都是专家……使用一种拥有完整开发工具的正规编程语言会更好。

              对于PowerShell,你可以在VSCode中用.ps1文件编写任何脚本。你觉得PowerShell缺少正规编程语言和开发工具的哪些功能?

            • Coffee_Ops says:

              尝试为PowerShell创建一个严肃的CI/CD管道将是一场噩梦,因为PowerShell 7缺少许多你在脚本中需要的类,而PowerShell 5需要Windows系统。

              因此,如果你想创建一个严肃的管道,你不再需要启动一个运行PowerShell Core的Docker容器,而是需要启动一个Windows实例来测试你的脚本。

              PowerShell 是单线程的,其常见使用模式更倾向于交互式操作而非程序构建。而且它的性能坦白说并不理想。

              当然你可以做到,但我的经验是这会变得一团糟,而很少有人具备这种 PowerShell 经验的事实也无助于改善情况。

              使用 Python、C#、Java 等语言构建会轻松得多。

          • ktoks says:

            150%。

            此外,还有几个版本彼此完全不兼容。

        • airodonack says:

          Bash最大的缺点在于它诞生于C语言成为通用语言之前,因此对现代程序员来说显得笨拙。不过Bash中仍有一些非常有趣的理念,使其独具魅力。

          • mzalewski says:

            这不正确。Bash于1989年首次发布,当时所有人都想摆脱C语言。

            也许你指的是原始的sh,有时被称为Bourne shell,它是在20世纪70年代中期开发的。

            但也不对,人们并不是抱怨 Bash 对熟悉 C 语言的人来说很奇怪。Bash 只是一个糟糕的编程语言。它充满了意外,晦涩的语法,而解决问题的最明显方法通常是错误的。令人惊讶的是,你需要学习多少才能正确使用它。你可以在更短的时间内学会足够的 Python。

            • BornToRune says:

              当你开始需要 POSIX 标准 shell 的 Bash 特定功能时,你本应早已使用某种合适的语言来完成该任务。

            • braiam says:

              Bash 作为一种脚本语言,用于调用二进制文件并将其连接在一起,它做得很好。

            • manzanita2 says:

              同意,但一旦你开始编写任何类型的循环,就需要考虑升级编程语言。当那门语言是C时,我能理解你为何坚持使用。但如今有了Python等语言,继续使用C就毫无意义了。

            • no_brains101 says:

              在Bash中使用循环没问题。但当你需要处理多个数组或哈希表时,就该考虑切换语言了

            • pokeybill says:

              Bash循环并非一无是处。我作为Python软件工程师已有近20年,仍有许多问题更倾向于使用Bash和Linux内置工具,甚至搭配少量awk来解决。关键在于不要试图用Bash替代其他语言或工具更擅长的领域。

              我不会为了调试Python解释器的运行时问题而登录服务器。我使用 Bash、grep、awk 等工具,除非控制结构需要相当复杂,那时我会快速编写一些 Python 代码。

            • dagbrown says:

              它从来就不是一种“语言”。它的设计初衷是与 Bourne 壳兼容,但同时提供实际可用的交互式支持——比如命令行历史记录和编辑功能。Bourne 壳从未具备过这些功能。即使是用于从历史记录重复命令的 !371 功能也来自 csh,而非 Bourne shell。

              如果你想要一个更像 Bourne 的编程 shell,你需要看看 ksh,它以与 Bourne 产生 Bourne-Again 类似的方式产生了 zsh。一些 ksh 功能被引入了 bash,但并非以全面的方式,因此 bash 现在是一个 Bourne 壳(带有其所有限制和怪癖)加上足够多的 ksh 风格,使得 bash 壳脚本与旧式的 Bourne 壳不兼容。

              bash 成为通用语言的原因是,它在 1992 年是最容易移植到 Linux 的 shell,因为当时 GNU 工具链的其他部分已经存在。

            • valarauca14 says:

              bash 成为通用语言的原因是,它在 1992 年是最容易移植到 Linux 的 shell,因为当时 GNU 工具链的其他部分已经存在。

              另一个关键因素是perl。1991年,著名的《骆驼书》(Perl编程)出版。一旦perl成为可行产品,“交互式shell作为脚本语言”就不再是优先事项。当Debian和Ubuntu转向dash时,几乎无人察觉。

              Bash之所以胜出,不仅因为它是早期默认选择,还因为它长期处于无人关注的状态。一旦它成为“标准”,世界便继续前行。

          • Bwob says:

            我认为Bash最大的缺点在于,有人曾提出“结束代码块的最佳方式是将关键字倒写”的荒谬想法,而当时无人及时制止。

            但仅限于某些代码块!

            if 块以 fi 结束,因为这合乎逻辑。case 语句?没错,用 esac。但 whilefor 循环?则以 done 结束。我只能假设,此时有人终于从 Brian Fox 手中夺回了键盘,以拯救所有人。

            我不确定这是否能表达清楚,但每次不得不与 Bash 脚本交互时,我感到的愤怒真的难以用语言形容。

          • theLittleGreenGuy says:

            喜欢?

        • Coffee_Ops says:

          PowerShell 粉丝们团结起来

        • gc3 says:

          行尾的空白字符可能破坏一个需要运行4小时的脚本,这很糟糕

        • agumonkey says:

          太忙于阅读Greg的维基,没空抱怨

        • shevy-java says:

          我不知道。我原本不喜欢shell脚本,但最终还是用ruby脚本解决了问题。每次需要shell脚本时,我都会用ruby编写对应的逻辑。(现在只剩下少数遗留和启动shell脚本)

        • -lq_pl- says:

          问题总是:既然每个发行版都预装了Python,为什么还要用bash?

      • qthulunew says:

        现代PHP其实没那么糟糕

      • erhmm-what-the-sigma says:

        Objective C不错。

      • shevy-java says:

        嗯……PHP是一种相当丑陋的语言,但我并不讨厌它。我在PHP中的生产力甚至比在Perl中更高。

        Objective-C有点奇怪;但我并不讨厌它。

        Java 我觉得还行。虽然仍然过于冗长,但随着时间的推移,它已经有所改进。COBOL 我不确定人们是否讨厌它;毕竟使用它的人太少了。

        C++ 确实有些复杂(人们尤其讨厌它的复杂性),但它也非常强大。

        我真正讨厌的唯一一种语言——而且我绝非孤例,从上面帖子作者获得的赞同票数就能看出——是 JavaScript。这种语言糟糕透顶。而我不得不使用它……这让我心情糟糕透顶。

      • ktoks says:

        基本上任何较旧的语言都可能让很多人讨厌。

        我最不喜欢的在这份列表中是Cobol和PHP,紧随其后的是Perl,但如果代码没有严格的……那就是我最不喜欢的。

      • pjmlp says:

        有大家讨厌的语言,也有不常用的语言。:)

      • bitman2049 says:

        赞同,Perl是垃圾。只写不读的语言。

        • PurpleYoshiEgg says:

          我认为阅读他人编写的Perl代码从未遇到过问题,只要他们使用了strict和warnings指令。这两者非常普遍,我很少看到缺少它们的情况。

    • Bedu009 says:

      咳咳 Java想说几句

    • badfoodman says:

      我不明白你怎么能写出这样一篇文章,而且在第一句话就犯了如此严重的错误。

      其实也许我可以理解,因为Go或Rust的支持者会做出这样的断言:他们为了宣传自己的语言而头脑被蒙蔽了,忘记了人们并不讨厌这种语言,他们讨厌的是听到为什么<其他语言>如此糟糕,我们都需要转换到<语言>。

      • kjk says:

        这叫做诗意的自由。你太认真地对待我的思考了。

        此外,我敢肯定Go是HN上最不受欢迎的编程语言,而HN是我主要的技术讨论来源。在Reddit上它也同样不受欢迎。

        此外,在整篇文章中我从未建议过你应该转用Go。事实上,别这么做。我可不需要有竞争力的对手。

        • chat-lu says:

          我们有相关数据。Stackoverflow每年都会进行一次调查。

          最受欢迎的语言是 Rust,最不受欢迎的是 Matlab。

          Go 的 67.7% 虽然远不及 Rust 的 82.2%,但仍然是排名最高的语言之一。

        • Whatever4M says:

          使用一种语言的人越多,有生产力的竞争对手就越多,但这并不是零和游戏,使用 Go 的人越多,它的工作量也就越大。

    • jdeneut says:

      肯定是SQL,尤其是存储过程。

    • s0ulbrother says:

      我目前参与的项目使用的是JS和Go

    • mothzilla says:

      或者Modula 2

    • Mrseedr says:

      这个问题是检验一个人偏见的一把试金石,哈哈

    • Mr_Axelg says:

      最近在推特上遭到一些批评,我认为完全没有必要

  2. rcls0053 says:

    我讨厌现在80/20原则被用于一切事情

  3. scottt732 says:

    简而言之:作者喜欢分数

  4. cashto says:

    如果 err!=nil 返回,可能是 80%

    • TomWithTime says:

      这很好,因为当高管强迫晚上使用 AI 时,我可以说是它写了 80% 的代码,仅限于错误检查。

      • syklemil says:

        现在甚至连 Go 团队本身都推荐了

        编写重复的错误检查可能会很乏味,但如今的 IDE 提供了强大的、甚至由大语言模型 (LLM) 辅助的代码完成功能。对于这些工具来说,编写基本的错误检查非常简单。在阅读代码时,冗长性最为明显,但工具也可以在这方面提供帮助;例如,具有 Go 语言设置的 IDE 可以提供一个切换开关来隐藏错误处理代码。其他代码部分(如函数体)已经存在这样的开关。

        为什么让编译器做大语言模型 (LLM) 可以做的事情呢?毕竟,LLM 的复杂程度要低得多,也不需要像编译器那样花费大量时间和资源。:)

        • fear_the_future says:

          这一定是开玩笑。

          • cashto says:

            Gophers:我们的工具已经如此强大,我们甚至不需要再编写或查看 iferrnotnillreturns。

            其他语言:看看他们需要模仿我们力量的多么微小部分。

          • Dospunk says:

            我认为这里隐含了一个“/s”

          • afiefh says:

            当我看到这篇文章甚至提出了一些非常合理的错误处理方案时,我感到惊讶!我最喜欢的是当然是func()?,如果需要修改错误(例如添加调用堆栈或错误消息),可以扩展为func()? { return modify_error(err); }

            • syklemil says:

              是的,多年来一直有许多关于更人性化错误处理的提案。但它们似乎都无疾而终,因为

              • 大量Go开发者无法接受函数在没有return关键字明确标注的情况下返回值。个人而言,我也能理解?的作用,但更困惑于日志语句导致整个程序终止,不过我认为Gopher开发者和我只能在这一点上各持己见。
              • 对于如何处理更复杂的场景存在分歧。依我之见,这完全可以通过参考另一种使用 ? 的语言来解决:

                • 确切地说,foo()? 应仅限于原型和临时代码。
                • 类似于

                  • foo().static_context(“foo should be able to run”)?
                  • foo(bar).fmt_context(“foo(%v) 应该能够运行”, bar)?

                  应该适用于需要提供上下文的情况,参见 另一种语言中广泛使用的上下文特性的例子。由于 Go 使用元组而非 ADT 进行错误处理,可能需要其他方法而非点方法,但该思路存在且可借鉴。

                • 对于更复杂的场景,我们仍希望使用 err := foo(),不期望 ? 方法覆盖 100% 的错误处理。这似乎也让部分 Go 开发者感到不适,因为他们希望仅有一种统一的实现方式。

              • 关于语法,存在无休止的争议。有人认为 ? 占用的空间不够。我个人认为,能够区分 =:= 的人也应该能看到 ?,但再次强调,我认为我们只能在这一点上各持己见。

              • 有些人可能只是患有“非我发明症”的严重症状;另见:泛型、迭代器。

        • TomWithTime says:

          为什么让编译器做大语言模型(LLM)能做的事呢?毕竟,LLM 的复杂程度远低于编译器,也不需要像编译器那样花费大量时间和资源。:)

          每次AI出错并编写出无法编译的代码,或错误地推断变量类型和方法签名时,我都忍不住发笑。它在一些本应轻松处理的琐事上屡屡失误。一个合格的编辑器本身就能查找定义、进行基本代码检查或编译并检测错误。

          我原本預期 AI 會出現運行時錯誤,但它連如此基礎的錯誤都搞砸,簡直可悲。我們已經投入這項技術多年並發明了 MCP,但 AI 卻要消耗電力和代幣來猜測簡單演算法能以 100% 準確度提供的內容?

          • syklemil says:

            每次AI出错,写出无法编译的代码,或错误地推断变量类型和方法签名时,我都忍不住发笑。它在处理本应轻松应对的琐事时却屡屡失误。一个合格的编辑器本身就能查找变量定义、进行基本代码检查或编译并检测错误。

            然而,编辑器和树坐器 (tree-sitter) 等工具是专门用于解析和理解代码的。另一方面,大语言模型 (LLMs) 将其作为输入来预测可能的输出。它们现在非常擅长预测,但仍然只是产生看起来相关的结果,无法“知道”一个语句是正确还是错误。

            • TomWithTime says:

              当然,我的意思是通过mcp或GPT封装器,AI工具应利用算法来节省资源、提高准确性、检查工作等。我不是指陷入循环不断尝试直到满足代码检查器要求,而是可能只需运行编译/代码检查步骤并通知我们问题,以便我们指导下一步操作。或利用mcp调用TreeSitter获取类型信息。也许这可以成为其向量数据库的一部分。

              我认为我的公司正在基于其他工具构建自己的工具来实现类似功能。对代码库进行索引以提升AI的认知能力。仅用了一周或两周就让它正常运行,所以我惊讶于大型AI公司为何没有想到这一点。如果它们处于亏损状态,为何不采取小步骤来减少因错误猜测方法签名而消耗的令牌数量?

        • Paradox says:

          这听起来很像Sun公司为何要让Java变得如此冗长。他们想出售NetBeans的副本,而一种具有合理默认设置的语言会破坏这一目标。因此,他们没有将public static设为默认值,甚至没有将private static设为默认值,也没有将所有函数默认设为void(除非另有说明或推断),而是要求你必须手动编写这些内容,因为他们的IDE会为你自动完成,前提是你购买了IDE的副本。

          • syklemil says:

            关于Go语言,以及著名的Pike名言

            关键点在于,我们的程序员是谷歌员工,他们不是研究人员。他们通常比较年轻,刚从学校毕业,可能学过Java,可能学过C或C++,可能学过Python。他们无法理解一种精妙的语言,但我们希望利用他们来构建优质软件。因此,我们给他们的语言必须易于理解和采用。

            这似乎有点像给不懂行的初学者提供 Go 这样的语言,而利用大语言模型(LLMs)减轻辛劳可能是大型企业获得最大回报的一种方式。这也是大语言模型和云提供商获得更多用户和收入的一种方式。

            不过,有趣的是,那家曾为SRE(站点可靠性工程师)撰写关于“ toil ”的权威著作的公司,似乎对开发者的“ toil ”问题并不太关心。

          • Famous_Object says:

            我之前一直以为NetBeans是免费软件,后来查了维基百科才发现它确实是付费产品,不过这种情况没持续多久。也许Sun在NetBeans之前就有其他IDE了。

            我想他们可能只是不希望用户手动输入所有代码,或者不希望它看起来像脚本语言。

            看看COBOL,这种最冗长的语言是在穿孔卡片时代创建的……

            • Paradox says:

              早期Java版本实际上比Java 2(1.2)之后的版本要简洁一些。我记得小时候还能写出不错的Java程序,后来才变得越来越冗长。

              COBOL 是一个有趣的存在。它最初是为商业人士编写代码而设计的,而非程序员。我们看到现代针对同一群体的“无代码”和“低代码”解决方案中,同样存在着感染 COBOL 的那些问题。

    • JohnnyLight416 says:

      这是我使用 C# 和 Kotlin 等拥有出色语法糖处理错误/空值的语言后,始终无法接受的一点。如果一种语言声称自己是现代的,却放弃了像空值处理运算符这样的基本语法优势,我无法认真对待它。

      但 Go 语言中还有许多其他糟糕的决策让我望而却步。

      • Paradox says:

        人们往往倾向于过度思考错误处理,感到沮丧,最终干脆放弃它。

        我主要使用 Elixir 作为编程语言,它拥有相当完善的错误处理系统,得益于 BEAM 架构,其中对意外行为的处理方式是直接停止并重置为已知良好状态。不过,这仍然是编程,你仍然会遇到需要处理可能在任何环节失败的功能管道的情况。

        传统上,你会用类似 Maybe 单子来解决这个问题(在 Rust 中,Result 和 Option 类型都以某种方式实现了 Maybe 单子,尽管使用 ? 运算符大大简化了它们的使用)。这就是我晚上 11 点昏昏欲睡的大脑立即想到的。我编写了一个管道来接收值,执行可能成功也可能失败的转换,并将值塞入一个 maybe 中。我正做到这里时,被叫去查看一个孩子:

        foo
        |> some_function_that_succeeds_or_returns_nil()
        |> Maybe.new()
        |> Maybe.and_then(&some_other_func_that_cant_handle_nils/1)
        |> Maybe.and_then(&a_third_similar_function/1)
        |> Maybe.unwrap_or(%{})
        

        我正准备将and_then中的函数重构为返回Maybe.just结构,以便管道能够继续。回到这个问题时,我立刻拍了自己一巴掌,因为Elixir对此有更优雅的解决方案。

        with
          a when not is_nil(a) <- some_function_that_succeeds_or_returns_nil(foo),
          b when not is_nil(b) <- some_other_func_that_cant_handle_nils(a),
          c when not is_nil(c) <- a_third_similar_function(b) do
            c
        else
          _ -> %{}
        end
        

        功能上相同,但对我来说,这样更容易理解。

        Maybe单子很优雅,当你需要使用它时,但你可能不需要使用它。如果我有中间函数在失败状态下返回不同值,例如{:error, msg}元组,我可以处理它们而无需修改API以适应调用点。

        我不知道如何用 Go 写出类似的代码

        • Axman6 says:

          这更像是语言本身对单子代码支持不足的问题,Haskell 的 do 语法让这类代码显得更加简洁:

          fromMaybe %{} $ do
            a <- some_function_that_succeeds_or_returns_nil
            let b = some_other_func_that_cant_handle_nils a
                  c = a_third_similar_function b
            pure c
          

          不过实际上,这似乎根本不需要任何单子相关的内容,

          fromMaybe %{} (
            some_function_that_succeeds_or_returns_nil
              <&> some_other_func_that_cant_handle_nils
              <&> a_third_similar_function
            )
          

          (我不知道 %{} 在 Elixir 中代表什么,所以就保留了)

          根据最后两个函数返回的类型,可能需要使用 b <- … 而不是 let b = …

          • Paradox says:

            Haskell 的 do 语法与 Elixir 的 with 其实差别不大,两者都允许某种程度的“铁路式”编程。

            至于单子,它们从未真正适合这种情况,只是我在疲惫地试图完成一个项目时随手拿来的工具。with 是最优雅的解决方案,无需修改原函数的签名,但如果我要这么做,可以修改它们在接收 nil 和有意义的值时采用不同的模式匹配,并在那里处理相关逻辑。

            %{} 是 Elixir 中的空映射。所有函数都接受并返回一个映射

            • Axman6 says:

              明白了。with 版本基本上就是单子为你抽象的内容,每行本质上都是 Maybe 的 >>= 实现——所以再看一遍,它实际上就是

              fromMaybe %{} $ do
                a <- some_function_that_succeeds_or_returns_nil
                b <- some_other_func_that_cant_handle_nils a
                a_third_similar_function b
              

              或者简单地

              fromMaybe %{} $ 
                some_function_that_succeeds_or_returns_nil
                >>= 无法处理空值的其他函数
                >>= 第三个类似函数
              
            • Paradox says:

              没错,with 真正与其他方式不同的唯一之处在于,它允许在匹配失败时更轻松地处理失败情况。

              with {:ok, bar} <- foo,
              {:ok, baz} <- ziz(bar) do
                baz
                |> wew()
                |> blarg()
              else
                {:error, “error message 1”} -> some_value
                {:ok, nil} -> nil
              end
              

              就像所有示例一样,这个示例为了说明目的有些牵强,但它为你提供了一个强大的工具来处理各种情况。

              Elixir 和 Erlang 实际上并没有明确的 Result、Option、Maybe 或类似的结构。惯例是将内容包装在元组中,其中 {:ok, value} 表示 Just,而 {:error, whatever} 表示 None。这种做法在 Elixir 标准库、OTP(Erlang 的标准库)以及第三方库中无处不在。

              我原例中的单子来自一个名为 FE 的库,该库为这些操作提供了一些便利功能,并在某些情况下直接处理元组风格的响应。我在管道末尾经常使用 FE.Result.ok/1,因为它非常方便。在纯粹(现代)的 Elixir 中,你可以用 then(&{:ok, &1}) 实现类似的功能,所以这更多是一种便利而非必要。

      • amestrianphilosopher says:

        被迫考虑你的代码在调用的函数抛出错误时应如何反应(并以标准方式传递这些错误),现在这算是一件坏事吗?

        很想听听你讨厌的其他糟糕决策

        • JohnnyLight416 says:

          天啊,这和你评论的内容完全不同。

          Go 实际上根本不会做你所说的事情。它不会强迫你处理错误。如果你忽略返回的错误,只对结果进行操作,它会正常编译和运行。

          相比之下,Rust 的 Result 和 Option 类型会在你尝试访问不存在的结果时真正失败。而且,Rust 还为你提供了一个很好的 ? 运算符来 向上传播 Result 或 Option,因此 result.is_err() 不会污染你的整个代码。我不是 Rust 的粉丝,但将 Result 和 Option 作为一流语言功能是一个很好的选择。

          Go 语言的其他糟糕决策包括:没有泛型(直到迫于需求才添加)、没有迭代器(同上)、没有联合类型、没有运算符重载,以及标准库中更多糟糕的实现: 我总是记得这篇文章的名字胜过其他文章,所以这里:https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride

          你可以用 Go 实现很多东西,专家们也用它做出了许多精彩的成果,但我敢打赌,对于任何需要长期维护和功能扩展的项目来说,它都不是最佳选择。

          • tyroneslothtrop says:

            如果你忽略返回的错误并仅对结果进行操作,程序仍能编译并正常运行

            此外,请记住,根据函数的返回值,结果要么是(可能)空值(因为即使在错误场景下,你也必须构造并返回有效值),要么是nil

            因此,你可能会遇到垃圾数据被引入程序的情况(这里保存到数据库的数字“零”是真实数据,还是因为忘记检查 err 导致的垃圾数据,而这个检查可能在距离数据写入位置 20 个文件和 1000 行代码之外?)……或者你可能会遇到空指针解引用导致的 panic。

            说实话,nil 异常可能是更好的情况,因为至少当它发生时应该很明显。

            Go 做的其他糟糕决定包括

            我还会加上零值。添加一个字段到结构体时,没有编译器的帮助来确保你更新每个使用位置,只是… 在你可能遗漏的地方有垃圾数据… 然后你总是会怀疑一个空字符串(或其他什么)是故意的,还是一个无意的垃圾“零值”。

          • amestrianphilosopher says:

            在我看来,Rust 对于 REST 服务这样的简单东西来说是过度杀伤力。它的内存管理机制虽然有其作用,但会让人感到精神疲惫。简单的 RESTful HTTP 服务并非其适用场景。链接无法加载,但我对这篇文章很熟悉,因为我们在决定使用 Go 之前就读过它。我记得其中一位作者最大的抱怨是关于文件权限的处理方式,并详细探讨了相关细节。这并非我使用 Go 的目的,且他的大部分抱怨与我的用例无关。

            Go 确实存在一些缺陷,例如它处理 nil 的方式。它的错误管理可能显得重复,但这种明确性让我非常欣赏。

            在更好的替代方案出现之前,我将继续使用 Go 处理每天在仅 2 个 CPU 上处理数十亿次请求的服务,以及支持公司数十亿美元收入的服务 🙂

        • crozone says:

          你一定喜欢Java的受检查异常。

    • Empanatacion says:

      Go之于异常,就像无人驾驶出租车之于激光雷达。某种程度上它被认为没有异常更好,但没人理解你的解释,你却不断遇到问题。

      • crozone says:

        它被认为更好,是因为设计语言的人忽略了大多数现代语言特性,然后在意识到人们实际上想要这些特性后,又无法将它们全部加回去。

        https://go.dev/blog/error-syntax

        对于通用类型也是同样的情况。不知何故,没有它们的世界似乎更好,但后来不得不硬塞进去,因为人们需要做太多绕行操作。

        如果Go从一开始就设计得当,而不是试图成为C语言的某种实用主义安全替代品(他们只是随意添加功能集并假装语言因此更好),它会更好。

        • 0xjvm says:

          说实话,如果 Go 重新设计,加入一些基本的生活质量改进,我一定会爱死它。但我没有真正需要使用它的理由,除了说 CLI 应用或基础设施工具,相比其他语言——当然,像 Java/Python/JS 也有它们的怪癖,但至少第三方库丰富,生产力是第一位的。使用 Go 语言时,a) 我必须自己完成所有事情,b) 它仍然有一些让我感到烦人的怪癖

      • Temik says:

        这是原始语言目的的产物。其目的是成为谷歌的新工具语言。在谷歌的生产环境中不能抛出异常。因此采用了这种特定的错误处理设计。

        来源:曾在那里工作过很长时间。

        • Empanatacion says:

          我们用剃须刀片替换了所有剪刀,因为孩子们拿着剪刀跑来跑去。

          除了技术上满足要求外,返回 nil 哪里算得上改进?

          • Temik says:

            啊,这种代码类型没人这么做。这只是针对“通用”场景的权宜之计。

            不是说这是好习惯,只是解释其来源。

    • yawara25 says:

      如果这是Go语言最糟糕的部分,我愿意接受。我喜欢Go的工具生态系统,作为一门语言,它从未“妨碍过我”。

  5. internetzdude says:

    “Go是最令人讨厌的语言。”

    [需要引用]

    • Axman6 says:

      Go 绝对是我最讨厌的语言,不是因为它像 JS 或 PHP 那样糟糕,而是因为它糟糕的原因是故意的https://www.reddit.com/r/ProgrammerHumor/s/4GmKRxKIt6

      • AdvancedSandwiches says:

        这门语言一般般。围绕它的文化简直一团糟。“熟悉带来简洁”,所以随随便便用单字母变量来表示一切。

        老兄,我连两周前自己写的代码都不熟悉,更别提五年前别人写的代码了。所以我们还是遵循这个推论吧:“不熟悉就无法简洁”。

        • 悖论 says:

          兄弟,我对两周前写的代码都不熟悉,更别提五年前别人写的代码了

          我曾与一位同事合作,他有一个Git钩子,会删除代码中所有不是显式文档标签的注释。

          同一个人在重构时总是手忙脚乱,因为他自己都不理解自己写的代码,而且没有注释作为指引,他完全迷失了方向。

          • idebugthusiexist says:

            也许我没有完全理解你评论的上下文,但我编程时的指导原则是:写代码时不应依赖注释来理解它。显然,例外情况是必要的,尤其是在出于优化等原因需要编写一些晦涩的代码时,但我尽量将其控制在最小范围内。那么,你是说这个人一直编写如此晦涩的代码,以至于他自己过了一段时间后也无法弄清楚它在做什么?这听起来像是故意积累技术债务的习惯性做法。

            • syklemil says:

              我的编程哲学是,编写代码时应确保无需注释即可理解其功能。当然,例外情况是必要的,尤其是在出于优化等原因需要编写一些晦涩的代码时,但我尽量将这种情况控制在最小范围内。

              我认为大多数人都同意这一点。我个人倾向于留下注释说明“为什么”,尤其是当似乎有更直观更好的实现方式时。比如

              // 这看起来可以用策略X实现,但那会引发问题Y
              

              如果这些注释被自动删除,我就会反复学习同一个教训,直到对策略X产生条件反射。

            • Paradox says:

              他就是那种对以“聪明”方式做事情着迷的程序员。再加上删除注释,我们中很多和他一起工作的人开玩笑说,他在编写代码来保障自己的工作安全。

            • idebugthusiexist says:

              明白了。是的,我以前也遇到过这种类型的人。

        • Axman6 says:

          抽象越强大,无意义的名称就越有意义——Haskell以使用此类名称而闻名,但这是因为代码往往过于通用,具体名称反而会模糊算法的通用性。

          不过,Go语言缺乏编写此类抽象的能力(除非使用interface {}之类的 hack),因此他们没有借口。所以我完全同意你的观点。

        • clickrush says:

          你是在攻击理论论点,而不是试图以善意理解其含义。

          喜欢 Go 语言的人强调,他们更可能熟悉任何程序,无论该程序是十多年前编写的,还是今天编写的。

          这并不是一个需要反驳的荒谬主张。它只是突显了该语言所取得的成功。

          当然,Go 语言也有明显的缺点。有趣的问题是,你的(或某个项目的)优先级是什么,以及哪一套权衡更适合。

          如果你编写的是信息处理密集型程序,那么你可能需要选择一种具有更强抽象能力、支持函数式编程且具备更通用数据操作能力的语言等。你不会选择Go。

          如果你的项目更侧重于I/O操作,并且你希望获得更高的性能、更轻松的维护和稳定性,那么你可能需要考虑Go语言。你今天编写的代码将需要更少的特殊/上下文知识(包括你自己)。即使在5到10年后也是如此。

          • AdvancedSandwiches says:

            喜欢 Go 的人强调,他们更可能熟悉任何程序。无论它是 10 多年前还是今天编写的。

            这可能不是你想要表达的意思。这是什么意思?

            你在攻击理论论点,而不是试图以善意理解它的含义。

            我是在攻击那些试图模仿Go风格时我不得不审查的糟糕代码。

      • BenchEmbarrassed7316 says:

        JS于1995年在10天内开发完成,其目标是“当你将鼠标悬停在其上时让猴子跳舞”。

        https://softwareengineering.stackexchange.com/questions/221615/why-do-dynamic-languages-make-it-more-difficult-to-maintain-large-codebases/221658#221658

        PHP是由一个人在同一时期开发的,其目的是增加一个用于统计网站访问者的变量,并以字符串形式返回计数器的值。

        将这些现代和进步的语言与 Go 进行比较有什么意义?

        Newsqueak(现已更名为 Go)是由一位名叫 Rob Pike 的开发者在 80 年代初开发的。

        https://en.wikipedia.org/wiki/Newsqueak https://www.cs.tufts.edu/comp/250RTS/archive/rob-pike/impl-new-TR.pdf

        所以我不知道将这些语言进行比较是否公平。

        • Axman6 says:

          这就是我的观点,这些语言做出糟糕的设计决策是有充分理由的,因为它们都是仓促完成的,且未借鉴数十年的语言设计研究成果。而Go的设计者们则拥有充足的时间,并且受益于数十年的语言研究成果,却有意识地选择忽视这一切。我选择它们是因为它们是常被憎恶的语言,但至少可以理解其原因,而Go则毫无借口——这一切都是故意为之。

          • BenchEmbarrassed7316 says:

            是的,但请仔细查看Newsqueak的链接及其文档。

            “` 类型声明:struct of{ x, y: int; } // 结合类型a和b的变量

            a := mk(array[10] of int) // mk 重命名为 make// 相反,所有现代语言都使用 new Slice()Slice.new()

            // 不用多说 select { case i = <-c1: a = 1; case c2 <- = i: a = 2; } “`

            Rob Pike 真的将他上世纪80年代的旧语言带了过来,并忽略了后来发明的大部分东西。

    • aanzeijar says:

      对我来说确实如此,但只是因为PHP用户不再试图推销PHP,而Go用户却真的相信OP写的东西。

      • BenchEmbarrassed7316 says:

        没错!这就是重点:做一件坏事,然后写出令人信服的文章,比如“坏就是好”。

        每种语言都有一些缺陷。但只有Go试图说服人们这些缺陷是优势。而其中大多数缺陷其实很容易修复。谁对阅读2009年(1.0版本发布前三年的)与作者的讨论感兴趣,讨论为何不应将空值添加到语言中,以及其他语言如何解决这一问题:

        https://groups.google.com/g/golang-nuts/c/rvGTZSFU8sY?pli=1

        剧透:从他们的评论来看,他们似乎不相信Haskell的存在。

      • CpnStumpy says:

        这是拼图中重要的一块。

        我开始相信Go语言不过是VB的翻版。它非常基础且易于理解,但尝试构建抽象时会变得一团糟,因此大多以过程式方式编写。更糟糕的是,大多数Go开发者都是新手,缺乏经验,无法合理地将其与其他语言进行比较。

        这是一个合理的运行时,但语言本身一团糟。或许几年后有人会说服大家开始使用Go++,它可以编译成Go并提供像现代GPL语言那样的语言级特性。

        • pauseless says:

          大多数Go开发者都是菜鸟

          这可能事实如此,但可以用流行度来解释。大多数Python/Java/C#/JS开发者也是菜鸟。为什么那么多优秀的程序员喜欢Go呢?

          我的朋友基本上在过去十年里一直在写Go。他在攻读博士学位期间发表了关于Haskell的论文,他编写了世界上最受欢迎的分布式系统之一的第一版,他还休假去编写了一个相当创新的分布式数据库,以探索其他领域。

          我用Perl、Tcl、JS/TS、Java、C#编写过生产代码, Clojure(+cljs)、APL、Python,当然还有Go。在大学期间,我几乎所有有选择权的工作都使用Standard ML或Prolog。我曾帮助他人学习Haskell,带朋友参加F#和Elixir的活动(当时它们都还很新),甚至与另一位朋友一起尝试过Idris。这些都不是夸张。

          那么,为什么我的类型理论朋友,尽管他仍然喜欢撰写数学内容丰富的论文,却偏爱 Go?为什么我如此喜欢 Go,尽管我见过这么多替代方案?

          编辑:顺便说一句,这是个真诚的辩论邀请。

          • aanzeijar says:

            编辑:顺便说一句,这是个真诚的辩论邀请。

            我认为Go仍然在很大程度上依赖于其周边工具链。对于它所处的两个特定领域(基于REST API的微服务和纯数据处理的C语言替代方案),其工具链非常出色,尤其与替代方案相比。go build比任何make方言都好得多。go mod比完全没有包管理系统要好得多。

            如果你以“编写更健壮的 C 语言”的心态进入 Go,那还算可以。虽然远未达到最佳状态,但如果你将编程思维模式限制在结构体、函数和按引用传递上,就不会注意到那些奇怪的地方。

            问题是:Go 不是一种 80/20 语言。它是一个60/5的语言,另外20%是被附加上去的,但使用这20%中的任何部分都会让你更接近缺失功能的边缘——这就是为什么Go本身不鼓励你使用超过60%的部分。

            • pauseless says:

              工具链非常出色,但这是从一开始就做出的有意选择。某些设计决策是为了支持工具链。有人说,目标是为谷歌的开发者提供支持,而语言是顺带产生的。

              它有超过两个细分领域,这并不是一个有趣的论点。我可以说 Clojure 基本上只是用于 Web API 和数据处理。Python 也是如此,等等。这基本上是当今开发者工作的主要内容。

              我认为无法否认Go在底层网络应用或命令行工具领域同样强大……

              其对并发与并行处理的实现极为出色。我尚未见过比它更适合单机并发的系统。是的,若需分布式计算,我更倾向于Erlang模型,但在我已完成的工作中,这种需求并不常见。(在JVM虚拟线程之前:这些线程仍然是隐式协作的,并且没有抢占机制以避免线程饥饿。)

              它的跨平台编译能力极为出色。

              哪些决策是离谱的?我建议提供证据证明它们确实阻碍了实际工作。

              我来谈一个:错误处理。改进它现在已被放弃。我对此无异议,因为我未看到任何提案有显著优势。有人抱怨if err != nil {…}过于冗余,这对我来说无关紧要。当我看到“newspaperman”这个词时,我不会将其解析为n、e、w……也不会解析为news、paper、man。我只是看到这个词。我阅读if err != nil的速度与阅读单个符号一样快。

              没有好的理由支持简洁性(编辑:或表达力,这是很多人争论的点),否则为什么当我说+⌿÷≢是APL中平均值函数的完整实现时,人们会感到惊讶?或者说≠⊆⊢是带分隔符的分区和去除分隔符的完整实现?我阅读这些与阅读错误检查一样迅速。人类可以快速进行模式匹配。

              我认为反对 Go 的大部分理由是美学上的。这其实没问题,在我看来!我可以接受单纯不喜欢它——我尝试的所有编程语言项目都是 Lisp,因为那是我的偏好。但我接受这是偏好,并欣赏其他语言的优势,根据需要使用它们。

            • sideEffffECt says:

              JVM虚拟线程:这些线程仍然是隐式协作的,并且没有抢占机制以避免线程饥饿

              你是如何想到这个想法的??

            • pauseless says:

              JEP 444

              调度器目前尚未实现虚拟线程的时间共享功能。时间共享是指在线程消耗了分配的 CPU 时间后,强制中断该线程。虽然在平台线程数量相对较少且 CPU 利用率为 100% 的情况下,时间共享可以有效降低某些任务的延迟,但目前尚不明确在拥有百万个虚拟线程的情况下,时间共享是否同样有效。

              Go 确实会对 goroutines 进行“强制抢占”,尽管这可能并不重要。

            • sideEffffECt says:

              “抢占式多任务处理”这一术语有时会被错误地使用,而其本意应更具体,指代称为时间共享调度(time-shared scheduling)或时间共享(time-sharing)的调度策略类别。

              首先,它们可能被任何对运行时(或任何库)的显式或隐式调用抢占。…

              其次,Loom的虚拟线程也可以在任何安全点被调度器强制抢占以实现时间共享。…

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

              这是来自/u/pron98,Loom的(合)作者。

              这表明Java虚拟线程确实以抢占式方式进行调度。

            • pauseless says:

              我对该帖子的解读完全印证了我的观点。他们对时间片切分毫无兴趣。

              这是一个术语问题,且颇具挑战性。据我所知,我阅读过的所有Java/JVM文档均未指出虚拟线程的实现可在除已建立的安全点之外的任何位置中断。

              不承诺调度何时发生并不意味着“在任何指令处”。事实上,给出的示例明确指出了实现具有安全点的具体位置。Math.sin可以因各种原因内部让出控制权,但你无需关心这一点,而不是说运行时会随意在Math.sin计算过程中中断它。

              Go语言在相当长一段时间内也是如此。这是一个优秀的实现,对于绝大多数情况都足够好,只有在一些极端情况下才会有问题。

              阅读讨论的其余部分。pron98明确表示没有必要强制预先中断。

              我对这类讨论有些反感,因为存在一个模糊地带,人们容易产生误解。Go 会乐意暂停操作系统线程,评估是否能将 goroutine 挂起,然后让出给另一个 goroutine。主要让出机制是在安全点(如函数调用、通道读取等),但它还额外提供这一机制以防止 goroutine 占用线程过久。

              坦白说,我同意pron98的观点,这并非绝对必要。在Go添加这一改动前,我从未遇到过相关问题;但肯定有其背后的动机。

              我的观点是,这是实现上的差异,Go有这一机制而Java没有。

            • aanzeijar says:

              现在你只是在重复Go语言的标准辩护,而忽视了该语言存在的非常合理的问题。核心问题在于:当没有“简单”的解决方法时,Go语言倾向于将复杂性掩盖起来,然后声称这是开发者的责任去解决混乱。

              错误处理就是一个典型例子,但问题不在于if err != nil的 boilerplate 代码。而是因为语言本身没有内置机制强制执行错误检查。这只能依赖代码检查工具来提醒开发者。这是语言的缺陷,因为它与“让语言更安全”的明确目标相悖。未检查的错误是巨大的隐患——但简单的实现方式只会重复Java的检查异常错误,而真正修复它会让语言变得更复杂。因此,为了保持简单性,这个问题被忽略了。

              然后你告诉我 Goroutines 很棒。我说:它们是一种模型,可以很好地完成某些事情,但在其他方面则完全糟糕透顶。围绕它的整个模型是“发射后不管”。如果你需要一个错误处理的后端通道,或者天啊,线程之间随机传递消息,你必须手动编写整个线程同步代码,而这本可以轻松地通过带互斥锁的共享内存代理来实现。

              与之相关的是:defer。它是一个陷阱,因为它无法处理错误。常见的用法defer f.Close()是错误的,因为关闭文件可能会抛出错误。Go知道这一点,因为延迟关闭一个通道可能会导致 panic,因此你必须处理它(尽管Go坚持认为异常不存在)。哦不,这太复杂了,所以我们干脆推出错误的解决方案并草草了事。而文件只是资源的一种类型。对于一种垃圾回收语言(你知道,它的整个设计目的就是管理资源),这简直是耻辱。

              另外,还记得那个关于循环变量别名的混乱吗?那导致并行测试的标准模式失效,而修复它花了 10 年时间?

              当然,有一件事我甚至不会指责设计师,但这在当今仍然是一个缺陷:Go 的隐式接口使得修改大型代码库变得极其困难,因为很难确定你正在处理的接口的具体实现。由于 Go 没有内置的依赖注入机制(他们差点就做到了!为什么不允许包注入呢!),一旦你的微服务变得更大,你将面临大量接收器,它们基本上具有相同的 CRUD 功能。Go 团队是如何解决这个问题的?只需看看他们的 AST 实现,你就会发现接口中充满了私有无操作成员,这样他们就可以显式地绑定隐式接口。

              我还可以继续说下去。它的 Unicode 实现声称一切都是 rune,却从未定义什么是 rune。如果你深入研究,他们实际上拥有完整的 Unicode 支持,但将字符串表示为 rune 数组又是一种破绽百出的简化。

              我能接受稍微冗长的代码。我闲暇时会编写 Perl 代码,过去也维护过企业级的 Java 项目。Go 语言并非极端。并非每种语言都必须达到 Golfscript 或 APL 的压缩级别。但一种语言应该保持一致,不鼓励其自身缺陷。这就像 PHP 和它的 mysql_real_escape 一样。

            • pauseless says:

              有太多需要探讨的内容……我觉得要写出与你同样长度的详细回应,我得写五倍的篇幅。简要版本:

              • 我个人倾向于不检查错误。许多语言设计师也持相同观点。
              • 你似乎误解了 goroutines,以及共享内存和互斥锁也是 Go 的典型用法。
              • 使用 GC 来清理资源是个坏主意。你希望清理操作与作用域绑定。
              • 循环变量陷阱是他们唯一一次决定进行破坏性更改,并且他们对影响进行了深入研究。我很高兴他们采取了缓慢的方法,也很高兴他们进行了更改。
              • 我从未遇到过接口问题。
              • 我不明白你的包注入点。
              • 字符串不是 runes 的数组。

              Go 是否有缺点?当然有。但我可以抱怨我使用过的每种语言。

              无论如何,我并不觉得自己在重复 Go 的标准辩护……充其量,我只是对一个常见的抱怨发表了个人看法……

            • aanzeijar says:

              哦,我也可以对大多数我使用的语言发表类似的抱怨,但大多数其他语言不会试图将它们的缺点包装成优点。正如我之前所说,在适当的情况下,核心部分足够好,工具也很好。

          • atheken says:

            我感觉你的朋友,可能还有你,正在编写更多学术性/小众的应用程序,这些应用程序可能没有生产系统那样的“规模”。

            当我提到“规模”时,我指的是需要参与代码库的人数,以及构建具有更大概念数量的系统。

            我过去两年使用这门语言的个人经验(在其他语言上已有20年专业开发经验),是创建任何标准化/直观的代码库结构都异常困难。你无法控制复杂性,它会“均匀”地分布在整个代码库中。

            虽然语法足够简单(尽管存在明显晦涩的缺陷),但一切都依赖于定义不清且随意被忽视的约定。这使得浏览代码库变得困难,重构则是一场噩梦。

            简单的事情都异常困难,比如根据通配符从文件系统获取文件列表(即使如此,你也会发现半成品的通配符语言无法胜任,最终不得不编写访问者函数来获取列表)。

            就连错误处理也需要你翻阅大量文档来猜测某个调用是否可能引发错误。如果你确实检查了错误,那也是基于错误消息的字符串,而这些字符串从未被文档化,编译器也无法提供任何帮助。错误处理本身意味着你必须在调用栈的每个层次都抛出无意义的噪音。

            在小型代码库中,这尚可忍受,但在更复杂的系统团队中,所有这些噪音的开销以及缺乏任何标准化工具来创建抽象,意味着无法管理复杂性,并且不得不不断为相同的低价值决策重复编写代码。

            • pauseless says:

              我假设讲个小故事是可以的……我也有20年的经验。我本科毕业后直接进入行业,编程语言只是我的业余爱好。

              我学习Go语言是因为我之前提到的那位朋友和我一起在一家咨询公司工作,他选择Go语言来开发一个大型旗舰项目,而我被拉入了这个项目。我们从三人团队起步,后来发展到十一人,随着时间的推移,团队规模时而扩大时而缩小——当我离开时,项目中已经没有一位第一年的成员了,尽管当时团队规模相当可观,人员流动频繁(咨询行业就是这样🤷)。我们还从客户方引入了开发人员和运维人员,过程相当顺利。我目睹并支持了数十人参与该项目,他们都迅速掌握了相关技能。

              其中没有一个人之前接触过Go语言。我认为,一个团队能够在失去所有创始成员后,引入新成员并成功交付产品,这符合你对“规模化”的定义。

              顺便说一下,我最喜欢的语言是Clojure,我认为我在Clojure上的职业生涯与Go相当。我见过Clojure出问题时会发生什么。如果我想为自己编程,我可能会使用Clojure。如果我想与“可替换”的开发人员一起编程,Go在过去十年中已经证明了自己。

              管理复杂性的愿望让我感兴趣,因为我真的看不到任何语言如何帮助解决本质上的复杂性。在Go中变得不可能管理的复杂性,是什么帮助管理它的?

          • Maybe-monad says:

            那么,为什么我的类型理论朋友仍然喜欢撰写数学内容丰富的论文,却偏爱Go?为什么我如此喜欢Go,尽管我见过这么多替代方案?

            我永远无法理解有人如何喜欢一种语言,它有像这个这样的陷阱。

            • 无暂停 says:

              是的。Go在学习时确实有陷阱。往往是那些让人想砸电脑的陷阱。这个需要理解什么是切片:一种指向可变底层数组的数据结构,具有长度和容量。基本上就是这样,但第一次遇到时确实让人头疼。

              如我之前所说,我本质上是个 Clojure 开发者,所以我不会辩称普遍可变性 somehow 更好或不会让人措手不及——不可变集合是一种奢侈。在 Go 中你必须更加警惕……不过其实也不是真的——我记不清上一次被这个坑到是什么时候了。九年前?

  6. burtgummer45 says:

    如果枚举类型不是简单到可笑,那对开发者来说会更“麻烦”吗?

    • PandaMoniumHUN says:

      枚举类型是我学习 Go 过程中不得不停下来感叹“这语言真垃圾”的节点。没有合理的、作用域限定的枚举支持,简直找不到任何借口。我能想到的另一个经常使用的语言中缺少此功能的是C,一种50多年前发明的语言。即使C++也意识到这是愚蠢的,并在2011年添加了作用域枚举。

      • Freddythereal says:

        缺乏原生枚举和集合功能让我放弃学习Go。我想要一种易于使用的语言,而不是简单的语言。如果我想要简单,我会去学习Brainfuck。Go确实是C的继承者,这意味着除了内存管理外,它几乎同样笨拙和繁琐。

      • aatd86 says:

        即使C#仍在完善其枚举功能。别着急,它会来的。这并不一定那么简单。我很好奇,你知道如何实现这些功能吗?

        • PandaMoniumHUN says:

          枚举是编译器中最简单的实现之一。我并不是在谈论模式匹配和每个条目都可以有自己字段的枚举(如 Rust),我指的是一个愚蠢的简单范围枚举。枚举值应该位于自己的命名空间中,值应该从 0 开始递增地分配给条目。在大多数编译器中,这可以在几小时内实现。

          • aatd86 says:

            所以你认为人们会对这种实现感到满意,而无需将其转换为新的类型类型,如果我理解正确的话?是否可以将这样的枚举作为函数参数传递以用作守护条件?这将如何与接口交互?如何在内存中表示枚举值?只是命名范围内的整数,还是可以是实际的类型值,只是通过索引访问?在这种情况下,关于不可变性,特别是与别名相关的问题如何处理?

            有几个问题需要自问。你不能在真空中实现一个功能。它取决于语言的其他部分。我毫不怀疑这是可行的,我当然希望未来能实现。但仍然需要做出设计决策。

            你所要求的几乎可以作为库实现。它要么因为需要防范空值而存在问题(在Go中目前无法实现),要么依赖反射。

            枚举本身还有许多细节。且行且看。

            • PandaMoniumHUN says:

              你看,这就是为什么我们不能拥有美好的事物。我提出最简单的解决方案,你却立即说“你不能在真空中实现一个功能”。语言中已经具备实现这一功能的所有工具,它只需要开箱即用的支持。没有人愿意仅仅为了使用枚举而使用库。

              回答你的问题:不,你误解了。显然它们需要是独立的类型,我只是用命名空间作为作用域的例子。现代编程语言不应允许整数类型与枚举类型之间的隐式转换。此外,由于Go语言已经允许定义强类型别名,这应该是很简单的。枚举值在内存中应使用能容纳所有不同值的最小整数类型(假设底层类型只能是整数类型)。这对任何写过哪怕只是玩具编译器的人来说都是显而易见的。你关于不可变性的问题没有意义,为什么你要修改枚举字段。

            • aatd86 says:

              是的,但你的要求并不符合所有人的要求。例如,我个人更希望枚举案例可以是任意类型的值,而不仅仅是整数。

              这就是人们需要理解的地方。在实现一个功能时,不仅仅是能够实现它,而是要考虑该功能是否能适当地满足不同的使用场景。

              这就是为什么这个功能在其他语言中仍然处于开发阶段。

              关于不可变性的问题源于在枚举中存储任意值。你希望枚举的取值是相对固定的。如果只有整数,当然这是显而易见的。但对我来说,这毫无意义。

              例如,如果我想要一个错误值的枚举,该怎么办?

              如果有人想要一些结构值的枚举,该怎么办?

              如果你的建议被采纳了,人们仍然会抱怨它非常像 C 语言,说“rUst eNuMs aRe beTter”(Rust 枚举更好)。Go 虽然历史上与 C 语言接近,但在能够实现简单性的地方往往会进行创新。

            • PandaMoniumHUN says:

              你抓不住重点。我说的是“让我们添加最简单的实现,这已经比语言当前支持的更好”。而你立刻说“不,我想要更多功能”。基本实现可以以后扩展,但至少现在我们有基本功能可用。

            • aatd86 says:

              这不一定就是运作方式。可能存在兼容性问题。因此,如果确实需要向语言添加功能,最好确保其正确性。否则,这可以保持为库。特别是如果你希望最大化语言拥有正交功能的机会,以便规范保持简洁。

              在提供任何功能之前,必须规划好该功能的未来方向。Hyrums定律会增加压力,尤其是当你的功能不幸成为一个泄露的抽象时。

              按照你的方法论,我们从一开始就可以通过模板实现某种形式的泛型。但如今我们正在研究受限泛型,这绝对更好。

              有时,等待并全力以赴比草率行事更好。人们总是会抱怨。

            • aatd86 says:

              对于那些没有解释就给出负面评价的人,请告诉我他们如何让枚举的实现作为类型约束使用?

              实际上,这需要研究多个功能之间的交互。😛

              当人们一无所知时,他们真是有趣。

    • Sisaroth says:

      如果你要使用OpenAPI。在C#中,让枚举与它良好配合相当困难,以至于在后续项目中,我选择使用字符串来表示本应是枚举的值。

  7. TheBigJizzle says:

    这让我想起“最差就是最好”的理念,而Go语言显然属于这一类。

    虽然我总体上同意上述观点,但我能想到几个例子,Go语言在不该简化的地方做了简化。比如它没有在正确的地方权衡复杂性和简洁性。

    枚举类型为例:无论你对枚举类型的看法如何,我认为Go语言在这方面的实现确实不够理想……拥有规范的枚举类型会比我们目前不得不编写的奇怪的iota代码更简单,同时也不会增加额外的复杂性。我认为这是其他现代语言中非常典型的特性,缺乏对它的良好支持会让切换到 Go 语言比必要时更困难。对它的“支持”只是一个权宜之计,而且不是一个好的权宜之计。

    另一个是错误处理的冗长性。我喜欢它在概念上的实现方式,但它太过冗长。最近他们暂时放弃了修复这个问题。原因是参与投票的1000人对少数提案无法达成一致。作为其他语言的用户,博客中提到的那些“糖衣”解决方案听起来已经足够好,而且肯定比我们现在拥有的更好。考虑到这是对语言的主要投诉,他们无法选择任何解决方案确实令人遗憾。该领域任何改进都将受到欢迎,但坚定的Go语言理念支持者不希望任何改变,我认为这将损害语言的发展。与其通过委员会来设计语言,我们本可以遵循BDFL的个人偏好,那将更好。现在显然他们希望通过委员会来设计语言,这就是为什么我们得到骆驼而不是马的原因。

    我不知道,我对坚持简单性持保留态度,但我看到像Java这样的语言被时代抛弃,最终会反噬自身。我们可以遵循80/20原则,但考虑到社区对任何解决常见痛点的提案反应如此激烈,我看不出来Go语言如何能避免因用户意识到该语言设计问题无法得到解决而逐渐衰落。

    • BenchEmbarrassed7316 says:

      例如,如果像以下这些功能:

      • (+) Option 和 Result
      • (+) 能够检查它们的值

      语言可以去除:

      • (-) 空值本身
      • (-) 默认值及其相关的逻辑错误
      • (-) nil 接口
      • (-) 在方法中检查指针接收者是否为 nil
      • (-) 声明局部变量时有两种选项
      • (-) 函数同时返回结果和错误(或反之)的能力,或者 nil, nil
      • (-) 在反序列化 JSON 时使用指针和注释(是 0 还是 null?)
      • (-) 向空切片写入数据可行,但向空映射写入会引发 panic
      • (-) 如果 err ≠ nil 且手动包装错误
      • (-) …这是非常重要的一点,可能我没有提到所有内容

      此外:

      并且:

      • (+) 简单的 C 风格枚举
      • (-) itoa
      • (-) 需要复杂的构造来确保函数接受枚举值,而不仅仅是任何整数

      并且:

      • (+) 元组
      • (-) 需要为闭包创建多个类型,如 iter.Seq 和 iter.Seq2,并重复代码

      每个设计良好的复杂事物都会以某种方式简化语言。通常收益远大于成本。此外,你无需撰写大量类似本文或“坏即是好”的文章。

      (编辑:格式调整)

    • codemuncher says:

      Go语言的错误处理存在问题:难以阅读。

      它用冗余的错误处理代码掩盖了程序的重要部分,你必须“忽略”这些代码才能看到代码中的真实问题。

      它以灵活的方式添加了额外的标记——你可以使用'err'作为变量名,也可以使用'err1'或任何其他名称!——但它几乎没有增加任何价值。

      令人沮丧的是,这是一种如此流行的后端语言。

  8. r1veRRR says:

    Go 拥有恰到好处的特性!它完美无缺!当然,除非 Go 的开发者决定添加新特性。那时我们就会发现这个特性是多么必要,而且显然完美无缺,但任何其他特性都会显得多余……直到他们添加下一个特性。

  9. ironykarl says:

    这几乎是“更糟糕更好”

  10. Ranger207 says:

    哦,是的,我同意它是一种 80/20 语言。但是:

    与 C# 或 Rust 中的异步相比,Goroutines 是 80⁄20 的并发设计。它没有那么多的功能和旋钮,但复杂度却只有一小部分(对于用户和实施者而言)。

    实现者,是的。用户,不。用户每次需要复杂性提供的功能时,都必须实现剩余的20%。而大多数用户并不像那些从事语言和标准库开发的人那样擅长。这就是Go为何是80/20语言:它提供80%的必要功能,让用户自行实现剩余的20%

    • BosonCollider says:

      我不同意,goroutines 的使用体验确实比大多数语言(除 BEAM 语言如 Elixir 外)更优雅,它们并非 Go 的弱点。JVM 近期新增了虚拟线程,其设计比 C# 更优雅且吸取了过往教训,但 JVM 和 C# 仍无法表达阻塞队列间的选择操作。

      Rust Async 更具表现力,tokio 可以完成 Go 能够完成的一切,但与普通的线程 Rust 不同,它非常难以使用。如果你不需要高并发性,那么使用 Crossbeam 等库的线程 Rust 非常不错。Crossbeam 比 Go 或 async Rust 更具表现力,更易于使用,但线程非常繁重。

      • codemuncher says:

        好的,Goroutines 没问题,但通道 API 中有太多陷阱。这太疯狂了。

        我花了很多很多时间试图弄清楚如何在集成测试中同时实现:(a)在测试结束时等待 ^C,(b)在测试过程中也能够 ^C,以及(c)在测试过程中对 HTTP 请求设置合理的超时时间。是的,所有这些都需要整合成一个可工作的解决方案,而使用通道非常困难。默认使用信号处理程序发送通道消息的做法……实在不理想。

        因此,是的,Goroutine 的基础部分没问题,但一旦超出基本用例,同步原语就会变得复杂,而且会耗费大量时间。

        • BosonCollider says:

          哦,在现代 Go 中,你会使用上下文 API 而不是取消通道来实现这一点,并且你可以使用 errgroup 库将 Goroutine 与上下文绑定。

          在大多數語言中,取消執行緒歷史上一直很困難。Python 和 Kotlin 社群提出了結構化並行處理,使取消操作變得更加便捷,而 Java 的 loom 專案也認真對待了這一概念。Go 中的 errgroup 是實現類似功能的最主流方式,conc 是另一種選擇,但它並未內建於語言中。

  11. SanityInAnarchy says:

    这有点修正主义:

    当 Go 发布时,它没有用户定义的泛型,但需要泛型的内置类型是泛型的:数组/切片、映射、通道。这种 8⁄20 设计在过去十多年里对 Go 非常有利。

    我不同意。它对 Go 的服务是糟糕的,而且证据积累到如此之高,以至于就连核心 Go 开发者也无法再假装这是一个好决定。此外:

    这就是为什么 80% 以上的语言需要编码规范。Google 为 C++ 制定了规范,因为如果没有对个人程序员可使用功能的限制,数百名程序员就无法有效地共同维护共享的 C++ 代码库。

    谷歌为Go也制定了规范。乍看之下似乎很简短,但点击进入Effective Go后,你会发现它实际上是一本专著。

    我基本上同意作者所阐述的原则:

    你无法跳过这种复杂性。即使你决定不学习如何将函数作为第一类概念使用,你的同事可能会使用,而你必须能够理解他的代码。或者某个有用的库使用了它,或者某个教程提到了它。

    而这正是Go语言的一大优势:你已经熟悉它。虽然有一些奇怪的语法细节需要花半天时间学习,但如果你精通任何其他现代编程语言,那么学习曲线会快得令人震惊。

    但当这种情况导致一些显而易见的缺陷在语言中存在超过十年时,这就成了一点问题。不是因为核心开发者认为这些不是缺陷,而是因为他们没有好的方法来修复它们。这在泛型历史中表现得非常明显。..


    但问题不止于此。我认为这并不总是与复杂性有关。我们仍然需要处理 if err != nil,因为整个社区在解决方案上争论了太久,以至于他们最终放弃了

    所有的错误处理提案都未能达成任何接近共识的意见,因此均被否决。即使是谷歌Go团队中最资深的成员,目前也未能就最佳前进方向达成一致意见(或许未来某时会有所改变)。但缺乏强有力的共识,我们无法合理地继续推进。

    阅读该回复时,我不禁想起Go语言团队另一项更为核心的理念:显式优于隐式,且这一原则被推向极致,仿佛冗余性完全不值一提:

    回到实际的错误处理代码,若错误得到妥善处理,冗余性便会退居次要地位。良好的错误处理往往需要在错误中添加额外信息。例如,用户调查中反复出现的一个问题是错误缺乏堆栈跟踪。这可以通过支持函数来生成并返回增强的错误来解决。在这个(坦率地说有些人为的)示例中,冗余代码的相对数量要小得多:

    func printSum(a, b string) error {
        x, err := strconv.Atoi(a)
        if err != nil {
            return fmt.Errorf(“无效整数: %q”, a)
        }
        y, err := strconv.Atoi(b)
        if err != nil {
            return fmt.Errorf(“无效整数: %q”, b)
        }
        fmt.Println(“结果:”, x + y)
        return nil
    }
    

    而我的问题并不是这个示例过于刻意,而是它完美地说明了为什么我希望Go语言既能拥有更好的错误处理语法,又能支持堆栈跟踪!按照这里处理错误的方式,你会得到一个关于无效整数的错误提示,但无法获得该错误发生的确切上下文。如果你在调用printSum的函数中以类似方式处理错误,也会得到同样的结果,但到一定程度,这基本上等同于以繁琐的手动方式构建自己的堆栈跟踪。

    如果将这段代码移植到Python语言中:

    def print_sum(a: str, b: str):
        x = int(a)
        y = int(b)
        print(f'result: {x+y}')
    

    不仅消除了冗余,堆栈跟踪实际上提供了更多信息——它不仅告诉我们哪个字符串无法解析为整数,还告诉我们失败的行号,从而明确该字符串来自 a 或 b。

    但这违反了Go语言的核心原则:查看这段代码时,你无法立即得知该函数至少还有两种其他退出方式。在标准的Go语言中,你只能通过return语句退出函数。

    我认为这并未使语言在学习上变得更简单。我甚至不确定它在阅读上是否更简单,大多数情况下。但它确实略微缺乏明确性。

    • codemuncher says:

      Go 在错误处理方面的冗长性非常令人烦躁,它掩盖了实际执行工作的代码,导致难以审查的 PR,更容易引入 bug,等等。手动完成大量操作意味着更多错误,就是这么简单。

      但冗长和明确性并非 Go 语言的核心特性。结构化类型系统是另一个看似不错但实际复杂且难以理解的特性。例如,很难确定一个类型实现了哪些接口。它并不明确——哦,我以为明确性是核心价值?看来不是?——而当阅读代码时,追踪哪些功能被支持则成了读者的任务。

      这种语言感觉像是半成品,因为它确实是半成品。而语言的原始性被其支持者视为美德。这令人遗憾。

  12. light-triad says:

    另一部分工作由语言实现者完成。Swift 是一个警示案例。尽管该项目由一群极具才华的人员在苹果公司的优先级项目上投入了超过 10 年的开发时间,且预算几乎无限,但 Swift 编译器仍存在速度慢、易崩溃且缺乏实质性跨平台支持的问题。他们设计了一种自己无法妥善实现的语言。相比之下,Go 虽然简单得多,但依然非常强大,从 1.0 版本开始就具备了快速、跨平台和健壮的特性。

    我认为 Kotlin 是更好的比较对象,因为它在功能集上与 Swift 非常相似,拥有非常可靠的编译器,并且在跨平台方面比 Go 表现更好。

    • mzalewski says:

      我不确定是否真的慢且容易崩溃,但我认为跨平台从来就不是苹果的目标。Swift是一种用于为苹果硬件(主要为iOS)编写软件的语言。感觉他们后来添加了对Linux的支持,是因为如今许多应用程序都有服务器端组件,而一些专注于苹果的软件公司希望用他们已经使用的语言来编写这些组件。

      • light-triad says:

        现在这是个目标。开发者可能担心 Kotlin 会抢走他们的饭碗。

        • RunicWhim says:

          是吗?除了空安全这一巨大优势外,我看不出来新 Java 版本在其他方面有什么不足。

          • light-triad says:

            上述帖子是在询问关于Swift的问题,但如果你想知道为什么选择Kotlin而非Java,答案在于多平台兼容性。Kotlin允许你编写一次代码,即可在多个不同平台上运行,完全独立于JVM(如iOS、浏览器、原生、WASM),同时也支持JVM平台,如JVM桌面和Android。这是Java无法实现的。

    • myringotomy says:

      我认为将编译器与语言本身进行比较是一种谬误。Swift 是一门优秀的语言,即使其编译器表现平平(而且它并不总是崩溃,那简直是荒谬的)。它在跨平台支持方面至少与 Go 语言相当。

      其他许多语言如 Crystal 也是如此。Crystal 是一门优雅的语言,但其编译器表现平平(速度较慢)。

      Kotlin 也是一个伟大的语言,但它(曾经?)与 JVM 绑定,无论好坏。

      • light-triad says:

        有道理。但 Kotlin 仅在编译时与 JVM 绑定。其主要优势之一是它被设计为可编译到任意运行时环境。目前支持的运行时环境包括 JVM、iOS、Android、浏览器和原生。

      • Perentillim says:

        Swift 是一门优秀的语言吗?我不得不深入研究的代码中的延续部分糟糕透顶,难以理解。

        • xtravar says:

          Swift 是一门出色的语言。只是大多数人写得不好。

          其中一个主要原因是试图让它对初学者易于理解。

          而苹果从未试图让他们的语言或 API 像其他平台一样。

          而且搭建 iOS 界面很有趣,因此学习重点往往放在掌握正确的语法上。

          最终,你得到了一群自学成才的新手,他们试图使用反模式并复制粘贴示例代码。

          我还可以继续抱怨,但就到此为止。

          总之,Swift 语言很棒。问题出在 Swift 开发者身上。

          • Paradox says:

            阻碍 Swift 广泛采用的一个关键因素是,相当一部分人不想或无法使用 XCode。我知道现在可以不用 XCode 编写 Swift 代码,我也用它写过几个小程序,但最初发布时情况并非如此。

            • syklemil says:

              再加上人们普遍认为“Swift是苹果为苹果平台设计的语言”,因此我们这些不认为自己是苹果开发者的人,也不会特别考虑使用它,就像我们当初对待Objective-C一样。我认为这主要是形象问题。

        • myringotomy says:

          很抱歉你无法跟上代码中的延续部分。我没有遇到任何问题,并且发现这种语言非常令人愉悦,无论是阅读还是编写。

      • AndrewNeo says:

        我的意思是它总是崩溃!……当我尝试使用SwiftUI时(我其实不怪Swift)

    • BenchEmbarrassed7316 says:

      Go编译器之所以快,是因为它没有做其他编译器做的绝大多数优化。可以添加编译选项,让用户选择是想要快速编译还是快速代码。但这很难实现。Go追求的是简单性。

    • simon_o says:

      嗯,当他们的运行时由“其他人”编写时,他们的工作就容易多了。

  13. simon_o says:

    我的总结:

    一篇由 Go 语言爱好者撰写的颇具防御性的文章,将人们对该语言的不满归咎于他们想要更多功能……而 Go 恰恰拥有恰到好处的功能(当然!)。

    我并不否认有人批评Go功能太少,但:

    我认为有很多人认为“80/20”是一个不错的语言设计目标,但认为Go并不是一个特别好的80/20语言。

    • chat-lu says:

      我认为我们批评Go是因为其功能设计得不够完善。

      有一篇经典文章(https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride)深入探讨了Go功能设计的不完善之处。

    • gmes78 says:

      完全正确。Go的问题不在于功能少,而在于它拥有的功能设计得并不出色。

      • Axman6 says:

        但这些功能是由ROB PIKE设计的,怎么可能不好呢???

        Go 语言及其流行程度令人沮丧,我感觉它似乎专门针对那些缺乏计算机科学基础知识的 Python 开发者,并且认为他们永远无法掌握这些基础知识。开发者很笨,给他们一种不太难的语言,不要让他们被抽象概念搞糊涂,然后告诉他们它比现在用的语言更快,这样就有一些理由使用它

        • perk11 says:

          我认为Go是C语言的现代版本。它仍然相当底层,而且C代码通常可以很好地转换为Go代码。但Go在细节处理上比C更流畅,也比C++简单得多。

          • simon_o says:

            如果依赖垃圾回收器进行内存管理没问题,那么“更流畅”就很容易实现。

            这使得与C/C++的比较完全没有意义,因为原本应该用C/C++编写的代码根本不应该/不能移植到Go。

            所以……Go擅长的是“本不该用C/C++编写的C/C++代码”这个细分领域?从我的角度看,这有点令人失望。

          • Axman6 says:

            Go 对我来说从未感觉像 C,它总是更像 Python 而不是 C。

            • syklemil says:

              有一篇 Pike 博客文章 详细介绍了它的起源:

              我们不想永远用C++编程,而我们——尤其是我——希望在编写谷歌代码时能随时调用并发功能。我们还希望直接解决“大规模编程”的问题,这部分内容稍后再详细说明。

              我们在大白板上写下了许多想要实现的功能,可以称之为“需求清单”。我们放眼大局,忽略了具体的语法和语义细节,专注于整体架构。

              我至今仍保留着那周的一封有趣的邮件链。以下是其中几段摘录:

              罗伯特:起点:C,修复一些明显缺陷,去除冗余代码,添加几个缺失的功能。

              […]

              注意罗伯特提到的是C作为起点,而非C++。我不确定,但我想他指的是标准C,尤其是因为肯也在场。但事实是,最终我们并没有真正从C开始。我们从头开始构建,只借用了少量内容,如运算符、大括号和几个常见关键字。(当然,我们也借鉴了其他语言的理念。)无论如何,我现在明白,我们是对C++的反应,通过回归基础、拆解一切并重新开始来做出回应。我们并非试图设计一个更好的C++,甚至不是一个更好的C。它旨在成为一种更适合我们关注的软件类型的语言。

              因此,考虑到参与Go开发的人员中也有许多参与过C开发,Go确实可以被视为对C的第二次尝试。虽然它在2025年甚至2010年的标准下并不完全“现代”,但无疑比C更具创新性。

              Go比Python更倾向于命令式编程;Python则更具混合性,允许同时进行面向对象和函数式编程。因此,我认为许多人会将Go解读为更接近C的语言。

              但同样,从同一篇博客文章中:

              虽然我们预计C++程序员会将Go视为替代方案,但实际上大多数Go程序员来自Python和Ruby等语言。很少有人来自C++。

              这确实有道理:Go和Python都使用鸭子类型,而Go和许多脚本语言都具有易于入门的特点。

              同样的思路,仍然引用同一篇博客文章,我好奇Pike等人是否可以通过放弃基本的类型系统来让Go变得更简单,因为他们似乎对类型并不特别感兴趣,而且Go似乎只是因为C有基本的类型系统而拥有基本的类型系统:

              在 Go 语言的早期推广阶段,有人告诉我,他无法想象在没有泛型类型的语言中工作。如我在其他地方所报告的,我认为这是一个奇怪的评论。

              […]

              但更重要的是,它表明类型是减轻这种负担的方式。类型。不是多态函数、语言原语或其他类型的辅助工具,而是类型。

              这就是让我印象深刻的细节。

              从 C++ 和 Java 转到 Go 的程序员往往缺乏对类型编程的理解,特别是继承和子类化等概念。或许我对类型的问题有些粗俗,但我从未觉得这种模型特别具有表现力。

            • Axman6 says:

              有趣的是,他似乎将类型面向对象编程(OOP)中关于类型应如何工作的理念混为一谈,而我对后者也并不感冒。

              我不确定他们所说的“大规模编程”具体指什么,但对我来说,构建一个似乎强迫开发者不断检查某项操作是否成功的错误处理系统,却又允许他们忘记执行这一操作,这显得相当荒谬。我认同谨慎的错误处理很重要,但我绝不会支持将程序的大部分代码都用于错误处理,从而模糊程序的执行流程。在Haskell中,Either单子允许你编写代码的正常流程,同时知道如果任何中间代码段失败,执行将在那里停止。如果你强制要求错误通过总和类型报告,并且所有情况都必须处理,你就能实现与Go相同的效果,而不会模糊代码的实际功能。

              令我困惑的是,错误处理竟如此简单粗暴,而使用defer进行资源管理其实相当优雅——为何不采用同样优雅的方式处理错误?

            • syklemil says:

              有趣的是,他似乎将类型与面向对象编程中对类型运作方式的理解混为一谈,而我对此也并不认同。

              同感,但当时他们确实是在使用C++。他曾列出过自己熟悉的编程语言,据我所记得,那份列表中并不包含任何支持抽象数据类型(ADT)或现代类型系统的语言。我过去曾开玩笑说,他对大学毕业后发生的计算机科学研究并不熟悉,而他似乎是在SML出现之前就离开了大学。

              我不确定他们所说的“大规模编程”是什么意思

              对于我们这些从未在FAANG工作过的人来说,这可能很难想象。但他们基本上在单一代码库中拥有数量惊人的开发人员,并且有一些指导原则,比如不允许使用异常。这显然影响了Go语言中异常的缺失,以及其包管理方式。

              构建一个错误处理系统,似乎强迫开发者不断检查某事是否成功,但又允许他们忘记这样做,这似乎很奇怪

              是的,但这本质上也是C语言的做法,而Go语言的创建者对C语言非常熟悉。无论是E foo(T* bar)还是func foo() (T, E),你总是会得到一个可能为垃圾的T以及一个指示T是否被批准使用的E。Go 的机制在一定程度上改进了一点,它将输出放在返回位置,而不是使用指针作为输入,但这也只是改进到此为止。

              这与受检查异常和抽象数据类型(ADTs)形成对比,在后者中,T foo() throws Efoo :: Either E Tfn foo() -> Result<T, E> 会让你得到一个好的 T 或者一个 E。遗憾的是,支持检查异常的语言并未让其特别易于使用,反而在未改进易用性的情况下,引入了对类型系统和调用点不可见的未检查异常。

              因此,Go 的创建者们事先就知道异常在 Google 代码中是不被允许的,他们似乎也不熟悉抽象数据类型(ADTs),而且他们希望编译器实现简单,所以最终他们得到了 仅作为语法而非类型系统中存在的伪元组

              在Haskell中,Either单子允许你编写代码的成功路径,同时知道如果任何中间代码段失败,执行将在那里停止。

              有趣的是,Pike实际上认为人们会手动重新实现带有错误值的单子。他可能不知道自己写的那段代码基本上就是他自己的单子类型、bind/>>= 以及一个不可见的 do 块。因此,我们处于一种状态:教人们如何使用单子并让类型系统引导他们是复杂且被排除的,但认为人们应该自己实现所有内容是被认为可信的。

              例如,如果这是可以接受的:

              b := bufio.NewWriter(fd)
              b.Write(p0[a:b])
              b.Write(p1[c:d])
              b.Write(p2[e:f])
              // 等等
              if b.Flush() != nil {
                      return b.Flush()
              }
              

              那么以下代码也应被接受(语法略有不同)

              runWriter fd $ b -> do
                write b p0[a:b]
                write b p1[c:d]
                write b p2[e:f]
                flush b
              
            • simon_o says:

              Go 真的感觉像是一种由从未在精神上或智力上离开贝尔实验室的人们创建的语言。

            • syklemil says:

              我认为它最像是在谷歌为解决谷歌问题而创建的语言,以至于我们可以将其描述为一种用于编写逃逸容器的 Kubernetes 微服务的 DSL。

              谷歌的运作规模对我们其他人来说是难以想象的,因此他们可以提出一些在其他情况下难以实现的方案。例如,Carbon 可能最终成为谷歌 C++ 单仓库的专用工具,类似于他们的 c++/rust 互操作项目

              然而,我们其他人也开始编写Kubernetes微服务,随后人们开始用这门语言在其他地方构建工具,且可能在更大规模和不同架构下使用,这与Go最初的设计初衷有所不同。如果有人抱怨Go在Windows上处理文件权限不佳,我不会惊讶于听到这样的回应:“……你们居然用这个在Windows上运行东西?”

              这有点类似于 JavaScript 最初只是用来在浏览器中让猴子跳舞,而现在我们却用它来运行各种应用。我不确定那些参加演示的人会如何看待现代网页应用、Node.js 和 Electron。

            • fear_the_future says:

              但更重要的是,它表明类型是减轻这种负担的方式。类型。不是多态函数、语言原语或其他类型的辅助工具,而是类型

              如果这句话出自Clojure的创建者Rich Hickey之口,这将是一个有趣的声明。在Clojure中,没有静态类型,而是通过在简单数据结构上编写大量多态函数和断言来实现。这是一种原则性的方法。但对于Rob Pike来说,这里没有值得深思的见解。这不是某种隐藏的智慧。Go语言中没有真正的原则。他只是对现代编程一无所知。

        • CpnStumpy says:

          这太对了。

          从一开始就将它定位为一种面向白痴的语言——我们确实这么做了,当时是Basic,后来是VB,那简直是一场灾难,花了整整十年才从混乱中挣脱出来,主要是通过大规模重写,因为那简直是一堆垃圾……

          嘿,让我们再做一次 🙃

          技术周期真是令人疲惫

        • Maybe-monad says:

          在我看来,Rob Pike就像一个不知道自己在做什么的人

          • KarelKat says:

            没错。这位“array.filter是多余的,只需使用for循环”先生。

            https://github.com/robpike/filter

            • Maybe-monad says:

              我开始相信C编程是一条通向疯狂的道路,希望自己不要变成这样的人

            • syklemil says:

              还有一篇博客文章,他基本上手写了一个单子和bind/>>=来展示如何使用错误值](https://go.dev/blog/errors-are-values)。认为仅仅使用单子是可怕的,但手动实现它们以及更多或少不明确的`do`块是可行的,这真是令人着迷。

            • Maybe-monad says:

              我想我得纹个单子纹身来保持距离。

              结尾部分堪称经典:

              无论你做什么,一定要检查错误!

              别忘了检查错误,因为我懒得在编译器里实现这个功能。

            • syklemil says:

              是的,我们可以大致将语言分为两类:

              • “我不在乎你是否检查错误”的语言,允许任何潜在垃圾值与错误值的组合:
              • C 语言及其 E foo(*T bar),你可以完全省略对 E 的检查
                • Go 语言中的 func foo() (T, E),如果将 E 绑定到一个名称但未使用它,它会发出警告,但你也可以直接使用 bar, _ := foo()。而在 func baz() E 的情况下,仅调用 baz() 时不会有警告。比 C 语言好一些,但仍然完全取决于用户。
              • “你只能得到一个有效值或需要处理的错误”的语言:
              • 异常可以将此表示为 T foo() throws E,但基于异常的语言通常会直接保留为 T foo()。无论如何,你的错误都会被检查,否则程序会崩溃。
                • 支持抽象数据类型(ADT)的语言,允许你使用 foo :: Either E Tfn foo() -> Result<T, E>,其中你有多种处理方式可选,但必须明确声明你的处理策略;忘记处理 E 情况是绝对不允许的。

              但不强制执行此规则可以让编译器保持简单,我想。如果人们最终使用类似golangci-lint(它变得非常占用资源并开始出现内存不足)之类的东西来弥补差异,那也不是他们的责任,对吧?

            • Maybe-monad says:

              但不强制执行它可以让编译器保持简单,我猜。如果人们最终通过使用类似golangci-lint(该工具变得非常占用资源并开始出现内存不足问题)来弥补这一差异,那也不是他们的责任,对吧?

              人们会在论坛上抱怨,并遭到作者的反驳,直到某家企业赞助商决定必须采取行动。

        • Paradox says:

          派克直言不讳地承认,Go 语言并非为成为一门优秀的编程语言而设计。它并非语言爱好者的首选。这门语言的初衷是让刚毕业的大学生和实习生能够安全地为一个比许多大型书籍还要庞大的代码库做出贡献。

          • Axman6 says:

            但我并不认为这是件好事。让某人立即变得高效,就像“24小时内学会X这个非常复杂的东西!”一样,要么你实际上没有学会如何解决困难的问题,要么这个东西本身不允许你解决那些问题。人们学习Python后却成为糟糕的软件开发者,因为“编写软件很简单!”的表象让他们从未真正掌握如何编写可维护、高效、结构良好、类型安全、能处理意外情况等优质软件。

            Go基本上对开发者说:“你今天就能做出能用的东西,一周内就能为公司做出贡献,六个月内就能掌握所有知识,然后你就会遇到语言的局限性,再也无法提升作为开发者的能力,因为语言本身阻止了你思考其他‘更复杂’语言允许你思考的问题。” 它缺乏许多基本抽象,并鼓励编写掩盖实际发生情况的代码——阅读 Go 代码非常令人沮丧,因为超过 50% 的代码行都是无意义的错误检查,且无法进行抽象。这不仅会掩盖程序的流程,还容易出错,如果你不小心忘记了某项检查。Haskell 的 Either monad 或 Rust 的 Result 迫使你真正进行检查,同时抽象出“出了问题,不要再执行任何代码”的想法。

            • Paradox says:

              问题是,就谷歌而言,他们并不关心这些。一旦你做出贡献,一旦你成为机器上的一个齿轮,你就成为为他们创造价值的劳动单位。如果你想作为工程师进步,他们认为你会利用自己的时间去做,如果不做,你仍然在产出工作。

              至于处理检查,我深知使用Maybe单子模式有多方便,但它并非适用于所有情况。虽然有用,但遗憾的是在Go中无法使用,不过有替代方案。请参阅我在该线程中的另一条评论,讨论 Maybe 与 Railway 编程的区别

            • lks-prg says:

              没有人强迫你永远只使用一种语言。即使是谷歌,也不是所有东西都用 Go 编写的。说实话,Go 教给初学者的关于计算机实际工作原理的知识,比 Java 之类的语言要多得多

          • Maybe-monad says:

            鉴于Go语言中存在大量潜在风险,我认为实习生无法安全地为任何Go代码库做出贡献

          • KarelKat says:

            我认为这是人们反复提及的后见之明。比如亚马逊用门板做桌子是因为节俭(实际上门板桌子更昂贵)。

            我认为Go并不是特别适合这种情况的语言。它声称通过减少功能来实现易于上手,但实际上我经常看到新手因为无法将他们已知的概念(如类、继承、枚举或异常)映射到Go的功能空间而感到困惑。更不用说它并不是一种安全的语言(NPE 很容易发生),而且在编写软件和系统时,它非常依赖于良好的约定和最佳实践。这种约定可能是谷歌可以强制执行的。(错误处理不佳、堆栈跟踪等)。还有一些非常奇怪的功能,比如通过大写字母来导出变量,我认为这是一种对新手不友好的功能。

            所以Go是谷歌的好语言?当然。对新手来说是好语言?我对此有疑虑,且有相反的经验。

            • Paradox says:

              Pike本人曾明确说过这一点。

              仅仅因为Go本身可能不适合其预期用途,并不减损它从一开始就是设计选择的事实。

            • KarelKat says:

              我的观点是,“易于学习”是一个高度主观的概念,甚至没有被Rob列为Go试图解决的关键痛点之一。Go存在一些缺陷,这些缺陷实际上使得它难以掌握,因此,经常被重复的“Go是一种专为新手设计以易于学习的语言”的说法,在现实中和重要性上都显得过于夸张,从他自己的话中可以看出。

          • aatd86 says:

            我怀疑他所说的话。😂 可能只是说Go不是一种研究语言。如果人们想要一种“好”的语言,就像你暗示的那样,他们可以在其他地方找到。如果Go真的那么糟糕,人们就不会使用它,也就没有必要发明它。😏

        • runevault says:

          无论你认为Go是否是一种伟大的语言,诉诸权威都不是一个很好的论点。

          聪明的人并不是100%的时间都聪明。仅仅因为他做过很多伟大的事情,他仍然可能创造出一个有缺陷的语言。

    • summerteeth says:

      认为你最喜欢的工具在功能与复杂性之间找到了恰到好处的实用平衡,这确实是对其的高度信任。我涉足过许多领域,感觉自己需要再花25年的编程经验和团队协作经验,才能有信心做出这样的评价。

      这有点像《精灵》中的一幕,布迪找到了一家声称拥有世界上最好咖啡的咖啡店,他兴奋地祝贺道:“祝贺社区!”

    • Plazmatic says:

      我对Go的看法是,它并非通用编程语言,却假装自己是。

      这让试图竭力为自己钟爱的语言辩护的人感到困惑,也让其他人试图在“到处”使用Go时感到恼火,因为他们试图遵循谷歌声称Go可以使用的范围,却发现它缺乏实现目标的基本功能。

      Go在成为通用语言的能力上所面临的问题,以及试图为它辩护的狂热程度,都让人联想到Matlab。

      • voidscaped says:

        什么才是更好的通用编程语言?Python?

        • Plazmatic says:

          问题不在于是否存在“更好的”通用编程语言,而在于它是否具备成为通用编程语言的能力。

          我知道Go语言在这一点上“解决了”泛型问题,但作为“Go问题”的例子:Go团队对语言中不包含该特性感到满意。他们使用的理由甚至有道理(他们将所有容器类型都设计为泛型,因此你无需自行使用泛型!)。当然,如果只针对Servlet应用程序(因此几乎不需要泛型函数,因为你几乎在所有情况下都已知具体类型),这种说法确实合理。

          相比之下,即使是C语言也没有这个问题(得益于宏),而C11甚至提供了_Generic(x)宏。

    • ammonium_bot says:

      功能太少,

      你好,你是想说“太少”吗?

      如果我犯了错误,请告诉我!请告诉我如果我犯了错误。祝你一天愉快!
      统计数据
      我是一个纠正语法/拼写错误的机器人。如果我错了或你有任何建议,请私信我。
      Github
      回复“STOP”到这条评论以停止接收更正。

    • kjk says:

      啊,在互联网上对陌生人进行心理分析的危险。

      我写下这些是为了整理我的想法,这是我对HN上有人抱怨结构标签不够强大(比如注释或宏)的评论的延伸。

      我没想到它会广为流传。

      我坚持认为Go是“最不受欢迎”的语言,这一观点基于HN和Reddit上的舆论。

      当然,过去Java、Perl或PHP曾被讨厌,但现在你找不出HN上有人在讨厌它们。

      我不会自称“Go爱好者”。对我来说,编程语言只是相对较小的恶。我最不讨厌Go;这并非热情。

  14. kane49 says:

    我认为它更像是一种 90210 语言。

  15. Razor_Storm says:

    这篇文章表明了作者对人们为什么不喜欢 Go 缺乏深刻的理解。

  16. Verwarming1667 says:

    我同意Go是我最讨厌的语言。Java至少有借口说它是在我们不知道设计有多糟糕之前设计的。JavaScript是一种原型语言,在短短几周的开发时间后就被推到了台前。但Go,Go拥有历史知识。它有无数例子展示如何做得更好。而他们却做出了一个垃圾,并投入了最大量的营销。

    • _xiphiaz says:

      我觉得市场部门一定在命名语言后才介入。讽刺的是,这是最不适合在谷歌上搜索的后谷歌语言

    • Plazmatic says:

      Go 面临与其他 C+ 语言相同的问题,而 Go 是否属于 C+ 语言类别则取决于你问谁。它们从 C 语言中学习,但基本上忽略了 C++ 的教训

    • tnnrk says:

      为什么它是垃圾?

      • arobie1992 says:

        我不会说它是个垃圾,但我确实不喜欢它。就语言设计本身而言,这是我使用过并达到熟练程度的语言中我最不喜欢的。这源于我与设计者之间存在根本分歧。他们的核心目标之一是创建一种任何人都能快速学习的语言,而他们实现这一目标的方法是去除功能,迫使所有人都以相同的方式编写代码。

        问题在于,他们决定省略的特性显得非常随意,且似乎完全基于设计师的个人偏好,而这些偏好又过多地受到了他们作为C语言开发者的背景影响。他们省略了像泛型和某种形式的类型检查枚举这类广为接受的特性,却保留了更具争议性的特性,比如没有静态空安全机制、以Go注释形式存在的超语言宏系统,以及支持goto语句。还有其他一些设计缺陷,比如defer是函数作用域而非代码块作用域,或者有类型化的空值。对于这些设计缺陷和缺失的安全特性,常见的应对方式就是学会避免使用它们,因为将这些特性添加到语言中会让语言变得过于复杂。我理解他们需要在某个地方划清界限,否则就会像C++一样,但这种做法并不觉得是正确的。

        我对Java有点偏好,因为它是第一个我熟练掌握的语言,而Java本身也有大量问题,但我认为现代Java的理念——即如果某个功能需求强烈且经过验证,才会添加——感觉是Go哲学的更健康版本。

      • Verwarming1667 says:

        对我来说,Go的通道机制纯粹糟糕,错误处理方式令人发指,而且无法构建抽象层。

  17. stonerbobo says:

    我自诩为坚定的Go语言反对者,它似乎公然违背了过去20年我们所学到的诸多经验,这让我非常恼火。

    但……在大型公司使用Go语言后,我开始逐渐欣赏它。当发生故障需要深入分析从未接触过的其他代码库时,Go语言能让这一过程变得轻松。它如此简单,以至于几乎没有变通的余地——每个代码库看起来都差不多,我能迅速上手。在编写Go代码时,我经常抱怨它功能太少且实现得不好,但不得不承认,阅读Go代码通常是一次流畅的体验。

    • Famous_Object says:

      我支持那些只有一种明显且正确方式来完成事情的语言。如果10种方式中有9种是奇怪的、过时的或容易出错的,那为什么还要有10种方式来完成同样的事情?这就是我为什么不喜欢Perl和Scala。

      但Go是否必须忽视30年的编程语言发展?它发布时的状态让Java看起来像是一种充满疯狂功能的大型复杂语言。

      他们本可以放弃Go,转而使用一个针对Java的原生代码生成器,并配备一个经过优化的垃圾回收器,然后就此打住。他们不是有自己的Android系统吗?

      我想他们想要一个不是由竞争对手开发的Java或C#,然后用一个不同的名字重新启用了Rob Pike的旧语言。

    • lunchpacks says:

      Go比其他语言领先20年,这就是为什么人们讨厌它

  18. teerre says:

    这只是一个毫无意义的博客。这些数字是什么意思?什么是15复杂度?80实用性?这个人到底在说什么?这个人犯了常见且令人厌倦的错误,将简单与熟悉混为一谈,甚至认为每个人都同意简单意味着什么

  19. myringotomy says:

    更像是60/40,但我明白你的意思。

  20. somebodddy says:

    “我只让这封信变得更长,因为我没有时间让它更短。”(布莱兹·帕斯卡)

    更糟糕更好的倡导者会认为这个著名引用很愚蠢。帕斯卡为什么需要更多时间让信件变短?他为什么不能随意删除任意段落直到信件足够短?即使这会让信件变成一团令人费解的乱麻,它也会让信件更短,因此更简单——而简单性,毕竟是至高无上的。

    让事物更简单而不牺牲关键属性并非不可能。但——正如帕斯卡的名言所暗示的——这往往需要时间、努力,以及“更糟糕更好”派最讨厌的东西——创造力。

    这在编程语言领域早已实现。Lisp和Smalltalk便是典范——这些语言以极简语法著称,但正因其简洁性经过精心设计,不仅未因复杂性降低而失去功能,反而利用简洁性实现了更强的表达力。

    当然,Lisp 和 Smalltalk 是动态类型、晚绑定、解释型语言,这使得这种智能简洁性比静态类型、早绑定、编译型语言(如 Go)更容易实现。但我仍然认为,既然我们已经看到可以拥有更多,就不应该满足于如此之少。


    即使假设Go确实做对了80/20原则——选择编程语言真的是一个应该遵循80/20原则的场景吗?

    80/20原则并不总是好主意。假设你需要穿越一个100公里宽的沙漠。火车票的合理价格是$10,但——既然文章提到有些语言能以400%的成本实现100%的功能——假设那辆能带你全程的火车收费$40。还有另一辆火车只收$2,但只能带你走80%的路程。

    于是你遵循80/20原则,选择了第二辆火车。你以$2的低价乘坐了80公里,然后火车停了下来。在沙漠的正中央。你现在必须步行20公里,烈日当空,沉重的随身行李在沙地上“滚动”,而你省下的$38仍留在口袋里。

    个人而言,我宁愿直接支付$40。

    80/20原则只有在可以放弃最后20%的情况下才有意义。当你从树上摘果子时,可以留下20%难以摘取的果子。但在沙漠中央,那最后20%绝不是你可以放弃的。我认为编程语言更接近后者而非前者。

    编程项目可以采用80/20原则,但需要自行分析哪些功能是实现80%价值所必需的。没有保证项目认为最佳的80%与编程语言决定提供的80%会完美契合——甚至可能完全不契合。如果你选择了一种80/20语言,你就有可能需要语言未覆盖的20%中的某些功能。你不能总是放弃自己的功能——即使你这样做了,那也太糟糕了。

    而且,没有这20%的功能实现你的功能并非不可能。这是可能的。在沙漠中步行20公里在技术上也是可能的。只是……这将是一次极其糟糕的经历,会耗尽你的精神和体力资源(在沙漠案例中体力消耗显而易见,但在编程案例中也是如此,因为你会不断撞墙,最终可能导致脑震荡)

    这样做值得吗?以编程语言为例,好处是什么?实现起来更简单?作为语言用户,我为什么要关心这个?我并不指望使用该语言的底层1%的程序员来维护它——我无需将代码库简化到他们的水平。

    易于学习与我是否选择它更相关。但这真的那么重要吗?我每天使用它时并不需要从头开始重新学习同一种语言。即使我需要一周而不是一天才能开始使用它——从长远来看,这真的重要吗?

    当然,像C++或Rust这样的语言中确实存在一些极其复杂的特性,复杂到足以让相当一部分开发者无法理解。但:

    1. Go语言中缺失的这些特性,其复杂程度根本无法与上述语言相提并论。
    2. 语言拥有这些普通用户无需触及的特性是正常的。如果你需要特性X来实现Y,而语言添加了该特性,那么即使你不知道如何使用它,也可能有人会发布一个利用该特性实现Y的库,你就可以使用该库。这比语言根本不添加X导致无法实现Y要好得多。
  21. summerteeth says:

    作者指出jUnit与Go标准测试库之间的复杂性差异,这很有趣。

    如果我是测试库的用户,我是否会在乎它比我需要的代码多出10倍,只要它能实现我想要的功能?

    假设添加新功能的成本会增加并延缓发布,但如果更简单的库从一开始就从未添加这些功能,对我作为用户来说有什么区别?

    • PiotrDz says:

      我认为你错过了重点。这从来不是关于代码行数,而是关于功能。jUnit 拥有更多功能,但 Go 希望保持更简洁(更少的代码行数),因此在功能上有所欠缺。

      • summerteeth says:

        是的,但如果这样说,作为用户,我为什么还要使用功能较少的工具?有什么好处?

        • PiotrDz says:

          但你有选择吗?Junit是为Java设计的,所以如果你使用Go,你只能使用更简洁的工具,因为80/20原则。

          这就是我对它的理解,当你选择Go时,某些功能会因语言选择而缺失

  22. Fuumarz says:

    在Go中编写异步代码时,你的代码是20/80安全的

  23. dex206 says:

    我讨厌这些由仁慈的独裁者附加的宗教式术语,它们纯粹是无稽之谈的教条。TypeScript也存在同样的问题。细微差别被忽视,这成了终结所有讨论的捷径

  24. lalaland4711 says:

    我认为这个论点为Go的糟糕设计找了太多借口。

    延迟、切片、远程作用以及多空值并非KISS原则,而是面条式设计。或者说,根本没有设计。

  25. GuyWithLag says:

    不,我的朋友;Go是20/20语言,它被故意削弱了。这是由FAANG公司为FAANG公司设计的语言。它的抽象能力已被削弱到初级工程师不会迷路的程度,因为在FAANG公司,初级工程师正在实现由中级工程师编写(并审核)的任务,而高级工程师则基于高级工程师创建的高级设计编写了低级设计——所有这些人都希望能够阅读代码。

    Go 语言的限制在于,初级工程师无法因语法错误而自掘坟墓/意外创建库/抽象到高级工程师无法立即理解的程度。

    它是一门优秀的入门语言,但是一门糟糕的成长语言——因为目标受众在第三年时将转向文档驱动开发。

    • Bitbuerger64 says:

      C 是最佳入门语言,因为它能让学习者理解指针、内存等机制的工作原理。其他语言则会阻碍学习者对这些概念的理解。

      • vplatt says:

        如果你要让初学者从低级语言开始学习,那么至少先让他们接触汇编语言。毕竟C语言是可移植的汇编语言,而没有汇编语言的基础,就无法理解C语言或机器架构的本质。

        此外,如果他们能完成汇编语言课程而没有中途放弃,那么你就知道他们是学习C语言及更高层次语言的理想人选。

        当年,我的大学首先使用Pascal而不是C,但我们还是学习了C、汇编语言等。Pascal在低级语言和高级语言概念之间提供了很好的平衡,并保护我们免受C从一开始就提出的更复杂的问题。我怀疑这就是为什么现在许多计算机科学课程都从Java、Python或JavaScript开始。我猜这也没关系,但如果问我,这多少有点降低难度。我不知道初学者如何从Java过渡到C和汇编语言而不感到困惑。

        说到这里,Go会是一个绝佳的入门语言,它提供了与Pascal在我身上发挥的许多相同优势。它感觉比任何虚拟机语言都更适合作为入门语言。

        • GuyWithLag says:

          我不知道初学者如何从Java这样的语言过渡到C和汇编语言而不感到困惑。

          C到汇编的转换相当直观(至少在-O0优化级别下),我认为任何计算机科学毕业生都应该能够编写一个不优化的编译器。

      • GuyWithLag says:

        开始在Steam上玩Turing Complete吧 😉

  26. tiedyedvortex says:

    听着,我对Rust有强烈的偏好。但我对后端使用“足够好”的语言已经厌倦至极。

    确实有适合使用一次性代码的场景,比如用Python或Bash脚本快速拼凑出一个低复杂度的日常任务。你明知这些代码最终会被丢弃并重写,但这是你主动做出的权衡。而且,现在你也可以用大语言模型(LLM)来为你编写这些垃圾代码了。

    但如果你要编写的是高性能后端解决方案的代码,那就不要满足于“够用”了。用Rust或Zig或(根据用例)Elixir来编写,甚至在必须的情况下用C/C++来编写,但要编写快速、正确且可持续的代码。编写能够作为其他所有构建内容稳定基础的代码。

    如果你编写的是“几乎”足够快的代码,是“基本上”可维护的代码,是“基本上”正确的代码,这会让你产生一种虚假的安全感。你将建立在裂缝的基础上,三年后你会意识到除了彻底重写外别无他法。

    而Go语言正是如此,它会让你误以为自己能快速编写出高效代码,但实际上你只是在制造无数微小的摩擦点和低效环节,这些问题会永久存在并不断累积,最终迫使你放弃并转而使用更优秀的语言重新开始。优质代码能持久,但“80/20”语言无法做到。

    • m_hans_223344 says:

      补充一点:关于如何用Go语言编写服务的文章或课程/培训多得令人发指。我不是说这些内容本身就是垃圾。其中一些确实很棒。但它们的存在本身就令人不安。为什么在2025年,Go语言社区的大部分人还建议自己从头构建一个后端API框架?我知道,Gin和其他框架存在,但如果你想成为一名真正的Gopher,你应该使用标准库自行创建。这简直是在浪费时间,而且容易犯一些愚蠢的错误。

      • KarelKat says:

        Go语言社区对低依赖或无依赖的解决方案有着强烈的执着。你会在许多项目中看到这被视为一种荣誉徽章。我明白这种想法的来源,但有时它会变得极端,甚至到了贬低他人的地步。

        • lvlint67 says:

          我认为……与TypeScript中需要引入isEven()库,或Java中开发者无法在不依赖Spring的情况下构建应用相比,这种观点是值得欢迎的。

    • vplatt says:

      Go 就是这样做的,它让你以为自己正在快速编写代码,而实际上你只是创造了无数微小的摩擦点和低效率,这些摩擦点和低效率会永远存在。

      嗯……除了它是 GC 之外,你到底在说些什么?我明白从性能的角度来看 Rust 更好,但 Go 仍然比其他选择(尤其是任何脚本语言)具有巨大的优势。

      • tiedyedvortex says:

        几个简单的例子:

        1. 用 OP 文章中的例子来说,缺少枚举。我并不是指 Rust 枚举,而是指 C/C++ 枚举。能够以低成本描述状态机对于管理程序流程至关重要,而 Go 却说“不行”。总和类型非常有用,
        2. if err != nil 让我抓狂,而且我知道我不是唯一一个。人们仍在争论如何让 Go 的错误处理变得更好,而 Rust 使用 Result 和 ? 语法非常难出错,因为这是语言的核心功能。
        3. 向量和切片之间存在歧义,字符串缓冲区和字符串引用之间也存在同样的歧义。在 Rust 中,Vec 可以解引用到切片,但切片的容量是不可改变的;一个拥有它,另一个则没有。但 Go 却让情况变得非常混乱,因为它允许你直接构建切片并附加到它。当你在进行数据复制与轻量级查看时,这会让人难以理解。
        4. 并发中的竞争条件。并发很难,当你持有垃圾收集器持有的对象的引用时,尤其困难。每种语言中竞争条件的解决方案都是相同的,但在 Rust 中,如果你犯了一个错误,编译器会捕获并通知你,而在 Go 中,它只会默默地出错。
        5. 将公共/私有作为命名约定的一部分,而不是关键字。由于公共/私有导出,struct FooBarstruct fooBar 在语义上存在差异,这太荒谬了。
        6. 鸭子类型接口。如果我想让我的结构体实现两个具有相同方法名称(例如 validate())的不同接口,该怎么办?在 Rust 中,两个同名的特性是不同的,编译器会要求你明确它们之间的区别。
        7. 没有中央包仓库。直接从 GitHub 下载包感觉很糟糕,而且会让发现新包的过程更加困难。集中化的包管理要方便得多,而且这意味着生态系统将更快地收敛到优质包上。

        这些问题单独来看都不是致命缺陷,但它们累积起来就成了问题。它们鼓励走捷径和选择轻松的解决方式,即使你没有意识到或有意为之。结果就是,这种语言整体上比其主要竞争对手Rust要差。每次与Go语言互动时,我都觉得语言中存在一些100%可解决的问题,但语言设计者却因某种误导性的“极简主义”理念而拒绝解决这些问题。

        • neutronbob says:

          我还要补充另一位评论者提到的内容:以Go注释形式存在的超语言宏系统

        • HornetThink8502 says:

          和楼主一样,这大概是60%的合理观点和40%的过度解读

          1) const块很糟糕,而总和类型很棒,我同意。但如果你在代码中真的需要实现梅利机器,那基本上就是披着大衣的goto。这并非“管理代码流的必要手段”。用某种延续机制(异步、协程)重写2) 这是口味和规模的问题。显式返回在客观上更容易发现。如果你不需要通过3层以上的抽象层传递错误,那就没问题。现实中,你可能作为编写者讨厌它,但作为读者并不那么讨厌(此外,如果你深入到7层以上的抽象层,异常更优越。这不是给所有语言都用异常的理由) 3) 同意 4) 胡说八道。Go 在并发模型上是相当理性的语言,这是其主要卖点。通道的工作方式符合大多数人的预期,当出现问题时,感觉像是逻辑错误,而不是陷阱 5) 这是个人喜好问题。Go 的做法避免了关键字膨胀和无谓的争论,但牺牲了一个奇怪的命名规则。坦白说,我更喜欢 Python 的做法,这更符合 Go 的设计理念 6) 再次涉及个人喜好和规模问题。你的接口会让读者产生类型混淆,在小型项目中,简单重命名是合理的。鸭子类型虽不那么通用,但更易于理解 7) 同意

          设计原则很明确:Go 优化了在庞大的代码库中快速查找内容的效率,尤其是在涉及多个微服务的情况下。它在这方面表现出色,但由于缺乏表达力,在处理复杂问题领域时会显得笨拙。如果这真的让你感到不快,很可能是因为你所处理的代码类型。

        • lvlint67 says:

          if err != nil 让我感到疯狂,而且我知道我不是唯一一个这样的人。人们仍在争论如何在 Go 中实现良好的错误处理,而 Rust 使用 Result 和 ? 语法却很难出错,因为这是语言的核心功能。

          如果 err != nil 几乎不可能出错……我不明白这里的争论。我乐意看到关于重复性的讨论……但上面的说法完全说不通。

          并发中的竞争条件。并发本身就很难,而当你持有垃圾回收器管理的对象的引用时,难度更是倍增。每种语言中竞争条件的解决方案都是相同的,但如果你在 Rust 中犯了一个错误,编译器会发现并通知你,而在 Go 中,它只会默默地出错。

          并发是一个陷阱,而 Rust 中的防护栏意味着你需要做额外的工作来完成简单的任务。

          将公共/私有作为命名约定的一部分,而不是关键字。由于公共/私有导出导致 struct FooBar 和 struct fooBar 在语义上不同,这简直荒谬。

          作为 Go 粉丝,我正在逐渐接受这种思维方式……我不会试图为这种立场辩护。这让我感到沮丧了很长时间。

          如果我想让我的结构体实现两个具有相同方法名称的接口,比如 validate(),该怎么办?

          答案:Go 允许你这样做吗?如果你编写了这两个接口,那么被编译器骂是合理的……如果它们来自依赖库,那么你就麻烦了。

          没有中央软件包存储库。直接从 GitHub 下载软件包感觉很糟糕

          我认为去中心化是一个优点。

          结果是,这种语言总体上比其主要竞争对手 Rust 更差。

          Rust 还不错……不过维护别人的 Rust 代码确实有些麻烦。

  27. BlueGoliath says:

    我敢肯定 Java 是最不受欢迎的编程语言。如果不是的话,它肯定是最容易成为梗的语言。

  28. bzbub2 says:

    那篇《我想要摆脱Go语言的疯狂之旅》的博客文章就是一个很好的例子。文中大部分抱怨都涉及Windows文件系统中的复杂细节,而这些细节恰恰是那20%的一部分。

    • AresFowl44 says:

      当然,“Windows 文件系统奥秘”,毕竟 Windows 是最大的消费级操作系统,而文件权限绝对是一项无人使用的奥秘。
      即便如此,其中只有一条抱怨与 Windows 文件系统相关。其余的包括:

      • 路径不是 UTF-8
      • 文件扩展名有问题
      • 平台依赖的代码很糟糕
      • 将库标记为不安全的方式很愚蠢(包括空文件)
      • 该不安全功能本身极具争议(使用标准库中未导出的符号)
      • 单调时间的解决方式非常奇怪
      • 此外,作为修复更改推出的破坏性更改

      我不知道你怎么看,但“大多数”通常意味着远超50%。这只是他抱怨内容的1/8。

      • syklemil says:

        当然,“Windows文件系统奥秘”,毕竟Windows是最大的消费级操作系统,而文件权限完全是没人使用的奥秘。

        有时感觉 Go 更像是用于编写 Kubernetes 微服务的领域特定语言(DSL),而用它做其他事情只是偶然可能。有点像用 Swift 开发非苹果产品,或在电信领域外使用 Erlang。

        • 路径 ≠ UTF-8

        这有点讽刺,因为 Pike 参与了 UTF-8 的创建,并且对 Unix 风格的操作系统有丰富经验。如果有人应该知道字符串很复杂以及操作系统在 Unicode 之前就有历史包袱,而且路径尤其是一种类似字符串的类型而非标准字符串,那个人就是他。

        不过另一方面,我也想忽略非 Unicode 甚至非 UTF-8 的内容,让用户自行修复编码问题。但实际上这样做更像是来自一位仍在大学就读、想要标新立异的语言设计者的设计决策。

        不过,当Pike自称“对类型一窍不通”时,我想这也就不难理解为什么最终会变成一种天真地基于字符串的类型系统。

  29. mblarsen says:

    我认为没有结构体就无法打造流行语言

    Lua,某种程度上

    • nekokattt says:

      我们这里是将类视为结构体,还是区分面向对象语义与过程式语义?

      如果算上脚本语言……可以包含Bash。缺乏结构体在shell脚本编写中非常痛苦。

  30. JustinsWorking says:

    该语言拥有你想要的80%功能,却让你只剩下20%的生产力……明白了

  31. Paradox says:

    我对Go的看法要简单得多:它太丑了。看到Go代码时,我会有看到被蛆虫侵蚀的动物尸体时那种直觉上的反感。它没有优雅,没有机器的美感。只是赤裸裸的内部结构,用蛮力组织起来。到处都是冗余的错误处理代码。

    这是个愚蠢的理由吗?也许吧。但即使是 Rust,尽管它到处都是奇怪的 &*<'a> 以及其他符号,甚至 Perl 及其 @#$@%& 之类的东西,都比 Go 看起来“漂亮”得多。

    • aatd86 says:

      各有所好,Go 非常接近研究论文中常见的伪代码。这正是其可读性的证明。

      也许你是特例。没有严肃的程序员会认真声称,更晦涩的符号能提升可读性。

      Go 对我来说非常“漂亮”,但话说回来,美在观者眼中……

  32. aksdb says:

    没有完美的语言。只有权衡取舍。我个人更倾向于 Go 团队所做的(以及正在做的)权衡。

    • myringotomy says:

      没有完美的语言,但 Go 勉强够用。他们好像知道“垃圾语言”和“刚摆脱垃圾”语言之间的界限,然后就停在了那里。

      • aksdb says:

        那么为什么它被如此广泛地使用呢?Docker 或 k8s 难道不能用 Rust、C++、Swift 或其他语言编写吗?

        我可以用其他语言编写我编写的东西吗?当然可以。但对我来说,Go在开发领域提供了最佳的权衡。

        • myringotomy says:

          那么为什么它被如此广泛地使用?

          因为谷歌,因为工具链优秀,因为它很流行。

          你应该明白,一种语言是否被使用与它的好坏无关,而完全取决于它是否流行,对吧?

          Docker 或 k8s 难道不能用 Rust、C++、Swift 或者其他语言编写吗?

          当然可以。谷歌选择用 Go 编写是因为他们发明了这种语言。

          但对我来说,Go 在我开发的东西中提供了最佳的平衡。

          故事很酷,老兄。

          • despacit0_ says:

            Docker 并不是由谷歌人开发的。你说 Go 被使用是因为它很时尚,但为什么在它变得时尚之前,人们选择用 Go 来创建大型项目?你必须承认,Go 一定有某种特质让人们选择它。

            • myringotomy says:

              你说 Go 被使用是因为它很时尚,但为什么在它变得时尚之前,人们选择用 Go 来创建大型项目?

              时尚使然。这就是所有事物成为时尚的方式。起初只有少数人选择它,随后追随者纷纷效仿。

              你认为 Python 为何如此流行?是因为它是一门优秀的语言?是因为其工具链卓越?是因为它性能优异且可扩展?不,是因为它在研究生群体中流行起来,随后他们用它编写了大量机器学习语言。

              Go 也是如此。Go 是一种平庸的语言,在许多方面都比 Python、Java 及其他在它诞生时流行的语言更差。

              你无法解释时尚。这是群体心理。

            • aksdb says:

              如果不是流行度,什么才是一个伟大语言的标志?如果人们喜欢使用它并能用它高效完成任务,那它就是一个好语言,甚至是一个伟大语言。语言只是工具;实现目标的手段。

            • myringotomy says:

              如果不是流行度,什么才是一个伟大语言的标志?

              一致性、语义、性能、标准库等。流行度是评判语言伟大程度时最不重要的因素。

              语言只是工具;实现目标的手段。

              是啊,那又怎样?你是说最好的工具就是最流行的工具吗?

    • simon_o says:

      “没有完美的语言”并不意味着每种语言都同样远离人们对完美语言的设想。

      • aksdb says:

        什么是完美语言的设想?有没有客观的要求清单?我怀疑没有。每个人都有不同的要求,而许多可能的要求相互矛盾。所以又回到了权衡取舍。

      • light-triad says:

        这取决于你优化什么。如果我们优化的是我个人认为代码应该如何编写,那么我会说 Haskell。如果我们优化的是我认为代码应该如何编写与现实世界实用性之间的平衡,那么我会说 Kotlin。如果我们优化的是人们在现实世界中如何编写代码,并围绕这些不完美之处设计语言,那么我会说 Go。

        • Mclarenf1905 says:

          我认为 Scala 能在你的 Haskell 偏好与现实世界之间取得更好的平衡。

          此外,我认为 Go 的吸引力在于其语法极其简单,但它有太多令人费解的设计决策,我实在无法理解有人如何能在复杂的 Go 代码库中享受工作。Go 语言会竭力阻止你使用任何形式的抽象或复用,其错误处理哲学也存在诸多问题。它似乎将异常处理的缺点与 result/either 类型的缺点结合在一起,却没有继承两者的任何优点。

          • light-triad says:

            我大量使用过Kotlin和Scala。我认为最初Scala无法决定是想成为更高效的Haskell,还是仅仅在JVM上运行Haskell,而且有两派人分别推动它朝这两个方向发展。我认为后者占了上风,而那些希望它成为更高效Haskell的人大多转投了其他语言。

            我发现将新手引入Kotlin代码库并通过Arrow库向他们介绍类似Haskell的概念要容易得多。我个人认为Kotlin在这方面做得更好,但具体情况可能因人而异。

            • Mclarenf1905 says:

              我的意思是,Scala的typelevel生态系统仍然非常强大,而所有受Haskell启发的范畴论库都源自于此,因此我认为它仍然具有相当强的影响力。但我不确定是否同意它会阻碍生产力。我只是对你的回应感到惊讶,因为我认为JVM和访问任何Java库(即连接到任何主流数据库或消息代理的接口)是阻止Haskell高效的唯一真正因素,而不是类型系统或语言特性本身。我真的很喜欢Haskell和Scala类型系统提供的确定性。

              还有ZIO框架和lihaoyi的库,它们更侧重于生产力方面。此外,Scala 3编译器正在进行许多有趣的工作,使用直接风格效果作为更重效果系统的替代方案。

              说实话,我感觉是Scala用户中“更好”的Java阵营离开了,转投Kotlin,而剩下的Spark用户则回到了Python。

              在我看来,Scala 3 才是真正导致社区分裂的主要原因。这令人遗憾,因为 Scala 3 实际上解决了许多人对 Scala 的问题或痛点,其中最大的问题是人们对 Scala 版本的常见误解,即在 2.x 版本中,每个 x 版本都是主要版本而非次要版本。

              我当然有偏见,因为 Scala 可能是我最喜欢的语言,但对我个人来说,它比 Kotlin 更适合我,但每个人都有自己的选择。

      • CyberWank2077 says:

        但这个理想对每个人或每个项目来说都是不同的。

    • TomWithTime says:

      我大约在2015年开始将Go作为爱好使用。我对它的简单语法和可执行文件印象深刻。然后大约在2021年,我得到了第一个使用Go的工作,我仍然对它非常满意。

      我曾努力编写一些代码,其中涉及多种类型与基础类型组合,并需共享一个顶级执行函数。最终我让该函数拥有2个泛型参数,而主参数是一个接口,该接口包含其中一个参数。

      与其他语言相比这显得有些凌乱,但我对结果感到满意。

    • s0ulbrother says:

      Visual Basic是最接近它的语言。它足以在Excel工作簿中完成基本任务,适合办公室人员使用。

      • aksdb says:

        同样的道理也适用于JS、Lua或Python。但仅仅因为它能满足特定人群的需求,并不意味着它在所有场景下都完美无缺,否则每个人都会用它来做一切事情。

  33. NCSUMach says:

    老兄,我喜欢 Go。

  34. stuartcarnie says:

    我喜欢 Go。我喜欢 Swift。我喜欢 Rust。我喜欢 C/C++。我喜欢 Python。它们都有各自的用途。

    • adamsdotnet says:

      没错,Go 和 C++ 提供了如何不设计编程语言的绝佳范例。

      Python是为什么不应该用脚本语言做其他事情的绝佳例子。

  35. ArkoSammy12 says:

    天啊。这么久以来,我以为Java是最不受欢迎的语言,因为它缺乏功能。

  36. pepiks says:

    我读了这篇文章,但没明白重点。是因为它不够复杂,还是语法太简单了?当你需要对硬件层面的更好控制时,你会选择像C/C++这样的语言,就像我们在TIOBE指数中看到的那样。

    没有哪种语言是完美的,当我们喜欢某种语言时,可能会用谬论来幻想一些不可能实现的东西。

  37. RunicWhim says:

    所以Go在做Java做过的事情?只是停滞不前?

    • KawaiiNeko- says:

      如果使用过Java 17之后的版本,Java 并非停滞不前。许多企业仍在使用Java 8,但这并非语言本身的缺陷。如今Java 21已重新实现了大量Kotlin的特性,实际上使用起来相当不错。

      Go 语言的开发理念是“开发者太蠢了,不配使用抽象概念”,因此 Go 语言被有意无意地塑造成了一团糟,与停滞不前的语言如出一辙。

      • RunicWhim says:

        Java 到底做了什么?什么都没做。

        它确实一直停滞不前,直到大约 2018 年。大约 12 年时间里只有两次重大版本发布。这就是停滞不前。是的,自 2010 年左右以来,随着 6 个月的发布周期,它已经现代化了很多。除了空安全。如果可以使用 21+,我真的看不到使用 Kotlin 的意义。我猜是 Android 开发,但我更愿意使用 Java。

        Go 语言是刻意设计成停滞不前的。这与 Java 过去的情况相去不远。Java 曾经做出过极具侵略性的向后兼容性决策,导致积累了大量冗余代码和臃肿的 API。

  38. shevy-java says:

    Go 已经达到了 80% 的完美。剩下的 20% 它甚至不会尝试去追求……

    C#、Swift、Rust——它们似乎都陷入了无休止的添加功能的循环中。

    嗯——Rust 一直都很复杂。

    我认为这并不适用于 Swift。

    C#更接近Java的领域,所以我将它们归为一类。Go则更努力地成为“更简单的C”,从而与C竞争。在某些方面它确实成功了;我认为Go比C简单得多。当然也比C++简单。

    有趣的是,所有试图取代C的语言最终都未能成功。不过,Go 做了一件不同的事:它尝试使用另一种语法。这很罕见,因为大多数试图取代 C 的语言最终都会复制粘贴语法。看看 C++,看看 Rust。它们几乎属于同一语法家族。

    甚至 JavaScript,它最初是一种 70⁄15 语言,但后来被那些以添加更多 JavaScript 功能为工作的人所占据。

    基本上,谷歌现在主导着 JavaScript。我们需要打破它对万维网的垄断。此外,JavaScript 很糟糕。我可以在其中编写可运行的代码,但必须一边咒骂一边编写。我使用 Ruby 或 Python 时从未遇到过这个问题(尽管 Python 对我来说有点别扭,但我不必咒骂,而 JavaScript 真的会触发我的大脑。JavaScript唯一有趣的地方是2012年的WAT演讲:https://www.destroyallsoftware.com/talks/wat)

    语言的用户需要付出努力。语言的每一项新功能都要求程序员去学习它。这比看起来要麻烦得多。

    这是真的。我最终没有使用 Ruby 的许多新功能,因为它们看起来无用且/或丑陋。

    你无法跳过这种复杂性。即使你决定不学习如何将函数作为第一类概念使用,你的同事可能会这样做,而你必须能够理解他的代码。

    这个论点很奇怪,因为无论谁编写了代码,如果是由其他人编写的,我总是需要花时间去理解它。因此,是的,更复杂的语言需要更多时间去理解,但即使在简单的Python中,我经常不得不问自己“他们到底在写代码时在做什么”。有时,那些“他们”就是我,几个月或几年前的自己。这也是我添加大量注释、文档和解释的一个重要原因;我需要向未来的自己解释我当时在做什么。后来我可能会意识到,我写代码时一定喝醉了。但我仍然不知道别人在写那段代码时在做什么。

    这就是为什么80%以上的语言需要编码规范。

    编码规范也是垃圾。许多指南毫无意义;看看RuboCop的指南就知道了。至少20%是垃圾;或许5-10%有用或勉强可接受,最多25%。但大多数最终只是个人意见。不过仍有一些客观标准适用,例如格式化方式。例如,使用两个空格是正确的;四个空格是错误的,但最糟糕的是使用制表符进行缩进。许多人并不明白原因。他们需要自行思考并领悟;向他们解释是没有用的。(这与Vim与Emacs的争论不同,双方都错了,尽管双方都认为自己正确。)

    Google为C++制定规范,是因为如果没有对个人程序员可使用功能的限制,数百名程序员将无法有效协作开发共享的C++代码库。

    好的,首先——并非所有人都像谷歌那样。其次:C++ 在许多领域都表现不佳,令人惊讶的是 TIOBE 仍将其排名第二。你可以说人们需要语言教程,但这绝非唯一原因,因为其他语言也需要教程,却无人搜索,因为这些语言鲜少被使用。显然,C++ 被广泛使用。它只是并非有史以来设计最好的语言。

    Swift 是一个警示案例。尽管有非常聪明的人花了超过10年的时间开发,而且预算几乎无限,而且这是一个对苹果来说优先级很高的项目,但 Swift 编译器仍然很慢,容易崩溃,而且不是真正意义上的跨平台。

    好吧,但他比较的是编译器。那不是语言本身。他提到了 Go 的特性,然后批评 Swift,而没有批评语言固有的特性。作者是否存在严重偏见?

  39. rover_G says:

    Go 是人们实际上喜欢却最被讨厌的语言。

  40. piizeus says:

    Go 并不被讨厌。Go 是一门非常无聊的语言。那些患有“新奇事物综合症”、害怕错过(FOMO)和追逐热潮的人认为他们总是需要一些新东西,而 Go 没有这些。

  41. seweso says:

    这更像是“无法让所有人永远满意”。

  42. maax3v3 says:

    有人曾说过:“语言只有两种:一种是人们抱怨的,另一种是没人使用的。”

  43. steve-7890 says:

    值得一读,尤其是结尾部分。

  44. DanKegel says:

    Go 通常都能正常工作。现在的软件包管理相当不错。代码可读性强。几乎没有神秘之处。测试和模糊测试支持也很好。

    如果你想要裸机性能,就使用 Rust(或 C)。如果 90% 的裸机性能可以接受,而且你不介意垃圾回收,那么 Go 就非常适合你。

发表回复

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