前言:设计哲学之与编程语言,就好比一个人的价值观和这个人的行为。
因为如果你不认同一个人的价值观,那你其实很难与之持续交往下去,即所谓道不同不相为谋。类似的,如果你不认同一门编程语言的设计哲学,那么大概率你在后续的语言学习中,就会遇到上面提到的这些问题,而且可能会让你失去继续学习的精神动力。
Go 语言的设计哲学分为五点:简单、显式、组合、并发和面向工程
在 Go 语言中看不到传统的面向对象的类、构造函数与继承,看不到结构化的异常处理,也看不到本属于函数编程范式的语法元素。其实,Go 语言也没它看起来那么简单,自身实现起来并不容易,但这些复杂性被 Go 语言的设计者们“隐藏”了,所以 Go 语法层面上呈现了这样的状态:
首先,执行如下代码:
package main
import "fmt"
func main() {
var a int16 = 5
var b int = 8
var c int64
c = a + b
fmt.Printf("%d\n", c)
}
得到类似这样的编译器错误:“invalid operation: a + b (mismatched types int16 and int)”
Go 不允许不同类型的整型变量进行混合计算,它同样也不会对其进行隐式的自动转换。
因此,如果要使这段代码通过编译,我们就需要对变量 a 和 b 进行显式转型,就像下面代码段中这样:
c = int64(a) + int64(b)
fmt.Printf("%d\n", c)
这是因为 Go 希望开发人员明确知道自己在做什么,你需要以显式的方式通过转型统一参与计算各个变量的类型。
此外,Go 设计者所崇尚的显式哲学还直接决定了 Go 语言错误处理的形态:Go 语言采用了显式的基于值比较的错误处理方案,函数 / 方法中的错误都会通过 return 语句显式地返回,并且通常调用者不能忽略对返回的错误的处理。
组合构建 Go 程序骨架的主要方式。
在 Go 语言设计层面,Go 设计者为开发者们提供了正交的语法元素,以供后续组合使用,包括:
在go中,无论是包、接口还是一个个具体的类型定义,Go 语言其实是为我们呈现了这样的一幅图景:一座座没有关联的“孤岛”,但每个岛内又都很精彩。那么现在摆在面前的工作,就是在这些孤岛之间以最适当的方式建立关联,并形成一个整体。而 Go 选择采用的组合方式,也是最主要的方式。
Go 语言为支撑组合的设计提供了类型嵌入(Type Embedding)。通过类型嵌入,我们可以将已经实现的功能嵌入到新类型中,以快速满足新类型的功能需求,这种方式有些类似经典面向对象语言中的“继承”机制,但在原理上却与面向对象中的继承完全不同,这是一种 Go 设计者们精心设计的“语法糖”。
垂直组合:被嵌入的类型和新类型两者之间没有任何关系,甚至相互完全不知道对方的存在,更没有经典面向对象语言中的那种父类、子类的关系,以及向上、向下转型(Type Casting)。通过新类型实例调用方法时,方法的匹配主要取决于方法名字,而不是类型。即通过类型嵌入,快速让一个新类型“复用”其他类型已经实现的能力,实现功能的垂直扩展
**水平组合:**和垂直组合的能力继承不同,水平组合是一种能力委托(Delegate),我们通常使用接口类型来实现水平组合
Go 敏锐地把握了 CPU 向多核方向发展这一趋势的结果,可以让开发人员在多核时代更容易写出充分利用系统资源、支持性能随 CPU 核数增加而自然提升的应用程序。
采用了用户层轻量级线程,Go 将之称为 goroutine。
goroutine 占用的资源非常小,Go 运行时默认为每个 goroutine 分配的栈空间仅 2KB。goroutine 调度的切换也不用陷入(trap)操作系统内核层完成,代价很低。因此,一个 Go 程序中可以创建成千上万个并发的 goroutine。而且,所有的 Go 代码都在 goroutine 中执行,哪怕是 go 运行时的代码也不例外
面向工程是 Go 语言在语言设计上的一个重大创新,它将语言要解决的问题域扩展到那些原本并不是由编程语言去解决的领域,从而覆盖了更多开发者在开发过程遇到的“痛点”,为开发者提供了更好的使用体验。
在面向工程设计哲学的驱使下,Go 在语法设计细节上做了精心的打磨。比如:
在标准库方面,Go 被称为“自带电池”的编程语言。如果说一门编程语言是“自带电池”,则说明这门语言标准库功能丰富,多数功能不需要依赖外部的第三方包或库,Go 语言恰恰就是这类编程语言。
在代码风格方面,使用gofmt统一语言的代码风格,提高了开发者效率,使开发者的代码阅读、理解和评审工作变得容易了很多。