Go最全大红大紫的-Golang-真的是后端开发中的万能药吗,大牛手把手带你
Go最全大红大紫的 Golang 真的是后端开发中的万能药吗?,大牛手把手带你
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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#,它们在写后端业务功能方面做得非常棒。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
法特性很丰富,特别是泛型,能够轻松应对一些逻辑复杂、重复性高的业务需求。因此,笔者认为在考虑用 Go 来编写后端 API 时候,可以提前调研一下 Java 或 C#,它们在写后端业务功能方面做得非常棒。
[外链图片转存中…(img-fzT7asFr-1715509970959)]
[外链图片转存中…(img-2u6TUnZ7-1715509970959)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!