前言
城外的人想进去,城里的人想进去。– 钱钟书《围城》
随着容器编排(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 并注明 “ 码之道 ”,笔者会将你拉入 “ 码之道 ” 交换群。