实在是想不到啊,我的博客里面竟然还会出现如此高大上的文章——设计哲学。为什么会想写关于这方面的文章呢,主要还是在跟面试官闲聊的时候提到了这方面的问题。面试还是能学到不少东西的嘛,毕竟我的眼界还是太窄了。

在学习一门语言时,有没有考虑过这个语言的语法和格式为什么要这么设计?这个问题一听就是一个好的话题啊,为什么这么设计?为什么在已经有了那么多语言后,还要设计出Go语言?

那么好,闲话少说,这周的主要内容就改为Go语言的设计哲学。当然,文章内容并不是我原创的,更多的还是网上查到的资料。

背景

Go 编程语言构思于 2007 年底,构思的目的是:为了解决在 Google 开发软件基础设施时遇到的一些问题。

Go 语言之父

图中的三位大佬就是 Go 语言最初的设计者,从左到右依次为:

  • Robert Griesemer:参与过 Google V8 JavaScript 引擎和 Java HotSpot 虚拟机的研发。
  • Rob Pike:Unix 操作系统早期开发者之一,UTF-8 创始人之一,Go 语言吉祥物设计者是 Rob Pike 的媳妇。
  • Ken Thompson:图灵奖得主,Unix 操作系统早期开发者之一,UTF-8 创始人之一,C 语言(前身 B 语言)的设计者。

那么好,我们来解释第一个问题,为什么这几个大佬要设计 Go 语言。

遇到的问题

曾经在早期的采访中,Google 大佬们反馈感觉 “编程” 太麻烦了,他们很不喜欢 C++,对于现在工作所用的语言和环境感觉比较沮丧,充满着许多不怎么好用的特性。

具体遭遇到的问题。如下:

  • 软件复杂:多核处理器、网络系统、大规模计算集群和网络编程模型所带来的问题只能暂时绕开,没法正面解决。
  • 软件规模:软件规模也发生了变化,今天的服务器程序由数千万行代码组成,由数百甚至数千名程序员进行工作,而且每天都在更新(据闻 Go 就是在等编译的 45 分钟中想出来的)。
  • 编译耗时:在大型编译集群中,构建时间也延长到了几分钟,甚至几小时。

设计目的

为了解决上述的问题,如果在既有语言上进行修改,需要解决很多根本性的问题,因此,他们决定重新设计一个语言。

这门新语言需要符合以下需求:

  • 目的:设计和开发 Go 是为了使在这种环境下能够提高工作效率
  • 设计:在 Go 的设计上,除了比较知名的方面:如内置并发和垃圾收集。还考虑到:严格的依赖性管理,随着系统的发展,软件架构的适应性,以及跨越组件之间边界的健壮性。

这门新语言就是现在的 Go。

Go 在 Google

Google 整体的应用软件很庞大,硬件也很庞大,有数百万行的软件,服务器主要是 C++ 语言,其他部分则是大量的 Java 和 Python。

数以千计的工程师在代码上工作,在一个由所有软件组成的单一树的 “头 “ 上工作,所以每天都会对该树的所有层次进行重大改变。一个大型的定制设计的分布式构建系统使得这种规模的开发是可行的,但它仍然很大。当然,所有这些软件都在几十亿台机器上运行,这些机器被视为数量不多的独立、联网的计算集群。

简而言之,Google 的开发规模很大,速度可能是缓慢的,而且往往是笨拙的。但它是有效的。

Go 项目的目标是:消除 Google 软件开发的缓慢和笨拙,从而使这个过程更富有成效和可扩展。这门语言是由编写、阅读、调试和维护大型软件系统的人设计的,也是为他们设计的

因此 Go 的目的不是为了研究编程语言的设计,而是为了改善其设计者及其同事的工作环境。

解决痛点

Go 的诞生,更多是为了方便程序员进行编程,而不是为了某项科学研究,这就是 Go 语言与其他编程语言的最大的不同。

当 Go 发布时,有些人声称它缺少被认为是现代语言的必要条件的特定功能或方法。在缺乏这些设施的情况下,Go怎么可能有价值?

我们的答案是:Go 所拥有的特性可以解决那些使大规模软件开发变得困难的问题。

这些问题包括:

  • 构建速度缓慢。
  • 不受控制的依赖关系。
  • 每个程序员使用不同的语言子集。
  • 对程序的理解不透彻(代码可读性差,文档不全等)。
  • 工作的重复性。
  • 更新的成本。
  • 版本偏移(version skew)。
  • 编写自动工具的难度。
  • 跨语言的构建。

纯粹一门语言的单个功能并不能解决这些问题,我们需要对软件工程有一个更大的看法。因此在 Go 的设计中,我们试图把重点放在这些问题的解决方案上。

少即是多

相信对 Go 语言有过了解的人都会经常听到诸如:less is more、少即是多、大道至简、大道不停地至简等黑话。

