为什么 Go 语言如此不受待见?

在 Quora 上,有个问题是比较 D/Rust/Go/Nim 等语言的表现,几乎一致地认为 Go 是最搓的,Rust 备受好评。各位看看何解? Of the Emerging Systems Languages Rust, D, Go and Nimrod, Which Is the Strongest Language and Why? 他们有一个观点,能够直接操作硬件的才被定义为系统级语言,而另外定义是适用于 web 后端或者分布式。Go 由于其 gc 而被直接否定。
关注者
1586
被浏览
314528

83 个回答

Rust 和 Nim 确实好呀

Rust 可以说是 D 语言二代目, 没有 D 里的一些经验主义设计, 而且更函数式, 作为 a better C++ 当之无愧. Pattern matching, Block, Generic 这些东西, Go 有么? 不好的地方是集成 feature 略贪心, 指针那么多类型是有道理但是学习者容易被吓跑.

Nim 不是函数式的, 但 Nim 支持卫生宏, 可以做 AST 重写, 可以自定编译规则, 是静态语言中的黑客语言有木有! 自定编译规则甚至可以编译出比 C 代码还快的结果, 作为 a better C 当之无愧. 人家 GC 可以手动步进的啊, 想要什么 feature 自己加(list comprehension? 没问题), 加个 const 就可以做编译期计算了(想想 C++ 和 D 里复杂难以掌握的 template 和 static if 多蛋疼), 改写 AST 的 pattern language 也是简单易懂(想想 Java 的 annotation processing tool 怎么用的就蛋碎...), 更重要的一点: 没有那么多哲学骑着你禁止你怎么怎么做, Go 能么?

人类思维有个巨大的缺点就是从众定势, 当然社区大了开发者多了语言会更容易成熟和变得实用, 但如果更多人懂得多了解学习, 理性比较而不是跟风, 现在的编程语言可以发展得更好.
@Irons Du , 不是为了反对他的答案,是因为基于这些问题,能解释 Go 不受待见的其中一个原因:Go 面对的问题和整个解决思路跟 Google 软件的规模相关,而这个规模是罕见的。20 亿行源码放在一个统一的代码库,全部主干提交,理论上代码库里任何两点都可以互相调用,几万个工程师(我是其中一名猪队友)在全球各个时区协同。Why Google Stores Billions of Lines of Code in a Single Repository. 这个设计前提导致了同样被黑得很惨的 C++ Style Guide (The Philosophy of Google's C++ Code) 和 Go 的一些看着很奇葩的设计点。

另外这个软件规模是一个问题,不是什么值得炫耀的东西。Go 为解决这些问题的设计,并不一定适合其他场景。回答这个问题本身,这里也不是说 Go 有多好,只是可以分享一些「为什么」,很有趣的设计权衡,例如 Quora 原文提到的 Go 几乎忽略了所有现代 PL 研究成果,但实际上这些成果在这个规模上还没能很好地工作。关于 Go 为什么是(或不是)系统编程语言的问题,我可以另外写一篇。

1:你能轻松知道哪些struct继承(实现)了哪些interface么?
能,Go guru. 2016-talks/slides.pdf at master · gophercon/2016-talks · GitHub 在超大软件库上一样很顺。显式 implements 声明上规模后会有问题,多余的依赖关系是其中之一,下面有关于依赖的更详细讨论。

2:你能轻松知道struct有哪些"成员函数"么?
能,godoc 啊

这两点正好说明了 Go 极端重视工具的设计思路,工具能解决大代码库上源代码级别无法解决的问题,比如全代码库索引、重构,计算改动影响范围触发集成测试等等。这里面也必须有权衡,例如,要为这个规模写编译器和各种工具,你最好别搞复杂的类型系统,不然事情会很困难。

3:手动维护defer能比RAII轻松?
RAII 很难。C++ destructor + exception, 这里的 exception 包括处理 destructor 的异常安全和 destructor 自己真的需要抛异常的情况。还有如果在 destructor 里放了重型操作,比如 flush 硬盘,defer 至少让你清楚地看到这种重操作会在哪里跑。这些问题当然都可以用很仔细的设计避免,但是在有几万个猪队友的时候,不要指望每个人都能做出好设计。

4:package只有一个层次
如果是指不能只 import 一个父节点而要显式 import 所有叶子节点。这是用来控制 dependency 的,不必要的 dependency 在大软件库是个严重问题。Go 奇葩的 import 多余 package 直接编译错误的规则也是这个目的。

5:访问控制只能限定在package之外。
个人体验,它省掉了很多语法规则,还工作得很好。有点不方便的是你看它 call 一个私有函数,但是在同一个文件里是找不到这个函数的定义的,它可能在同一个 package 的另一个文件里。这个是用工具补足的 —— 在内部的 code search 工具里我没感觉不方便,在 github 没有交叉引用的情况下看代码就比较郁闷。

6:基于源代码的开发(复用),这是否违背了以前书上说的实现隐藏(只暴露接口)?
没,主要是因为 Google 统一代码库,Go 一开始压根没考虑二进制库发布的问题。这跟软件工程的隐藏实现是两回事。依赖版本管理问题同理,因为统一代码库+全主干提交,这个问题在 Google 是不存在的…… 当然问题就是问题,现在外部使用越来越多他们也在逐步补锅了。

7:推崇error作为返回值是不对的。另外(panic+recover)对比下C++在C之上添加的异常处理(+RAII)的类型安全
推荐一篇微软 Midori 项目 (Rethinking the software stack) 语言 leader 的 Joe Duffy - The Error Model (超长)。error 功能不够好,但 C++ 和 Java 的 exception 机制在上规模后也有无法解决的工程和性能的问题,Optional是好,但是语言就要变复杂,这里面有 tradeoff. 另外,「异常安全」是个看起来遵守规则写就可以的简单事情,但实际上非常困难,比如事务的回滚,文中也有专门描述。
为什么?