乐趣区

关于golang:大红大紫的-Golang-真的是后端开发中的万能药吗

前言

城外的人想进去,城里的人想进去。– 钱钟书《围城》

随着容器编排(Container Orchestration)、微服务(Micro Services)、云技术(Cloud Technology)等在 IT 行业一直流行,2009 年诞生于 Google 的 Golang(Go 语言,简称 Go)越来越受到软件工程师的欢送和追捧,成为现在煊赫一时的后端编程语言。在用 Golang 开发的软件我的项目列表中,有 Docker(容器技术)、Kubernetes(容器编排)这样的颠覆整个 IT 行业的明星级产品,也有像 Prometheus(监控零碎)、Etcd(分布式存储)、InfluxDB(时序数据库)这样的弱小实用的出名我的项目。当然,Go 语言的应用领域也绝不局限于容器和分布式系统。现在很多大型互联网企业在大量应用 Golang 构建后端 Web 利用,例如今日头条、京东、七牛云等;长期被 Python 统治的框架爬虫畛域也因为简略而易用的爬虫框架 Colly 的崛起而一直受到 Golang 的挑战。Golang 曾经成为了现在大多数软件工程师最想学习的编程语言。下图是 HackerRank 在 2020 年考察程序员技能的相干后果。

那么,Go 语言真的是后端开发人员的救命良药呢?它是否可能无效进步程序员们的技术实力和开发效率,从而帮忙他们退职场上更进一步呢?Go 语言真的值得咱们花大量工夫深刻学习么?本文将具体介绍 Golang 的语言特点以及它的优缺点和实用场景,带着上述几个疑难,为读者剖析 Go 语言的各个方面,以帮忙初入 IT 行业的程序员以及对 Go 感兴趣的开发者进一步理解这个热门语言。

Golang 简介

Golang 诞生于互联网巨头 Google,而这并不是一个偶合。咱们都晓得,Google 有一个 20% 做业余我的项目(Side Project)的企业文化,容许工程师们可能在轻松的环境下发明一些具备颠覆性翻新的产品。而 Golang 也正是在这 20% 工夫中一直孵化进去。Go 语言的创始者也是 IT 界内赫赫有名的行业首领,包含 Unix 外围团队成员 Rob Pike、C 语言作者 Ken Thompson、V8 引擎外围贡献者 Robert Griesemer。Go 语言被公众所熟知还是源于容器技术 Docker 在 2014 年被开源后的爆发式倒退。之后,Go 语言因为其简略的语法以及迅猛的编译速度受到大量开发者的追捧,也诞生了很多优良的我的项目,例如 Kubernetes。

Go 语言绝对于其余传统热门编程语言来说,有很多长处,特地是其 高效编译速度 人造并发个性 ,让其成为 疾速开发分布式应用 的首选语言。Go 语言是 动态类型语言 ,也就是说 Go 语言跟 Java、C# 一样须要编译,而且有齐备的类型零碎,能够无效缩小因类型不统一导致的 代码品质问题 。因而,Go 语言非常适合构建对 稳定性 灵活性 均有要求的大型 IT 零碎,这也是很多大型互联网公司用 Golang 重构老代码的重要起因:传统的动态 OOP 语言(例如 Java、C#)稳定性高但不足灵活性;而动静语言(例如 PHP、Python、Ruby、Node.js)灵活性强但不足稳定性。因而,“熊掌和鱼兼得”的 Golang,受到开发者们的追捧是自然而然的事件,毕竟,“天下苦 Java/PHP/Python/Ruby 们久矣“。

不过,Go 语言并不是没有毛病。用辩证法的思维形式能够揣测,Golang 的一些突出个性将成为它的双刃剑。例如,Golang 语法简略的劣势特点将限度它解决简单问题的能力。尤其是 Go 语言不足泛型(Generics)的问题,导致它构建通用框架的复杂度大增。尽管这个突出问题在 2.0 版本很可能会无效解决,但这也反映进去明星编程语言也会有毛病。当然,Go 的毛病还不止于此,Go 语言使用者还会吐槽其啰嗦的错误处理形式(Error Handling)、短少严格束缚的鸭子类型(Duck Typing)、日期格局问题等。上面,咱们将从 Golang 语言特点开始,由浅入深多维度深入分析 Golang 的优缺点以及我的项目实用场景。

语言特点

简洁的语法特色

Go 语言的语法非常简单,至多在变量申明、构造体申明、函数定义等方面显得十分简洁。

