探索新的编程语言:Go是你的首选吗?

发表时间: 2019-12-28 01:44

快速的运行时、高效的并发、简单易学的语法,这些都是Go语言最吸引人的特性。

作者 | Lewis Fairweather

译者 | 弯月,责编 | Elle

以下为译文:

Go语言的入门门槛之低令我感到惊讶。

刚开始学习Go时,我就用它开发了一个个人项目,我强迫自己熟悉它的语法(每次学习新语言时我都会开始新的项目)。

我决定建立一个命令行应用程序,能够列出所有子域名,帮助我的bug赏金活动。因此,该应用程序需要同时发送多个HTTP请求,有点像gobuster,但我希望重新发明这个轮子,并添加一些功能,如抓取HTML响应内容,以找到我关心的安全相关的信息。

问题是我想用Go-routines来解决。由于需要发送的HTTP请求数是位置的,所以我需要了解怎样才能有效地处理这些请求。

第一印象

刚上手Go语言我就觉得它的语法非常熟悉,而在这之前我甚至连文档都没看过。那些概念对我而言似乎非常直观(也许别人会有不同的意见)。defer的用法也非常合理。用于解决字符串格式的fmt包似乎解决了我之前甚至不知道的问题。我开始体会到为什么Go语言开发者要开发一门新语言。所以我决定深入到Go的最初目的,来判断它是否值得学习。

为什么要开发Go

目标

Google开发Go语言的初衷就是让多进程处理的开发更高效、更安全,从而改善服务器软件的可维护性、可靠性和可验证性。Go语言是Google面对自己遇到的大数据处理(这也是Google目前最流行的功能之一)方面的超长编译时间等问题交出的答卷。他们需要一种语言,该语言的重点在于可扩展性、可读性和并发性。Go语言从诞生起就避免了其他语言必须面对的这些烦恼。Go语言的发明者们从其他语言中吸取了最需要的概念,然后进行改进,再合并到一起,形成了Go语言。例如fmt包就是个非常高效的字符串处理工具:

“fmt实现了I/O的格式化,提供了类似于C语言的printf和scanf的函数。格式的‘动词’的概念是从C语言继承的,但要简单得多。”

fmt就是一个从流行的成功预言(如C语言)吸取并改进的例子。

Go的并发采用了CSP的模型,使用通道(channel)避免了同步共享数据。他们认为这样进行通信更简单、更安全。

另一个重点就是简单性。Go语言需要一种有偏向性的编程风格,于是Go社区构建了这种风格额,称为go style。这个风格贯穿所有项目,从而减少在配置lint规则和学习不同编程风格所花费的时间,这对于团队是非常重要的。

“理论上这能够减少开发者之间的代码风格和实现风格的差异,这些现象在其他语言(如JavaScript,它需要使用大量的Eslint规则)中很常见。”

Go语言的方法

Go语言采用的方法结合了动态类型的解释语言的易用性,以及静态类型的编译语言的效率和安全性。它有int、byte和string等基本类型,也有内置的maps类型,还有指针。正交原则是Go开发中的一个重要原则,它是函数式方法的基础。

GO使用结构(structs)表示数据,使用用户接口来表示抽象。人们一直在争论Go是否是面向对象的。如果你是Java开发者,那么你第一眼很难在Go语言中看到面向对象的东西。这是因为你在寻找类型层次结构,而实际上Go的类型没有层次结构。它只有structs,不能继承,但却是对象风格的。只是与继承相比,Go更倾向于使用组合。你可以通过接受接口来实现多态。接口可以被任何符合该接口的类型对象接受。

除了这些核心概念之外,Go还意识到现代的多核心处理对于并发的需求。强并发通过goroutines和通道实现。在大型并发程序中,自动垃圾回收和高效的内存管理同样重要。单元测试也非常简单,只需在源代码同一目录下使用_test.go前缀编写文件即可。

为什么要学习Go语言?

并发

并发是刻在Go语言骨子里的。它是一等公民,而且非常容易使用,只需要用go关键字给函数加上前缀即可。goroutines是低成本、轻量级的线程执行。在Go语言中实现并发非常简单。只需要用go关键字生成一个新的线程,该线程在同一个线程组内可以在多个核心上共享。Goroutine只有几KB大小,由Go运行时负责处理,它会将goroutines移动到不同的可运行的线程上,以避免阻塞。因此,执行是异步的,而且非常快;几乎和C/C++一样快。你可以使用通道来控制goroutines的数量,尽管用起来感觉像是同步的,但实际上是异步的。