那么少即是多这种观点是谁提起的呢,正是 Go 语言之父的 Rob Pike。

Rob Pike 在多个场合提到过类似 “少即是多” 的观点,该观点广为流传。这种设计理念体现在 Go 语言的语法设计上,它的语法非常简洁,没有复杂的继承和泛型,也没有异常处理,但这并不影响它的功能性和表达力。

以下内容多为 Rob Pike 在一次演讲时提到的内容,对此感兴趣的可以去阅读一下 Rob Pike 的 Less is exponentially more 演讲稿。

Go 特性清单

  • 规范的语法(无需用于解析的符号表)。
  • 垃圾收集(唯一)。
  • 没有头文件。
  • 明确依赖
  • 无循环依赖。
  • 常量只能为数字。
  • int 和 int32 是不同的类型。
  • 字母大小写设定可见性。
  • 任何类型都可以有方法(没有类)。
  • 没有子类型继承(没有子类)。
  • 包级别初始化和定义好的初始化顺序。
  • 文件编译到一个包中。
  • 包级别的全局表达与顺序无关。
  • 没有算术转换(常量做了辅助处理)。
  • 隐式的接口实现(无需“implements”定义)。
  • 嵌入(没有向父类的升级)。
  • 方法如同函数一样进行定义(没有特的别位置要求)。
  • 方法就是函数。
  • 接口仅仅包含方法(没有数据)。
  • 方法仅通过名字匹配(而不是通过类型)。
  • 没有构造或者析构方法。
  • 后自增和后自减是语句,而不是表达式。
  • 没有前自增或前自减。
  • 赋值不是表达式。
  • 按照赋值、函数调用定义时的顺序执行(没有“sequence point”)。
  • 没有指针运算。
  • 内存总是零值初始化。
  • 对局部变量取地址合法。
  • 方法没有“this”。
  • 分段的堆栈。
  • 没有静态或其他类型注解。
  • 没有模板。
  • 没有异常。
  • 内建 string、slice、map。
  • 数组边界检查。

除了这个简化清单和一些未提及的琐碎内容,我相信,Go 相比 C 或者 C++ 是更加有表达力的。少既是多。

无法想象没有泛型

当然明显缺少的是类型层次化。

在 Go 最初的版本中,有人告诉我他无法想像用一个没有泛型范型的语言来工作。就像之前在某些地方提到过的,我认为这绝对是神奇的评论。

公平的说,他可能正在用其自己的方式来表达非常喜欢 STL 在 C++ 中为他做的事情。在辩论的前提下,让我们先相信他的观点。

他说编写像 int 列表或 map string 这样的容器是一个无法忍受的负担。我觉得这是个神奇的观点。

即便是那些没有泛型范型的语言,我也只会花费很少的时间在这些问题上。

面向对象的方式

但是更重要的是,他说类型是放下这些负担的解决途径。类型。不是函数多态,不是语言基础,或者其他协助,仅仅用类型。

这就是卡住我的细节问题。

从 C++ 和 Java 转过来 Go 的程序员怀念工作在类型上的编程方式,尤其是继承和子类,以及所有相关的内容。可能对于类型来说,我是门外汉,不过我真得从未发现这个模型十分具有表达力。

我已故的朋友 Alain Fournier 有一次告诉我说他认为学术的最低级形式就是分类。那么你知道吗?类型层次化就是分类。

你必须对哪块进哪个盒子作出决策,包括每个类型的父级,不论是 A 继承自 B,还是 B 继承自 A。

一个可排序的数组是一个排序过的数组还是一个数组表达的排序器?如果你坚信所有问题都是由类型驱动设计的,那么你就必须作出决策。

我相信这样思考编程是荒谬可笑的。核心不是东西之间的祖宗关系,而是它们可以为你做什么。

当然,这就是接口进入 Go 的地方。但是它们已经是蓝图的一部分,那是真正的 Go 哲学。

如果说 C++ 和 Java 是关于类型继承和类型分类的,Go 就是关于组合的。

Unix pipe 的最终发明人 Doug McIlroy 在 1964 (!) 这样写到:

我们应当像连接花园里的龙头和软管一样,用某种方式一段一段的将消息数据连接起来。这同样是 IO 使用的办法。

这也是 Go 使用的办法。Go 用了这个主意,并且将其向前推进了一大步。这是一个关于组合与连接的语言。

一个显而易见的例子就是接口为我们提供的组合元件的方式。只要它实现了方法 M,就可以放在合适的地方,而不关心它到底是什么东西。

另一个重要的例子是并发如何连接独立运行的计算。并且也有一个不同寻常(却非常简单)的类型组合模式:嵌入。

这就是 Go 特有的组合技术,滋味与 C++ 或 Java 程序完全不同。

为什么 Go 不被 C++ 程序员喜欢

