我最近开始在Go中编写一些代码,主要是出于好奇。我事先已经知道了很多,但从未在实践中尝试过(没有必要)。但是现在Go被认为是我工作团队中项目的选项之一,所以我认为获得一点经验会很好。
到目前为止,我一直在用语言本身度过一段相当愉快的时光。我会写更多关于我认为Go的真正实力在这篇博文的末尾。
不那么令人愉快的是社区,或者更具体地说,是由于其所谓的简单性而提倡Go的人。看起来 简单 在Go社区已经成为一种模因/流行语,很多人一遍又一遍地重复它而不给它太多实际的想法。
这对我来说似乎很不幸,因为在我看来,Go是一种“非常简单的语言”:
不应该考虑使用Go的主要原因
从他们应得的关注中推出其他好的理由
甚至不是真的
在本文中,我想看看围绕Go的许多简单声明。
在深入研究之前,我想强调一件事: 这篇文章 并不是 对Go的批评,而是Go广告和倡导的方式。有时我可能会批评某种语言的某个方面,但这不是重点,我会尝试仅以偶然的,事实的,每种语言的方式提及这些方式。
我来自哪里
我在专业和业余爱好中使用多种编程语言。我并不赞成拥有“最喜欢的语言”的概念。过去我曾经有过一些最喜欢的语言,但认识到情绪通常最好不会被忽视,这只是时间问题。
现在,作为我工作的一部分,我在大型服务的后端使用C ++和Python进行编码。在过去,我曾经在你可能知道的操作系统上工作,我也做过嵌入式工作作为合同。作为爱好项目的一部分,我做了各种其他事情。
我不是说所有这些都是吹嘘或任何事情(我不是特别的),我只是想表明我对编程的许多方面至少有一些欣赏,我试图保持开放的心态。
所以,不用多说,让我们开始讨论商业并看看几个主张。
1.与主流语言相比,“Go的关键词很少”
我从最明显的例子开始,这一点绝对是我在Go倡导方面的宠儿。
首先,即使它确实如此,我也不知道为什么关键词计数对学习曲线或语言的复杂性有任何重要意义。当然,如果有成千上万的关键字,这可能是一个问题。但是大多数语言或多或少都有几十个关键词,而且在那个规模上,确切地说有多少关键词是相当微不足道的。
由于有很多关键词,我还没有听到有人抱怨某种语言。
其次,Go的“低”关键字数量实际上只不过是一个聪明的律师的伎俩(也许我甚至可以说是'虚假广告')。在 围棋规范 列出了25个关键字,这比大多数其他语言有所回落,但是,在我看来,与其具有较低数量由关键字在其他语言所代表的概念,GO根本没有对这些概念的关键字, 但仍 实际拥有他们的语言(即实际的复杂性保持不变)的一部分。
为了说明我的意思,考虑一个 while
循环。Go没有这个关键字,这是真的,但它仍然有一个while循环, 文档甚至这样说,它只是为了这个目的重用另一个关键字。
另一个这样的例子是 private
和 public
。Go没有这些关键字,但它仍然有私有和公共,它只是使用字母大小写而不是语法中的关键字。
用于削减关键字的另一个技巧叫做 Predeclared标识符,它在技术上不是关键字,但在实践中仍然需要它们,并且创建名为相同的变量仍然不是一个好主意,因此,在一天结束时......他们基本上都是关键词。此外,这些预先声明的标识符中的一些是其他语言的关键字,因此仅将它们与Go的关键字列表进行比较是非常不公平的。苹果到橘子。
2.接收者的论点
该 接收器的参数 是一个怪胎我的位。似乎Go不想拥有 this
或者 self
,但仍然想要方法,所以它有一个'接收器参数',这基本上是相同的东西,除了它使方法签名看起来很奇怪。
接收器参数的问题在于,当读取方法时,我需要知道接收器arg的名称(这是任意的),以查看方法在接收器上的作用。缺少关键字会阻碍语法highliting。(参见?关键字的减少实际上会使事情变得更复杂。)这有点像 C ++中隐含的一样 this
。
确实 有 一个新人被这个混淆的例子。
恕我直言,表达接收器的最简单,最直接的方式是 UFCS 而不是C ++或Go。但就像我说的那样,我并不是在抱怨Go,我不介意接收器的论点(如果我不能忍受C ++的怪异,我可以忍受Go的)。
3.函数返回值
好像接收器参数不够,函数signuature可以通过各种返回值声明获得更多巴洛克式。一种典型的语言将允许您从带有return
语句的函数返回一个值 。在Go中,您还可以返回多个值(我认为可以使用元组以更优雅的方式解决,但是没问题)并且最重要的是,还有 Named返回值。哪个IMO不是一个好主意,因为它允许人们在难以找到函数实际返回的内容时编写令人困惑的代码。结合receiver参数,您可以创建如下函数签名:
......那是有效的Go代码。如您所见,有 三个 参数列表。我真的不希望任何人试图把它作为“简单”出售给我,因为这种语法不过是简单的。
4.“不继承”
Go(或者只是社区?)似乎非常反对“传统的OOP”(无论这意味着什么 - 可能是Java或C ++)。我见过人们声称Go没有那个好东西。
除此之外。Go有一个称为嵌入的功能 ,文档以及一些博客文章声称不是继承。我尝试过以各种方式使用它,我无法真正摆脱它只是重新转换的继承的印象。上面链接的文档说:
有一种重要的方式,嵌入与子类化不同。当我们嵌入一个类型时,该类型的方法成为外部类型的方法,但是当它们被调用时,方法的接收者是内部类型,而不是外部类型。
有什么不同?继承通常以完全相同的方式工作,继承的方法也作用于内部类型。
IMO真正唯一的区别是,在Go中,多态性是从结构中减少的,即。你需要使用接口将多态性带入游戏中。但是一旦你做了,你可以做一些非常类似于传统OOP的事情,包括方法过度 - 这是一个演示。
让Go感到惊讶的一件事 - 一种所谓的简单语言 - 就是你甚至可以做多重继承。 而且非常糟糕。golang-nuts邮件列表中的 某 个人发现,Go不能很好地处理继承模糊。我已经调整了其中提到的代码,以便它也展示了众所周知的“可怕的钻石问题”:
1package main
2import "fmt"
3type T1 struct {
4 T2
5 T3
6}
7type T2 struct {
8 T4
9 foo int}
10type T3 struct {
11 T4
12}
13type T4 struct {
14 foo int}
15func main() {
16 t2 := T2{ T4{ 9000 }, 2 }
17 t3 := T3{ T4{ 3 } }
18 fmt.Printf("foo=%d\n", t2.foo)
19 fmt.Printf("foo=%d\n", t3.foo)
20 t1 := T1{
21 t2,
22 t3,
23 }
24 fmt.Printf("foo=%d\n", t1.foo)
25}
这是Go操场上的相同代码。
我突然想到上面的代码编译没有任何警告/错误。 这是C ++中的类似代码,你可以看到它由于环境不明而无法编译。
后果是什么?嗯,首先,我认为具有多重继承几乎排除了在所讨论的编程语言的描述中任何地方使用“简单”一词。 没有人可以说服我,Go是最简单的语言之一,或者甚至 是一种简单的语言,在我看到上面的代码之后。 它甚至没有其他一些你可以用嵌入做的事情,例如通过指针嵌入或通过指针嵌入接口。(我甚至不确定这些功能的所有含义是什么。)
其次,我想在这里做一个简短的例外,并批评Go语言本身。不处理这样的歧义似乎是一个设计/实现错误。甚至C ++都没有足够的疯狂来编译这样的代码而没有抱怨,这应该告诉你一些事情。
5.错误处理
各种错误处理通常是巨大的火焰材料。我不想进入那个。我已经在不同语言中使用了所有常见的错误处理方式(我认为),并且我非常不喜欢所有这些风格 。我认为,无论如何,错误处理总是会成为PITA。通过将一种风格换成另一种风格,您只需将一组问题换成另一种问题。没有银弹。
回到简单的主题:Go选择不使用异常,这确实使事情变得更简单。什么不会使事情变得更简单是多个返回值的功能,这意味着不用返回任何错误或更迭值,你可以返回 两个 人或 没有 (在CS行话,你可以说的问题是产品的使用类型而不是和类型)。实际上,我已经看到了新人的代码的多次代码审查,其中人遇到了这个步枪。
如果Go不允许多个返回值,而是有一些合适的sum /类似类型,那将使事情变得更加简单IMO。出于同样的原因,很容易忽略Go中的错误和/或无法正确地将它们报告给调用者或其他适当的目标。
让事情变得不简单的另一件事是恐慌。不要误会我的意思,我理解它存在于Go中的原因以及它是如何有用的,事实上,其他一些语言也有类似的安排。我只是提到它作为反对简单主张的一点 - 一个新手是恕我直言,可能会混淆两个设施之间的区别是什么,哪个适合。
6.泛型
这个主题可能是一个比错误处理更大的蠕虫。
与错误一样,我只想在这里考虑复杂性/简单性方面。Go社区中的许多人似乎都认为泛型本质上是复杂的(= 坏,mmkay)并且具有这种或那种的巨大开销。这在某种程度上是正确的,但我认为它并不像有些人喜欢描绘那样糟糕。看起来这些人似乎对C ++模板有过痛苦的经历,并且从那时起每当有人提到泛型时都会受到PTSD攻击。
听听这里的人, 仿制药不是一个怪物。他们绝对不需要像C ++(或其他一些bogeyman语言)那样复杂。我的意思是,即使是前端人员最近也使用泛型(TypeScript,Flow,...),如果他们不害怕泛型,那么任何其他程序员都没有理由:)(抱歉前端开发者,只是开玩笑。)
人们也没有意识到,如果合理使用,泛型可以使许多类型和功能的用户更加简单。例如,考虑Go的 堆接口。这是您从实现此接口的堆弹出的方式:
1popped := heap.Pop(&someheap)
2myfoo := popped.(*Foo) // ZOMG what just happened here?
图片向新手解释,包括恐慌的风险。也许还要考虑如果他们没有真正完成所有事情会发生什么 interface{}
。比较一下:
1myfoo := heap.Pop(&someheap) // myfoo has the correct type
这更容易阅读,更容易解释(你解释它就像解释 Go中已经存在的类型一样 map
)并且在编写代码时也更难搞定。
在 缺乏 仿制药的是,这里将导致额外的复杂的事情,它会导致相当多的额外围棋的其他部分的复杂性,以及,大多是由需要各种“神奇”功能/类型的存在。地图,切片和通道类型是神奇的,以及附带的 make()
函数,它是所有这三个函数的构造函数 。切片类型既可以作为对数组的引用,也可以作为动态数组。(无论发生什么事情都做“做一件事,做得好”?)
(只是为了提醒大家,我并不介意其中任何一个,只是为了不简单的论点而提及它。)
7.杂项
我想我已经把主要的简单违规者排除在外了。我的清单上还剩下几个小的:
在 <-
和 ->
运营商。这些可能很容易就是通道类型的方法。
iota
- 基本上就像枚举,但更奇怪。
内置复杂的数字
如果有一个简短的陈述 (有时可能会有用,但会使 if
语法比我以前用过的语言更复杂)
......我想是的。可能已经忘记了什么,但我认为我们已经受够了。
那么,如果不是简单的话,我认为Go实际上会带来什么?
任务 - “goroutines”
这可能看起来很明显/预期,因为goroutines是一个经常被提到的功能,就像“简单”一样,所以我觉得需要区分: 我不认为它是 通常意义上的并发构成Go的强度。别误会我的意思,Go的并发性还不错。它在任何方面都不是特别的。你有渠道,这肯定很好,但基本上他们只是像我习惯在其他地方的并发队列。然后你有通常的并发原语调色板,如互斥,读写锁,条件变量等。你可以同步你的东西,你可以获得与许多其他语言完全一样的竞争条件和死锁。
我喜欢goroutines(除了显而易见的事实,它们是轻量级用户空间线程)是它们可用于I / O的方式 - 调度连接到主机操作系统的低级I / O API的方式(epoll, kqueue,IOCP,...)。对于程序员来说,这通常很难令人愉快和愉快,特别是在编译到本机语言中。我现在还在了解这里的细节,但在我看来,这是我做得非常好的原因以及为什么我认为Go作为未来项目的假设。
正如已经暗示的那样,我也喜欢Go编译为本机代码的事实。很高兴看到使用垃圾收集(或其他形式的自动内存管理 - Swift浮现在脑海中)保留这种新的语言。结论
那么,所有这些都离开了你,读者呢?是复杂还是什么?
不,绝对不像C ++或Haskell这样的语言那么复杂。与那些相比,Go确实很简单。另一方面,当比较Go和其他常见语言(如Java,JavaScript,Python等)的复杂性时,事情就不太清楚了,我希望我已经展示过了。(此外,这是一项艰巨而且定义不明确的任务。)
我可以提供类似的内容。在某些方面,Go可能比那些语言更简单,在某些方面并不是那么多......总的来说,我说它与其他常见语言的平均值大致相同。此外,我不认为简单,无论是感知的还是实际的,无论如何都是最重要的。我认为不是。
最后,这篇文章在哪里留给我,作家?我不肯定。我不知道Go是否会被选为我日常工作中的(子)项目,或者我是否可以在爱好项目中使用它。我想避免社区的一部分推动维护本文中提到的那种教条。是否有一个地方在某种程度上不那么以意识形态为导向的人们会出去玩?随意建议我。
我对Rust社区也有同样的问题,请注意,在那里我也学会了更好地远离那些狂热的支持者。(问:“ 你可以用Rust重写你的项目吗? ”答:“ 迷路了。 ”)也许这些新语言的本质和他们为一片阳光而奋斗的动机让人们如此。
原文发布时间为:2018-11-28