变量的申明不像 Java 或 C 那样啰嗦,在 Golang 中能够用 := 这个语法来申明新变量。例如上面这个例子,当你间接应用 := 来定义变量时,Go 会主动将赋值对象的类型申明为赋值起源的类型,这节俭了大量的代码。

func main() {
valInt := 1  // 主动推断 int 类型
valStr := “hello”  // 主动推断为 string 类型
valBool := false  // 主动推断为 bool 类型
}

Golang 还有很多帮你节俭代码的中央。你能够发现 Go 中不会强制要求用 new 这个关键词来生成某个类(Class)的新实例(Instance)。而且,对于公共和公有属性(变量和办法)的约定不再应用传统的 publicprivate 关键词,而是间接用属性变量首字母的大小写来辨别。上面一些例子能够帮忙读者了解这些特点。

// 定义一个 struct 类
type SomeClass struct {
PublicVariable string  // 公共变量
privateVariable string  // 公有变量
}

// 公共办法
func (c *SomeClass) PublicMethod() (result string) {
return “This can be called by external modules”
}

// 公有办法
func (c *SomeClass) privateMethod() (result string) {
return “This can only be called in SomeClass”
}

func main() {
// 生成实例
someInstance := SomeClass{
PublicVariable: “hello”,
privateVariable: “world”,
}
}

如果你用 Java 来实现上述这个例子,可能会看到简短的 .java 类文件,例如这样。

// SomeClass.java
public SomeClass {
public String PublicVariable;  // 公共变量
private String privateVariable;  // 公有变量

// 构造函数
public SomeClass(String val1, String val2) {
this.PublicVariable = val1;
this.privateVariable = val2;
}

// 公共办法
public String PublicMethod() {
return “This can be called by external modules”;
}

// 公有办法
public String privateMethod() {
return “This can only be called in SomeClass”;
}
}

// Application.java
public Application {
public static void main(String[] args) {
// 生成实例
SomeClass someInstance = new SomeClass(“hello”, “world”);
}
}

能够看到,在 Java 代码中除了容易看花眼的多层花括号以外,还充斥着大量的 publicprivatestaticthis 等润饰用的关键词,显得异样啰嗦;而 Golang 代码中则靠简略的约定,例如首字母大小写,防止了很多重复性的修饰词。当然,Java 和 Go 在类型零碎上还是有一些区别的,这也导致 Go 在解决简单问题显得有些力不从心,这是后话,前面再探讨。总之,论断就是 Go 的语法在动态类型编程语言中十分简洁。

内置并发编程

Go 语言之所以成为分布式应用的首选,除了它性能弱小以外,其最次要的起因就是它人造的并发编程。这个并发编程个性次要来自于 Golang 中的协程(Goroutine)和通道(Channel)。上面是应用协程的一个例子。

func asyncTask() {
fmt.Printf(“This is an asynchronized task”)
}

func syncTask() {
fmt.Printf(“This is a synchronized task”)
}

func main() {
go asyncTask()  // 异步执行,不阻塞
syncTask()  // 同步执行,阻塞
go asyncTask()  // 期待后面 syncTask 实现之后,再异步执行,不阻塞
}

能够看到,关键词 go 加函数调用能够让其作为一个异步函数执行,不会阻塞前面的代码。而如果不加 go 关键词,则会被当成是同步代码执行。如果读者相熟 JavaScript 中的 async/awaitPromise 语法,甚至是 Java、Python 中的多线程异步编程,你会发现它们跟 Go 异步编程的简略水平不是一个量级的!

异步函数,也就是协程之间的通信能够用 Go 语言特有的通道来实现。上面是对于通道的一个例子。

func longTask(signal chan int) {
// 不带参数的 for
// 相当于 while 循环
for {
// 接管 signal 通道传值
v := <- signal

// 如果接管值为 1,进行循环
if v == 1 {
break
}

time.Sleep(1 * Second)
}
}

func main() {
// 申明通道
sig := make(chan int)

// 异步调用 longTask
go longTask(sig)

// 期待 1 秒钟
time.Sleep(1 * time.Second)

// 向通道 sig 传值
sig <- 1

// 而后 longTask 会接管 sig 传值,终止循环
}

面向接口编程

Go 语言不是严格的面向对象编程(OOP),它采纳的是面向接口编程(IOP),是绝对于 OOP 更先进的编程模式。作为 OOP 体系的一部分,IOP 更加强调规定和束缚,以及接口类型办法的约定,从而让开发人员尽可能的关注更形象的程序逻辑,而不是在更细节的实现形式上浪费时间。很多大型项目采纳的都是 IOP 的编程模式。如果想理解更多面向接口编程,请查看“码之道”集体技术博客的往期文章《为什么说 TypeScript 是开发大型前端我的项目的必备语言》,其中有对于面向接口编程的具体解说。

