几个礼拜之前我被问到:“对于Go语言,最令你惊讶的是什么?”当时我就明确地给出了答案:“虽然我希望C++程序员能够使用Go作为替代拼,但实际上大部分Go程序员都是从Python和Ruby转过来的,其中却少有C++程序员。” 我、Ken以及Robert都曾是C++程序员,在我们编写软件时觉得应该设计一门更适合解决这个问题的编程语言。奇怪的是,其他程序员似乎却不关心。 今天我将说说是什么让我们决定创造Go语言的,及其出乎意料的结果。这里我谈的更多的会是Go而不是C++,所以即使你不懂C++也没关系。 主旨可以简单地总结为:你更同意Less is more还是Less is less? 这里有一个真实的故事。贝尔实验室中心本来分配3位数字作代号:物理搜索是111,计算科学搜索是127,以及等等。20世纪80年代,随着对搜索的进一步理解,我们认为有必要新增数位以更好地特征化工作,于是我们的中心就成了1127。 Ron Hardin半开玩笑半正经地说如果我们真的更好地理解了世界,我们已经可以去掉一个数位,把127变成27。当然,管理人员并没有相信这个玩笑,当然Ron也没指望他们会相信,但我认为这很有哲理。有时候少意味着更多,你理解得越深,就能越干练。 请记住这个想法。 回想2007年9月的时候,我正在为Google庞大的C++程序做一些比较琐碎但是核心的工作(对,就是你们都用过的那个!),我工作在Google庞大的分布式编译集群上都需要花45分钟。之后有几个C++标准委员会的Google员工为我们做了个演讲,他们给我们介绍了下C++0x(现在被叫做C++11)里会有什么新东西。 在那一个小时的演讲中,我们大概听到了约35种计划中的新特性。当然实际上还有更多,但是在那次演讲中我们只听到了35种左右。有的特性比较小,当然演讲中所提到的任何一种特性都可以称得上标志性的:有的比较精妙,但是很难理解,像右值引用(RValue Reference);其它的很有C++的特色,例如variadic模板;还有一些只是一些相当疯狂的,一如用户定义文字(user-defined literal)。 这时候,我问了自己一个问题:C++委员会真的认为C++的特性还不够多?当然,不同于Ron的玩笑,简化这门语言必是一门更大的成就!也许这很可笑,但是请把这个想法记在心里。 在那个C++演讲的几个月之前,我做了一个演讲(你可以在YouTube上看到),是关于一个我在上世纪80年代开发的玩具性的并发性语言。那个语言叫作Newsqueak,也就是Go的前身。 我做那个演讲是因为我在Google工作的时候忘记了一些Newsqueak里的好想法,现在回顾一下。我确信它能简化服务器端代码编写,而且对Google也有好处。 实际上我也试过把这个想法加入C++,但是失败了。把并发操作和C++控制结构整合到一起非常困难,反过来也意味着即使侥幸成功收益也会非常小。 不过C++0x的演讲让我又重新思考了一遍。新的C++内存模型使用了原子类型,这让我很困扰,我认为Ken和Robert肯定也不喜欢。感觉把这样一个如此细节的功能加入已经超负载的类型系统里并不是一个明智的选择。这看起来也很目光短浅,因为硬件也可能在接下来的十年里发生明显的变化,所以把语言和当今的硬件捆绑地太紧不见得是一个正确的选择。 演讲结束后,我们又回到了办公室。我又开始了编译工作,我转过椅子好面向Robert,然后开始问一些关键性的问题。在编译结束之前,我们说服了Ken,决定一起做一些事情。我们再也不想用C++了,同时也很希望能在写Google的代码时能用到并行性特性。“大代码”问题也是一个需要关注的问题,但我们最终决定这个问题最后再解决。 然后我们找了块白板,在上面写下希望能有哪些功能。这里我们只关注大的方面,语法和语义学这些细节性的东西先放到一边去。 之后我们在邮件上交流,这里是内容摘选:
Robert:以C语言为原型,修补部分明显的缺陷,去掉垃圾功能,添加一些缺失的功能。Rob:命名为“Go”,好处有很多,这名字非常简短,容易拼写。工具可以叫做:goc、gol、goa。如果有可交互的调试器/解释器也可以简单地叫它“Go”,后缀名用 .go。 Robert:定义空接口语法:interface{}。所有接口都通过这个实现,因此可以用它代替void*。 |
我们没有一开始就设计出所有的功能,之后花了一年的时间才确定了array和slice功能,不过也有很多语言的特色在最初几天就已经确定下来的。 注意:Robert说要以C语言为原型,而不是C++!但实际上我们也并不是真的从C开始,只是从中借了部分内容,比如运算符、括号和几个相同的关键字。当然为了让它成为最适合我们的语言,我们从所有了解的语言里都借取了一些特性。 最后结果毫无疑问跟C或C++大不相同。以下是Go从C和C++简化的功能:
- 规范的语法(不需要符号表来解析)
- 垃圾回收(独有)
- 无头文件
- 明确的依赖
- 无循环依赖
- 常量只能是数字
- int和int32是两种类型
- 字母大小写设置可见性(letter case sets visibility)
- 任何类型(type)都有方法(不是类型)
- 没有子类型继承(不是子类)
- 包级别初始化以及明确的初始化顺序
- 文件被编译到一个包里
- 包package-level globals presented in any order
- 没有数值类型转换(常量起辅助作用)
- 接口隐式实现(没有“implement”声明)
- 嵌入(不会提升到超类)
- 方法按照函数声明(没有特别的位置要求)
- 方法即函数
- 接口只有方法(没有数据)
- 方法通过名字匹配(而非类型)
- 没有构造函数和析构函数
- postincrement(如++i)是状态,不是表达式
- 没有preincrement(i++)和predecrement
- 赋值不是表达式
- 明确赋值和函数调用中的计算顺序(没有“sequence point”)
- 没有指针运算
- 内存一直以零值初始化
- 局部变量取值合法
- 方法中没有“this”
- 分段的堆栈
- 没有静态和其它类型的注释
- 没有模板
- 没有异常
- 内建string、slice和map
- 数组边界检查
因为有这么多功能的简化,我相信Go比C和C++更有表现力。Less can be more! 但是也不能全部丢弃。你需要把想法分为模块,例如:类型如何工作、能够实际上良好运行的语法以及有助于保证类库交互的东西。 我们也加入了一下C和C++里没有的东西,比如slice和map、复合文本(composite literal)、顶层文件表达式(这是一个大问题,但经常被忽视)、映射(reflection)、垃圾回收等等,我们还非常自然地加入了并发性。 很明显还缺少了类型层次结构,这让我很生气。 在Go语言的首次启航中,我得到了一个“我不能在没有泛型的环境下工作”的反馈,这真是一个奇怪的言论。 平心而论,我觉得他可能是因为在C++里用惯了STL的原因。从字面意思来看,这就是说着写一个int list或者string map对他来说是一个非常大的负担。我不会在这种奇怪的问题上花太长的时间,所以这种需要泛型支持的也一样。 但更重要的是,只有类型是用来解决这样的问题的,既不是多态函数(polymorphic function)也不是语言原语(language primitive)或者其它类型的帮助,只有类型! 这就是卡住我的的细节。 从C++和Java转向Go的程序员很怀念使用类型编程的方式,特别是继承和子类这样的概念。也许关于“类型”我只是一个门外汉,但我真没发现模板有什么好处。 我已故的朋友Alain Fournier曾告诉我,他认为学术工作最基本的要求就是分工。你知道吗?类型层次也只是一种分类法。你需要决定什么东西封装在什么里面,每个对象的父类型是什么?到底是A继承B还是B继承A?可排序数组究竟是用来排序的数组还是一个通过数组实现的排序器?如果你坚信类型决定设计那这个就是必须理清的问题! 我认为这是一个对于编程的荒谬的思考方式。真正重要的是这些类能为你做什么而不是它们之间是什么关系! 当然这就是Go语言里接口的关系,不过这只是部分规则,Go语言的设计理念实际上比这还要复杂得多。 如果说C++和Java是关于类的层次和分类,那么Go的核心思想就是组合(composition)。 Doug McIlroy,Unix管道原理的最终发明者,在1964年写道: 当我们需要将数据以另一种方式整合到一起时,我们需要一些类似花园里软管的方法将程序耦合在一起,这也和IO的处理方式一样。 同样Go也是这样——Go取自这一思想,但是更进一步——它就是一个组合与联结的语言。 举个例子:接口提供了组合的方式,只要实现了方法M就可以直接放在那儿,无论它是什么都会很合适。 另一个例子是关于并发性如何给我们带来独立执行计算能力。 Go有一个非常不同寻常但是很简单的类型组成方式:嵌入。这些组合技术正是Go的独特之处,与C++或者Java程序截然不同。
虽然这与Go语言的设计不是很相关,但我还是想说:Go是为大型程序设计的,需要大的团队编写和维护。 大型程序设计,通常被认为是C++和Java的领域。我认为,这只是一个历史遗留问题,或许还是一个产业事故,但普遍被认为这和面向对象设计有关。 我不赞同这个观点,大软件需要确定的理念,但强依赖管理、整洁的接口、抽象化以及优秀的文档工具这些更为重要!不幸的是,这些没一个是C++的强项(不过这点Java明显就好得多)。 当然,现在还不能确定,因为用Go语言编写的程序还不够多,但我相信Go会成为一个非常好的大型程序编程语言。时间会证明一切!
现在,回到我们讲话的中心: 为什么Go从头到尾都是以C++为目标来设计的却无法吸引到C++程序员? Joke认为,这可能是因为Go和C++的设计理念相差甚大吧。 C++希望所有解决方案都能很容易得到/使用。我在C++11 FAQ(常见问题)上看到了这句话:
C++能够优美地、灵活地并且零成本地表现出抽象事物的能力,相比专门编写的代码要高效很多。 |
这种思考方式和Go的运转方式并不一样 。零成本不是目标,至少零CPU成本不是。Go的目标是解放程序员! Go并没有包罗万象,你不要期待它什么都有,你也无法精确地控制每一个细节。例如:Go没有RAII,但是你可以使用一个垃圾清理器做替代,虽然你甚至无法使用内存释放函数。 你可以从Go中得到很多易于理解但是强大的工具集来组合出问题的解决方案。它跟别的语言比起来也许没有那么快或者精致,但肯定写起来更简单、读起来更容易,也更易于理解,也许还更安全。 从另一个角度看,Go肯定更加简化: Python和Ruby程序员转向Go是因为他们不需要学更多的关心却可以获得更好的性能,甚至还可以使用并发特性。 C++程序员不愿意转向Go是因为他们竭尽全力只为了对自己的程序的完全掌控,并不希望这样改变。对于他们,软件并不仅是完成工作,而是做好工作。 现在的问题在于,如果Go成功了,会颠覆他们的世界观。 而且我们应该在一开始就注意到:对C++11新特性感兴趣的人是不会对一个功能如此简洁的语言感兴趣的,即使它能完成更多的任务。