Go语言精进之路:从新手到高手的编程思想、方法和技巧(1)
上QQ阅读APP看书,第一时间看更新

3.1 追求简单,少即是多

简单是一种伟大的美德,但我们需要更艰苦地努力才能实现它,并需要经过一个教育的过程才能去欣赏和领会它。但糟糕的是:复杂的东西似乎更有市场。

——Edsger Dijkstra,图灵奖得主

当我们问Gopher“你为什么喜欢Go语言”时,我们通常会得到很多答案,如图3-1所示。

029-1

图3-1 Gopher喜欢Go的部分原因

但在我们得到的众多答案中,排名靠前而又占据多数的总是“简单”(Simplicity),这与官方Go语言用户调查的结果[1]是一致的,见图3-2。(由于2016年之后的官方Go用户调查中不再设置“你最喜欢Go的原因是什么”这一调查项,因此这里引用的是2016年Go用户调查的结果。)

029-2

图3-2 2016年Go语言用户调查结果节选

不同于那些通过相互借鉴而不断增加新特性的主流编程语言(如C++、Java等),Go的设计者们在语言设计之初就拒绝走语言特性融合的道路,而选择了“做减法”,选择了“简单”,他们把复杂性留给了语言自身的设计和实现,留给了Go核心开发组自己,而将简单、易用和清晰留给了广大Gopher。因此,今天呈现在我们眼前的是这样的Go语言:

  • 简洁、常规的语法(不需要解析符号表),它仅有25个关键字;
  • 内置垃圾收集,降低开发人员内存管理的心智负担;
  • 没有头文件;
  • 显式依赖(package);
  • 没有循环依赖(package);
  • 常量只是数字;
  • 首字母大小写决定可见性;
  • 任何类型都可以拥有方法(没有类);
  • 没有子类型继承(没有子类);
  • 没有算术转换;
  • 接口是隐式的(无须implements声明);
  • 方法就是函数;
  • 接口只是方法集合(没有数据);
  • 方法仅按名称匹配(不是按类型);
  • 没有构造函数或析构函数;
  • n++和n--是语句,而不是表达式;
  • 没有++n和--n;
  • 赋值不是表达式;
  • 在赋值和函数调用中定义的求值顺序(无“序列点”概念);
  • 没有指针算术;
  • 内存总是初始化为零值;
  • 没有类型注解语法(如C++中的const、static等);
  • 没有模板/泛型;
  • 没有异常(exception);
  • 内置字符串、切片(slice)、map类型;
  • 内置数组边界检查;
  • 内置并发支持;

……

任何设计都存在权衡与折中。Go设计者选择的“简单”体现在,站在巨人肩膀上去除或优化在以往语言中已被证明体验不好或难于驾驭的语法元素和语言机制,并提出自己的一些创新性的设计,比如首字母大小写决定可见性,内存分配初始零值,内置以go关键字实现的并发支持等)。Go设计者推崇“最小方式”思维,即一件事情仅有一种方式或数量尽可能少的方式去完成,这大大减少了开发人员在选择路径方式及理解他人所选路径方式上的心智负担。

正如Go语言之父Rob Pike所说:“Go语言实际上是复杂的,但只是让大家感觉很简单。”这句话背后的深意就是“简单”选择的背后是Go语言自身实现层面的复杂性,而这种复杂性被Go语言的设计者“隐藏”起来了。比如并发是复杂的,但我们通过一个简单的关键字“go”就可以实现。这种简单其实是Go开发团队缜密设计和持续付出的结果。

此外,Go的简单哲学还体现在Go 1兼容性的提出。对于面对工程问题解决的开发人员来说,Go 1大大降低了工程层面语言版本升级所带来的消耗,让Go的工程实践变得格外简单。

Go 1兼容性说明摘录

Go 1定义了两件事:第一,语言的规范;第二,一组核心API的规范,即Go标准库的“标准包”。Go 1的发布包括两个编译器套件gc和gccgo,以及核心库本身。

符合Go 1规范的程序将在该规范的生命周期内得到正确编译和运行。也许在某个不确定的时间点会出现Go 2规范,但在那之前,在Go 1的未来版本(Go 1.1、Go 1.2等)下,今天能工作的Go程序仍应该继续正常工作。

兼容性体现在源码级别。版本之间无法保证已编译软件包的二进制兼容性。Go语言新版本发布后,源码需要使用新版本Go重新编译和链接。

API可能会增长,会增加新的包和功能,但不会破坏现有的Go 1代码。

从Go 1.0发布起至今,Go 1的兼容性得到很好的保障,当初使用Go 1.4编写的代码如今也可以顺利通过最新的Go 1.16版本的编译并正常运行起来。

正如前面引用的图灵奖得主Edsger Dijkstra的名言,这种创新性的简单设计并不是一开始就能得到程序员的理解的,但在真正使用Go之后,这种身处设计哲学层面的简单便延伸到Go语言编程应用的方方面面,持续影响着Go语言编程思维。

在Go演化进入关键阶段(走向Go 2)的今天,有人向Go开发团队提出过这样一个问题:Go后续演化的最大难点是什么?Go开发团队的一名核心成员回答道:“最大的难点是如何继续保持Go语言的简单。”