从上面的内容中,我们不难看出,Go 语言的设计者是十分不喜欢使用C++ 进行编程设计的。当然,在 Go 语言被设计并发布后,程序员没有了更多的选择。但是,相较于 C++ 程序员,其他语言的使用者好像更加喜欢 Go 语言。

为什么 Go,一个被设计为用于摧毁 C++ 的语言,并为并未获得 C++ 程序员的芳心?

这是因为 Go 和 C++ 有着完全不同的哲学。

C++ 是让你的指尖解决所有的问题

  • C++ 与那些巨大增长的特别编写的手工代码相比,具有更加广泛的抽象,优雅、灵活并且零成本的表达能力。

Go 的主张更多考虑的是最小化程序员的工作量

  • Go 不是无所不包的。你无法通过内建获得所有东西。你无法精确控制每个细微的执行。
  • 你得到的是功能强大,但是容易理解的,容易用来构建一些用于连接组合解决问题的模块
  • 这可能最终不像你使用其他语言编写的解决方案那么快,那么精致,在思想体系上那么明确,但它确实会更加容易编写,容易阅读,容易理解,容易维护,并且更加安全。

换句话说,当然,有些过于简单:

  • Python 和 Ruby 程序员:转到 Go 是因为他们并未放弃太多的表达能力,但是获得了性能,并且与并发共舞。
  • C++ 程序员:无法转到 Go 是因为他们经过艰辛的战斗才获得对其语言的精确控制能力,而且也不想放弃任何已经获得的东西。对于他们,软件不仅仅是关于让工作完成,而是关于用一个确定的方式完成。

语言环境

本段则是来自《The Go Programming Language and Environment》这篇演讲稿。

出身 Google 的 Go 语言严格意义上来说就是出身于名门望族了,那么十多年过去了,它发展地怎么样了?

Go 怎么样了

Rob Pike 表示其实 Go 目前还不能算做主流语言,但是在全世界的影响力和发展都大大的超出了预期。

img

像在国内的我们,能够很明显感知到,Go 在近 3~5 年的用户群体不断增大,一些大厂也已经开始转 Go ,甚至有的公司就是全部用 Go 语言来进行工作的。

Go 并不是那种非常 “有趣” 的语言,在技术上(语言理论、设计)几乎没有什么大进步。当然,这也不是 Go 核心团队的设计目标。

但就是这么一门语言,他主导了大部分 CNCF 中的项目,例如:K8s、Docker 等,特别牛。Go 是云基础设施的语言,这是怎么发生的

Go 为什么成功

Go 从一门无人问津的语言,到现在承担了各云基础设施的核心,变得很重要,也是一种成功实践。

Rob Pike 认为成功的因素有如下:

img

核心观点:一门编程语言的成功取决于其他很多方面,Go 语言是面向软件开发的,而不仅仅只是编程

为此,Go 就是为了解决软件开发而生,而非只是编程,这是成功的关键因素。

回答面试官的问题

在最后回答一下面试官的问题,不知道还有没有机会了,就在自己的博客里写一下吧。

问:一个语言在设计时将其语法结构设计成某种特殊的样子,如 Go 语言中花括号必须要和语句在同一行、Python 中使用缩进来替代花括号,这样的设计有什么原因吗?

答:无论结构怎么设计,对于机器来说都是没有区别的,无论是花括号还是缩进,能影响的就是有人。那么对于设计者来说,设计语言的一些特性主要还是根据他的思路以及设计哲学。

Python使用缩进替代花括号的原因有以下几种:

  • 缩进语法,更加优雅
  • 缩进语法,更加清晰
  • 使用缩进,保持一致性,避免造成误读
  • 使用缩进,代码更紧凑,便于浏览,没有累赘
  • 使用缩进,已足够令解释器执行,没必要使用多余的符号
  • 强制缩进,源自古老的 ABC 语言,Guido 是这门语言的设计者之一
  • 其思想可能出自 Don Knuth(高德纳,著名计算机科学家,经典巨著《计算机程序设计艺术》的作者),他在 1974 年提出,在当时是很时髦和前卫的思想
  • 使用缩进,可以终结大括号放在函数名后面还是再换一行的终极争论(据说此话题能令不同派系的程序员大打出手!)

从 Python 发布至今,关于缩进是不是其设计的败笔就一直被大家讨论,我只能说见仁见智。


对了,还有一个问题,关于我的博客界面左上角一直显示的加载中的字样,已经修复好了。

至于是怎么修好的,其实到现在我都不知道是哪里出了问题,也找不到类似的情况和解决方法,就只能把前端的美化“恢复出厂设置”了。

总结

至此,我们大致了解了 Go 语言的开发背景和开发理念。

当然本文中的大多数内容都是我看到别人的文章以及 Go 语言之父的演讲稿。

关于更加细节的开发和设计理念,我还在学习当中,后续会根据学习的情况更新文章,Go语言最为重要的并发还没开始,这个系列就肯定不会结束。

参考文章