Go 编程语言已经存在了十多年,并且逐渐流行起来。以下是一些原因,以及为什么您可能会发现自己想用 Go 编写下一个项目。
1. 恰到好处的运行时间
编程语言的“运行时”是它附带的代码,是支持语言本身特性所必需的。
Go 的运行时间比例如 C++ 大得多,但这是有充分理由的。
当我查看语言中包含的运行时功能时,我看到了一个模式:
- 垃圾收集——你不必手动分配和释放,Go 会为你处理这些
- Goroutines — 高效的多线程和 I/O
- Channels——用于在 goroutine 之间推送数据
- 接口——简约但高效的动态类型断言
- 切片——使用方式与在另一种语言中使用数组的方式相同,但旨在更有效地处理分配(在很多情况下是懒惰的)
- Maps — 语言中内置的良好的旧键值哈希映射,语法简洁
- 指针——就像 C 语言一样,除了没有指针运算,值与指向它们的指针都被清楚地指出,但没有导致分段错误和其他崩溃的东西
- 类型推断的类型安全——一切都有一个类型,但有一些简写方式不必在每种情况下都写出来(例如,“a := b”使“a”与“b”的类型一致)
- 该语言也明显没有某些东西,例如异常和elvis运算符“?:”。
这些是几乎每个应用程序都需要的东西,因此被认为足够重要,可以放入运行时。但是,他们也对自己添加的内容表现出一些克制,并专注于“物超所值”的功能。
2.Go代码更加冗长
是的,你没看错,Go代码更节制是件好事。要了解原因,你必须考虑使代码不那么啰嗦的原因和手段,并看看它的代价。
从表面上看,短一点似乎更好,对吗?当你可以写"=>"时,为什么还要写 "函数"?当你可以使用"{}"时,为什么还要写出 "Object "呢?
答案很简单。认知的开销。或者,换一种说法,每当你增加一个新的功能,人们就不得不记住和思考另一件事。
随着时间的推移,这种影响会越来越严重,因为新功能的加入,一种语言的代码曾经是最新的,现在因为所有的新功能的加入而变得过时。今天写的C++代码看起来与十年前有很大不同。在这种情况下,问题不在于语言本身,而在于最佳实践和所使用的通用库,但重点是一样的:虽然事情在改进,人们在寻找更好的方法来做事情,这是好事,但发明新的方法来做同样的事情会令人厌烦,并增加不必要的复杂性。
有时候,我觉得需要有人告诉Javascript的人,在语言中不断添加东西不应该是某人的工作。也许他们应该雇人开始删除一些东西。只是一个想法。
在其他语言中,我认为其他的便利条件是一种糟糕的权衡,比如异常。在Java(和JS)中,你可以用 "try "来包围一个巨大的代码块,然后给一个代码块来处理其中发生的任何错误。那么你认为大多数新手开发者是如何利用 "try "和 "catch "的呢?你猜对了,就是把一些行为不端的代码块拿出来,把错误消音。试图写出好的、可靠的软件就这么多了。
Go通过允许函数返回多个值来解决这个问题,按照惯例,其中一个值通常是一个错误。然后你就可以检查这个错误。你知道的,用一个 "if "语句。它很难看,很啰嗦,很可怕,孩子们在它的大坏蛋啰嗦中跑来跑去。然而,它工作得非常好。当你抱怨完不得不输入所有这些 "if err != nil {"行时,谷歌一下"[你的编辑器名称]片段",看看如何为自己制作一个快捷方式。
3.现在有了泛型,就没什么可抱怨的了
很久以来,我一直听到人们不想写Go代码,因为它没有泛型。而我真的很高兴他们增加了这个功能。不是因为我想使用它,而是因为我不喜欢听人们抱怨它。泛型是这些表面上相当简单的东西之一,但在引擎盖下有很多细微的差别和隐藏的复杂性。考虑到这样一个事实:C++模板和Java泛型在语法上看起来非常相似,但它们在引擎盖下的实现却完全不同。这种复杂性你作为开发者可以忽略,但对语言本身有巨大的维护影响。不管怎么说,既然已经做了,人们就不能再抱怨了,可以去发现这样一个事实:一旦写好的代码中,通常只有一小部分需要或受益于泛型。
不要误会我的意思,将类型作为参数在特定情况下是非常有用的。我只是认为这些情况比人们通常认为的 "唉,Go没有泛型 "要少得多。
4.令人敬畏的构建工具
仅仅是文件夹中的文件和一个简单的go.mod文件就可以在不同的系统上以完全相同的源代码输入产生可靠的构建,这一事实听起来很简单,也很明显,但它实际上正是其他构建系统所做不到的。试着构建一个node.js项目,等待6个月,再运行 "npm ci",看看你会得到什么。构建问题是一个持续的痛苦来源,而Go终于把这个问题解决了。
这里的关键概念是,一组兼容的API得到一个导入路径,以及这个想法(https://research.swtch.com/vgo-mvs)[最小版本选择]。
再进一步分解一下。第一个概念是一个主要的概念,也是大多数其他版本系统所错误的概念。这个概念是,一个模块的每个独特的不兼容的版本应该有一个独特的导入路径。换句话说,如果你有版本1的库X和版本2的库X,你猜怎么着?这些都是不一样的东西,因此需要不同的导入路径。在Go模块中,他们引入了在导入路径中附加"/v2 "的想法,以对应模块的版本2,等等。你可以阅读一下细节,有一些零碎的东西需要学习,但我认为他们在这里得到了正确的核心思想。如果你对一个库做了破坏性的修改,它就会得到一个不同的导入路径。这种简单的做法具有重要的副作用,可以极大地提高可靠性并减少构建的混乱。
一旦你有了这个概念,那么最小版本选择就更进一步了,比如说我们不应该只是 "使用最新的2.x "库,而是应该手动升级依赖关系,只使用在构建配置(go.mod)中指明的测试版本的库。这样一来,如果你今天构建了一个软件,明天运行同样的构建,你就能保证得到相同的输出(使用了相同的确切版本作为输入)。现在,我们当然想跟上升级和安全补丁之类的东西,但我们的想法是这应该明确地完成,而不是随机地作为构建过程的一部分。
与其他许多构建系统相比,这些变化使构建Go程序成为一个梦想。
5.大型项目的类型安全是必须的
很多其他语言也有类型安全,但JavaScript和Python是很好的例子,这些语言对小项目来说很有成效,但由于缺乏类型安全,很容易失控。
现代编辑器通过为常见的情况提供合理的错误符号来减轻这种痛苦,即使在像JS和Python这样类型不安全的语言中也是如此。但这并不能改变这样一个事实:随着你的应用程序的复杂性增加,如果你使用的语言除了在运行时没有对任何字段或方法的访问进行检查,你迟早会写出一些目前看来还不错的代码,然后在生产中突然爆发,而且是在最糟糕的时候,因为一些愚蠢的错字。如果有一个编译器和一个类型安全的语言,这个错字是可以避免的。在编辑代码时,你也能得到同样的好处。在编译步骤中出现 "a.SomeThing() "的错误(因为它应该是 "a.Something()")可以节省你的时间,因为你不需要运行那行代码来发现问题,你只需要编译。
Go的类型系统虽然因其与其他语言的差异而备受争议,但它具有你所期望的其他类型系统的所有主要优点。用它们来做好事。
总结
总而言之,虽然它只有 10 年的历史,但 Go 在成为许多团队解决业务编程问题的事实上的标准方面已经取得了重大进展。它真的会“接管”吗?说不定呢。但我认为有很多令人信服的论据使它稳步继续发展。而罗马也不是一天建成的。