Go 语言跟 TypeScript 一样,也是采纳鸭子类型的形式来校验接口继承。上面这个例子能够形容 Go 语言的鸭子类型个性。

// 定义 Animal 接口
interface Animal {
Eat()  // 申明 Eat 办法
Move()  // 申明 Move 办法
}

// ==== 定义 Dog Start ====
// 定义 Dog 类
type Dog struct {
}

// 实现 Eat 办法
func (d *Dog) Eat() {
fmt.Printf(“Eating bones”)
}

// 实现 Move 办法
func (d *Dog) Move() {
fmt.Printf(“Moving with four legs”)
}
// ==== 定义 Dog End ====

// ==== 定义 Human Start ====
// 定义 Human 类
type Human struct {
}

// 实现 Eat 办法
func (h *Human) Eat() {
fmt.Printf(“Eating rice”)
}

// 实现 Move 办法
func (h *Human) Move() {
fmt.Printf(“Moving with two legs”)
}
// ==== 定义 Human End ====

能够看到,尽管 Go 语言能够定义接口,但跟 Java 不同的是,Go 语言中没有显示申明接口实现(Implementation)的关键词修饰语法。在 Go 语言中,如果要继承一个接口,你只须要在构造体中实现该接口申明的所有办法。这样,对于 Go 编译器来说你定义的类就相当于继承了该接口。在这个例子中,咱们规定,只有既能吃(Eat)又能流动(Move)的货色就是动物(Animal)。而狗(Dog)和人(Human)凑巧都能够吃和动,因而它们都被算作动物。这种依附实现办法匹配度的继承形式,就是鸭子类型:如果一个动物看起来像鸭子,叫起来也像鸭子,那它肯定是鸭子。这种鸭子类型绝对于传统 OOP 编程语言显得更灵便。然而,前面咱们会探讨到,这种编程形式会带来一些麻烦。

错误处理

Go 语言的错误处理是臭名远扬的啰嗦。这里先给一个简略例子。

package main

import “fmt”

func isValid(text string) (valid bool, err error){
if text == “” {
return false, error(“text cannot be empty”)
}
return text == “valid text”, nil
}

func validateForm(form map[string]string) (res bool, err error) {
for _, text := range form {
valid, err := isValid(text)
if err != nil {
return false, err
}
if !valid {
return false, nil
}
}
return true, nil
}

func submitForm(form map[string]string) (err error) {
if res, err := validateForm(form); err != nil || !res {
return error(“submit error”)
}
fmt.Printf(“submitted”)
return nil
}

func main() {
form := map[string]string{
“field1”: “”,
“field2”: “invalid text”,
“field2”: “valid text”,
}
if err := submitForm(form); err != nil {
panic(err)
}
}

尽管下面整个代码是虚构的,但能够从中看出,Go 代码中充斥着 if err := ...; err != nil {...} 之类的错误判断语句。这是因为 Go 语言要求开发者本人治理谬误,也就是在函数中的谬误须要显式抛出来,否则 Go 程序不会做任何错误处理。因为 Go 没有传统编程语言的 try/catch 针对错误处理的语法,所以在谬误治理上短少灵便度,导致了“err 满天飞”的场面。

不过,辩证法则通知咱们,这种做法也是有益处的。第一,它强制要求 Go 语言开发者从代码层面来标准谬误的治理形式,这驱使开发者写出更强壮的代码;第二,这种显式返回谬误的形式防止了“try/catch 一把梭”,因为这种“一时爽”的做法很可能导致 Bug 无奈精确定位,从而产生很多不可预测的问题;第三,因为没有 try/catch 的括号或额定的代码块,Go 程序代码整体看起来更清新,可读性较强。

其余

