Go 语言从诞生那一刻到今天已经有十年多了,Go 语言的魅力使得其在全世界范围内拥有了百万级的拥趸1。那究竟是什么让大量的开发人员学习 Go 或从其他语言转向 Go 语言呢?笔者认为 Go 魅力的根源就来自于 Go 语言的设计哲学。
关于 Go 语言的设计哲学,Go 语言之父们以及 Go 核心团队的开发者们并没有给出明确的官方说法。但在这里我将根据我个人对他们以及 Go 社区主流观点和代码行为的整理、分析和总结,列出四条 Go 语言的设计哲学。理解这些设计哲学将对读者形成 Go 原生编程思维、编写高质量 Go 代码起到积极的影响。
第一条原则: 追求简单,少即是多
简单是一种伟大的美德,但我们需要更艰苦地努力才能实现它,并需要经过一个教育的过程才能去欣赏和领会它。但糟糕的是:复杂的东西似乎更有市场。- 图灵奖获得者迪杰·斯特拉(1984)
通常当我们向 Gopher 们提出这样一个问题:“你为什么喜欢 Go 语言”后,我们会得到很多种答案,诸如:
- 编译速度快
- 执行速度快
- 单一二进制文件,部署简单
- 好棒的工具集
- 自带的标准库超级强大
- 内置并发
- interface 很棒
- 跨平台 Easy
- … …
但在我们得到的众多答案中:排名靠前而又占据多数的总是“Go 很简单”,这也和官方 Go 语言调查的结果是一致的。
和那些通过相互借鉴而不断增加新特性来吸引程序员眼球的主流编程语言相比,比如 C++、Java 等,Go 的设计者们在语言设计之初就选择拒绝走语言特性融合的道路,选择了“做减法”,选择了“简单”,他们把复杂性留给了语言自身的设计和实现,留给了 Go 核心开发组自身,而将简单、易用和清晰留给了广大 gopher 们。因此,今天呈现在我们在眼前的是这样一门 Go 语言:
- 简洁、常规的语法(不需要解析符号表),它仅有 25 个关键字
- 内置垃圾收集,降低开发人员内存管理的心智负担
- 没有头文件
- 显式依赖(package)
- 没有循环依赖(package)
- 常量只是数字
- 头母大小写决定可见性
- 任何类型都可以拥有方法(没有类)
- 没有子类型继承(没有子类)
- 没有算术转换
- 接口是隐式的(无需“implements”声明)
- 方法就是函数
- 接口只是方法集合(没有数据)
- 方法仅按名称匹配(不是按类型)
- 没有构造函数或析构函数
- n++和 n–是语句,而不是表达式
- 没有++n 和–n
- 赋值不是表达式
- 在赋值和函数调用中定义的求值顺序(无“序列点”概念)
- 没有指针算术
- 内存总是初始化为零值
- 没有 const 或其他类型注解语法
- 没有模板/泛型
- 没有异常(exception)
- 内置字符串、切片(slice)、字典(map)类型
- 内置数组边界检查
- 内置并发支持
- … …
任何的设计都存在着权衡与折中(tradeoff)。我们看到 Go 设计者选择的“简单”体现在站在巨人肩膀上去除或优化了以往语言中已被开发者证明为体验不好或难于驾驭的语法元素和语言机制,并提出了自己的一些创新性的设计(比如:头母大小写决定可见性、内存分配初始零值、内置以 go 关键字实现的并发支持等)。并且 Go 设计者推崇“最小方式”思维,即一个事情仅有一种方式或数量尽可能少的方式去完成,这大大减少了开发人员在路径方式选择上以及理解其他人所选择路径方式上的心智负担。
正如 Rob Pike 所说的那样:“Go 语言实际上是复杂的,但只是让大家感觉很简单”。这句话背后的深意就是“简单”选择的背后则是 Go 语言自身实现层面的复杂,而这些复杂性被 Go 语言的设计者“隐藏”起来了,就比如我们通过一个简单的关键字“go”就可以搞定并发,这种简单的背后其实是 Go 团队缜密设计和持续付出的结果。
此外,Go 的简单哲学还体现在 Go1 兼容性2的提出。对于面对工程问题解决的开发人员来说,Go1 大大降低了工程层面上语言版本升级所带来的消耗,让 Go 的工程实践变得格外简单。
Go1兼容性说明摘录
Go1定义了两件事:第一,语言的规范; 第二,一组核心API的规范,即Go标准库的“标准包”。Go1的发布包括两个编译器套件gc和gccgo,以及核心库本身。
编写符合Go1规范的程序将在该规范的生命周期内继续得到正确编译和运行。在某个不确定的点上,可能会出现Go2规范,但在那之前,在Go1的未来版本(Go 1.1,Go 1.2等)下,今天工作的Go程序仍应该继续正常工作。
兼容性体现在源码级别上。版本之间无法保证已编译软件包的二进制兼容性。Go语言新版本发布后,源码需要使用新版本Go重新编译和链接。
API可能会增长,增加新的包和功能,但不会以破坏现有Go1代码的方式进行。
从 Go 1.0 发布至今,Go1 兼容性始终被很好地遵守着,当初使用 Go 1.4 编写的代码如今也可以顺利地通过最新的 Go 1.14 版本的编译并正常运行起来。
就像前面引用的图灵奖获得者迪杰·斯特拉的名言所说的那样,这种创新性的“简单”设计并不是一开始就能得到程序员的理解的,但在真正使用 Go 之后,这种身处设计哲学层面的“简单”便延伸到 Go 语言编程应用的方方面面,持续影响着 Go 语言编程思维的形成。
在 Go 演化进入关键阶段(走向 Go23)的今天,有人曾经向 Go 核心团队提出这样一个问题:Go 后续演化的最大难点是什么?Go 的一名核心团队成员回答道:“最大的难题是如何能继续保持 Go 语言的简单”。
Russ Cox 《How Many Go Developers Are There?》https://research.swtch.com/gophercount ↩︎
Go1 兼容性 https://golang.org/doc/go1compat ↩︎