一只由Rob Pike的夫人Renee French设计的地鼠(见图1-2),从此地鼠成为世界各地Go程序员的象征。Go程序员也被昵称为Gopher(后文会直接使用Gopher指代Go语言开发者),Go语言官方技术大会被称为GopherCon。国内最负盛名的Go技术大会同样以Gopher命名,被称为GopherChina。
2018年8月25日,Go 1.11版本发布。Go 1.11是Russ Cox在GopherCon 2017大会上发表题为“Toward Go 2”的演讲之后的第一个Go版本,它与Go 1.5版本一样也是具有里程碑意义的版本,因为它引入了新的Go包管理机制:Go module。
简单是一种伟大的美德,但我们需要更艰苦地努力才能实现它,并需要经过一个教育的过程才能去欣赏和领会它。但糟糕的是:复杂的东西似乎更有市场。——Edsger Dijkstra,图灵奖得主
Go设计者推崇“最小方式”思维,即一件事情仅有一种方式或数量尽可能少的方式去完成,这大大减少了开发人员在选择路径方式及理解他人所选路径方式上的心智负担。
Go 1定义了两件事:第一,语言的规范;第二,一组核心API的规范
Go后续演化的最大难点是什么?Go开发团队的一名核心成员回答道:“最大的难点是如何继续保持Go语言的简单。”
Go语言遵从的设计哲学也是组合。
Go语言提供的最为直观的组合的语法元素是类型嵌入(type embedding)。通过类型嵌入,我们可以将已经实现的功能嵌入新类型中,以快速满足新类型的功能需求。这种方式有些类似经典OO语言中的继承机制,但在原理上与其完全不同,这是一种Go设计者们精心设计的语法糖。被嵌入的类型和新类型之间没有任何关系,甚至相互完全不知道对方的存在,更没有经典OO语言中的那种父类、子类的关系以及向上、向下转型(type casting)。在通过新类型实例调用方法时,方法的匹配取决于方法名字,而不是类型。这种组合方式,笔者称之为“垂直组合”,即通过类型嵌入,快速让一个新类型复用其他类型已经实现的能力,实现功能的垂直扩展。
从复杂程度来看,确实比继承简单许多
我们在标准库中还经常看到如下的interface类型嵌入的代码:[插图]通过在interface的定义中嵌入interface类型来实现接口行为的聚合,组成大接口,这种方式在标准库中尤为常用,并且已经成为Go语言的一种惯用法。
接口的继承
interface是Go语言中真正的“魔法”,是Go语言的一个创新设计,它只是方法集合,且与实现者之间的关系是隐式的,它让程序各个部分之间的耦合降至最低,同时是连接程序各个部分的“纽带”。隐式的interface实现会不经意间满足依赖抽象、里氏替换、接口隔离等设计原则,这在其他语言中是需要很刻意的设计谋划才能实现的,但在Go interface看来,一切却是自然而然的。
总说低耦合低耦合,一下子这么降低耦合,还有点不习惯,实现类和接口的关系太模糊了
并发是有关结构的,而并行是有关执行的。——Rob Pike(2012)
Go的设计者敏锐地把握了CPU向多核方向发展的这一趋势,在决定不再使用C++而去创建一门新语言的时候,果断将面向多核、原生内置并发支持作为新语言的设计原则之一。
Go果断放弃了传统的基于操作系统线程的并发模型,而采用了用户层轻量级线程或者说是类协程(coroutine),Go将之称为goroutine。goroutine占用的资源非常少,Go运行时默认为每个goroutine分配的栈空间仅2KB。goroutine调度的切换也不用陷入(trap)操作系统内核层完成,代价很低。因此,在一个Go程序中可以创建成千上万个并发的goroutine。所有的Go代码都在goroutine中执行,哪怕是Go的运行时代码也不例外。
不过,一个Go程序对于操作系统来说只是一个用户层程序。操作系统的眼中只有线程,它甚至不知道goroutine的存在。goroutine的调度全靠Go自己完成,实现Go程序内goroutine之间公平地竞争CPU资源的任务就落到了Go运行时头上。而将这些goroutine按照一定算法放到CPU上执行的程序就称为goroutine调度器(goroutine scheduler)。
由于goroutine的开销很小(相对线程),Go官方鼓励大家使用goroutine来充分利用多核资源。
难怪写go代码时,经常开协程
并发是有关结构的,它是一种将一个程序分解成多个小片段并且每个小片段都可以独立执行的程序设计方法;并发程序的小片段之间一般存在通信联系并且通过通信相互协作。并行是有关执行的,它表示同时进行一些计算任务。以上观点的重点是,并发是一种程序结构设计的方法,它使并行成为可能。
并发程序的结构设计不要局限于在单核情况下处理能力的高低,而要以在多核情况下充分提升多核利用率、获得性能的自然提升为最终目的。
并发与组合的哲学是一脉相承的,并发是一个更大的组合的概念,它在程序设计层面对程序进行拆解组合,再映射到程序执行层面:goroutine各自执行特定的工作,通过channel+select将goroutine组合连接起来。并发的存在鼓励程序员在程序设计时进行独立计算的分解,而对并发的原生支持让Go语言更适应现代计算环境。
经过调查发现,虽然Python的缩进结构在构建小规模程序时的确很方便,但是当代码库变得更大的时候,缩进式的结构非常容易出错。从工程的安全性和可靠性角度考虑,Go团队最终选择了大括号代码块结构。
包名不必是唯一的约定大大降低了开发人员给包起唯一名字的心智负担。
和目录无关,包名不能重复的话就太难受了
包名不必是唯一的约定大大降低了开发人员给包起唯一名字的心智负担。
Go被称为“自带电池”(battery-included)的编程语言。“自带电池”原指购买了电子设备后,在包装盒中包含了电池,电子设备可以开箱即用,无须再单独购买电池。如果说一门编程语言“自带电池”,则说明这门语言标准库功能丰富,多数功能无须依赖第三方包或库,Go语言恰是这类编程语言。由于诞生年代较晚,且目标较为明确,Go在标准库中提供了各类高质量且性能优良的功能包,其中的net/http、crypto/xx、encoding/xx等包充分迎合了云原生时代关于API/RPC Web服务的构建需求。Go开发者可以直接基于这些包实现满足生产要求的API服务,从而减轻对第三方包或库的依赖,降低工程代码依赖管理的复杂性,也降低开发人员学习第三方库的心智负担。
Go团队还在golang.org/x路径下提供了暂未放入标准库的扩展库/补充库供广大Gopher使用,包括text、net、crypto等。这些库的质量也是非常高的,标准库中部分包也将golang.org/x下的text、net和crypto包作为依赖包放在标准库的vendor目录中。
值得重点提及的是gofmt统一了Go语言的编码风格,在其他语言开发者还在为代码风格争论不休的时候,Go开发者可以更加专注于领域业务。同时,相同的代码风格让以往困扰开发者的代码阅读、理解和评审工作变得容易了很多,至少Go开发者再也不会有那种因代码风格的不同而产生的陌生感。
这点确实NB,官方统一风格,虽然降低自由度,但从带来的效果来看,是值得的
简单是Go语言贯穿语言设计和应用的主旨设计哲学。
德国建筑大师路德维希·密斯·凡德罗将“少即是多”这一哲学理念应用到建筑设计当中后取得了非凡的成功,而Go语言则是这一哲学在编程语言领域为数不多的践行者。“少”绝不是目的,“多”才是其内涵。Go在语言层面的简单让Go收获了不逊于C++/Java等的表现力的同时,还获得了更好的可读性、更高的开发效率等在软件工程领域更为重要的元素。
“高内聚、低耦合”是软件开发领域亘古不变的管理复杂性的准则。Go在语言设计层面也将这一准则发挥到极致。Go崇尚通过组合的方式将正交的语法元素组织在一起来形成应用程序骨架,接口就是在这一哲学下诞生的语言精华。
在java中,组合是优于继承的:一个类存在多个独立变化的维度,通过组合的方式,让多个维度可自由地扩展。维度可以理解为继承结构中的某一个节点。通过组合代替继承,避免了继承层次的指数级爆炸,JavaIO 就是一个例子。
不同于C、C++、Java等诞生于20世纪后段的面向单机的编程语言,Go语言是面向未来的。Go设计者对硬件发展趋势做出了敏锐且准确的判断——多核时代是未来主流趋势,于是将并发作为语言的“一等公民”,提供了内置于语言中的简单并发原语——go(goroutine)、channel和select,大幅降低了开发人员在云计算多核时代编写大规模并发网络服务程序时的心智负担。
自带电池,天生高并发。写go代码时,需要培养并发思维,毕竟并发的成本这么低,得好好利用。