本文视频地址
Go 语言从诞生到今天已经有十年多了,Go 语言的魅力使得其在全世界范围内拥有了百万级的支持用户。那究竟是什么让大量的开发人员学习 Go 或从其他语言转向 Go 语言呢?根源就在于 Go 语言的设计哲学。
关于 Go 语言的设计哲学,Go 语言之父们以及 Go 核心团队的开发者们并没有给出明确的官方说法。但在这里我将根据我个人对他们以及 Go 社区主流观点和代码行为的整理、分析和总结,列出三条 Go 语言的设计哲学。理解这些设计哲学将对读者形成 Go 原生编程思维、编写高质量 Go 代码起到积极的影响。
第一条原则: 简单,少即是多
通常当我们向 Go语言爱好者 们提出这样一个问题:“你为什么喜欢 Go 语言”后,我们会得到很多种答案,诸如:
- 编译速度快
- 执行速度快
- 单一二进制文件,部署简单
- 好棒的工具集
- 自带的标准库超级强大
- 内置并发
- interface 很棒
- 跨平台 Easy
- … …
排名靠前而又占据多数的答案是“Go 很简单”,这也和官方 Go 语言调查的结果是一致的。
和其他语言比如 C++、Java 等相比,通过不断增加新特性来吸引程序员的主流编程语言相比,Go 的设计者们在语言设计之初就选择拒绝走语言特性融合的这种道路,选择了“做减法”,选择了“简单”,让使用者用起来足够的--简单,他们把复杂性留给了语言自身的设计和实现,留给了 Go 核心开发组自身,而将简单、易用和清晰留给了广大 Go语言使用者们。因此,今天呈现在我们在眼前的是这样的Go 语言:
- 简洁--它仅有 25 个关键字
- 内置垃圾收集,降低开发人员内存管理的负担
- 没有头文件
- 显式依赖(package)
- 没有循环依赖(package)
- 常量只是数字
- 头字母大小写决定可见性
- 任何类型都可以拥有方法(没有类)
- 没有子类型继承(没有子类)
- 接口是隐式的(无需“implements”声明)
- 方法就是函数
- 接口只是方法集合(没有数据)
- 方法仅按名称匹配(不是按类型)
- 没有构造函数或析构函数
- 赋值不是表达式
- 在赋值和函数调用中定义的求值顺序(无“序列点”概念)
- 内存总是初始化为零值
- 没有 const 或其他类型注解语法
- 没有模板/泛型
- 没有异常(exception)
- 内置字符串、切片(sl典(map)类型
- 内置数组边界检查
- 内置并发支持
- … …
任何的设计都存在着利弊的妥协。我们看到 Go 设计者选择的“简单”体现在去除或优化了以往语言中已被开发者证明为体验不好或难于驾驭的语法元素和语言机制,并提出了自己的一些创新性的设计(比如:头母大小写决定可见性、内存分配初始零值、内置以 go 关键字实现的并发支持等)。
Go 设计者使用“最小原则”的方式,即一个事情仅有一种方式或数量尽可能少的方式去完成,这大大减少了开发人员在路径方式选择上以及理解其他人所选择路径方式上的心智负担。
Rob Pike 曾说:“Go 语言实际上是复杂的,但只是让大家感觉很简单”。这句话背后的深意就是“简单”选择的背后则是 Go 语言自身实现层面的复杂,而这些复杂性被 Go 语言的设计者“隐藏”起来了,就比如我们通过一个简单的关键字“go”就可以搞定并发,这种简单的背后其实是 Go 团队缜密设计和持续付出的结果。
此外,Go 的简单哲学还体现在 Go1 兼容性2的提出。对于面对工程问题解决的开发人员来说,Go1 大大降低了工程层面上语言版本升级所带来的消耗,让 Go 的工程实践变得格外简单。
Go1兼容性说明摘录
Go1定义了两件事:
第一,语言的规范;
第二,一组核心API的规范,即Go标准库的“标准库”。
API可能会增长,增加新的包和功能,但不会以破坏现有Go1代码的方式进行。
从 Go 1.0 发布至今,Go1 兼容性始终被很好地遵守着,当初使用 Go 1.4 编写的代码如今也可以顺利地通过最新的 Go 1.15 版本的编译并正常运行起来。
Go语言的这种创新性的“简单”设计并不是一开始就能得到程序员的理解的,但在真正使用 Go 之后,这种身处设计哲学层面的“简单”便延伸到 Go 语言编程应用的方方面面,持续影响着 Go 语言编程思维的形成。
有疑问加站长微信联系(非本文作者)