Go运行时使用可变大小的、有界的栈,因此栈可以更小。运行时会改变存储栈的内存区域的大小。同一个地址空间内可以运行几十万个goroutines。

简单

Go语言开发时采用了极简方式。没有类,也没有集成。所有这些流行语言(如Java、Python)等的功能都用structs代替。Go是强类型、静态类型语言。它鼓励在任何地方使用接口。静态类型的目标是减少编译时错误。也让语言更容易学习。

在其他语言如JavaScript中,你必须在多种方式、范式和惯例中做出抉择,但使用Go,就只需遵守唯一的一种被所有人接受的代码规范。这样,在团队中分析和论证代码就非常容易,集成也会更顺畅。

尽管没有隐式转换,但带来的语法额外开销依旧非常小。这样产生的代码更易读、复杂度更低。

速度快

编译器是静态连接的。这样在编译生成二进制可执行文件时无需处理外部依赖。代码会编译成由机器代码组成的可执行文件,运行时无需使用虚拟机,因此速度更快、更便携,尽管尺寸会增大。

而且,Go语言的其他方面也很快:比如前面说过的编译速度,以及生产环境上线的事件。Go语言专注于开发者的生产力,这主要归功于它的简单性。因此从初始创意到生产环境上线的速度很快。

Go语言有什么问题?

没有泛型

这一点众说纷纭。使用泛型的语言(如Java)能大幅度地增强代码的可复用性,同时保证类型安全。Go社区已经提出了这个问题,并且在考虑中。但是,Go团队目前的态度是,泛型带来的好处并不能超过没有泛型时的简单性和可阅读性。

竞争条件

“不要使用共享内存进行通信,应该使用通信来共享内存。”

这条原则虽然带来了好处,但也让Go更容易产生竞争条件。

由于Go的结构是可修改的(而且没有不可修改的数据结构),开发者只能在多个并发进程之间共享可修改的数据。举个例子,你可以将指针发送到通道,而不需要做深度拷贝,而数据的可修改性就可能导致竞争条件。通道也可以改善并发编程,但竞争条件的确存在,而且通道并没有办法防止它发生。

不过,Go CLI内置了一个竞争条件检测器,来帮助检测竞争条件。

错误检查

错误检查必须显式进行。Go语言没有try-catch语句。因此,你必须改变错误处理的思路,特别是在你早已习惯了其他语言的情况下。Go语言团队认为,不使用异常可以避免造成过度复杂的代码,也可以避免重载返回值。这与Go语言追求简单时一直的。不过,在确实必要的时候你可以使用panic和recover来处理异常。而且还有个正统的error接口类型,会通过Error返回一个错误字符串。

Go语言的开发者采用了多值返回值来检查错误。某个可能会产生错误的函数可以返回一个错误。通常需要用if err != nil的写法污染代码。

对于某些人来说可能太简单了?

简单是有代价的。Go不像JavaScript那样有丰富的表现形式。Go语言没有默认值。它还缺乏抽象,缺乏泛型,因此实现DRY原则变得非常困难、非常不直观。

要知道的是,Go语言依然很年轻。泛型在考虑中,随着Go语言不断成熟,以后还会有更大的考虑空间。社区在努力开发并改进Go语言。就像任何语言一样,Go有自己的优势和弱点。我肯定,如果足够多的Go程序员认为某个语言特性很重要,那么这个特性肯定会被实现。

但是,尽管似乎缺乏一些语言特性,但有时候只需要从不同的角度考虑问题即可。

通常,问题总有另一种更适合Go语言的方式来解决。

什么时候使用Go

我们可以认为,目前Go语言并不能解决所有问题,特别是与GUI有关的问题,另外还需要大量抽象的复杂系统。

但又有哪个语言能解决一切问题呢?

我们应该取其所长。如果你认为Go语言太简单,很难用干净的方式增加复杂性,那么就应该用它来构建简单的微服务,而不是用来构建复杂的系统。使用Go构建网络工具和系统工具,而不应该用它来代替更适合某项任务的语言。

所以最重要的是,根据自己的需要选择最合适的工具。如果Go适合你的需求,那么就选择Go,因为这就是Go的优势所在。

所以现在就开始学起来吧。

原文:https://medium.com/swlh
/want-to-learn-a-new-programming-language-consider-go-golang-1c32dc3788ba

本文为 CSDN 翻译,转载请注明来源出处。