我为什么要把 Go 作为主力语言

2019-10-12

让我开始学习 Go 的有两个主要原因,一是我不求甚解浅尝辄止的「表面学习」热情最近又开始萌动,二是公司的业务需要,两者刚好结合,是一个学习新技术并提高自己的好契机。这不学不要紧,一展开对 Go 的了解,我便狠狠地喜欢上了这门语言(可能和一见钟情的感觉类似)。也出于此,我想写一篇博客来谈一谈我在与 Go 接触的第一印象中,到底是什么吸引住了我,以至于我想要把 Go 从今往后作为自己的主力语言。

在写下本文之前,我虽然对自己的技术栈没有什么明确的定位,但是非要挑一门我的主力语言的话,那应该是 Python。谈及原因,倒不是因为我对 Python 情有独钟(不过也不能排除这一层原因),但毕竟我写过的大多数项目都是围绕 Web 展开的 Python 开发,所以勉强能拿得出手的也只有这样了。

不过近些年来,有关注相关相关领域的同学可以看到许多公司都在用 Go 重构自己的代码,例如知乎,例如 Salesforce,不幸的是,故事中被重构的对象往往都是 Python,诚然动态语言的特性让快速开发成为可能,但凡是硬币都有两面,Everything costs,随着时间的流逝,Python 在大型项目上产生的性能开销已经成为一个不可忽视的问题,以及动态类型带来一些开发隐患在大型项目中变得更具风险,Python 也在尝试引入类似函数参数类型申明的语言特性,以及 mypy 这样的类型检查工具来规避这些风险,但毕竟这些都是一种后期的补偿,类似 patch 一样的在给语言的使用做加法,并未改变语言本身,以及 Python 还有形如多线程性能差这样较难解决的问题存在,遇到这些问题并被困扰的人们很难不会这么想:那么有没有一门既有动态语言的特性,又能在运行时有良好性能,甚至拥有很好的多线程表现的语言存在呢?结果我认为是肯定的,这门语言就是 Go。(Python 并不是不好,但任何事物都有缺点,希望各位 Python 的忠实拥簇能先坐下,把手里的砖放好)

如 Guillaume Le Stum 所言:

The reality of enterprise software is that you spend a lot more time reading code than writing it. We appreciated that Go makes the code easy to understand. In Python, you could write super elegant list comprehensions and beautiful code that’s almost mathematical. But if you didn’t write the code, then that elegance can come at the expense of readability.

所以第一个要提到的便是 Go 的语法设计非常简洁,一共只有 25 个关键字,虽然没有类的存在,但是 Go 通过 Interface 实现了抽象程序行为的特性,其思想有点类似多态的概念,使用起来也十分的灵活。

// io 标准库中对 Reader 和 Closer 的接口定义,代表了任意能读取 bytes 和关闭的类型
package io

type Reader interface {
    Read(p []byte) (n int, err error)
}
type Closer interface {
    Close() error
}

尽管 Go 是一门静态的强类型编译语言,但是 Go 也提供了一些类似动态语言的特性。例如使用类型推导来减少代码的工作量。(并不是偷懒)

// 显示的声明一个 int 变量
var a int
// 使用类型推导声明一个 int 变量
b := 0

通过切片来实现动态数组的一些特性,语法和 Python 有类似。

// 声明并初始化一个数组
months := [...]string{1: "January", /* ... */, 12: "December"}
// 创建两个切片 Slice
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2)     // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]

Go 语言还提供了一种名叫反射的机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。同时,反射也可以让我们将类型本身作为第一类的值类型处理。反射的存在增强了 Go 的表达力,例如标准库中的 fmt 包提供的字符串格式功能便是使用了反射机制,它可以用来对任意类型的值格式化并打印,当然,任意类型包括了程序员可能自己定义的类型。

Python 被广为诟病一点便是 GIL 全局解释器锁,尽管不能把所有的并发问题都归咎于它,但它的存在让程序无法利用起机器的多核优势,总归是让人有点不爽。Go 语言中的并发程序有两大法宝。即 goroutine 和 channel,其基于一种名为「顺序通信进程 Communicating Sequential Processes 」的现代并发编程模型而来,在这种编程模型中值会在不同的运行实例 goroutine 中通过 channel 来传递。

func main() {
    conn, err := net.Dial("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    done := make(chan struct{})
        // 使用 go 关键字和匿名函数快速开启一个 goroutine
    go func() {
        io.Copy(os.Stdout, conn)
        log.Println("done")
        done <- struct{}{} // 把结果传回主进程
    }()
    mustCopy(conn, os.Stdin)
    conn.Close()
    <-done // 主进程在收到值前会保持阻塞
}

最后需要再次强调,Go 是一门静态的强类型编译语言,这也注定了其性能和效率非 Python 这样的解释型语言所能比拟。Python 非常适合敏捷开发,即快速写出具有许多高级功能的程序,但并不总是能够提供大型项目所需的高性能。而 C 可以创建高性能的可执行文件,但是添加功能会花费更多时间。Go 被称为 21 世纪的 C 语言,不得不说其确实具有一定两全其美的特性。

我前几天在公司的日报里写了这么一段话:

Go 让我觉得它是一种「有着动态语言感觉的静态语言」。加上之前写 Python 的经历,我对 Go 有一种莫名的好感,设计和功能上既有足够的灵活性(channel、slice、struct、interface 以及大名鼎鼎的 goroutine),又有静态语言的安全感,不用担心类似动态语言那种防不胜防的各种类型错误,所以比起 Python 的 error 我更喜欢看到 Go 的 painc(当然,线上环境除外)。

这大致就是 Go 相较于 Python 给我的感受。虽然我也是才开始接触 Go,上文所提也仅是 Go 语言特性的冰山一角,还有许多诸如数据类型、包管理和方法等语言特性还未涉及,但我相信这些灵活好用的特性足以支撑起我成为 Go 拥簇的选择,希望 Go 能够日益完善的发展下去,我也能伴随着 Go 的进化一同成长成为一个合格的 Gopher。

Tagged in : Go Python