Golang 的主要特点
我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,可以更好的编写软件。结果发现只是对 Go 的模仿。缺乏语言层面的支持,只能是一个拙劣的模仿。 --云风
极简设计
Go 语言给人的第一感觉便是简洁。Go 语言通过减少关键字的数量(25 个,截止至发稿日期)来简化编码过程中的复杂度。这些关键字在编译过程中少到不需要符号表来协助解析,这也是 Go 语言的编译速度也是非常快的原因之一。极少的关键字,极简的语法都极大减少开发者编码的工作量,也提高了代码的可读性。
Go 语言的强类型系统禁止一切隐式类型转换,让代码更加容易阅读,减少犯错的机会。
defer 实现 RAII 也比 C++ 中通过对象生命周期和析构函数的实现方式更加容易理解和简洁明了。
file, err := os.Open("test.txt")
if err != nil {
panic(err)
}
defer file.Close()
Go 语言默认所有类型 zero 初始化,省去了很多无意义的初始化操作,也降低了开发者出错的几率。
Go 语言部署非常简单,编译出来一个静态可执行文件,除了 glibc 外没有其他外部依赖便可以直接运行。并且 Go 语言支持交叉编译,使用自带的工具 go build 可以直接将源代码编译成不同平台上的可执行程序。
比如,我们在 Mac 或者 Windows 上为 Linux 编译应用:
GOOS=linux GOARCH=amd64 go build main.go
只需要声明目标系统(GOOS)与 CPU 架构(GOARCH)即可。
Go 语言从设计上就坚持极简理念,并且极力给作者提供简单高效的开发体验。
开发效率与运行效率齐飞
“少即是多” 这就是 Go 语言贯穿始终的哲学。极简的语法,语言级别的并发管理,自动垃圾回收让开发者可以用最少的代码实现功能强大的程序。Go 语言没有隐式转换,没有构造函数和析构函数,没有运算符重载,没有继承...,极大的降低了开发者的心智负担。完善的类型系统让 Go 语言可以避免动态语言那种粗心的类型错误,同时又没有 C++ 那样繁杂的具体类型属性需要考量。Go 语言强制统一代码风格,减少了不少口水战,也让代码的可读性,可维护性更高了,这也是提高开发效率的关键。Go 语言致力于提供更少的语言特性,通过简洁的设计,减少代码出错的机会,让开发者更容易写出更高质量的代码。
Go 语言出现之前,各种语言在运行效率和开发效率上都不能兼备。Python 开发效率高,但是性能差强人意; C/C++ 运行效率毋庸置疑,但是开发效率略低。Go 语言运行效率高是因为 Go 语言是编译型的静态语言,它在执行速度上比解释型语言具有先天的优势,但是同时其简洁的语法又让开发者有种写动态语言的轻松感。Go 语言的运行效率直逼 C/C++ ,之所以稍逊于 C/C++ 主要还是因为 GC(自动垃圾回收机制),考虑到开发效率上的提升,这一点性能损失还是值得的。
强大的内置类型和标准库
Go 语言除了几乎所有语言都支持的简单内置类型(比如整型和浮点型等)外, 也内置了一些比较新的语言中内置的高级类型,比如数组、字符串、字典类型(map)。Go 语言的标准库覆盖网络、系统、加密、编码、图形等各个方面,可以直接使用标准库的 http 包进行 HTTP 协议的收发处理;网络库基于高性能的操作系统通信模型(Linux 的 epoll、Windows 的 IOCP);所有的加密、编码都内建支持,不需要再从第三方开发者处获取。
Go 语言标准库包名 | 功 能 |
---|---|
bufio | 带缓冲的 I/O 操作 |
bytes | 实现字节操作 |
container | 封装堆、列表和环形列表等容器 |
crypto | 加密算法 |
database | 数据库驱动和接口 |
debug | 各种调试文件格式访问及调试功能 |
encoding | 常见算法如 JSON、XML、Base64 等 |
flag | 命令行解析 |
fmt | 格式化操作 |
go | Go 语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改 |
html | HTML 转义及模板系统 |
image | 常见图形格式的访问及生成 |
io | 实现 I/O 原始访问接口及访问封装 |
math | 数学库 |
net | 网络库,支持 Socket、HTTP、邮件、RPC、SMTP 等 |
os | 操作系统平台不依赖平台操作封装 |
path | 兼容各操作系统的路径操作实用函数 |
plugin | Go 1.7 加入的插件系统。支持将代码编译为插件,按需加载 |
reflect | 语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值 |
regexp | 正则表达式封装 |
runtime | 运行时接口 |
sort | 排序接口 |
strings | 字符串转换、解析及实用函数 |
time | 时间接口 |
text | 文本模板及 Token 词法器 |
... | ... |
并发
并发编程可以充分发挥多核处理器的性能。在 C/C++ 中,可以通过编写多线程程序来实现并发,但是滥用线程会加重系统负担,所以更优的做法是使用通过 epoll 等方式来实现 IO 多路复用,以及使用各种协程库。除此之外,多个线程之间肯定还需要传递数据,可以通过 shared_ptr 来做,但是也需要小心翼翼,整个编码过程非常容易犯错。
goroutine 是 Go 语言并发设计的核心。goroutine 其实就是协程,比线程更轻量,是一种运行在用户态的用户线程。goroutine 并不是对应于内核线程,一个内核线程会调度若干个协程,goroutine 是在语言层面提供了调度器,并且对网络 IO 库进行了封装,屏蔽了复杂的细节,对外提供统一的语法关键字支持,简化了并发程序编写的成本。channel 是设计来在 goroutine 之间传递数据,channel 在实现原理上其实是一个阻塞的消息队列。在一个 goroutine 中将消息发送到 channel 中,然后在监听这个 channel 的 goroutine 处理,实现了不同 goroutine 的解耦。
接口设计
接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。
Go 语言的主要设计者之一 Rob Pike 曾经说过,如果只能选择一个 Go 语言的特性移植到其他语言中,他会选择接口。可见接口在 Go 语言中的地位,及其对 gloang 这门语言所带来的活力。
C++,Java 中使用侵入式接口,实现类需要明确声明自己实现了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程中一个争议颇多的特性。
Go 语言采用的是非侵入式接口,只要某类型的公开方法完全满足接口的要求,就可以把此类型的对象用在需要该接口的地方。满足接口的要求,即是指实现了接口所规定的一组成员(方法)。Go 语言的接口实现者无需指明实现了哪一个接口,编译器会去完成这项工作并发现错误。