Go 语言必定还有很多其余个性,但笔者认为以上的个性是 Go 语言中比拟有特色的,是区分度比拟强的个性。Go 语言其余一些个性还包含但不限于如下内容。

  • 编译迅速
  • 跨平台
  • defer 提早执行
  • select/case 通道抉择
  • 间接编译成可执行程序
  • 非常规依赖治理(能够间接援用 Github 仓库作为依赖,例如 import "github.com/crawlab-team/go-trace"
  • 非常规日期格局(格局为 “2006-01-02 15:04:05″,你没看错,据说这就是 Golang 的开创工夫!)

优缺点概述

后面介绍了 Go 的很多语言个性,想必读者曾经对 Golang 有了一些根本的理解。其中的一些语言个性也暗示了它绝对于其余编程语言的优缺点。Go 语言尽管当初很火,在称誉并拥抱 Golang 的同时,不得不理解它的一些毛病。

这里笔者不打算简明扼要的解析 Go 语言的优劣,而是将其中相干的一些事实列举进去,读者能够自行判断。以下是笔者总结的 Golang 语言个性的不残缺优缺点比照列表。

个性

长处

毛病

语法简略

晋升开发效率,节省时间

难以解决一些简单的工程问题

人造反对并发

极大缩小异步编程的难度,进步开发效率

不相熟通道和协程的开发者会有一些学习老本

类型零碎

  • Go 语言是动态类型,绝对于动静类型语言更稳固和可预测
  • IOP 鸭子类型比严格的 OOP 语言更简洁
  • 没有继承、形象、动态、动静等个性
  • 短少泛型,导致灵活性升高
  • 难以疾速构建简单通用的框架或工具

错误处理

强制束缚谬误治理,防止“try/catch 一把梭”

啰嗦的错误处理代码,充斥着 if err := ...

编译迅速

这相对是一个长处

怎么可能是毛病?

非常规依赖治理

  • 能够间接援用公布到 Github 上的仓库作为模块依赖援用,省去了依赖托管的官方网站
  • 能够随时在 Github 上公布 Go 语言编写的第三方模块
  • 自在的依赖公布意味着 Golang 的生态倒退将不受官网依赖托管网站的限度

重大依赖 Github,在 Github 上搜寻 Go 语言模块绝对不精准

非常规日期格局

依照 6-1-2-3-4-5(2006-01-02 15:04:05),相对来说比拟好记

对于曾经习惯了 yyyy-MM-dd HH:mm:ss 格局的开发者来说十分不习惯

其实,每一个个性在某种情境下都有其相应的劣势和劣势,不能一概而论。就像 Go 语言采纳的动态类型和面向接口编程,既不短少类型束缚,也不像严格 OOP 那样简短繁冗,是介于动静语言和传统动态类型 OOP 语言之间的古代编程语言。这个定位在晋升 Golang 开发效率的同时,也阉割了不少必要 OOP 语法个性,从而不足疾速构建通用工程框架的能力(这里不是说 Go 无奈构建通用框架,而是它没有 Java、C# 这么容易)。另外,Go 语言“奇葩”的错误处理标准,让 Go 开发者们又爱又恨:能够开发出更强壮的利用,但同时也就义了一部分代码的简洁性。要晓得,Go 语言的设计理念是为了“大道至简”,因而才会在谋求高性能的同时设计得尽可能简略。

无可否认的是,Go 语言内置的并发反对是十分近年来十分翻新的个性,这也是它被分布式系统宽泛采纳的重要起因。同时,它绝对于动辄编译十几分钟的 Java 来说是十分快的。此外,Go 语言没有因为语法简略而就义了稳定性;相同,它从简略的束缚标准了整个 Go 我的项目代码格调。因而,“快”(Fast)、“简”(Concise)、“稳”(Robust)是 Go 语言的设计目标。咱们在对学习 Golang 的过程中不能无脑的接收它的所有,而是应该依据它本身的个性判断在理论我的项目利用中的状况。

实用场景

通过前文对于 Golang 各个维度的探讨,咱们能够得出结论:Go 语言并不是后端开发的万能药 。在理论开发工作中,开发者应该防止在任何状况下无脑应用 Golang 作为后端开发语言。相同, 工程师在决定技术选型之前应该全面理解候选技术(语言、框架或架构)的方方面面,包含候选技术与业务需要的切合度,与开发团队的融合度,以及其学习、开发、工夫老本等因素 。笔者在学习了包含前后端的一些编程语言之后,发现它们各自有各自的劣势,也有相应的劣势。 如果一门编程语言能广为人知,那它相对不会是一门蹩脚语言。因而,笔者不会断言“XXX 是世界上最好的语言“,而是给读者分享集体对于特定利用场景下技术选型的思路。当然,本文是针对 Go 语言的技术文,接下来笔者将分享一下集体认为 Golang 最适宜的利用场景。

分布式应用

Golang 是非常适合在分布式应用场景下开发的。分布式应用的次要目标是尽可能多的利用 计算资源和网络带宽 ,以求最大化零碎的整体性能和效率,其中重要的需要性能就是并发(Concurrency)。而 Go 是反对 高并发 异步编程 方面的佼佼者。后面曾经提到,Go 语言内置了 协程(Goroutine) 通道(Channel)两大并发个性,这使后端开发者进行异步编程变得非常容易。Golang 中还内置了 sync,蕴含 Mutex(互斥锁)、WaitGroup(期待组)、Pool(长期对象池)等接口,帮忙开发者在并发编程中能更平安的掌控 Go 程序的并发行为。Golang 还有很多 分布式应用开发工具,例如分布式贮存零碎(Etcd、SeaweedFS)、RPC 库(gRPC、Thrift)、支流数据库 SDK(mongo-driver、gnorm、redigo)等。这些都能够帮忙开发者无效的构建分布式应用。

网络爬虫

略微理解网络爬虫的开发者应该会据说过 Scrapy,再不济也是 Python。市面上对于 Python 网络爬虫的技术书籍不可胜数,例如崔庆才的《Python 3 网络开发实战》和韦世东的《Python 3 网络爬虫宝典》。用 Python 编写的高性能爬虫框架 Scrapy,自公布以来始终是爬虫工程师的首选。

不过,因为近期 Go 语言的迅速倒退,越来越多的爬虫工程师留神到用 Golang 开发网路爬虫的微小劣势。其中,用 Go 语言编写的 Colly 爬虫框架,现在在 Github 上曾经有 13k+ 标星。其简洁的 API 以及高效的采集速度,吸引了很多爬虫工程师,占据了爬虫界一哥 Scrapy 的局部份额。后面曾经提到,Go 语言内置的并发个性让重大依赖网络带宽的爬虫程序更加高效,很大的进步了数据采集效率。另外,Go 语言作为动态语言,绝对于动静语言 Python 来说有更好的束缚下,因而健壮性和稳定性都更好。

后端 API

Golang 有很多优良的后端框架,它们大部分都十分齐备的反对了古代后端系统的各种性能需要:RESTful API、路由、中间件、配置、鉴权等模块。而且用 Golang 写的后端利用性能很高,通常有十分快的响应速度。笔者已经在开源爬虫治理平台 Crawlab 中用 Golang 重构了 Python 的后端 API,响应速度从之前的几百毫秒优化到了几十毫秒甚至是几毫秒,用实践证明 Go 语言在后端性能方面全面碾压动静语言。Go 语言中比拟出名的后端框架有 Gin、Beego、Echo、Iris。

当然,这里并不是说用 Golang 写后端就齐全是一个正确的抉择。笔者在工作中会用到 Java 和 C#,用了各自的支流框架(SpringBoot 和 .Net Core)之后,发现这两门传统 OOP 语言尽管语法啰嗦,但它们的语法个性很丰盛,特地是泛型,可能轻松应答一些逻辑简单、重复性高的业务需要。因而,笔者认为在思考用 Go 来编写后端 API 时候,能够提前调研一下 Java 或 C#,它们在写后端业务性能方面做得十分棒。

总结

本篇文章从 Go 语言的次要语法个性动手,循序渐进剖析了 Go 语言作为后端编程语言的长处和毛病,以及其在理论软件我的项目开发中的试用场景。笔者认为 Go 语言与其余语言的次要区别在于 语法简洁 人造反对并发 面向接口编程 错误处理 等方面,并且对各个语言个性在正反两方面进行了剖析。最初,笔者依据之前的剖析内容,得出了 Go 语言作为后端开发编程语言的实用场景,也就是 分布式应用 网络爬虫 以及 后端 API。当然,Go 语言的理论应用领域还不限于此。实际上,不少出名数据库都是用 Golang 开发的,例如时序数据库 Prometheus 和 InfluxDB、以及有 NewSQL 之称的 TiDB。此外,在机器学习方面,Go 语言也有肯定的劣势,只是目前来说,Google 因为 Swift 跟 TensorFlow 的动向单干,仿佛还没有大力推广 Go 在机器学习方面的利用,不过一些潜在的开源我的项目曾经涌现进去,例如 GoLearn、GoML、Gorgonia 等。

在了解 Go 语言的劣势和实用场景的同时,咱们必须意识到 Go 语言并不是全能的。它相较于其余一些支流框架来说也有一些毛病。开发者在筹备采纳 Go 作为理论工作开发语言的时候,须要全面理解其语言个性,从而做出最正当的技术选型。就像打网球一样,不仅须要把握正反手,还要会发球、低压球、截击球等技术动作,这样能力把网球打好。

社区

如果您对笔者的文章感兴趣,能够加笔者微信 tikazyq1 并注明 “ 码之道 ”,笔者会将你拉入 “ 码之道 ” 交换群。

退出移动版