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 版本开始就具备快速、跨平台和健壮的特性。
哦,你一定没听说过 JavaScript
对bash没有爱(我是说恨)?
这也不是人们特别会为之辩护的东西。你听到的厌恶通常是某种来回争论的一部分。bash在某种程度上作为粘合剂存在于各个地方,介于那些更愿意使用POSIX shell(
#!/bin/sh
而不是#!/ bin/bash
,可能由dash
或其他非GNU Bash提供),以及像我这样的人,更愿意将shell仅用于某种配置好的程序调用,即使如此,也会使用set -euo pipefail
尝试严格模式(它仍然没有你想象的那么严格),并使用shellcheck
来捕捉更多“哦,该死,Bash”Bash是最奇怪的东西。大约十年前我被告知的一条经验法则是:除非脚本少于十行、没有比单个if语句更复杂的控制流、且不需要任何数据结构(否则直接用Python)
在我看来,这一原则在实际应用中大多成立。总体而言,Bash 脚本(从广义上说)已显过时。
我已掌握足够的 Bash 技能,可使用哈希表、理解字符串分割的细节,并能通过完整的 GNU 风格长短选项实现预期功能。
我只能说:除非你想接受挑战,否则最好不要使用。
如果我需要一个看起来像命令行程序的东西,我就使用Perl。如果我需要哈希表,同样使用Perl。如果我需要超过几屏的内容或更复杂的逻辑,同样使用Perl。
Perl 在 Unix 世界中的定位非常精准,尽管它不像 Bash 那样支持管道(坦白说,这是任何语言中都非常直观且强大的功能,以至于我在其他语言中都感到缺乏),但变量行为的一致性与 Bash 相比是一个强大的优势。
对我来说也是如此,不过我使用的是 Ruby。
为什么不支持?我有点困惑。在 Ruby 中,我们也可以通过 ARGF 从文件中接受输入。而且管道本质上就是一个方法调用,所以方法链在这里是自然而然的。
我特指管道作为从左到右的函数应用,比如
command_a | command_b | command_c
(这在大部分语言中可以轻松重写为类似从右到左的函数应用command_c(command_b(command_a(()))
)。对我来说,管道是一个很棒的功能,因为它意味着你按照读取顺序应用函数,因此更容易扩展以提供增量功能(特别是在你试图将数据增量转换为你需要的形式时,通常通过 sed 和 awk 实现)。
Bash 的额外优势在于支持并行处理,因此第一个命令的行缓冲输出可以在第二个命令读取并生成输出时,为另一行生成输出。遗憾的是,这在 Perl 中并不简单(可能是因为其支持的操作系统可能不支持正确的进程 fork,例如 Windows 或其他非 Unix 类操作系统)。
试试 Nushell。它拥有所有实用功能且极易阅读。
创建类似 pandas 的高效数据管道非常简单。
确保获取内置 Polaris 的版本。
Perl 是大多数 Linux 发行版的默认安装包
是的,这也是我的基本态度。其中一部分原因是,如果脚本很可能继续增长,那么最好早点退出,而不是先编写复杂的 Bash 脚本,然后在发现这是一个糟糕的主意时再将其移植。
我将这条规则应用于每次需要编写 shell 脚本的情况,无论其行数多少。
Ruby 取代了所有这些逻辑。Ruby 就像我与计算机之间使用的粘合剂。它实际上是 C 的封装层。
我认为唯一能认同的批评是 Ruby 的速度不如 C。对于小型脚本这无关紧要,但在处理更多任务的大型脚本中,例如加载并处理一个巨大的 .yml 文件时,我能明显感受到速度差异。(或许应该转用 SQL,但 .yml 文件修改起来相当方便。)
Python 已经基本上取代了 Bash 成为首选的脚本语言。只有那些已经熟悉 Bash 的人仍然使用它来编写脚本
是的,我已经淘汰了一些复杂的 Bash 脚本,通常使用 Python 来完成这些任务。
Python 在分发方面也可能有点麻烦。如果你知道目标操作系统并能通过包管理器安装软件,或者你将软件打包在容器中,那就没问题。
uv
可以帮助你。在 Python 中确保有正确的库可用,与在 Bash 中确保有正确的可执行文件可用,基本上是同一个问题,所以它们在这一点上其实有点相似。但 Python 允许你在pyproject.toml
或requirements.txt
等文件中声明依赖项,而 Bash 没有类似的约定,而且你不需要以 root 身份运行uv
或pip
。但 Go 也抢占了部分市场。许多 Go 用户来自 Python,而发布一个静态二进制文件确实有其优势。我们有一些 Go 代码,长度在 20-30 行左右,如果是在十多年前,这些代码肯定会用 Python 或 Perl 实现。
不幸的是,尽管 Go 很容易上手,但似乎很多人没有意识到,普通的
git
对于二进制文件来说相当糟糕,因此将二进制构建结果提交到仓库并不是最佳的分发策略。讨厌PowerShell的人比讨厌bash的人多。
我讨厌PowerShell并非因为它比Bash差(它并不差)。而是因为至少Bash有充分的理由解释其怪异之处——它是在几十年前为性能仅相当于我腕表的机器拼凑而成的。PowerShell则没有这样的借口。
PowerShell还可以。Bash更快,管道的异步处理很棒,但PowerShell的哈希表更好,也有一个糟糕的结构体之类的东西。
PowerShell在简单任务上更差,但在复杂任务上更具表达力,但速度更慢,而且到那时你应该使用另一种语言了
认真使用过两者的用户大概都讨厌 Bash。
我对两者都算专家,但同样讨厌两者……
它们都有一些非常奇怪的特性,最终会让你陷入困境。
我认为两者都不应用于除胶水代码以外的任何场景。
我见过太多用这两种语言构建的过于复杂的系统,维护起来噩梦般困难,本应使用具备完善开发工具的正规编程语言。
在这些语言中构建复杂系统的理由是它们能在任何环境中运行。
我见过很多环境对随机可执行文件或运行时环境(如Python)感到不安。
大多数环境都允许运行Bash或PowerShell。
是的,权衡在于维护噩梦,但至少环境中的每个人都应该能够阅读代码。
对于PowerShell,你可以在VSCode中用
.ps1
文件编写任何脚本。你觉得PowerShell缺少正规编程语言和开发工具的哪些功能?尝试为PowerShell创建一个严肃的CI/CD管道将是一场噩梦,因为PowerShell 7缺少许多你在脚本中需要的类,而PowerShell 5需要Windows系统。
因此,如果你想创建一个严肃的管道,你不再需要启动一个运行PowerShell Core的Docker容器,而是需要启动一个Windows实例来测试你的脚本。
PowerShell 是单线程的,其常见使用模式更倾向于交互式操作而非程序构建。而且它的性能坦白说并不理想。
当然你可以做到,但我的经验是这会变得一团糟,而很少有人具备这种 PowerShell 经验的事实也无助于改善情况。
使用 Python、C#、Java 等语言构建会轻松得多。
150%。
此外,还有几个版本彼此完全不兼容。
Bash最大的缺点在于它诞生于C语言成为通用语言之前,因此对现代程序员来说显得笨拙。不过Bash中仍有一些非常有趣的理念,使其独具魅力。
这不正确。Bash于1989年首次发布,当时所有人都想摆脱C语言。
也许你指的是原始的sh,有时被称为Bourne shell,它是在20世纪70年代中期开发的。
但也不对,人们并不是抱怨 Bash 对熟悉 C 语言的人来说很奇怪。Bash 只是一个糟糕的编程语言。它充满了意外,晦涩的语法,而解决问题的最明显方法通常是错误的。令人惊讶的是,你需要学习多少才能正确使用它。你可以在更短的时间内学会足够的 Python。
当你开始需要 POSIX 标准 shell 的 Bash 特定功能时,你本应早已使用某种合适的语言来完成该任务。
Bash 作为一种脚本语言,用于调用二进制文件并将其连接在一起,它做得很好。
同意,但一旦你开始编写任何类型的循环,就需要考虑升级编程语言。当那门语言是C时,我能理解你为何坚持使用。但如今有了Python等语言,继续使用C就毫无意义了。
在Bash中使用循环没问题。但当你需要处理多个数组或哈希表时,就该考虑切换语言了
Bash循环并非一无是处。我作为Python软件工程师已有近20年,仍有许多问题更倾向于使用Bash和Linux内置工具,甚至搭配少量awk来解决。关键在于不要试图用Bash替代其他语言或工具更擅长的领域。
我不会为了调试Python解释器的运行时问题而登录服务器。我使用 Bash、grep、awk 等工具,除非控制结构需要相当复杂,那时我会快速编写一些 Python 代码。
它从来就不是一种“语言”。它的设计初衷是与 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 工具链的其他部分已经存在。
另一个关键因素是
perl
。1991年,著名的《骆驼书》(Perl编程)出版。一旦perl
成为可行产品,“交互式shell作为脚本语言”就不再是优先事项。当Debian和Ubuntu转向dash
时,几乎无人察觉。Bash之所以胜出,不仅因为它是早期默认选择,还因为它长期处于无人关注的状态。一旦它成为“标准”,世界便继续前行。
我认为Bash最大的缺点在于,有人曾提出“结束代码块的最佳方式是将关键字倒写”的荒谬想法,而当时无人及时制止。
但仅限于某些代码块!
if
块以fi
结束,因为这合乎逻辑。case
语句?没错,用esac
。但while
和for
循环?则以done
结束。我只能假设,此时有人终于从 Brian Fox 手中夺回了键盘,以拯救所有人。我不确定这是否能表达清楚,但每次不得不与 Bash 脚本交互时,我感到的愤怒真的难以用语言形容。
这实际上源自 Algol。Bash 在其源代码中大量使用模板,以便以 Algol 风格编写 C 代码。
情况可能更糟,Algol中你用
od
结束do
块听起来我们需要结束Algol。Logla!
布莱恩·福克斯并非该语言的创建者,如果你想责怪某人,那应该是史蒂芬·伯恩(不是杰森)和POSIX委员会。
有同感。
你看,
od
本来就是一个命令,所以他们不能用它来结束do
块。我的猜测是,因为有人可能会不小心输入EOF而不是ROF。
将关键字倒过来结束块有什么问题?对我来说这很有道理。
喜欢?
模式匹配的switch case。
展开语法
单引号(
'
)和双引号("
)字符串的区别在当时是超前的。直到很久以后,正确的字符串插值语法才流行起来。我喜欢管道功能,以及启动独立子进程的便捷性。这种语法适合将所有内容写在一行内,无需过多输入。将输出重定向到文件也很酷。
同感,兄弟
你试过 F# 吗?它应该是管道操作的黄金标准,尽管 Bash 在这方面无疑拥有最多的市场份额
PowerShell 粉丝们团结起来
行尾的空白字符可能破坏一个需要运行4小时的脚本,这很糟糕
在Bash中,行尾的空白字符何时会影响任何内容?
几乎从不。
我能想到的一种情况是,当你有一个很长的命令,比如:
foobar --option1 --option2 --option3
如果你不小心在其中一个
字符后面添加了空格,那么命令的延续就会被中断(因为它不再转义换行符),命令会被分成两部分。
这就是确切的情况。更糟糕的是,Git 差异比较会忽略行尾的空格
哦天啊,明白了,有道理。我也听说 Git diff 可能会对换行模式和编码更改处理得比较微妙,甚至完全隐藏这些变化…
行继续字符后的空格
我怀疑他们可能在混淆Makefile和Bash。Makefile对格式要求非常严格。
太忙于阅读Greg的维基,没空抱怨
我不知道。我原本不喜欢shell脚本,但最终还是用ruby脚本解决了问题。每次需要shell脚本时,我都会用ruby编写对应的逻辑。(现在只剩下少数遗留和启动shell脚本)
问题总是:既然每个发行版都预装了Python,为什么还要用bash?
现代PHP其实没那么糟糕
这并不能阻止人们讨厌它。
Objective C不错。
这并不能阻止人们讨厌它。
程序员真是保守的人,讨厌任何不符合舒适的C语法的东西。
嗯……PHP是一种相当丑陋的语言,但我并不讨厌它。我在PHP中的生产力甚至比在Perl中更高。
Objective-C有点奇怪;但我并不讨厌它。
Java 我觉得还行。虽然仍然过于冗长,但随着时间的推移,它已经有所改进。COBOL 我不确定人们是否讨厌它;毕竟使用它的人太少了。
C++ 确实有些复杂(人们尤其讨厌它的复杂性),但它也非常强大。
我真正讨厌的唯一一种语言——而且我绝非孤例,从上面帖子作者获得的赞同票数就能看出——是 JavaScript。这种语言糟糕透顶。而我不得不使用它……这让我心情糟糕透顶。
我没有做出任何判断,只是列出人们似乎非常讨厌的语言。
基本上任何较旧的语言都可能让很多人讨厌。
我最不喜欢的在这份列表中是Cobol和PHP,紧随其后的是Perl,但如果代码没有严格的……那就是我最不喜欢的。
有大家讨厌的语言,也有不常用的语言。:)
赞同,Perl是垃圾。只写不读的语言。
我认为阅读他人编写的Perl代码从未遇到过问题,只要他们使用了strict和warnings指令。这两者非常普遍,我很少看到缺少它们的情况。
咳咳 Java想说几句
与JS相比,Java还不错
别提现代Java,大家都知道企业会一直用Java 8到地老天荒
我在银行工作,我们用的是Java 21。
开发者调查显示,截至2025年,仅约23%的组织仍在使用Java 8。这是自我报告的数据,因此不必过分关注具体百分比,但使用率确实正在下降。
我正在为政府开发一个税务会计系统(或类似的东西,不知道如何用英文称呼),我们使用的是Java 11
为什么?
客户(州收入委员会)做出了这个选择。你可能在想:这是什么鬼,为什么客户要选择技术栈?!问题是,我们的技术总监未能向他们解释这是一个糟糕的主意
Java 8 还不错。我记得 Java 4 没有泛型——就像某些 Go 版本一样……
我从 1.2 版本开始,就在集合变得合理之后……
现代Java仍然笨拙得要命(虽然我们也可以怪罪旧版本吧!)
我曾参与过一个Java后端与TypeScript前端的项目。使用这两种语言的切换体验非常痛苦。
TypeScript是一个非常优秀的语言规范。有人应该为它打造一个合适的运行时环境。
加入Spring后效果不错
Java很棒,甚至可以说是完美
我不明白你怎么能写出这样一篇文章,而且在第一句话就犯了如此严重的错误。
其实也许我可以理解,因为Go或Rust的支持者会做出这样的断言:他们为了宣传自己的语言而头脑被蒙蔽了,忘记了人们并不讨厌这种语言,他们讨厌的是听到为什么<其他语言>如此糟糕,我们都需要转换到<语言>。
这叫做诗意的自由。你太认真地对待我的思考了。
此外,我敢肯定Go是HN上最不受欢迎的编程语言,而HN是我主要的技术讨论来源。在Reddit上它也同样不受欢迎。
此外,在整篇文章中我从未建议过你应该转用Go。事实上,别这么做。我可不需要有竞争力的对手。
我们有相关数据。Stackoverflow每年都会进行一次调查。
最受欢迎的语言是 Rust,最不受欢迎的是 Matlab。
Go 的 67.7% 虽然远不及 Rust 的 82.2%,但仍然是排名最高的语言之一。
使用一种语言的人越多,有生产力的竞争对手就越多,但这并不是零和游戏,使用 Go 的人越多,它的工作量也就越大。
肯定是SQL,尤其是存储过程。
根据Stackoverflow调查,是Matlab。
我想你年纪大了。
为什么像SQL这样简单的语言比JS更令人讨厌?
我目前参与的项目使用的是JS和Go
或者Modula 2
这个问题是检验一个人偏见的一把试金石,哈哈
最近在推特上遭到一些批评,我认为完全没有必要
我讨厌现在80/20原则被用于一切事情
它大约80%的时间都用于点击诱饵。;)
简而言之:作者喜欢分数
*虚构的分数
简而言之:有时70 + 15 = 100
70/15 意味着用 15% 的努力实现 70% 的功能。80/20 法则中的数字加起来正好是 100,这更多是巧合。
我猜 8⁄20 是打字错误…
我原本以为这是在讽刺没有泛型。
如果 err!=nil 返回,可能是 80%
这很好,因为当高管强迫晚上使用 AI 时,我可以说是它写了 80% 的代码,仅限于错误检查。
现在甚至连 Go 团队本身都推荐了!
为什么让编译器做大语言模型 (LLM) 可以做的事情呢?毕竟,LLM 的复杂程度要低得多,也不需要像编译器那样花费大量时间和资源。:)
这一定是开玩笑。
Gophers:我们的工具已经如此强大,我们甚至不需要再编写或查看 iferrnotnillreturns。
其他语言:看看他们需要模仿我们力量的多么微小部分。
我认为这里隐含了一个“/s”
是的,“:)”实际上在很久以前就作为一种“/s”开始了它的生命[https://www.cs.cmu.edu/%7Esef/Orig-Smiley.htm%5D。
当我看到这篇文章甚至提出了一些非常合理的错误处理方案时,我感到惊讶!我最喜欢的是当然是
func()?
,如果需要修改错误(例如添加调用堆栈或错误消息),可以扩展为func()? { return modify_error(err); }
。是的,多年来一直有许多关于更人性化错误处理的提案。但它们似乎都无疾而终,因为
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 开发者感到不适,因为他们希望仅有一种统一的实现方式。关于语法,存在无休止的争议。有人认为
?
占用的空间不够。我个人认为,能够区分=
和:=
的人也应该能看到?
,但再次强调,我认为我们只能在这一点上各持己见。有些人可能只是患有“非我发明症”的严重症状;另见:泛型、迭代器。
每次AI出错并编写出无法编译的代码,或错误地推断变量类型和方法签名时,我都忍不住发笑。它在一些本应轻松处理的琐事上屡屡失误。一个合格的编辑器本身就能查找定义、进行基本代码检查或编译并检测错误。
我原本預期 AI 會出現運行時錯誤,但它連如此基礎的錯誤都搞砸,簡直可悲。我們已經投入這項技術多年並發明了 MCP,但 AI 卻要消耗電力和代幣來猜測簡單演算法能以 100% 準確度提供的內容?
然而,编辑器和树坐器 (tree-sitter) 等工具是专门用于解析和理解代码的。另一方面,大语言模型 (LLMs) 将其作为输入来预测可能的输出。它们现在非常擅长预测,但仍然只是产生看起来相关的结果,无法“知道”一个语句是正确还是错误。
当然,我的意思是通过mcp或GPT封装器,AI工具应利用算法来节省资源、提高准确性、检查工作等。我不是指陷入循环不断尝试直到满足代码检查器要求,而是可能只需运行编译/代码检查步骤并通知我们问题,以便我们指导下一步操作。或利用mcp调用TreeSitter获取类型信息。也许这可以成为其向量数据库的一部分。
我认为我的公司正在基于其他工具构建自己的工具来实现类似功能。对代码库进行索引以提升AI的认知能力。仅用了一周或两周就让它正常运行,所以我惊讶于大型AI公司为何没有想到这一点。如果它们处于亏损状态,为何不采取小步骤来减少因错误猜测方法签名而消耗的令牌数量?
https://imgflip.com/i/9ytkna
这听起来很像Sun公司为何要让Java变得如此冗长。他们想出售NetBeans的副本,而一种具有合理默认设置的语言会破坏这一目标。因此,他们没有将public static设为默认值,甚至没有将private static设为默认值,也没有将所有函数默认设为void(除非另有说明或推断),而是要求你必须手动编写这些内容,因为他们的IDE会为你自动完成,前提是你购买了IDE的副本。
关于Go语言,以及著名的Pike名言
这似乎有点像给不懂行的初学者提供 Go 这样的语言,而利用大语言模型(LLMs)减轻辛劳可能是大型企业获得最大回报的一种方式。这也是大语言模型和云提供商获得更多用户和收入的一种方式。
不过,有趣的是,那家曾为SRE(站点可靠性工程师)撰写关于“ toil ”的权威著作的公司,似乎对开发者的“ toil ”问题并不太关心。
我之前一直以为NetBeans是免费软件,后来查了维基百科才发现它确实是付费产品,不过这种情况没持续多久。也许Sun在NetBeans之前就有其他IDE了。
我想他们可能只是不希望用户手动输入所有代码,或者不希望它看起来像脚本语言。
看看COBOL,这种最冗长的语言是在穿孔卡片时代创建的……
早期Java版本实际上比Java 2(1.2)之后的版本要简洁一些。我记得小时候还能写出不错的Java程序,后来才变得越来越冗长。
COBOL 是一个有趣的存在。它最初是为商业人士编写代码而设计的,而非程序员。我们看到现代针对同一群体的“无代码”和“低代码”解决方案中,同样存在着感染 COBOL 的那些问题。
这是我使用 C# 和 Kotlin 等拥有出色语法糖处理错误/空值的语言后,始终无法接受的一点。如果一种语言声称自己是现代的,却放弃了像空值处理运算符这样的基本语法优势,我无法认真对待它。
但 Go 语言中还有许多其他糟糕的决策让我望而却步。
人们往往倾向于过度思考错误处理,感到沮丧,最终干脆放弃它。
我主要使用 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 写出类似的代码
这更像是语言本身对单子代码支持不足的问题,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 = …
。Haskell 的 do 语法与 Elixir 的
with
其实差别不大,两者都允许某种程度的“铁路式”编程。至于单子,它们从未真正适合这种情况,只是我在疲惫地试图完成一个项目时随手拿来的工具。
with
是最优雅的解决方案,无需修改原函数的签名,但如果我要这么做,可以修改它们在接收nil
和有意义的值时采用不同的模式匹配,并在那里处理相关逻辑。%{}
是 Elixir 中的空映射。所有函数都接受并返回一个映射明白了。
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 >>= 无法处理空值的其他函数 >>= 第三个类似函数
没错,
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})
实现类似的功能,所以这更多是一种便利而非必要。被迫考虑你的代码在调用的函数抛出错误时应如何反应(并以标准方式传递这些错误),现在这算是一件坏事吗?
很想听听你讨厌的其他糟糕决策
天啊,这和你评论的内容完全不同。
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 实现很多东西,专家们也用它做出了许多精彩的成果,但我敢打赌,对于任何需要长期维护和功能扩展的项目来说,它都不是最佳选择。
此外,请记住,根据函数的返回值,结果要么是(可能)空值(因为即使在错误场景下,你也必须构造并返回有效值),要么是
nil
。因此,你可能会遇到垃圾数据被引入程序的情况(这里保存到数据库的数字“零”是真实数据,还是因为忘记检查
err
导致的垃圾数据,而这个检查可能在距离数据写入位置 20 个文件和 1000 行代码之外?)……或者你可能会遇到空指针解引用导致的 panic。说实话,
nil
异常可能是更好的情况,因为至少当它发生时应该很明显。我还会加上零值。添加一个字段到结构体时,没有编译器的帮助来确保你更新每个使用位置,只是… 在你可能遗漏的地方有垃圾数据… 然后你总是会怀疑一个空字符串(或其他什么)是故意的,还是一个无意的垃圾“零值”。
在我看来,Rust 对于 REST 服务这样的简单东西来说是过度杀伤力。它的内存管理机制虽然有其作用,但会让人感到精神疲惫。简单的 RESTful HTTP 服务并非其适用场景。链接无法加载,但我对这篇文章很熟悉,因为我们在决定使用 Go 之前就读过它。我记得其中一位作者最大的抱怨是关于文件权限的处理方式,并详细探讨了相关细节。这并非我使用 Go 的目的,且他的大部分抱怨与我的用例无关。
Go 确实存在一些缺陷,例如它处理 nil 的方式。它的错误管理可能显得重复,但这种明确性让我非常欣赏。
在更好的替代方案出现之前,我将继续使用 Go 处理每天在仅 2 个 CPU 上处理数十亿次请求的服务,以及支持公司数十亿美元收入的服务 🙂
你一定喜欢Java的受检查异常。
Go之于异常,就像无人驾驶出租车之于激光雷达。某种程度上它被认为没有异常更好,但没人理解你的解释,你却不断遇到问题。
它被认为更好,是因为设计语言的人忽略了大多数现代语言特性,然后在意识到人们实际上想要这些特性后,又无法将它们全部加回去。
https://go.dev/blog/error-syntax
对于通用类型也是同样的情况。不知何故,没有它们的世界似乎更好,但后来不得不硬塞进去,因为人们需要做太多绕行操作。
如果Go从一开始就设计得当,而不是试图成为C语言的某种实用主义安全替代品(他们只是随意添加功能集并假装语言因此更好),它会更好。
说实话,如果 Go 重新设计,加入一些基本的生活质量改进,我一定会爱死它。但我没有真正需要使用它的理由,除了说 CLI 应用或基础设施工具,相比其他语言——当然,像 Java/Python/JS 也有它们的怪癖,但至少第三方库丰富,生产力是第一位的。使用 Go 语言时,a) 我必须自己完成所有事情,b) 它仍然有一些让我感到烦人的怪癖
这是原始语言目的的产物。其目的是成为谷歌的新工具语言。在谷歌的生产环境中不能抛出异常。因此采用了这种特定的错误处理设计。
来源:曾在那里工作过很长时间。
我们用剃须刀片替换了所有剪刀,因为孩子们拿着剪刀跑来跑去。
除了技术上满足要求外,返回 nil 哪里算得上改进?
啊,这种代码类型没人这么做。这只是针对“通用”场景的权宜之计。
不是说这是好习惯,只是解释其来源。
如果这是Go语言最糟糕的部分,我愿意接受。我喜欢Go的工具生态系统,作为一门语言,它从未“妨碍过我”。
“Go是最令人讨厌的语言。”
[需要引用]
Go 绝对是我最讨厌的语言,不是因为它像 JS 或 PHP 那样糟糕,而是因为它糟糕的原因是故意的。https://www.reddit.com/r/ProgrammerHumor/s/4GmKRxKIt6
这门语言一般般。围绕它的文化简直一团糟。“熟悉带来简洁”,所以随随便便用单字母变量来表示一切。
老兄,我连两周前自己写的代码都不熟悉,更别提五年前别人写的代码了。所以我们还是遵循这个推论吧:“不熟悉就无法简洁”。
我曾与一位同事合作,他有一个Git钩子,会删除代码中所有不是显式文档标签的注释。
同一个人在重构时总是手忙脚乱,因为他自己都不理解自己写的代码,而且没有注释作为指引,他完全迷失了方向。
也许我没有完全理解你评论的上下文,但我编程时的指导原则是:写代码时不应依赖注释来理解它。显然,例外情况是必要的,尤其是在出于优化等原因需要编写一些晦涩的代码时,但我尽量将其控制在最小范围内。那么,你是说这个人一直编写如此晦涩的代码,以至于他自己过了一段时间后也无法弄清楚它在做什么?这听起来像是故意积累技术债务的习惯性做法。
我认为大多数人都同意这一点。我个人倾向于留下注释说明“为什么”,尤其是当似乎有更直观更好的实现方式时。比如
// 这看起来可以用策略X实现,但那会引发问题Y
如果这些注释被自动删除,我就会反复学习同一个教训,直到对策略X产生条件反射。
他就是那种对以“聪明”方式做事情着迷的程序员。再加上删除注释,我们中很多和他一起工作的人开玩笑说,他在编写代码来保障自己的工作安全。
明白了。是的,我以前也遇到过这种类型的人。
抽象越强大,无意义的名称就越有意义——Haskell以使用此类名称而闻名,但这是因为代码往往过于通用,具体名称反而会模糊算法的通用性。
不过,Go语言缺乏编写此类抽象的能力(除非使用
interface {}
之类的 hack),因此他们没有借口。所以我完全同意你的观点。你是在攻击理论论点,而不是试图以善意理解其含义。
喜欢 Go 语言的人强调,他们更可能熟悉任何程序,无论该程序是十多年前编写的,还是今天编写的。
这并不是一个需要反驳的荒谬主张。它只是突显了该语言所取得的成功。
当然,Go 语言也有明显的缺点。有趣的问题是,你的(或某个项目的)优先级是什么,以及哪一套权衡更适合。
如果你编写的是信息处理密集型程序,那么你可能需要选择一种具有更强抽象能力、支持函数式编程且具备更通用数据操作能力的语言等。你不会选择Go。
如果你的项目更侧重于I/O操作,并且你希望获得更高的性能、更轻松的维护和稳定性,那么你可能需要考虑Go语言。你今天编写的代码将需要更少的特殊/上下文知识(包括你自己)。即使在5到10年后也是如此。
这可能不是你想要表达的意思。这是什么意思?
我是在攻击那些试图模仿Go风格时我不得不审查的糟糕代码。
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
所以我不知道将这些语言进行比较是否公平。
这就是我的观点,这些语言做出糟糕的设计决策是有充分理由的,因为它们都是仓促完成的,且未借鉴数十年的语言设计研究成果。而Go的设计者们则拥有充足的时间,并且受益于数十年的语言研究成果,却有意识地选择忽视这一切。我选择它们是因为它们是常被憎恶的语言,但至少可以理解其原因,而Go则毫无借口——这一切都是故意为之。
是的,但请仔细查看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年代的旧语言带了过来,并忽略了后来发明的大部分东西。
是的,这确实感觉像是一种上世纪80年代设计的语言,现在一切都变得更有意义了。
同一位作者(原文如此!)
对我来说确实如此,但只是因为PHP用户不再试图推销PHP,而Go用户却真的相信OP写的东西。
没错!这就是重点:做一件坏事,然后写出令人信服的文章,比如“坏就是好”。
每种语言都有一些缺陷。但只有Go试图说服人们这些缺陷是优势。而其中大多数缺陷其实很容易修复。谁对阅读2009年(1.0版本发布前三年的)与作者的讨论感兴趣,讨论为何不应将空值添加到语言中,以及其他语言如何解决这一问题:
https://groups.google.com/g/golang-nuts/c/rvGTZSFU8sY?pli=1
剧透:从他们的评论来看,他们似乎不相信Haskell的存在。
这是拼图中重要的一块。
我开始相信Go语言不过是VB的翻版。它非常基础且易于理解,但尝试构建抽象时会变得一团糟,因此大多以过程式方式编写。更糟糕的是,大多数Go开发者都是新手,缺乏经验,无法合理地将其与其他语言进行比较。
这是一个合理的运行时,但语言本身一团糟。或许几年后有人会说服大家开始使用Go++,它可以编译成Go并提供像现代GPL语言那样的语言级特性。
这可能事实如此,但可以用流行度来解释。大多数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,尽管我见过这么多替代方案?
编辑:顺便说一句,这是个真诚的辩论邀请。
我认为Go仍然在很大程度上依赖于其周边工具链。对于它所处的两个特定领域(基于REST API的微服务和纯数据处理的C语言替代方案),其工具链非常出色,尤其与替代方案相比。go build比任何make方言都好得多。go mod比完全没有包管理系统要好得多。
如果你以“编写更健壮的 C 语言”的心态进入 Go,那还算可以。虽然远未达到最佳状态,但如果你将编程思维模式限制在结构体、函数和按引用传递上,就不会注意到那些奇怪的地方。
问题是:Go 不是一种 80/20 语言。它是一个60/5的语言,另外20%是被附加上去的,但使用这20%中的任何部分都会让你更接近缺失功能的边缘——这就是为什么Go本身不鼓励你使用超过60%的部分。
工具链非常出色,但这是从一开始就做出的有意选择。某些设计决策是为了支持工具链。有人说,目标是为谷歌的开发者提供支持,而语言是顺带产生的。
它有超过两个细分领域,这并不是一个有趣的论点。我可以说 Clojure 基本上只是用于 Web API 和数据处理。Python 也是如此,等等。这基本上是当今开发者工作的主要内容。
我认为无法否认Go在底层网络应用或命令行工具领域同样强大……
其对并发与并行处理的实现极为出色。我尚未见过比它更适合单机并发的系统。是的,若需分布式计算,我更倾向于Erlang模型,但在我已完成的工作中,这种需求并不常见。(在JVM虚拟线程之前:这些线程仍然是隐式协作的,并且没有抢占机制以避免线程饥饿。)
它的跨平台编译能力极为出色。
哪些决策是离谱的?我建议提供证据证明它们确实阻碍了实际工作。
我来谈一个:错误处理。改进它现在已被放弃。我对此无异议,因为我未看到任何提案有显著优势。有人抱怨
if err != nil {…}
过于冗余,这对我来说无关紧要。当我看到“newspaperman”这个词时,我不会将其解析为n、e、w……也不会解析为news、paper、man。我只是看到这个词。我阅读if err != nil
的速度与阅读单个符号一样快。没有好的理由支持简洁性(编辑:或表达力,这是很多人争论的点),否则为什么当我说+⌿÷≢是APL中平均值函数的完整实现时,人们会感到惊讶?或者说≠⊆⊢是带分隔符的分区和去除分隔符的完整实现?我阅读这些与阅读错误检查一样迅速。人类可以快速进行模式匹配。
我认为反对 Go 的大部分理由是美学上的。这其实没问题,在我看来!我可以接受单纯不喜欢它——我尝试的所有编程语言项目都是 Lisp,因为那是我的偏好。但我接受这是偏好,并欣赏其他语言的优势,根据需要使用它们。
你是如何想到这个想法的??
JEP 444
Go 确实会对 goroutines 进行“强制抢占”,尽管这可能并不重要。
https://news.ycombinator.com/item?id=27885569
这是来自/u/pron98,Loom的(合)作者。
这表明Java虚拟线程确实以抢占式方式进行调度。
我对该帖子的解读完全印证了我的观点。他们对时间片切分毫无兴趣。
这是一个术语问题,且颇具挑战性。据我所知,我阅读过的所有Java/JVM文档均未指出虚拟线程的实现可在除已建立的安全点之外的任何位置中断。
不承诺调度何时发生并不意味着“在任何指令处”。事实上,给出的示例明确指出了实现具有安全点的具体位置。Math.sin可以因各种原因内部让出控制权,但你无需关心这一点,而不是说运行时会随意在Math.sin计算过程中中断它。
Go语言在相当长一段时间内也是如此。这是一个优秀的实现,对于绝大多数情况都足够好,只有在一些极端情况下才会有问题。
阅读讨论的其余部分。pron98明确表示没有必要强制预先中断。
我对这类讨论有些反感,因为存在一个模糊地带,人们容易产生误解。Go 会乐意暂停操作系统线程,评估是否能将 goroutine 挂起,然后让出给另一个 goroutine。主要让出机制是在安全点(如函数调用、通道读取等),但它还额外提供这一机制以防止 goroutine 占用线程过久。
坦白说,我同意pron98的观点,这并非绝对必要。在Go添加这一改动前,我从未遇到过相关问题;但肯定有其背后的动机。
我的观点是,这是实现上的差异,Go有这一机制而Java没有。
现在你只是在重复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 一样。
有太多需要探讨的内容……我觉得要写出与你同样长度的详细回应,我得写五倍的篇幅。简要版本:
Go 是否有缺点?当然有。但我可以抱怨我使用过的每种语言。
无论如何,我并不觉得自己在重复 Go 的标准辩护……充其量,我只是对一个常见的抱怨发表了个人看法……
哦,我也可以对大多数我使用的语言发表类似的抱怨,但大多数其他语言不会试图将它们的缺点包装成优点。正如我之前所说,在适当的情况下,核心部分足够好,工具也很好。
我感觉你的朋友,可能还有你,正在编写更多学术性/小众的应用程序,这些应用程序可能没有生产系统那样的“规模”。
当我提到“规模”时,我指的是需要参与代码库的人数,以及构建具有更大概念数量的系统。
我过去两年使用这门语言的个人经验(在其他语言上已有20年专业开发经验),是创建任何标准化/直观的代码库结构都异常困难。你无法控制复杂性,它会“均匀”地分布在整个代码库中。
虽然语法足够简单(尽管存在明显晦涩的缺陷),但一切都依赖于定义不清且随意被忽视的约定。这使得浏览代码库变得困难,重构则是一场噩梦。
简单的事情都异常困难,比如根据通配符从文件系统获取文件列表(即使如此,你也会发现半成品的通配符语言无法胜任,最终不得不编写访问者函数来获取列表)。
就连错误处理也需要你翻阅大量文档来猜测某个调用是否可能引发错误。如果你确实检查了错误,那也是基于错误消息的字符串,而这些字符串从未被文档化,编译器也无法提供任何帮助。错误处理本身意味着你必须在调用栈的每个层次都抛出无意义的噪音。
在小型代码库中,这尚可忍受,但在更复杂的系统团队中,所有这些噪音的开销以及缺乏任何标准化工具来创建抽象,意味着无法管理复杂性,并且不得不不断为相同的低价值决策重复编写代码。
我假设讲个小故事是可以的……我也有20年的经验。我本科毕业后直接进入行业,编程语言只是我的业余爱好。
我学习Go语言是因为我之前提到的那位朋友和我一起在一家咨询公司工作,他选择Go语言来开发一个大型旗舰项目,而我被拉入了这个项目。我们从三人团队起步,后来发展到十一人,随着时间的推移,团队规模时而扩大时而缩小——当我离开时,项目中已经没有一位第一年的成员了,尽管当时团队规模相当可观,人员流动频繁(咨询行业就是这样🤷)。我们还从客户方引入了开发人员和运维人员,过程相当顺利。我目睹并支持了数十人参与该项目,他们都迅速掌握了相关技能。
其中没有一个人之前接触过Go语言。我认为,一个团队能够在失去所有创始成员后,引入新成员并成功交付产品,这符合你对“规模化”的定义。
顺便说一下,我最喜欢的语言是Clojure,我认为我在Clojure上的职业生涯与Go相当。我见过Clojure出问题时会发生什么。如果我想为自己编程,我可能会使用Clojure。如果我想与“可替换”的开发人员一起编程,Go在过去十年中已经证明了自己。
管理复杂性的愿望让我感兴趣,因为我真的看不到任何语言如何帮助解决本质上的复杂性。在Go中变得不可能管理的复杂性,是什么帮助管理它的?
我永远无法理解有人如何喜欢一种语言,它有像这个这样的陷阱。
是的。Go在学习时确实有陷阱。往往是那些让人想砸电脑的陷阱。这个需要理解什么是切片:一种指向可变底层数组的数据结构,具有长度和容量。基本上就是这样,但第一次遇到时确实让人头疼。
如我之前所说,我本质上是个 Clojure 开发者,所以我不会辩称普遍可变性 somehow 更好或不会让人措手不及——不可变集合是一种奢侈。在 Go 中你必须更加警惕……不过其实也不是真的——我记不清上一次被这个坑到是什么时候了。九年前?
如果枚举类型不是简单到可笑,那对开发者来说会更“麻烦”吗?
枚举类型是我学习 Go 过程中不得不停下来感叹“这语言真垃圾”的节点。没有合理的、作用域限定的枚举支持,简直找不到任何借口。我能想到的另一个经常使用的语言中缺少此功能的是C,一种50多年前发明的语言。即使C++也意识到这是愚蠢的,并在2011年添加了作用域枚举。
缺乏原生枚举和集合功能让我放弃学习Go。我想要一种易于使用的语言,而不是简单的语言。如果我想要简单,我会去学习Brainfuck。Go确实是C的继承者,这意味着除了内存管理外,它几乎同样笨拙和繁琐。
即使C#仍在完善其枚举功能。别着急,它会来的。这并不一定那么简单。我很好奇,你知道如何实现这些功能吗?
枚举是编译器中最简单的实现之一。我并不是在谈论模式匹配和每个条目都可以有自己字段的枚举(如 Rust),我指的是一个愚蠢的简单范围枚举。枚举值应该位于自己的命名空间中,值应该从 0 开始递增地分配给条目。在大多数编译器中,这可以在几小时内实现。
所以你认为人们会对这种实现感到满意,而无需将其转换为新的类型类型,如果我理解正确的话?是否可以将这样的枚举作为函数参数传递以用作守护条件?这将如何与接口交互?如何在内存中表示枚举值?只是命名范围内的整数,还是可以是实际的类型值,只是通过索引访问?在这种情况下,关于不可变性,特别是与别名相关的问题如何处理?
有几个问题需要自问。你不能在真空中实现一个功能。它取决于语言的其他部分。我毫不怀疑这是可行的,我当然希望未来能实现。但仍然需要做出设计决策。
你所要求的几乎可以作为库实现。它要么因为需要防范空值而存在问题(在Go中目前无法实现),要么依赖反射。
枚举本身还有许多细节。且行且看。
你看,这就是为什么我们不能拥有美好的事物。我提出最简单的解决方案,你却立即说“你不能在真空中实现一个功能”。语言中已经具备实现这一功能的所有工具,它只需要开箱即用的支持。没有人愿意仅仅为了使用枚举而使用库。
回答你的问题:不,你误解了。显然它们需要是独立的类型,我只是用命名空间作为作用域的例子。现代编程语言不应允许整数类型与枚举类型之间的隐式转换。此外,由于Go语言已经允许定义强类型别名,这应该是很简单的。枚举值在内存中应使用能容纳所有不同值的最小整数类型(假设底层类型只能是整数类型)。这对任何写过哪怕只是玩具编译器的人来说都是显而易见的。你关于不可变性的问题没有意义,为什么你要修改枚举字段。
是的,但你的要求并不符合所有人的要求。例如,我个人更希望枚举案例可以是任意类型的值,而不仅仅是整数。
这就是人们需要理解的地方。在实现一个功能时,不仅仅是能够实现它,而是要考虑该功能是否能适当地满足不同的使用场景。
这就是为什么这个功能在其他语言中仍然处于开发阶段。
关于不可变性的问题源于在枚举中存储任意值。你希望枚举的取值是相对固定的。如果只有整数,当然这是显而易见的。但对我来说,这毫无意义。
例如,如果我想要一个错误值的枚举,该怎么办?
如果有人想要一些结构值的枚举,该怎么办?
如果你的建议被采纳了,人们仍然会抱怨它非常像 C 语言,说“rUst eNuMs aRe beTter”(Rust 枚举更好)。Go 虽然历史上与 C 语言接近,但在能够实现简单性的地方往往会进行创新。
你抓不住重点。我说的是“让我们添加最简单的实现,这已经比语言当前支持的更好”。而你立刻说“不,我想要更多功能”。基本实现可以以后扩展,但至少现在我们有基本功能可用。
这不一定就是运作方式。可能存在兼容性问题。因此,如果确实需要向语言添加功能,最好确保其正确性。否则,这可以保持为库。特别是如果你希望最大化语言拥有正交功能的机会,以便规范保持简洁。
在提供任何功能之前,必须规划好该功能的未来方向。Hyrums定律会增加压力,尤其是当你的功能不幸成为一个泄露的抽象时。
按照你的方法论,我们从一开始就可以通过模板实现某种形式的泛型。但如今我们正在研究受限泛型,这绝对更好。
有时,等待并全力以赴比草率行事更好。人们总是会抱怨。
对于那些没有解释就给出负面评价的人,请告诉我他们如何让枚举的实现作为类型约束使用?
实际上,这需要研究多个功能之间的交互。😛
当人们一无所知时,他们真是有趣。
如果你要使用OpenAPI。在C#中,让枚举与它良好配合相当困难,以至于在后续项目中,我选择使用字符串来表示本应是枚举的值。
这让我想起“最差就是最好”的理念,而Go语言显然属于这一类。
虽然我总体上同意上述观点,但我能想到几个例子,Go语言在不该简化的地方做了简化。比如它没有在正确的地方权衡复杂性和简洁性。
枚举类型为例:无论你对枚举类型的看法如何,我认为Go语言在这方面的实现确实不够理想……拥有规范的枚举类型会比我们目前不得不编写的奇怪的iota代码更简单,同时也不会增加额外的复杂性。我认为这是其他现代语言中非常典型的特性,缺乏对它的良好支持会让切换到 Go 语言比必要时更困难。对它的“支持”只是一个权宜之计,而且不是一个好的权宜之计。
另一个是错误处理的冗长性。我喜欢它在概念上的实现方式,但它太过冗长。最近他们暂时放弃了修复这个问题。原因是参与投票的1000人对少数提案无法达成一致。作为其他语言的用户,博客中提到的那些“糖衣”解决方案听起来已经足够好,而且肯定比我们现在拥有的更好。考虑到这是对语言的主要投诉,他们无法选择任何解决方案确实令人遗憾。该领域任何改进都将受到欢迎,但坚定的Go语言理念支持者不希望任何改变,我认为这将损害语言的发展。与其通过委员会来设计语言,我们本可以遵循BDFL的个人偏好,那将更好。现在显然他们希望通过委员会来设计语言,这就是为什么我们得到骆驼而不是马的原因。
我不知道,我对坚持简单性持保留态度,但我看到像Java这样的语言被时代抛弃,最终会反噬自身。我们可以遵循80/20原则,但考虑到社区对任何解决常见痛点的提案反应如此激烈,我看不出来Go语言如何能避免因用户意识到该语言设计问题无法得到解决而逐渐衰落。
例如,如果像以下这些功能:
语言可以去除:
此外:
并且:
并且:
每个设计良好的复杂事物都会以某种方式简化语言。通常收益远大于成本。此外,你无需撰写大量类似本文或“坏即是好”的文章。
(编辑:格式调整)
Go语言的错误处理存在问题:难以阅读。
它用冗余的错误处理代码掩盖了程序的重要部分,你必须“忽略”这些代码才能看到代码中的真实问题。
它以灵活的方式添加了额外的标记——你可以使用'err'作为变量名,也可以使用'err1'或任何其他名称!——但它几乎没有增加任何价值。
令人沮丧的是,这是一种如此流行的后端语言。
Go 拥有恰到好处的特性!它完美无缺!当然,除非 Go 的开发者决定添加新特性。那时我们就会发现这个特性是多么必要,而且显然完美无缺,但任何其他特性都会显得多余……直到他们添加下一个特性。
这让我想起苹果产品周围的狂热追随者。
你拿错了
苹果是发布“革命性”技术的王者,但这些技术往往在市场上存在多年后才推出。我迫不及待想看到他们“发明”可折叠手机。真勇敢。
那则广告其实是真的。恢复原尺寸的iPhone SE。
我认为作者在这里采取了不同的策略:
换句话说:我们并不总是需要这个功能,直到我们确实需要它。作者没有说明是什么变化让泛型在决策前变得“过于复杂”,而决策后又占了80%……
不错,现在他们有了新的语法:
func ProcessMaps(std map[string]int, custom MyMap[string, int]) { }
感觉他们完全不在乎它会有多好。
我们一直与东亚处于战争状态。
😂
这几乎是“更糟糕更好”
这是保罗·格雷厄姆的Blub悖论,其中Blub被重新塑造成故事的英雄。
哈哈,没错。关于Lisp程序员的事实:
他们他妈的热爱Lisp
他们讨厌“更糟糕就是更好”
(是的,这些事实是相关的)
对于不了解的人:原版的更差更好是比较Lisp和C语言方法的摘录。C是“更差更好”的语言,用于“更差更好”的操作系统Unix。Go语言的开发者们对C和Unix也非常熟悉(其中一些人甚至参与了它们的最初创建!)
80/20 语言是一种语言,其中简单的事情很容易实现,而困难的事情也能够实现。
Go 在前者方面还算不错,但在后者方面则相当糟糕。
这是“更差就是更好”的 80% 版本。嗯,也许是 30% 版本。
哦,是的,我同意它是一种 80/20 语言。但是:
实现者,是的。用户,不。用户每次需要复杂性提供的功能时,都必须实现剩余的20%。而大多数用户并不像那些从事语言和标准库开发的人那样擅长。这就是Go为何是80/20语言:它提供80%的必要功能,让用户自行实现剩余的20%
我不同意,goroutines 的使用体验确实比大多数语言(除 BEAM 语言如 Elixir 外)更优雅,它们并非 Go 的弱点。JVM 近期新增了虚拟线程,其设计比 C# 更优雅且吸取了过往教训,但 JVM 和 C# 仍无法表达阻塞队列间的选择操作。
Rust Async 更具表现力,tokio 可以完成 Go 能够完成的一切,但与普通的线程 Rust 不同,它非常难以使用。如果你不需要高并发性,那么使用 Crossbeam 等库的线程 Rust 非常不错。Crossbeam 比 Go 或 async Rust 更具表现力,更易于使用,但线程非常繁重。
好的,Goroutines 没问题,但通道 API 中有太多陷阱。这太疯狂了。
我花了很多很多时间试图弄清楚如何在集成测试中同时实现:(a)在测试结束时等待 ^C,(b)在测试过程中也能够 ^C,以及(c)在测试过程中对 HTTP 请求设置合理的超时时间。是的,所有这些都需要整合成一个可工作的解决方案,而使用通道非常困难。默认使用信号处理程序发送通道消息的做法……实在不理想。
因此,是的,Goroutine 的基础部分没问题,但一旦超出基本用例,同步原语就会变得复杂,而且会耗费大量时间。
哦,在现代 Go 中,你会使用上下文 API 而不是取消通道来实现这一点,并且你可以使用 errgroup 库将 Goroutine 与上下文绑定。
在大多數語言中,取消執行緒歷史上一直很困難。Python 和 Kotlin 社群提出了結構化並行處理,使取消操作變得更加便捷,而 Java 的 loom 專案也認真對待了這一概念。Go 中的 errgroup 是實現類似功能的最主流方式,conc 是另一種選擇,但它並未內建於語言中。
这有点修正主义:
我不同意。它对 Go 的服务是糟糕的,而且证据积累到如此之高,以至于就连核心 Go 开发者也无法再假装这是一个好决定。此外:
谷歌为Go也制定了规范。乍看之下似乎很简短,但点击进入Effective Go后,你会发现它实际上是一本专著。
我基本上同意作者所阐述的原则:
而这正是Go语言的一大优势:你已经熟悉它。虽然有一些奇怪的语法细节需要花半天时间学习,但如果你精通任何其他现代编程语言,那么学习曲线会快得令人震惊。
但当这种情况导致一些显而易见的缺陷在语言中存在超过十年时,这就成了一点问题。不是因为核心开发者认为这些不是缺陷,而是因为他们没有好的方法来修复它们。这在泛型历史中表现得非常明显。..
但问题不止于此。我认为这并不总是与复杂性有关。我们仍然需要处理
if err != nil
,因为整个社区在解决方案上争论了太久,以至于他们最终放弃了:阅读该回复时,我不禁想起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
语句退出函数。我认为这并未使语言在学习上变得更简单。我甚至不确定它在阅读上是否更简单,大多数情况下。但它确实略微缺乏明确性。
Go 在错误处理方面的冗长性非常令人烦躁,它掩盖了实际执行工作的代码,导致难以审查的 PR,更容易引入 bug,等等。手动完成大量操作意味着更多错误,就是这么简单。
但冗长和明确性并非 Go 语言的核心特性。结构化类型系统是另一个看似不错但实际复杂且难以理解的特性。例如,很难确定一个类型实现了哪些接口。它并不明确——哦,我以为明确性是核心价值?看来不是?——而当阅读代码时,追踪哪些功能被支持则成了读者的任务。
这种语言感觉像是半成品,因为它确实是半成品。而语言的原始性被其支持者视为美德。这令人遗憾。
我认为 Kotlin 是更好的比较对象,因为它在功能集上与 Swift 非常相似,拥有非常可靠的编译器,并且在跨平台方面比 Go 表现更好。
我不确定是否真的慢且容易崩溃,但我认为跨平台从来就不是苹果的目标。Swift是一种用于为苹果硬件(主要为iOS)编写软件的语言。感觉他们后来添加了对Linux的支持,是因为如今许多应用程序都有服务器端组件,而一些专注于苹果的软件公司希望用他们已经使用的语言来编写这些组件。
现在这是个目标。开发者可能担心 Kotlin 会抢走他们的饭碗。
是吗?除了空安全这一巨大优势外,我看不出来新 Java 版本在其他方面有什么不足。
上述帖子是在询问关于Swift的问题,但如果你想知道为什么选择Kotlin而非Java,答案在于多平台兼容性。Kotlin允许你编写一次代码,即可在多个不同平台上运行,完全独立于JVM(如iOS、浏览器、原生、WASM),同时也支持JVM平台,如JVM桌面和Android。这是Java无法实现的。
我认为将编译器与语言本身进行比较是一种谬误。Swift 是一门优秀的语言,即使其编译器表现平平(而且它并不总是崩溃,那简直是荒谬的)。它在跨平台支持方面至少与 Go 语言相当。
其他许多语言如 Crystal 也是如此。Crystal 是一门优雅的语言,但其编译器表现平平(速度较慢)。
Kotlin 也是一个伟大的语言,但它(曾经?)与 JVM 绑定,无论好坏。
有道理。但 Kotlin 仅在编译时与 JVM 绑定。其主要优势之一是它被设计为可编译到任意运行时环境。目前支持的运行时环境包括 JVM、iOS、Android、浏览器和原生。
Swift 是一门优秀的语言吗?我不得不深入研究的代码中的延续部分糟糕透顶,难以理解。
Swift 是一门出色的语言。只是大多数人写得不好。
其中一个主要原因是试图让它对初学者易于理解。
而苹果从未试图让他们的语言或 API 像其他平台一样。
而且搭建 iOS 界面很有趣,因此学习重点往往放在掌握正确的语法上。
最终,你得到了一群自学成才的新手,他们试图使用反模式并复制粘贴示例代码。
我还可以继续抱怨,但就到此为止。
总之,Swift 语言很棒。问题出在 Swift 开发者身上。
阻碍 Swift 广泛采用的一个关键因素是,相当一部分人不想或无法使用 XCode。我知道现在可以不用 XCode 编写 Swift 代码,我也用它写过几个小程序,但最初发布时情况并非如此。
再加上人们普遍认为“Swift是苹果为苹果平台设计的语言”,因此我们这些不认为自己是苹果开发者的人,也不会特别考虑使用它,就像我们当初对待Objective-C一样。我认为这主要是形象问题。
很抱歉你无法跟上代码中的延续部分。我没有遇到任何问题,并且发现这种语言非常令人愉悦,无论是阅读还是编写。
我没有说我不能。
与 Flutter/Dart 相比,我知道我会选择哪一个
酷故事,兄弟。
我的意思是它总是崩溃!……当我尝试使用SwiftUI时(我其实不怪Swift)
什么崩溃了?编译器?你是说代码无法编译?还是代码本身崩溃?
也许我只是在想那些晦涩的编译器错误,我不知道
Go编译器之所以快,是因为它没有做其他编译器做的绝大多数优化。可以添加编译选项,让用户选择是想要快速编译还是快速代码。但这很难实现。Go追求的是简单性。
嗯,当他们的运行时由“其他人”编写时,他们的工作就容易多了。
Go语言的设计者本可以选择以一种能够利用这些运行时的设计方式来设计语言的编译器。他们没有这样做是有充分理由的,但他们做出了决定,并从中获得了利弊。
我的评论是关于 Kotlin 的。
我的总结:
一篇由 Go 语言爱好者撰写的颇具防御性的文章,将人们对该语言的不满归咎于他们想要更多功能……而 Go 恰恰拥有恰到好处的功能(当然!)。
我并不否认有人批评Go功能太少,但:
我认为有很多人认为“80/20”是一个不错的语言设计目标,但认为Go并不是一个特别好的80/20语言。
我认为我们批评Go是因为其功能设计得不够完善。
有一篇经典文章(https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride)深入探讨了Go功能设计的不完善之处。
完全正确。Go的问题不在于功能少,而在于它拥有的功能设计得并不出色。
但这些功能是由ROB PIKE设计的,怎么可能不好呢???
Go 语言及其流行程度令人沮丧,我感觉它似乎专门针对那些缺乏计算机科学基础知识的 Python 开发者,并且认为他们永远无法掌握这些基础知识。开发者很笨,给他们一种不太难的语言,不要让他们被抽象概念搞糊涂,然后告诉他们它比现在用的语言更快,这样就有一些理由使用它。
我认为Go是C语言的现代版本。它仍然相当底层,而且C代码通常可以很好地转换为Go代码。但Go在细节处理上比C更流畅,也比C++简单得多。
如果依赖垃圾回收器进行内存管理没问题,那么“更流畅”就很容易实现。
这使得与C/C++的比较完全没有意义,因为原本应该用C/C++编写的代码根本不应该/不能移植到Go。
所以……Go擅长的是“本不该用C/C++编写的C/C++代码”这个细分领域?从我的角度看,这有点令人失望。
Go 对我来说从未感觉像 C,它总是更像 Python 而不是 C。
有一篇 Pike 博客文章 详细介绍了它的起源:
因此,考虑到参与Go开发的人员中也有许多参与过C开发,Go确实可以被视为对C的第二次尝试。虽然它在2025年甚至2010年的标准下并不完全“现代”,但无疑比C更具创新性。
Go比Python更倾向于命令式编程;Python则更具混合性,允许同时进行面向对象和函数式编程。因此,我认为许多人会将Go解读为更接近C的语言。
但同样,从同一篇博客文章中:
这确实有道理:Go和Python都使用鸭子类型,而Go和许多脚本语言都具有易于入门的特点。
同样的思路,仍然引用同一篇博客文章,我好奇Pike等人是否可以通过放弃基本的类型系统来让Go变得更简单,因为他们似乎对类型并不特别感兴趣,而且Go似乎只是因为C有基本的类型系统而拥有基本的类型系统:
有趣的是,他似乎将类型与面向对象编程(OOP)中关于类型应如何工作的理念混为一谈,而我对后者也并不感冒。
我不确定他们所说的“大规模编程”具体指什么,但对我来说,构建一个似乎强迫开发者不断检查某项操作是否成功的错误处理系统,却又允许他们忘记执行这一操作,这显得相当荒谬。我认同谨慎的错误处理很重要,但我绝不会支持将程序的大部分代码都用于错误处理,从而模糊程序的执行流程。在Haskell中,Either单子允许你编写代码的正常流程,同时知道如果任何中间代码段失败,执行将在那里停止。如果你强制要求错误通过总和类型报告,并且所有情况都必须处理,你就能实现与Go相同的效果,而不会模糊代码的实际功能。
令我困惑的是,错误处理竟如此简单粗暴,而使用defer进行资源管理其实相当优雅——为何不采用同样优雅的方式处理错误?
同感,但当时他们确实是在使用C++。他曾列出过自己熟悉的编程语言,据我所记得,那份列表中并不包含任何支持抽象数据类型(ADT)或现代类型系统的语言。我过去曾开玩笑说,他对大学毕业后发生的计算机科学研究并不熟悉,而他似乎是在SML出现之前就离开了大学。
对于我们这些从未在FAANG工作过的人来说,这可能很难想象。但他们基本上在单一代码库中拥有数量惊人的开发人员,并且有一些指导原则,比如不允许使用异常。这显然影响了Go语言中异常的缺失,以及其包管理方式。
是的,但这本质上也是C语言的做法,而Go语言的创建者对C语言非常熟悉。无论是
E foo(T* bar)
还是func foo() (T, E)
,你总是会得到一个可能为垃圾的T
以及一个指示T
是否被批准使用的E
。Go 的机制在一定程度上改进了一点,它将输出放在返回位置,而不是使用指针作为输入,但这也只是改进到此为止。这与受检查异常和抽象数据类型(ADTs)形成对比,在后者中,
T foo() throws E
和foo :: Either E T
或fn foo() -> Result<T, E>
会让你得到一个好的T
或者一个E
。遗憾的是,支持检查异常的语言并未让其特别易于使用,反而在未改进易用性的情况下,引入了对类型系统和调用点不可见的未检查异常。因此,Go 的创建者们事先就知道异常在 Google 代码中是不被允许的,他们似乎也不熟悉抽象数据类型(ADTs),而且他们希望编译器实现简单,所以最终他们得到了 仅作为语法而非类型系统中存在的伪元组。
有趣的是,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
Go 真的感觉像是一种由从未在精神上或智力上离开贝尔实验室的人们创建的语言。
我认为它最像是在谷歌为解决谷歌问题而创建的语言,以至于我们可以将其描述为一种用于编写逃逸容器的 Kubernetes 微服务的 DSL。
谷歌的运作规模对我们其他人来说是难以想象的,因此他们可以提出一些在其他情况下难以实现的方案。例如,Carbon 可能最终成为谷歌 C++ 单仓库的专用工具,类似于他们的 c++/rust 互操作项目。
然而,我们其他人也开始编写Kubernetes微服务,随后人们开始用这门语言在其他地方构建工具,且可能在更大规模和不同架构下使用,这与Go最初的设计初衷有所不同。如果有人抱怨Go在Windows上处理文件权限不佳,我不会惊讶于听到这样的回应:“……你们居然用这个在Windows上运行东西?”
这有点类似于 JavaScript 最初只是用来在浏览器中让猴子跳舞,而现在我们却用它来运行各种应用。我不确定那些参加演示的人会如何看待现代网页应用、Node.js 和 Electron。
如果这句话出自Clojure的创建者Rich Hickey之口,这将是一个有趣的声明。在Clojure中,没有静态类型,而是通过在简单数据结构上编写大量多态函数和断言来实现。这是一种原则性的方法。但对于Rob Pike来说,这里没有值得深思的见解。这不是某种隐藏的智慧。Go语言中没有真正的原则。他只是对现代编程一无所知。
这太对了。
从一开始就将它定位为一种面向白痴的语言——我们确实这么做了,当时是Basic,后来是VB,那简直是一场灾难,花了整整十年才从混乱中挣脱出来,主要是通过大规模重写,因为那简直是一堆垃圾……
嘿,让我们再做一次 🙃
技术周期真是令人疲惫
在我看来,Rob Pike就像一个不知道自己在做什么的人
没错。这位“array.filter是多余的,只需使用for循环”先生。
https://github.com/robpike/filter
我开始相信C编程是一条通向疯狂的道路,希望自己不要变成这样的人
还有一篇博客文章,他基本上手写了一个单子和
bind
/>>=
来展示如何使用错误值](https://go.dev/blog/errors-are-values)。认为仅仅使用单子是可怕的,但手动实现它们以及更多或少不明确的`do`块是可行的,这真是令人着迷。我想我得纹个单子纹身来保持距离。
结尾部分堪称经典:
别忘了检查错误,因为我懒得在编译器里实现这个功能。
是的,我们可以大致将语言分为两类:
E foo(*T bar)
,你可以完全省略对E
的检查func foo() (T, E)
,如果将E
绑定到一个名称但未使用它,它会发出警告,但你也可以直接使用bar, _ := foo()
。而在func baz() E
的情况下,仅调用baz()
时不会有警告。比 C 语言好一些,但仍然完全取决于用户。T foo() throws E
,但基于异常的语言通常会直接保留为T foo()
。无论如何,你的错误都会被检查,否则程序会崩溃。foo :: Either E T
或fn foo() -> Result<T, E>
,其中你有多种处理方式可选,但必须明确声明你的处理策略;忘记处理E
情况是绝对不允许的。但不强制执行此规则可以让编译器保持简单,我想。如果人们最终使用类似golangci-lint(它变得非常占用资源并开始出现内存不足)之类的东西来弥补差异,那也不是他们的责任,对吧?
人们会在论坛上抱怨,并遭到作者的反驳,直到某家企业赞助商决定必须采取行动。
派克直言不讳地承认,Go 语言并非为成为一门优秀的编程语言而设计。它并非语言爱好者的首选。这门语言的初衷是让刚毕业的大学生和实习生能够安全地为一个比许多大型书籍还要庞大的代码库做出贡献。
但我并不认为这是件好事。让某人立即变得高效,就像“24小时内学会X这个非常复杂的东西!”一样,要么你实际上没有学会如何解决困难的问题,要么这个东西本身不允许你解决那些问题。人们学习Python后却成为糟糕的软件开发者,因为“编写软件很简单!”的表象让他们从未真正掌握如何编写可维护、高效、结构良好、类型安全、能处理意外情况等优质软件。
Go基本上对开发者说:“你今天就能做出能用的东西,一周内就能为公司做出贡献,六个月内就能掌握所有知识,然后你就会遇到语言的局限性,再也无法提升作为开发者的能力,因为语言本身阻止了你思考其他‘更复杂’语言允许你思考的问题。” 它缺乏许多基本抽象,并鼓励编写掩盖实际发生情况的代码——阅读 Go 代码非常令人沮丧,因为超过 50% 的代码行都是无意义的错误检查,且无法进行抽象。这不仅会掩盖程序的流程,还容易出错,如果你不小心忘记了某项检查。Haskell 的 Either monad 或 Rust 的 Result 迫使你真正进行检查,同时抽象出“出了问题,不要再执行任何代码”的想法。
问题是,就谷歌而言,他们并不关心这些。一旦你做出贡献,一旦你成为机器上的一个齿轮,你就成为为他们创造价值的劳动单位。如果你想作为工程师进步,他们认为你会利用自己的时间去做,如果不做,你仍然在产出工作。
至于处理检查,我深知使用Maybe单子模式有多方便,但它并非适用于所有情况。虽然有用,但遗憾的是在Go中无法使用,不过有替代方案。请参阅我在该线程中的另一条评论,讨论 Maybe 与 Railway 编程的区别
没有人强迫你永远只使用一种语言。即使是谷歌,也不是所有东西都用 Go 编写的。说实话,Go 教给初学者的关于计算机实际工作原理的知识,比 Java 之类的语言要多得多
鉴于Go语言中存在大量潜在风险,我认为实习生无法安全地为任何Go代码库做出贡献
我认为这是人们反复提及的后见之明。比如亚马逊用门板做桌子是因为节俭(实际上门板桌子更昂贵)。
我认为Go并不是特别适合这种情况的语言。它声称通过减少功能来实现易于上手,但实际上我经常看到新手因为无法将他们已知的概念(如类、继承、枚举或异常)映射到Go的功能空间而感到困惑。更不用说它并不是一种安全的语言(NPE 很容易发生),而且在编写软件和系统时,它非常依赖于良好的约定和最佳实践。这种约定可能是谷歌可以强制执行的。(错误处理不佳、堆栈跟踪等)。还有一些非常奇怪的功能,比如通过大写字母来导出变量,我认为这是一种对新手不友好的功能。
所以Go是谷歌的好语言?当然。对新手来说是好语言?我对此有疑虑,且有相反的经验。
Pike本人曾明确说过这一点。
仅仅因为Go本身可能不适合其预期用途,并不减损它从一开始就是设计选择的事实。
我的观点是,“易于学习”是一个高度主观的概念,甚至没有被Rob列为Go试图解决的关键痛点之一。Go存在一些缺陷,这些缺陷实际上使得它难以掌握,因此,经常被重复的“Go是一种专为新手设计以易于学习的语言”的说法,在现实中和重要性上都显得过于夸张,从他自己的话中可以看出。
我怀疑他所说的话。😂 可能只是说Go不是一种研究语言。如果人们想要一种“好”的语言,就像你暗示的那样,他们可以在其他地方找到。如果Go真的那么糟糕,人们就不会使用它,也就没有必要发明它。😏
无论你认为Go是否是一种伟大的语言,诉诸权威都不是一个很好的论点。
聪明的人并不是100%的时间都聪明。仅仅因为他做过很多伟大的事情,他仍然可能创造出一个有缺陷的语言。
这就是我的观点。
哦,抱歉我误解了,请原谅。
认为你最喜欢的工具在功能与复杂性之间找到了恰到好处的实用平衡,这确实是对其的高度信任。我涉足过许多领域,感觉自己需要再花25年的编程经验和团队协作经验,才能有信心做出这样的评价。
这有点像《精灵》中的一幕,布迪找到了一家声称拥有世界上最好咖啡的咖啡店,他兴奋地祝贺道:“祝贺社区!”
我对Go的看法是,它并非通用编程语言,却假装自己是。
这让试图竭力为自己钟爱的语言辩护的人感到困惑,也让其他人试图在“到处”使用Go时感到恼火,因为他们试图遵循谷歌声称Go可以使用的范围,却发现它缺乏实现目标的基本功能。
Go在成为通用语言的能力上所面临的问题,以及试图为它辩护的狂热程度,都让人联想到Matlab。
什么才是更好的通用编程语言?Python?
问题不在于是否存在“更好的”通用编程语言,而在于它是否具备成为通用编程语言的能力。
我知道Go语言在这一点上“解决了”泛型问题,但作为“Go问题”的例子:Go团队对语言中不包含该特性感到满意。他们使用的理由甚至有道理(他们将所有容器类型都设计为泛型,因此你无需自行使用泛型!)。当然,如果只针对Servlet应用程序(因此几乎不需要泛型函数,因为你几乎在所有情况下都已知具体类型),这种说法确实合理。
相比之下,即使是C语言也没有这个问题(得益于宏),而C11甚至提供了
_Generic(x)
宏。你好,你是想说“太少”吗?
如果我犯了错误,请告诉我!请告诉我如果我犯了错误。祝你一天愉快!
统计数据
我是一个纠正语法/拼写错误的机器人。如果我错了或你有任何建议,请私信我。
Github
回复“STOP”到这条评论以停止接收更正。
啊,在互联网上对陌生人进行心理分析的危险。
我写下这些是为了整理我的想法,这是我对HN上有人抱怨结构标签不够强大(比如注释或宏)的评论的延伸。
我没想到它会广为流传。
我坚持认为Go是“最不受欢迎”的语言,这一观点基于HN和Reddit上的舆论。
当然,过去Java、Perl或PHP曾被讨厌,但现在你找不出HN上有人在讨厌它们。
我不会自称“Go爱好者”。对我来说,编程语言只是相对较小的恶。我最不讨厌Go;这并非热情。
为什么你更喜欢 Go 而不是 Rust?
我认为它更像是一种 90210 语言。
这篇文章表明了作者对人们为什么不喜欢 Go 缺乏深刻的理解。
我同意Go是我最讨厌的语言。Java至少有借口说它是在我们不知道设计有多糟糕之前设计的。JavaScript是一种原型语言,在短短几周的开发时间后就被推到了台前。但Go,Go拥有历史知识。它有无数例子展示如何做得更好。而他们却做出了一个垃圾,并投入了最大量的营销。
我觉得市场部门一定在命名语言后才介入。讽刺的是,这是最不适合在谷歌上搜索的后谷歌语言
甚至是由谷歌开发的
Go 面临与其他 C+ 语言相同的问题,而 Go 是否属于 C+ 语言类别则取决于你问谁。它们从 C 语言中学习,但基本上忽略了 C++ 的教训
切片的设计方式证明它们从 C 语言中一无所获
为什么它是垃圾?
我不会说它是个垃圾,但我确实不喜欢它。就语言设计本身而言,这是我使用过并达到熟练程度的语言中我最不喜欢的。这源于我与设计者之间存在根本分歧。他们的核心目标之一是创建一种任何人都能快速学习的语言,而他们实现这一目标的方法是去除功能,迫使所有人都以相同的方式编写代码。
问题在于,他们决定省略的特性显得非常随意,且似乎完全基于设计师的个人偏好,而这些偏好又过多地受到了他们作为C语言开发者的背景影响。他们省略了像泛型和某种形式的类型检查枚举这类广为接受的特性,却保留了更具争议性的特性,比如没有静态空安全机制、以Go注释形式存在的超语言宏系统,以及支持goto语句。还有其他一些设计缺陷,比如defer是函数作用域而非代码块作用域,或者有类型化的空值。对于这些设计缺陷和缺失的安全特性,常见的应对方式就是学会避免使用它们,因为将这些特性添加到语言中会让语言变得过于复杂。我理解他们需要在某个地方划清界限,否则就会像C++一样,但这种做法并不觉得是正确的。
我对Java有点偏好,因为它是第一个我熟练掌握的语言,而Java本身也有大量问题,但我认为现代Java的理念——即如果某个功能需求强烈且经过验证,才会添加——感觉是Go哲学的更健康版本。
对我来说,Go的通道机制纯粹糟糕,错误处理方式令人发指,而且无法构建抽象层。
开发者太蠢了,不配使用抽象层。这是Go的设计哲学,而且是故意如此。这是一种对开发者的侮辱。
这与我在某些大型企业中见过的“有人以错误方式使用工具/功能,因此我们要将其从所有人手中移除以防止任何人做坏事”的逻辑如出一辙。
Go 频道有什么问题?
简而言之,它们是自掘坟墓的工具。https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-feel-bad/
等等,什么?我已经好几年没用Go了,但通道一直被列为Go的杀手级功能之一。
它们确实是。
是的,它们极其具有欺骗性。使用起来非常简单,这让人们普遍认为它们非常出色。但根据我的经验,大多数使用通道的场景都存在 bug。尽管它们使用起来很简单,但也很容易被误用。而且通常不清楚误用的原因。
这完全是营销噱头。我曾专业从事Go语言开发两年,我们代码库中每个使用通道的场景都存在某种缺陷。有时是内存泄漏等小问题,更多时候是死锁、错误被静默忽略或堆内存损坏等重大问题。
我听说我的团队在我离开后几个月终于开始放弃Go语言,因为那些神秘且无法修复的错误数量最终变得无法忽视。
例如,在许多情况下,它们仅仅被用来返回函数的结果。而这是一种不必要的复杂化。
https://www.reddit.com/r/ProgrammerHumor/comments/1lmghj2/comment/n09k1hf/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1& utm_content=share_button
它们还营造出多线程高效运行的错觉。
从外部观察,似乎库应尽量避免显式使用Go通道,这样应用程序可以自行决定何时需要使用它们。
哦,我以为大家喜欢他们的通道,挺有意思的。我是一个前端网页开发者,所以对此没有意见。
阿门
我自诩为坚定的Go语言反对者,它似乎公然违背了过去20年我们所学到的诸多经验,这让我非常恼火。
但……在大型公司使用Go语言后,我开始逐渐欣赏它。当发生故障需要深入分析从未接触过的其他代码库时,Go语言能让这一过程变得轻松。它如此简单,以至于几乎没有变通的余地——每个代码库看起来都差不多,我能迅速上手。在编写Go代码时,我经常抱怨它功能太少且实现得不好,但不得不承认,阅读Go代码通常是一次流畅的体验。
我支持那些只有一种明显且正确方式来完成事情的语言。如果10种方式中有9种是奇怪的、过时的或容易出错的,那为什么还要有10种方式来完成同样的事情?这就是我为什么不喜欢Perl和Scala。
但Go是否必须忽视30年的编程语言发展?它发布时的状态让Java看起来像是一种充满疯狂功能的大型复杂语言。
他们本可以放弃Go,转而使用一个针对Java的原生代码生成器,并配备一个经过优化的垃圾回收器,然后就此打住。他们不是有自己的Android系统吗?
我想他们想要一个不是由竞争对手开发的Java或C#,然后用一个不同的名字重新启用了Rob Pike的旧语言。
Go比其他语言领先20年,这就是为什么人们讨厌它
这只是一个毫无意义的博客。这些数字是什么意思?什么是15复杂度?80实用性?这个人到底在说什么?这个人犯了常见且令人厌倦的错误,将简单与熟悉混为一谈,甚至认为每个人都同意简单意味着什么
更像是60/40,但我明白你的意思。
更糟糕更好的倡导者会认为这个著名引用很愚蠢。帕斯卡为什么需要更多时间让信件变短?他为什么不能随意删除任意段落直到信件足够短?即使这会让信件变成一团令人费解的乱麻,它也会让信件更短,因此更简单——而简单性,毕竟是至高无上的。
让事物更简单而不牺牲关键属性并非不可能。但——正如帕斯卡的名言所暗示的——这往往需要时间、努力,以及“更糟糕更好”派最讨厌的东西——创造力。
这在编程语言领域早已实现。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这样的语言中确实存在一些极其复杂的特性,复杂到足以让相当一部分开发者无法理解。但:
作者指出jUnit与Go标准测试库之间的复杂性差异,这很有趣。
如果我是测试库的用户,我是否会在乎它比我需要的代码多出10倍,只要它能实现我想要的功能?
假设添加新功能的成本会增加并延缓发布,但如果更简单的库从一开始就从未添加这些功能,对我作为用户来说有什么区别?
我认为你错过了重点。这从来不是关于代码行数,而是关于功能。jUnit 拥有更多功能,但 Go 希望保持更简洁(更少的代码行数),因此在功能上有所欠缺。
是的,但如果这样说,作为用户,我为什么还要使用功能较少的工具?有什么好处?
但你有选择吗?Junit是为Java设计的,所以如果你使用Go,你只能使用更简洁的工具,因为80/20原则。
这就是我对它的理解,当你选择Go时,某些功能会因语言选择而缺失
在Go中编写异步代码时,你的代码是20/80安全的
他们不会意识到这一点,因为内存会被乱码填满
我讨厌这些由仁慈的独裁者附加的宗教式术语,它们纯粹是无稽之谈的教条。TypeScript也存在同样的问题。细微差别被忽视,这成了终结所有讨论的捷径
我认为这个论点为Go的糟糕设计找了太多借口。
延迟、切片、远程作用以及多空值并非KISS原则,而是面条式设计。或者说,根本没有设计。
不,我的朋友;Go是20/20语言,它被故意削弱了。这是由FAANG公司为FAANG公司设计的语言。它的抽象能力已被削弱到初级工程师不会迷路的程度,因为在FAANG公司,初级工程师正在实现由中级工程师编写(并审核)的任务,而高级工程师则基于高级工程师创建的高级设计编写了低级设计——所有这些人都希望能够阅读代码。
Go 语言的限制在于,初级工程师无法因语法错误而自掘坟墓/意外创建库/抽象到高级工程师无法立即理解的程度。
它是一门优秀的入门语言,但是一门糟糕的成长语言——因为目标受众在第三年时将转向文档驱动开发。
C 是最佳入门语言,因为它能让学习者理解指针、内存等机制的工作原理。其他语言则会阻碍学习者对这些概念的理解。
如果你要让初学者从低级语言开始学习,那么至少先让他们接触汇编语言。毕竟C语言是可移植的汇编语言,而没有汇编语言的基础,就无法理解C语言或机器架构的本质。
此外,如果他们能完成汇编语言课程而没有中途放弃,那么你就知道他们是学习C语言及更高层次语言的理想人选。
当年,我的大学首先使用Pascal而不是C,但我们还是学习了C、汇编语言等。Pascal在低级语言和高级语言概念之间提供了很好的平衡,并保护我们免受C从一开始就提出的更复杂的问题。我怀疑这就是为什么现在许多计算机科学课程都从Java、Python或JavaScript开始。我猜这也没关系,但如果问我,这多少有点降低难度。我不知道初学者如何从Java过渡到C和汇编语言而不感到困惑。
说到这里,Go会是一个绝佳的入门语言,它提供了与Pascal在我身上发挥的许多相同优势。它感觉比任何虚拟机语言都更适合作为入门语言。
C到汇编的转换相当直观(至少在-O0优化级别下),我认为任何计算机科学毕业生都应该能够编写一个不优化的编译器。
据说有很多计算机科学毕业生不会编程
开始在Steam上玩Turing Complete吧 😉
听着,我对Rust有强烈的偏好。但我对后端使用“足够好”的语言已经厌倦至极。
确实有适合使用一次性代码的场景,比如用Python或Bash脚本快速拼凑出一个低复杂度的日常任务。你明知这些代码最终会被丢弃并重写,但这是你主动做出的权衡。而且,现在你也可以用大语言模型(LLM)来为你编写这些垃圾代码了。
但如果你要编写的是高性能后端解决方案的代码,那就不要满足于“够用”了。用Rust或Zig或(根据用例)Elixir来编写,甚至在必须的情况下用C/C++来编写,但要编写快速、正确且可持续的代码。编写能够作为其他所有构建内容稳定基础的代码。
如果你编写的是“几乎”足够快的代码,是“基本上”可维护的代码,是“基本上”正确的代码,这会让你产生一种虚假的安全感。你将建立在裂缝的基础上,三年后你会意识到除了彻底重写外别无他法。
而Go语言正是如此,它会让你误以为自己能快速编写出高效代码,但实际上你只是在制造无数微小的摩擦点和低效环节,这些问题会永久存在并不断累积,最终迫使你放弃并转而使用更优秀的语言重新开始。优质代码能持久,但“80/20”语言无法做到。
补充一点:关于如何用Go语言编写服务的文章或课程/培训多得令人发指。我不是说这些内容本身就是垃圾。其中一些确实很棒。但它们的存在本身就令人不安。为什么在2025年,Go语言社区的大部分人还建议自己从头构建一个后端API框架?我知道,Gin和其他框架存在,但如果你想成为一名真正的Gopher,你应该使用标准库自行创建。这简直是在浪费时间,而且容易犯一些愚蠢的错误。
Go语言社区对低依赖或无依赖的解决方案有着强烈的执着。你会在许多项目中看到这被视为一种荣誉徽章。我明白这种想法的来源,但有时它会变得极端,甚至到了贬低他人的地步。
我认为……与TypeScript中需要引入isEven()库,或Java中开发者无法在不依赖Spring的情况下构建应用相比,这种观点是值得欢迎的。
嗯……除了它是 GC 之外,你到底在说些什么?我明白从性能的角度来看 Rust 更好,但 Go 仍然比其他选择(尤其是任何脚本语言)具有巨大的优势。
几个简单的例子:
if err != nil
让我抓狂,而且我知道我不是唯一一个。人们仍在争论如何让 Go 的错误处理变得更好,而 Rust 使用 Result 和 ? 语法非常难出错,因为这是语言的核心功能。struct FooBar
和struct fooBar
在语义上存在差异,这太荒谬了。validate()
)的不同接口,该怎么办?在 Rust 中,两个同名的特性是不同的,编译器会要求你明确它们之间的区别。这些问题单独来看都不是致命缺陷,但它们累积起来就成了问题。它们鼓励走捷径和选择轻松的解决方式,即使你没有意识到或有意为之。结果就是,这种语言整体上比其主要竞争对手Rust要差。每次与Go语言互动时,我都觉得语言中存在一些100%可解决的问题,但语言设计者却因某种误导性的“极简主义”理念而拒绝解决这些问题。
我还要补充另一位评论者提到的内容:以Go注释形式存在的超语言宏系统
和楼主一样,这大概是60%的合理观点和40%的过度解读
1) const块很糟糕,而总和类型很棒,我同意。但如果你在代码中真的需要实现梅利机器,那基本上就是披着大衣的goto。这并非“管理代码流的必要手段”。用某种延续机制(异步、协程)重写2) 这是口味和规模的问题。显式返回在客观上更容易发现。如果你不需要通过3层以上的抽象层传递错误,那就没问题。现实中,你可能作为编写者讨厌它,但作为读者并不那么讨厌(此外,如果你深入到7层以上的抽象层,异常更优越。这不是给所有语言都用异常的理由) 3) 同意 4) 胡说八道。Go 在并发模型上是相当理性的语言,这是其主要卖点。通道的工作方式符合大多数人的预期,当出现问题时,感觉像是逻辑错误,而不是陷阱 5) 这是个人喜好问题。Go 的做法避免了关键字膨胀和无谓的争论,但牺牲了一个奇怪的命名规则。坦白说,我更喜欢 Python 的做法,这更符合 Go 的设计理念 6) 再次涉及个人喜好和规模问题。你的接口会让读者产生类型混淆,在小型项目中,简单重命名是合理的。鸭子类型虽不那么通用,但更易于理解 7) 同意
设计原则很明确:Go 优化了在庞大的代码库中快速查找内容的效率,尤其是在涉及多个微服务的情况下。它在这方面表现出色,但由于缺乏表达力,在处理复杂问题领域时会显得笨拙。如果这真的让你感到不快,很可能是因为你所处理的代码类型。
https://www.reddit.com/r/ProgrammerHumor/comments/1lmghj2/comment/n09k1hf/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1& utm_content=share_button
如果 err != nil 几乎不可能出错……我不明白这里的争论。我乐意看到关于重复性的讨论……但上面的说法完全说不通。
并发是一个陷阱,而 Rust 中的防护栏意味着你需要做额外的工作来完成简单的任务。
作为 Go 粉丝,我正在逐渐接受这种思维方式……我不会试图为这种立场辩护。这让我感到沮丧了很长时间。
答案:Go 允许你这样做吗?如果你编写了这两个接口,那么被编译器骂是合理的……如果它们来自依赖库,那么你就麻烦了。
我认为去中心化是一个优点。
Rust 还不错……不过维护别人的 Rust 代码确实有些麻烦。
我敢肯定 Java 是最不受欢迎的编程语言。如果不是的话,它肯定是最容易成为梗的语言。
编程语言只有两种,一种是人们抱怨的,另一种是没人使用到可以抱怨的。
Java只是相较于其他语言拥有更多开发者吧。
功归功主:
https://www.stroustrup.com/quotes.html
是的,这就是我因为记忆力不佳而无法准确引用的一段话,谢谢。
精彩的引语!我特别喜欢这一段:
Java 赚大钱!!
我觉得 JS 是最被调侃的,但 Java 紧随其后。
Java、JavaScript、PHP和COBOL之间需要一场对决,我猜。也许再加Visual Basic。但对我来说,它(或曾经是)要么被那些从未学过其他语言的人喜爱,要么被其他人憎恨。
JavaScript的类型相等性很有趣,但System.out.println是经典。
也许在某些地方是这样,但我的观察(我写了那篇文章)是基于HN,而HN显然比Java更讨厌Go。或者至少有非常 dedicated 的讨厌者主导了大多数与Go相关的文章讨论,他们肯定认为Java比Go更好。
现代Java和Java 8是完全不同的东西,其中一个在我这里获得了比Go多得多的好感。此外,Java至今仍在不断改进,而Go则不然。Go的设计可以追溯到1985年,谷歌认为它已经达到了最佳状态。
那篇《我想要摆脱Go语言的疯狂之旅》的博客文章就是一个很好的例子。文中大部分抱怨都涉及Windows文件系统中的复杂细节,而这些细节恰恰是那20%的一部分。
当然,“Windows 文件系统奥秘”,毕竟 Windows 是最大的消费级操作系统,而文件权限绝对是一项无人使用的奥秘。
即便如此,其中只有一条抱怨与 Windows 文件系统相关。其余的包括:
我不知道你怎么看,但“大多数”通常意味着远超50%。这只是他抱怨内容的1/8。
有时感觉 Go 更像是用于编写 Kubernetes 微服务的领域特定语言(DSL),而用它做其他事情只是偶然可能。有点像用 Swift 开发非苹果产品,或在电信领域外使用 Erlang。
这有点讽刺,因为 Pike 参与了 UTF-8 的创建,并且对 Unix 风格的操作系统有丰富经验。如果有人应该知道字符串很复杂以及操作系统在 Unicode 之前就有历史包袱,而且路径尤其是一种类似字符串的类型而非标准字符串,那个人就是他。
不过另一方面,我也想忽略非 Unicode 甚至非 UTF-8 的内容,让用户自行修复编码问题。但实际上这样做更像是来自一位仍在大学就读、想要标新立异的语言设计者的设计决策。
不过,当Pike自称“对类型一窍不通”时,我想这也就不难理解为什么最终会变成一种天真地基于字符串的类型系统。
Lua,某种程度上
我们这里是将类视为结构体,还是区分面向对象语义与过程式语义?
如果算上脚本语言……可以包含Bash。缺乏结构体在shell脚本编写中非常痛苦。
该语言拥有你想要的80%功能,却让你只剩下20%的生产力……明白了
20%的生产力是相对于什么而言?
说实话,我用过Go,它让我迅速进入高效状态。
你试过吗?
我对Go的看法要简单得多:它太丑了。看到Go代码时,我会有看到被蛆虫侵蚀的动物尸体时那种直觉上的反感。它没有优雅,没有机器的美感。只是赤裸裸的内部结构,用蛮力组织起来。到处都是冗余的错误处理代码。
这是个愚蠢的理由吗?也许吧。但即使是 Rust,尽管它到处都是奇怪的
&
、*
和<'a>
以及其他符号,甚至 Perl 及其@#$@%&
之类的东西,都比 Go 看起来“漂亮”得多。各有所好,Go 非常接近研究论文中常见的伪代码。这正是其可读性的证明。
也许你是特例。没有严肃的程序员会认真声称,更晦涩的符号能提升可读性。
Go 对我来说非常“漂亮”,但话说回来,美在观者眼中……
没有完美的语言。只有权衡取舍。我个人更倾向于 Go 团队所做的(以及正在做的)权衡。
没有完美的语言,但 Go 勉强够用。他们好像知道“垃圾语言”和“刚摆脱垃圾”语言之间的界限,然后就停在了那里。
那么为什么它被如此广泛地使用呢?Docker 或 k8s 难道不能用 Rust、C++、Swift 或其他语言编写吗?
我可以用其他语言编写我编写的东西吗?当然可以。但对我来说,Go在开发领域提供了最佳的权衡。
因为谷歌,因为工具链优秀,因为它很流行。
你应该明白,一种语言是否被使用与它的好坏无关,而完全取决于它是否流行,对吧?
当然可以。谷歌选择用 Go 编写是因为他们发明了这种语言。
故事很酷,老兄。
Docker 并不是由谷歌人开发的。你说 Go 被使用是因为它很时尚,但为什么在它变得时尚之前,人们选择用 Go 来创建大型项目?你必须承认,Go 一定有某种特质让人们选择它。
时尚使然。这就是所有事物成为时尚的方式。起初只有少数人选择它,随后追随者纷纷效仿。
你认为 Python 为何如此流行?是因为它是一门优秀的语言?是因为其工具链卓越?是因为它性能优异且可扩展?不,是因为它在研究生群体中流行起来,随后他们用它编写了大量机器学习语言。
Go 也是如此。Go 是一种平庸的语言,在许多方面都比 Python、Java 及其他在它诞生时流行的语言更差。
你无法解释时尚。这是群体心理。
如果不是流行度,什么才是一个伟大语言的标志?如果人们喜欢使用它并能用它高效完成任务,那它就是一个好语言,甚至是一个伟大语言。语言只是工具;实现目标的手段。
一致性、语义、性能、标准库等。流行度是评判语言伟大程度时最不重要的因素。
是啊,那又怎样?你是说最好的工具就是最流行的工具吗?
“没有完美的语言”并不意味着每种语言都同样远离人们对完美语言的设想。
什么是完美语言的设想?有没有客观的要求清单?我怀疑没有。每个人都有不同的要求,而许多可能的要求相互矛盾。所以又回到了权衡取舍。
Brainfuck 是否与其他语言一样好?
很少有语言能与其他语言一样好。如果你有特定的要求,它可能仍然是最好的选择。如果你急需编译器占用 150 字节左右的空间,并且希望将简单程序存储在字符串字段中,Brainfuck 甚至可能是一个不错的选择。
完全正确。“没有完美的语言”并不意味着没有语言达到完美。它意味着根本不存在这样的语言。
看啊,多么睿智的头脑!/s
这取决于你优化什么。如果我们优化的是我个人认为代码应该如何编写,那么我会说 Haskell。如果我们优化的是我认为代码应该如何编写与现实世界实用性之间的平衡,那么我会说 Kotlin。如果我们优化的是人们在现实世界中如何编写代码,并围绕这些不完美之处设计语言,那么我会说 Go。
我认为 Scala 能在你的 Haskell 偏好与现实世界之间取得更好的平衡。
此外,我认为 Go 的吸引力在于其语法极其简单,但它有太多令人费解的设计决策,我实在无法理解有人如何能在复杂的 Go 代码库中享受工作。Go 语言会竭力阻止你使用任何形式的抽象或复用,其错误处理哲学也存在诸多问题。它似乎将异常处理的缺点与 result/either 类型的缺点结合在一起,却没有继承两者的任何优点。
我大量使用过Kotlin和Scala。我认为最初Scala无法决定是想成为更高效的Haskell,还是仅仅在JVM上运行Haskell,而且有两派人分别推动它朝这两个方向发展。我认为后者占了上风,而那些希望它成为更高效Haskell的人大多转投了其他语言。
我发现将新手引入Kotlin代码库并通过Arrow库向他们介绍类似Haskell的概念要容易得多。我个人认为Kotlin在这方面做得更好,但具体情况可能因人而异。
我的意思是,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 更适合我,但每个人都有自己的选择。
但这个理想对每个人或每个项目来说都是不同的。
我大约在2015年开始将Go作为爱好使用。我对它的简单语法和可执行文件印象深刻。然后大约在2021年,我得到了第一个使用Go的工作,我仍然对它非常满意。
我曾努力编写一些代码,其中涉及多种类型与基础类型组合,并需共享一个顶级执行函数。最终我让该函数拥有2个泛型参数,而主参数是一个接口,该接口包含其中一个参数。
与其他语言相比这显得有些凌乱,但我对结果感到满意。
Visual Basic是最接近它的语言。它足以在Excel工作簿中完成基本任务,适合办公室人员使用。
同样的道理也适用于JS、Lua或Python。但仅仅因为它能满足特定人群的需求,并不意味着它在所有场景下都完美无缺,否则每个人都会用它来做一切事情。
老兄,我喜欢 Go。
我喜欢 Go。我喜欢 Swift。我喜欢 Rust。我喜欢 C/C++。我喜欢 Python。它们都有各自的用途。
没错,Go 和 C++ 提供了如何不设计编程语言的绝佳范例。
Python是为什么不应该用脚本语言做其他事情的绝佳例子。
天啊。这么久以来,我以为Java是最不受欢迎的语言,因为它缺乏功能。
你睡着了,自从17年后,很多事情都变了
是的,我有点过时了,但Java 21确实缩小了与Kotlin的差距。Kotlin仍因Java的一些遗留问题和理念立场(如记录仅限只读)而领先,但回归Java后,使用过许多新语言后,发现它竟是如此令人愉快的体验。
对我来说,虚拟线程才是革命性的。命令式编程结合响应式编程的优势?太棒了!
那是因为大家都停留在8.0版本 /s
我读了这篇文章,但没明白重点。是因为它不够复杂,还是语法太简单了?当你需要对硬件层面的更好控制时,你会选择像C/C++这样的语言,就像我们在TIOBE指数中看到的那样。
没有哪种语言是完美的,当我们喜欢某种语言时,可能会用谬论来幻想一些不可能实现的东西。
所以Go在做Java做过的事情?只是停滞不前?
如果使用过Java 17之后的版本,Java 并非停滞不前。许多企业仍在使用Java 8,但这并非语言本身的缺陷。如今Java 21已重新实现了大量Kotlin的特性,实际上使用起来相当不错。
Go 语言的开发理念是“开发者太蠢了,不配使用抽象概念”,因此 Go 语言被有意无意地塑造成了一团糟,与停滞不前的语言如出一辙。
Java 到底做了什么?什么都没做。
它确实一直停滞不前,直到大约 2018 年。大约 12 年时间里只有两次重大版本发布。这就是停滞不前。是的,自 2010 年左右以来,随着 6 个月的发布周期,它已经现代化了很多。除了空安全。如果可以使用 21+,我真的看不到使用 Kotlin 的意义。我猜是 Android 开发,但我更愿意使用 Java。
Go 语言是刻意设计成停滞不前的。这与 Java 过去的情况相去不远。Java 曾经做出过极具侵略性的向后兼容性决策,导致积累了大量冗余代码和臃肿的 API。
发布速度虽慢,但每次更新都非常重大。2014年的Java 8带来了lambda表达式,这是该语言有史以来最大的变革之一。
几年前Go语言引入了泛型,这也是该语言有史以来最大的变革之一。
我并不反对。
Go 已经达到了 80% 的完美。剩下的 20% 它甚至不会尝试去追求……
嗯——Rust 一直都很复杂。
我认为这并不适用于 Swift。
C#更接近Java的领域,所以我将它们归为一类。Go则更努力地成为“更简单的C”,从而与C竞争。在某些方面它确实成功了;我认为Go比C简单得多。当然也比C++简单。
有趣的是,所有试图取代C的语言最终都未能成功。不过,Go 做了一件不同的事:它尝试使用另一种语法。这很罕见,因为大多数试图取代 C 的语言最终都会复制粘贴语法。看看 C++,看看 Rust。它们几乎属于同一语法家族。
基本上,谷歌现在主导着 JavaScript。我们需要打破它对万维网的垄断。此外,JavaScript 很糟糕。我可以在其中编写可运行的代码,但必须一边咒骂一边编写。我使用 Ruby 或 Python 时从未遇到过这个问题(尽管 Python 对我来说有点别扭,但我不必咒骂,而 JavaScript 真的会触发我的大脑。JavaScript唯一有趣的地方是2012年的WAT演讲:https://www.destroyallsoftware.com/talks/wat)
这是真的。我最终没有使用 Ruby 的许多新功能,因为它们看起来无用且/或丑陋。
这个论点很奇怪,因为无论谁编写了代码,如果是由其他人编写的,我总是需要花时间去理解它。因此,是的,更复杂的语言需要更多时间去理解,但即使在简单的Python中,我经常不得不问自己“他们到底在写代码时在做什么”。有时,那些“他们”就是我,几个月或几年前的自己。这也是我添加大量注释、文档和解释的一个重要原因;我需要向未来的自己解释我当时在做什么。后来我可能会意识到,我写代码时一定喝醉了。但我仍然不知道别人在写那段代码时在做什么。
编码规范也是垃圾。许多指南毫无意义;看看RuboCop的指南就知道了。至少20%是垃圾;或许5-10%有用或勉强可接受,最多25%。但大多数最终只是个人意见。不过仍有一些客观标准适用,例如格式化方式。例如,使用两个空格是正确的;四个空格是错误的,但最糟糕的是使用制表符进行缩进。许多人并不明白原因。他们需要自行思考并领悟;向他们解释是没有用的。(这与Vim与Emacs的争论不同,双方都错了,尽管双方都认为自己正确。)
好的,首先——并非所有人都像谷歌那样。其次:C++ 在许多领域都表现不佳,令人惊讶的是 TIOBE 仍将其排名第二。你可以说人们需要语言教程,但这绝非唯一原因,因为其他语言也需要教程,却无人搜索,因为这些语言鲜少被使用。显然,C++ 被广泛使用。它只是并非有史以来设计最好的语言。
好吧,但他比较的是编译器。那不是语言本身。他提到了 Go 的特性,然后批评 Swift,而没有批评语言固有的特性。作者是否存在严重偏见?
Go 是人们实际上喜欢却最被讨厌的语言。
Go 并不被讨厌。Go 是一门非常无聊的语言。那些患有“新奇事物综合症”、害怕错过(FOMO)和追逐热潮的人认为他们总是需要一些新东西,而 Go 没有这些。
这更像是“无法让所有人永远满意”。
有人曾说过:“语言只有两种:一种是人们抱怨的,另一种是没人使用的。”
值得一读,尤其是结尾部分。
Go 通常都能正常工作。现在的软件包管理相当不错。代码可读性强。几乎没有神秘之处。测试和模糊测试支持也很好。
如果你想要裸机性能,就使用 Rust(或 C)。如果 90% 的裸机性能可以接受,而且你不介意垃圾回收,那么 Go 就非常适合你。