前言
城外的人想进去,城里的人想进去。-- 钱钟书《围城》
随着容器编排(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)。而且,对于公共和公有属性(变量和办法)的约定不再应用传统的 public
和 private
关键词,而是间接用属性变量首字母的大小写来辨别。上面一些例子能够帮忙读者了解这些特点。
// 定义一个 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 代码中除了容易看花眼的多层花括号以外,还充斥着大量的 public
、private
、static
、this
等润饰用的关键词,显得异样啰嗦;而 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/await
、Promise
语法,甚至是 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 并注明 "码之道",笔者会将你拉入 "码之道" 交换群。