在这个竞争越来越烈的社会,掌握一门新语言或新技能,意味着你能比别人多一个机会。
但万事开头难,学习新东西亦如此。如果开发员想学一门新的编程语言,该选择什么呢?
Go语言学起来简单得令人惊讶
当我第一次开始学习Go语言时,我正着手开发一个个人项目,为此我不得不掌握新的语法(我总是在学习一门新的编程语言时想出一个项目)。
我决定创建一个命令行应用程序来枚举子域,以辅助寻找资产中存在的漏洞奖金计划。为实现这一功能,与gobuster相似,该应用程序必须并行地发出多个HTTP请求,但我想通过增加一些功能(例如抓取HTML响应以获取与安全相关的有趣信息)来重新构建特定循环。
我尝试用go-routine来解决此问题,其中很具挑战性的一点是程序发出的HTTP请求数量未知,因此需要学习如何有效处理这些请求。
第一印象
很快,我发现语法异常熟悉,尽管我之前从未阅读过相关文档。在我看来,这些概念很直观(其他人可能不赞成)。Defer的使用直接明了。用于格式化字符串的fmt包好像解决了我之前未发现的问题。我开始认识到Go作为新兴编程语言近年来得到快速发展的原因。因此,我决定更深入地研究Go语言的初衷,以确定它是否值得花时间学习。
为什么开发Go语言
目的
Go语言由谷歌开发,目的是使多进程开发更加高效和安全,以提高服务器长期运行的可维护性、可靠性和有效性。对谷歌来说,该语言可解决其当前面临的编译时间过长和当今已在生产中取得普遍应用的大规模数据处理问题。谷歌希望开发出一种注重于可伸缩性、可读性和并发性的语言,而其他语言无法满足这些要求,因此诞生了Go语言。谷歌开发人员从现有的语言中提取了最简单明了的概念,并将这些概念改进和组合,最终形成了Go。以处理字符串的高效数据库——fmt数据包为例:
“fmt包使用类似于C的printf和scanf的函数,用来实现格式化的I/O。动词形式源自C,但更简单。”
这就是从一种成功且通用的语言(在本例中是C语言)中提取功能并对其进行改进的例子。
Go语言的并发机制基于CSP建模;使用通道可避免共享数据出现同步错误,这种信息交互方式更简单也更安全。
Go语言关注的另一个重点是简洁化。使用Go语言需要在其框架下形成一种公认的特有代码风格,并在开发不同项目时保持一致,以减少配置linting规则和在开发过程中学习不同的代码风格的时间;而时间,是在团队中工作的一个要素。
从理论上讲,这将减少开发人员在代码风格和编程方法上的差异,正如包含了许多Eslint规则的JavaScript语言。
方法
Go语言所采用的方法将解释型动态型语言的编程简便性与编译性静态型语言的效率和安全性相结合。其内置映射定义了int、byte和string等基本类型。有指针。除此之外,在使用Go语言进行开发时还应注意的一个重要的原则就是正交性,该原则也是函数方法的基础。
Go使用结构(struct)表示数据,用户接口表示抽象。关于Go语言是否面向对象一直存在争议,Java开发人员起初很难理解为什么对此会存在争议。争议的焦点在于Go中没有类型层次,而普遍判断是否面向对象的依据是类型层次。有些结构不能继承,但确实符合对象样式。Go更倾向于组合而不是继承。多态性可以通过接口来实现。满足该接口的任何类型对象都可与其对接。
除了这些核心概念之外,Go还通过多核处理实现了对并发的现代需求。强并发性以goroutines和channels的形式实现。在大型并发程序中,自动垃圾回收作为一种有效的内存管理手段非常重要。单元测试简单到只需使用前缀_test.go即可,该前缀在与源文件相同的目录中声明。
学习Go语言的理由
1、简洁性
Go语言采用极简方法开发。没有类或继承。流行语言(如Java和Python)中的这部分功能在Go中被结构取代了。Go是强静态类型且鼓励在各种情况下使用接口。静态类型旨在减少编译错误,也使Go更易学。
在使用其他语言如JavaScript时,多种固有方法、范例和公约令人为难,而Go提供了一种方法作为通用样式指南。从团队的角度出发,个人代码的分析和推理更容易,集成也更顺畅。
尽管没有隐式转换,但是花费在语法上的工作仍然非常少。这使代码可读性更强、更简单。
2、快速性
静态连接的编译器通过编译生成二进制可执行文件,而无需处理外部依赖项。可执行的二进制文件已编译为本机代码,无需使用虚拟机,尽管其数据量有所增加,但编译速度更快、可移植性更强。
此外,如前文所述,Go的编译时间和生产时间也很快。由于其简洁性,在使用Go语言时,开发者的工作效率得到了关注,即从最初的概念/想法到产成品的过程更快。
3、并发性
在Go语言中,并发性是核心概念,具有较高优先级,就像使用go关键字为函数添加前缀一样容易。Goroutines是简单轻量级的执行线程。在Go中实现并发非常容易。使用go关键字产生一个新线程,该线程在一组线程的多个核心之间共享。Goroutines只有几千字节,由Go运行时处理,Go运行时将go-routines移动到不同的可运行线程上,以避免通道被阻塞。这种方法使得异步执行速度几乎和C/ C++一样快。您可以使用channel来控制goroutine的数量,各channel看似同步,但实质上是异步的。
Go语言的运行时使用可调整大小的有界堆栈,从而使堆栈变小。运行时会更改内存大小以自动存储堆栈。数十万个goroutine可以在同一地址空间上运行。
存在的问题
没有范型
此问题存在争议。在Java这样的语言中,范型的使用提高了代码的可重用性,同时确保了类型安全。Go的使用者们已经提出了这个“问题”,并对此进行了思考。这里的建议可参考。然而,主流意见是使用范型的好处不会超过简单性和可读性(没有范型)的好处。
竞争形势
“不要通过分享记忆来交流;相反,通过交流来共享记忆。”
这一理念带来了优势,也使Go容易受到竞争条件的影响。
由于go结构的可变性(以及缺少不可变的数据结构),共享可变数据被迫要跨越多个并发进程实现。例如在没有深度复制的情况下沿通道发送指针,本质上可变特性引发了竞争形势。通道可能会改进并发编程,但确实存在竞争风险,这种情况channel无能为力。
然而,Go CLI中内置了一个竞态检测器来帮助检测竞态条件。
错误检查
错误检查非常明确,没有try…catch语句。在处理错误时,必须改变原有方法和思维方式,尤其是已习惯于其他语言的处理方式。Go开发团队认为,减少异常可以防止代码复杂化和返回值重载。这与其简洁性需求一致。但是,在真正异常的情况下,可使用panic和recover来处理异常并进行恢复。Go还有一个标准的error接口类型,它返回一个带有error()的错误字符串。
Go开发人员使用多值返回检查错误值来处理错误。可以从预设产生错误的函数中返回错误。通常用if err != nil来从代码库中识别错误。
对某些问题而言太简单
Go语言的简洁性是有代价的。Go不如JavaScript富有表现力。没有默认值。缺少抽象和范式使得实现DRY原理更加困难、复杂,不直观。
值得注意的一点是Go还很年轻。开发团队正在考虑使用范型,随着Go的成熟还有很大改进的空间。该团队非常努力地不断开发和改进Go。和任何一种语言一样,Go也有其长处和短处。可以确定的是,如果足够多的Gophers(Go程序员)觉得需要某种功能,该功能将得以实现。
尽管看上去某些功能缺失了,但换个角度看待可以了解到如何在Go中实现看似缺少的功能。
通常可以通过不同的方法来实现同一件事,即更加友好的Go方法。
何时使用
可以说在当前阶段,Go并不能解决所有问题;特别是与需要大量抽象的GUI和复杂系统相关时。
但是,又有哪种语言可以解决所有问题呢?
利用Go的优势。如果觉得该语言过于简单,并且很难以一种简明的方式增加复杂性,则可以用Go来构建简单的微服务而不是复杂系统。将Go作为构建网络和系统工具,而不是替代一种更适合当前任务的语言。
因此最重要的是使用正确的工具完成工作。如果这个工具是Go,那么Go应擅长解决该问题。
切记不要张冠李戴,病急乱投医。
Go作为一种开源编程语言,可轻松构建简单,可靠和高效的软件。