为什么除了Go语言, 其他类C语言都是垃圾[翻译]

orangecat 发布于2年前 阅读28734次
0 条评论

英文原文:

原始翻译:


原文在这里:http://www.syntax-k.de/projekte/go-review。作者是 Jörg Walter,是个德国人。

根据 Rob Pike 在推上的说法,文章有一些错误,但看起来值得阅读。


简介

这是关于 Robert Griesemer,Rob Pike 和 Ken Thompson 在 Google 从 2007 年开发的 Go 语言的综述。现在,Ian Taylor,Russ Cox 和 Andrew Gerrand 已经加入了核心团队。这是一个类 C 语言,有一些动态脚本语言的特性,并且提供了一些关于并行和面向对象的新奇的(至少在通用语言领域)特性。它的目的是成为系统开发语言,这也是为什么这篇综述选择同其他类 C 语言,而不是脚本语言进行比较。

在编写这个综述的时候我发现,与其对 Go 进行评估,不如说它有更多值得详细解说的功能。Go 是完全不同的,不能用标准的 OO 背景知识去判断它。因此,这个综述可能更像是一个关于 Go 的介绍。我是一个 Go 新手。编写这个也帮助我去了解 Go 是什么,并且如何做,但务必记得我的第一个 Go 应用只开发了一半。我用过许多语言进行编码,因此将会用 Go 同它们做一些比较。

Go 很年轻。它在今年才被标识为稳定版本。通过这个综述,我同样期望能够对 Go 未来的方向的讨论做一些贡献,使其成为一个了不起的语言。 这包括了指出仍然存在的一些缺陷。

关于 C 系语言的咒骂

我总是对新的编程语言感兴趣。通常,我使用方便的动态语言,例如 JavaScript、Perl 或者更新一些的 Python。大多数情况下,我更喜欢可读的、可维护的、高开发效率超过单一的性能测试。过早的优化通常都不值得。安全也是一个方面,而脚本语言将你从缓冲区溢出、字符串格式化缺陷和其他底层攻击的世界中解救出来(假设无法利用运行时环境)。

但是这些语言也有负面问题。它们不够灵活,而我的工作从 ARM 系统上的 8-bit MCU 和智能手机到标准的桌面应用。我尝试在其上使用C(++),但那使我感到很痛苦。没有便利的字符串运算符、依赖外部库的笨重的正则表达式、手工内容管理,当然还有持续了 40 年的安全梦魇。除此之外它能很好的工作,提供了几乎能实现的最快的内存效率。

在底层语言中,缺失了一些基础的东西。用于编写底层工具和操作系统的东西,都相当古老,并且被现代系统环境所遗忘。这就是我这里所期望展示的。仅涵盖了 C 血统语言,就像人们习惯的那样, 但是你可以很容易的增加 Pascal、Modula、Oberon、Smalltalk、Lisp 以及其他任何在计算机系统中做过核心的语言。

C

我喜欢 C。确实是这样。它的简单使得其相当优美。除非你去做一些愚蠢的事情,例如用 C 作为主要 API 来编写复杂的 GUI 开发包。

一个的确非常好的事情是,你可以像用汇编那样进行控制,在特定场景中这确实是非常重要的。除了进行优化,绝对是。然后救是来自 C 的令人费解的语法的回击,例如不能对存放敏感数据的内存回收,或者在记录声明之间进行同步的障碍。这不可避免。如果有指针和运算,就需要语言中隐含的语义来保证优化不会变得更糟。

但是真正糟糕的是字符串、内存管理、数组等等……任何可能被利用的东西。并且难以使用。所以,C 可能是像其表现的那样轻量级的,它并不适合那些超过 10k 行代码的项目(译注:偏激了点,不是吗?)。

C++

C++ 改进了 C 的一些方面,但是带来了其他一些更糟糕的东西,尤其是无用的冗长的语法。你总是能找到那些束缚。不过对于标准应用开发它总是被优先选择的,一些不错的轻量的全功能 GUI 开发包很好的使用了这些优点。

但是当你尝试一些现代的动态语言功能(lambda 函数、map/reduce、类型推断……)时,通常都是这个路子“嘿,很酷!这个其实可以用 C++ 实现!你只需要这样做

dynamic_cast<MyData*>(funky_iterator<MyData &const*>(foo::iterator_type<MyData> (obj))

好了,就这样!”

别误导我,我钟爱模板,但是使用 STL 的 C++ 看起来就像一个标准的情况“如果你只有一把锤子”综合症。GCC 不得不实现特别的诊断和简化,最终发现其实长达 5 行的错误消息只是在使用 std::string 方法时,简单的常量化错误。同样糟糕的是,它们像地狱般的缓慢。那么等待 Boost 编译如何?好的主意,糟的现实。

Objective C

现在我觉得自己有点像异教徒。我不应该贬低任何来自 NeXT 的东西。但是,我忍不住——束缚感同样存在于 Objective C。它的冗长并不像 C++ 那样,但是括号语法就像进入来 C 的平行世界,这样 C 的全部语法都可用。

如果你对在 C++ 中编写模板转换不是那么印象深刻(这或许是个优点^^),并且仍然在使用手工方式管理内存。在 C++ 中至少可以用引用计数的方式释放内存(如果你曾经在数千错误的模板中找到语法正确的那个)。

Objective C 针对这个问题,跨越性的官方提供了可选的 GC。等等——这对于 C 系语言来说总是可选的。Boehm-GC 存在了多少年了?但是标准是采用内存池,在许多情况下很好的兼容性使得其能够工作,但是兼容性也就好成那样。

Java

你不会真以为在咒骂 C 系语言的时候我会忘记 Java,是吗?当前来说,Java 几乎总是解决方案。几乎得有点不真实。

有二进制夸平台能力,有着各种实现的、没有重大陷阱的语言细节的规格说明书,标准的 OO,垃圾回收,一些真正的动态特性,最终甚至是泛型——以及禁止消耗内存和拖慢启动时间。

其实没有明确的需求来解释为什么是这样。增强的 JIT 编译器(理论上)可以比其他任何预先进行优化的静态编译器做得更好。GCJ 编译器可处理机器码,如果你希望它这么做的话。VM 也有完全匹配的硬件实现。但是 JDK 使得基础臃肿,同时许多 Java 项目也有着拜占庭风格的复杂(译注:拜占庭风格总是绚丽且奢华的,但是似乎总有点华而不实,不是么?)。

现在,可以很舒适的编写现代的 Java 了。Web 服务提供了一些真正出色的抽象。直到你掀开帽子,然后发现一些小题大做的机制。每一层都用上年最流行的抽象层构建。你不能对向下兼容妥协,对吗?

看看你通常的 Web 应用的库目录。我不会对看到上百个 JAR 文件感到惊讶,对于用 PHP 实现的简单的数据库搜索或者购物网站,可以在 10k 行代码以内。或者你很有冒险精神,尝试自行编译。世界上最有乐趣的事情!设置一个 Linux 系统,没有任何 step-by-step 的介绍,这很容易。相信我,我都做过。在你开始之前,请务必确保你知道如何正向和反向拼写“dependency hell”(依赖的地狱)。

而所有这些都包含在繁琐的语法和古老的学校里的对象模型中。这基本上没有什么错误,但是有更好的办法。艺术品味是另一回事。看看 Perl6,尝试将所有现代语言设计放到一起,使其成为可用(真正有价值的“可用”)的语言。而其第一个功能生产可用的版本已经有十年以上了!Java 现在也快如此了,除了泛型以外。

C#.

我差点忘了这个。其实我确实忘了这个,直到有反馈提醒了我。坦白说,我不了解 C#。作为语言,它看起来不错,C 和 C++ 的很好的发展。让我对其保持距离的是它非自由的出身。是的,有 Mono,但是我从不喜欢让我的工作建立在一个微软随时可以转为专利诉讼的慈善性质许可的语言上。我们都知道关于这个公司(好吧,实际上是任何大公司)实施它奴役的诡计。

我看不到编写不能夸平台的代码的亮点,因此尝试 Mono 对于我来说是种危险,我总是远离它。同样,当 Java VM 的强势和弱势被广为流传的时候,CLR 想必也已经获得了足够多的声誉。我可能会在某个时间写 C# 代码,但这天不会很快到来。

没有开放的生态系统的促进,以及特殊目的的实现,它不适合作为系统编程语言。仅仅适合企业开发。

哦,还有企业期望从语言的开发中赚取金钱,这不是一个健康的发展途径。商业兴趣将会强加一些对于语言不好的东西。改进的压力长期存在,否则就卖不出去。作为对比,来看看 Tex,有着 30 年以上历史的,并且作为软件可能达到的无 bug 的状态。无法真正对比两者,不过可以展示哪里是终点,而 C# 站在了错误的地方。

JavaScript

JavaScript 似乎不应该出现在这儿,因为它是一个完全动态的语言。它是最广泛使用的语言之一,然而,它也是 C 系的。并且,更重要的是,JS 引擎这段时间支持了相当高级的 JIT 编译器优化,因此性能同这里提到的其他语言相差无几。

那么,JS 有什么本质错误呢?不多。只是它不适宜用在小型系统上。它是很棒的应用嵌入式语言,也适合编写网络服务,但是设计使得它没有办法同外部世界交互。主应用在实现容器中定义了所有的交互 API,因此决定了它无法作为一个系统的主语言。

更进一步,JIT 和运行时的需求局限了它的嵌入用途。如果有一个 Palm Pre,并使用 JS 作为嵌入语言,这是相当方便的。当 500MHz/256MB 的系统是底线时,是可用的。可能最低的使用 JS(或者 ECMAScript) 作为系统主要语言的设备,是 Chumby,在 450MHz/64MB 上播放 Adobe Flash Lite 影片。那里并没有真正的通用语言。

心愿

亲爱的圣诞老人,所有的底层语言都很烂。圣诞节我想要一个有以下特点的编程语言(用已有语言作为例子),排名不分先后:

一般设计原则

1. 表达能力

我期望有一个语言可以有足够高的层次用来表达我的想法和算法,而不是在乏味的任务管理或 80% 都是模板代码上浪费时间和屏幕空间。更重要的体现表达能力的元素被单独列出。编写词法分析器是一个好的测试。如果最终的结果代码与分析的构造有许多相似之处,这就是一个正确的方向。(例如:Perl 6 的语法)

2. 简单

我期望有一个语言有着优雅的简单。只需要几个概念就可以表达所有的可能。正交性是这个情况的关键。简单也使得语言容易学习。在相同的目的中重用语法结构,并且让用户代码与这些结构对接,这样就可以在其上进行扩充。同样的,不要强迫用户使用复杂的结构。提供类、单元测试框架或者文档注释没有错,但是如果用户不想要的话,不要让它们进入视线。 理想情况,让它们“Do what I mean”。

3. 平等权利

如果内建的联合数组算法对于特定的数据集是低效的,我期望可以建立一个能像内建那个一样使用的实现来代替。语言不应该有特权。因此,操作符应当可以被重载。噢,在 Python 里做得那些魔法般的事……同样,内建数据类型应当可以作为对象语法在语言中使用。所有这些无须动态,多数都可以用静态的语法糖来实现。(例如:Perl 的 prototyped subs,Python 的操作符重载)

4. 元编程

这里有两个方面。一个是模板以及/或者泛型,这太有用了,以至于不能没有。虽然 C 有一些邪恶的 hack 来模拟这个:预处理程序。每个人都需要这个。更加高级的方面是编译时执行代码生成代码,像 lisp 的宏和 Perl 的代码 filter。额外说明一下,这允许创建新的语言结构或者特定领域的语言。

5. 高效的和可预测的

如果语言瞄准的是系统编程,它必须相当高效并且完全可以被预测。必须能够直观的预测确定的操作带来了哪些时间消耗和内存分配。核心的语言功能必须很好的优化。库提供了更高层次的结构使得编码更有效率,而执行却低效一些。对象系统无须昂贵的运行时支持(不论是时间还是空间),静态优化应当是可能的。

理想情况,用几千行代码和几百字节内存编写一个有用的控制程序是可能的。当然,效率和功能通常作用相反,需要取舍,所以语言应当提供这样的机会让我来决定。

6. 可用性

最终,语言必须可用。所有想法放到一边,最重要的还是它解决实际问题。用一点实用主义不会有伤害。语言应当与其他语言接近,这样就可以用之前思考的模式去思考它。如果严重的偏离常规,那它就必须是值得的。忠于原则,而避免惊异!

7. 模块化的库和代码仓库

我期望在成长过程中使用过的脚本语言内建的或部分标准库的那种精巧。一个包的公共代码仓库和适当的可移植的包管理则更好。通常,包包含网络协议、常用格式的解析、GUI、加密、通用数学算法、数据处理等等。(例如:Perl 5 CPAN)

特别的功能

8. 数据结构

我要我的哈希!一个没有同语言良好集成的联合数组数据类型的语言是一个笑话。显而易见,OO(或者其他便利的封装)也是必须的。布尔类型很好,但是更重要的是各种类型在布尔上下文中的明确的解释。额外的,在标准库中有多种高级数据结构,例如不同的树、列表、集合等等。如果已经有了跳跃链表,那就是上轨道了。

让字符串也是 OO 的,尤其是相似的运算符(如 len())作用于字符串应当像作用在数组或模拟数组的对象上一样。附加的,还要看是否所有原始数据类型支持与其他所有对象相同的 OO 语法。无须像 Ruby 那样。只要有语法糖即可。(例如:Python 和它的标准库)

9. 控制结构

听上去显而易见,不过允许一次从多个嵌套的循环中跳出的控制是恰当的。消除 goto,真的。上一次我用到它是……好吧,就是上周。我在奥尔登堡计算机博物馆里的 Commodore C64 上做怀旧程序演示。这不算。所以丢掉 goto 吧,但是给我 foreach。除此之外别无所求。JavaScript 的 with 语句我从来没有用过,不过这也是个好主意,我想这是某种形式的“少即是多”。(例如:所有的脚本语言在这方面都很出色)

事实上,有一些东西在其他地方还从未见过。之前,我遇到过许多循环,每个循环的入口和条件测试/循环退出都不相邻。那么如果有一种方法来表达一个循环从中间某个位置开始,看起来会很酷。否则,就需要复制循环的一些部分。有点类似 Duff 的设备,不是为了优化,仅仅是让代码不要那么冗长。

10. 表达式语法

许多人刻意避开,但是 ?: 三元运算符是个好主意,如果使用正确的话。Python 实现了 foo if bar else baz,有点罗嗦,不过也还算 OK。然而,JS 和 Perl 的布尔运算符 AND 和 OR 不是用来计算 true 和 false 的,但是对于特定的值可以被认为是 true。假设赋值 value = cmdline_option || “default”。这需要在所有数据类型上有恰当的布尔含义。

11. 表达式的函数性质

如果我想要写 Lisp,我会这么做的。我并不需要一个完整的函数编程语言。但是没有什么比 map() 更好的了。闭包和匿名函数(lambda表达式)也是杀手级的功能。可能“超级复杂”领域是 hyper 运算符(在 Perl6 中是这么叫它们)如 any() 和 all() (在 Python 中是这个名字),但是它们都很棒,并且暗含着并行的含义。欢迎来到新世纪,至少是九十年代吧。

12. 对象

已经有若干面向对象的模型,但是至少需要封装和多态。有一些组合类的方法也同样存在,如继承和混合。接口应该有,或者用多重继承来代替(译注:多重继承?梦魇,绝对的梦魇!)重载是很重要的,或者至少给我默认参数。命名参数也是很酷的办法。

13. 并行

对于这种情况我是个失败者。generator 和协程在某些情况下适用于此。要点是不要内建的多线程,但是让多个代码方便的在一起工作。它们不需要同时执行,但是应当可以同时工作于数据集的多个部分。将应用构造成事件驱动应当比较容易。这一机制对于真正的多核来说,非常棒。(例如:Perl5 的 POE,python 的 generator)

14. 字符串和 Unicode

这是该死的 2011,除了 Unicode 什么都不需要。所以务必包含安全的字符串类型,并且所有都是 Unicode 的,不要有例外。我对额外的双编码的隐式转换感到恶心,也不想再见到,或着手工转换而不是语言支持。顺便说一下,我更喜欢 UTF-8。谁会在意常量字符串的索引呢?在这种特例下用字符数组吧。一般情况使用正则表达式。在原始字符串的 API 上要有正则表达式支持。

15. 底层接口

有观点认为,可能会需要手工操作 bit。特别当目标是微控制器或嵌入式 ARM 内核,应当有方法吓到裸设备去。理想情况是用语言直接编写 OS 内核,而无须任何汇编(除了平台特定的启动代码,它们没办法用其他办法完成)。

进入 Go 的世界

概述

在第一次听说 Google 的新编程语言时,我有一些怀疑。于是忽略了那条新闻。在那之后,下一代新的、伟大的语言就充满了各个地方。其中一些享受于璀璨夺目,然后就暗淡消沉;有一些走上了邪路;还有一些截止现在已经准备了十年的发布。

过了一段时间,我再次与它相遇。这回我凑近看了看。有一个我之前没有留意的事情:其中一个发明者是因 Unix 和 Plan9 而闻名遐迩的 Ken Thompson, 而他同样也间接参与了 C。那么如果新的编程语言是由曾经参与过大型主机时代的领军任务人物设计的,那么可能还是有料的。

因此 Go 到底可以给我什么是 Perl、Python 和 JavaScript 无法做到的?它能做什么是它们之前可以做到的?是什么使得它与其他失败或者有限的成功的语言不同的?它会在以后的 30 年里称雄吗?以及最重要的:它能满足我的需求吗?

第一次接触

Go 的一个重要事实是它面向的用户群。它是按照系统编程语言设计的,所以瞄准的是底层软件,甚至是 OS 的内核。因此,由于复杂度和与硬件结构的吻合度,高层次的构架可能会缺失。有趣的是,大部分便利却仍然存在。

接下来了解到的是,这是一个 C 血统的语言,因此使用 US 键盘布局让花括号容易按。但是当阅读例子代码时,它看起来并没有想象中的那么像 C 系的。更少的括号,看不到分号,几乎没有变量声明,至少第一看看上去没有。语法相当轻量,有着明显不同的保留字和控制结构,但是还是很好懂的。

与 C 不同的要点

对于那些熟悉 C/C++ 的人,来一起对那些不同做一个快速的对比:

没有分号!

不是玩笑!当然,实际上是有分号的,但是被省略了。这就像 JavaScript,有简单的规则让语法分析器在正确的行结束处添加分号。而这个规则相当简单,所以它很容易在源码处理工具中实现。

OTBS(译注:K&R样式)

接下来是“真正的邪端”:Go 定义了声明规范和 One True Bracing 样式(译注:就是 K&R 的代码样式)。而 RMS(译注:Richard Stallman)可能不会对此感到高兴。 以至于提供了 gofmt,一个用规范格式化代码的工具。当然,Java 开发者已经这么做了,当他们停留在一些导致脑瘫的情况下(缩进为2?SRSLY(译注:我真不知道这是什么,另一种代码格式?)?)。Go 代码的格式化可以归纳为:

  • 用 tab 缩进(这让用户可以按照他舒服的空格数量设置编辑器)
  • 花括号与其隶属的控制语句在同一行
  • 折行不能以反花括号或标识符结束,例如操作符留在上一行,而不是新行的开始。

简单的分号插入原则的结果其实导致了第三点。我希望能有办法绕过它,因为我喜欢连接的操作符在折行的开始,来强调发生了什么,而不是将其隐藏在一堆东西的后面(译注:开始我也觉得这点有点恶心,不过习惯了之后,就会先从后面看起了)。

不过除了这个,其他多数都还是非常明智的。许多人已经深入的解释了这个。要点是更少视觉干扰的可读性。就像 Python 那样,只是我觉得 Python 看起来过于缺少可视的提示。缩进并不总是足够清晰的,所以仍然保留心爱的花括号吧。

强制花括号

关于花括号,在 if 和 loop 上是没有无花括号的模式的,我认为这令人遗憾。代码样式纯粹论者可能会喜欢它。但是最终,我并不在意这个。对于确实短的语句,我可以这么做

if cur < min { min = cur }

双选项

前一个要点的重要的技术原因之一是控制语句可以接收双选项。只有 Perl 6 在没有花括号的情况下会尝试解析,而我们都知道 Perl 的语法解析器之前有多么复杂(显而易见,现在也是)。所以,这实际上是一个关于什么是强制的,什么不是的抉择。由于在多数情况下都需要用到花括号,所以这是非常明智的。这阅读起来不同寻常,必须适应有这样的划分,但是一旦习惯了它,Go 代码感觉比 C 代码更轻量一些。

明确命名类型、函数和变量

为了说明这个,需要用到保留字 type、func 和 var。这很清晰,会有一个很好的阅读“流”。技术上的原因,接着读吧。

隐式定义,自动设定类型

变量总是静态的指定类型,就像 C 那样。但看起来并不是这样。如果你不指定类型,类型代由赋值语句指定。利用新的定义和初始化操作符,甚至可以连同声明一起省略:

foo := Bar(1,2,3)

这定义了叫做 foo 的变量,并向其赋 Bar 类型的值。这个作用与

var foo Bar = Bar(1,2,3)

完全相同。

这并不是在介绍动态类型,这不允许改变已经定义了的变量的类型,也没有将变量定义的需要移除,这不允许将变量定义两次。这和之前完全一样,语义上,在语法上轻量很多。感觉像特定的脚本语言,但是仍然有静态类型的好处。

倒序的变量定义

在 Go 中,变量的类型和函数返回值的类型在名字之后,要用

var amount int

代替

int amount;

这补充了之前可以省略显示的类型定义的特色,提供了更加完整的概念。

没有运算的指针

仍然有指针,只是它们现在仅作为值的引用。所有的对象是值类型,因此赋值会复制整个对象。指针提供了在 Java 中作为默认的引用语义。但是没有指针运算。你被强制像数组这样的方式来访问,这为安全信任问题的解决带来了回旋的余地。是的,先生,我们都有边界检查!

因此,指针不再是算法的核心。它们仅服务于一个目的,引用 vs. 值的语义。这使得无法通过指针访问未引用的成员的实现变得简单。foo.Bar() 同时工作于指针和值的情况下。

垃圾回收

了之前的要点大量描述了是否能传递值以及所有的指针是否都是安全的概念,以及对于每个和所有变量来说是否能够获取地址,就像你在 C 和 C++ 中不能做的。

然后:内存管理是由垃圾回收处理的。最终!通过集成 boehm-gc 这平衡的类垃圾回收使得可以安全的传递一个指针到局部变量,或者获取一个临时变量的地址,这都将正常工作!嘢!

对那些没有与时俱进的研究垃圾回收的人来说,可能会不仅仅对 GC 解决了若干使用 malloc/free 的错误导致的安全性感兴趣,可能还关注 GC 是否足够快。一个适当的垃圾回收实际上可以比手工内存管理更快,通过恰当时间的延迟记录,或通过重用未使用的对象完全避免记录。一些更加高级的 GC 将这些联合使用,使得内存更加有效率,缓存使用内存减少碎片的产生。这不再是 C64 了。

现阶段 Go 的 GC 相当简单,但是一个更加高级的实现正在进行中。在大多数情况下,GC 是胜利者,但是它当然也有局限。在一些临界情况下,可以使用预分配对象的数组。

变长数组

这与那些在运行时就确定了大小,并不再变化的数组无关。这是那些你可能不得不使用 realloc() 的东西。数组总是有固定的大小,这是来自 GNU-扩展 C 的一个小的向下兼容。但是,作为代替,就有了 slice。

slice 看起来和感觉起来都像数组,但实际上它们只是映射到一个固定大小的原始数组的一个子范围。由于有垃圾回收,slice 可以引用到匿名数组。这是获得动态大小的数组的真相。有内建函数用于重新指定 slice 的大小,并且当底层数组过小时进行替换,因此在其上编写一个向量类也是很容易的。

反射

Go 支持反射,也就是说,可以查看任何类型,并获取其类型信息、结构、方法等等。Java 和 C++ RTTI 支持这个,但是 C 里面没有。

不定大小常量

常量可以不指定类型,甚至不指定大小。数字常量可以用在其数字是合法的任何上下文中,并且会使用相关的数据类型指定的精度。常量表达式用完整精度进行计算,然后当需要时截断到目标类型的精度。没有类似 C 中的常量大小的后缀。

错误处理

Go 没有异常。等等——是严肃的吗?这藐视了常识中接受的关于安全和稳定的程序的常规!这不是巨大的倒行逆施吗?

实际上不是。诚实的说,什么时候你很好的检查并处理了异常?大多数时间,它们是这样的:

try {
    openSomeFile();
    doSomeWorkOnTheData();
    yadda...
    yadda...
    yadda...
    closeItAgain();
} catch (IOException foo) {
    alert("Something failed, but there's not enough time to do proper error handling");
}

罗嗦,增加一个缩进级别而没有任何好处,也没有解决问题的根源,开发人员的懒惰。如果想要单独处理每一个调用的错误,如此罗嗦的代码使得更加不清晰。

因此总是可以在复杂的代码过程上回到学校中按部就班的错误处理。返回值使用了许多年了,但是 Go 有多值返回这个很好的现代功能。所以忘记会导致脑瘫的 atoi() 吧,我们有附带的标记。为了那些在乎的人们。为了那些不在乎的人们,那些即使 Java 尝试强制要求错误处理,也都不在乎的人们。

这里有 panic。它用于“可能无法继续”类型的错误。严重的初始化错误, 威胁正常的数据和运算的情况,诸如这类问题。无法恢复的错误,简单来说。语言的运行时环境也可能产生 panic,例如数组越界。

当然,这将我们带回到清理使用过的资源这个问题了。为了这个,defer 语句出现了,并且它相当优美,让错误处理属于它应在的地方,正确的对待问题:

handle, err := openSomeFile()
if err != nil { return nil, err }
defer closeSomeFile(handle)
return happilyDoWork()

defer 非常像 Java 中的 finally 分支,但它看起来更像修饰函数调用的。它确保了 closeSomeFile 被调用,无论函数是如何退出的。另一方面,当成功时也可以越过关闭它。很少的代码冗余,简单并且明了的错误处理。多个 defer 也是允许的,可以按照 LIFO 顺序正确执行。

对于那些在 panic 后想要继续的情况下,有 recover。panic 和 recover 一起就可以实现通常意义上的异常。由于它们会引起程序流程的混乱,官方建议非致命 panic 不应该越过包的边界。对于错误处理没有核心思想,所以两种情况最好都处理好,并且应当手工选择对于任务来说不复杂的那个方法。

控制结构

感谢 range 保留字使得仅有的 for 循环可以像 foreach 那样工作。在特殊情况下使用 for,这种语法使得更轻量(看上面),这让我感觉很好。或者更好的:可以将标签放在嵌套的循环中,通过它们可以一次跳出多层。完美了!

同样可以用 goto 拿自己来打靶。好吧,开个玩笑,那些邪恶的东西只是简单禁止。但是如果谁愿意,为了一些简单的理由也可以这么做。

这些仅仅是一些概览。还有一些细节,等会会涉及到,但是所有一切对于 C 来说都是重大的改进。在许多个快乐的夜晚编写没有对象的过程代码就已经足够了,不是吗?

扩展

Go 真正强大的在于到现在为止,那些无法在 C、C++或者其他上面提到的任何语言中找到对应的地方。这些才是真正让 Go 光彩夺目的:

基于类型的对象 vs. 封装

没有类。类型和类型上的方法相互并无依赖。可以在任何类型上定义方法,也可以定义任何类型为一个新的类型,这和 C 中的 typedef 相似。与 C 或 C++ 的不同之处在于新的命名的类型有其自己的方法集合,而主要类型(那些作为基础的)的也会有方法集合:

type Handle int64

func (this Handle) String() string {
    return "This is a handle object that cannot be represented as String."
}

var global Handle

在这个例子中,global.String() 可以被调用。更进一步,我们获得了一个没有虚方法的对象系统。这没有任何运行时的麻烦,只是语法糖而已。

鸭子类型(译注:参考维基百科) vs. 多态

类型定义不能让两个独立的类型看起来相似。独立的类型就是独立的类型,在类型严格的语言中不允许创建一个通用类型实现多态。在大多数语言中有一个很流行的例子,就是值转换表达它的字符串的规则。Handle 类型用 Go 的规则定义了这样一个方法,但是没有展示如何让任意的类型都有这样一个 String 方法。

C++ 使用接口(可能利用虚基类)并且重载运算符来实现这个。Java 的 toString 是根类的一部分,因此会被继承,而其他调用规则都是根据接口来表达的。

Go 使用接口有些特别。不像 Java,它无须定义给定的类型匹配某个接口。如果可行,那么就自动作为那个接口:

type Stringer interface {
    String() string
}

这就是所有需要的。自动的,Handle 对象现在可以作为 Stringer 对象使用。如果它走起来像鸭子,叫起来像鸭子,并且看起来像鸭子,那么从任何实际用途出发,它就是鸭子。现在最棒的部分:它可以动态的工作。无须导入,甚至开发者无须知道接口的定义。

当类型作为接口使用时,运行时环境为了得到接口的运行时的反射的能力,构建了一个函数指针的表格。这样就会有一些运行时的开销。然而这进行了优化,所以只有很小的损失。接口表格只在真正使用的时候才进行计算,对于每个类型来说只计算一次。如果编译时能够确定实际的类型,就完全避免在运行时处理。方法调度应当比 Apple(已经相当酷了)的 Objective C 的调度略快。

类型嵌入 vs. 继承

类型的作用跟类类似,但是由于没有继承的层级,它们实际上是不同的。在之前的例子中,Handle 并没有从 int64 中继承任何方法。可以通过在声明体中包含基础数据类型,来定义一个类型结构实现类似继承的东西:(无耻的从“Effective Go”中窃取的例子)

type ReadWriter struct {
    *bufio.Reader    
    *bufio.Writer
}

bufio.Readerbufio.Writer 有的所有方法这个类型都有。冲突用一个很简单的规则解决。毕竟这不是多重继承!每个基础类型都作为联合类型中的一个独立的数据对象存在,而来自子类型的每个方法只能看见它所拥有的对象。这个方法,就可获得良好实用的行为,而不存在类的多重继承导致的那些麻烦。抛开所有概念——这多多少少又是一个语法糖,无须运行时开销就让代码更加有表现力。

这同样工作于接口。联合类型匹配全部组成类型匹配的接口。解决引用的方法歧义的规则非常简单,而无法预测的情况被简单的禁止了。联合类型可以自由的按照需要重写组成的方法。

可见控制

开发中的主要单元是包。一个或者多个文件实现一个包,并且可以控制从包外访问的可见情况。这里没有复杂的可见系统,仅仅是可见或不可见。这是由排版规则控制的:公共名字由大写字母开始,私有名字用小写字母。这对于所有命名都有效。

这是个相当务实的方案。不像类型系统,这在竞争中相当有力,这里 Go 占领了中土:多数脚本语言完全不关心可见性或者依赖自觉规定,而学校式的 C 系语言有访问控制的细节。同样,这对于 Go 的对象模型也是一件好事。由于没有类继承并且嵌入对象是完全结合的,就没有需要有访问保护规则。下面会看到这是如何在实践中工作的。

没有构造函数

对象系统没有特别的构造函数。这里有一个零值的概念,例如用零初始化类型的所有字段。这要求在编写代码时,零值对于合法的“空”对象处理是有意义的。如果无法做到,就提供作为包函数的构造函数。

Goroutine 和 Channel

对于这种通用编程语言来说,这是最不寻常的功能。Goroutine 可以认为是极为轻量的一种线程。Go 运行时将这些映射为 pth 形式的多任务协作伪线程或真正的操作系统线程,前者拥有较低的开销,后者通过线程得到了非阻塞的行为,因此得到了两者的最优。

Channel 是有类型的缓存或无缓存的消息队列。一个简单的实现,真的。从一边装填入对象,从另一边取出来。你不得不在并行算法中做的许多事情都不再需要了。

Go 让 Goroutine 作为一等公民是一大进步,许多算法都可以用其作为核心。同段的栈使得每个线程的最小栈使用都比较低,通过模拟线程得到性能上的优势,除非使用阻塞的系统调用,也就是仅比普通函数调用的开销略多一点。

综上所述,Go 有真正的终极武器,Goroutine 可以用在许多地方,而不是标准的并发算法。Python 的 generator 函数可以作为模型,或者一个自定义的自由对象内存管理表。参阅在线文档,如此简单的并发实现是一件神奇的事情。

顺便说一下,通过设置环境变量,你可以告诉 Go 运行时希望使用多少个 CPU,这样 Goroutine 将会在开始的时候映射若干个原生线程(默认如果没有阻塞的系统调用,就不开启任何其他线程)。

缺陷

没有系统是完美的。Go 有一些缺陷,这里的列表列出了现在我遇到的:

二进制大小/运行时依赖

基本的 Go 二进制是静态链接,如果编译不包括调试信息,大约 750k。这和类似的 C 程序大小相同。我已经用 Go 主页上的“比较树”例子进行了测试,将其同类似结构的我的 C 实现进行了比较。

gccgo 可以编译动态链接的执行文件,但是 libc 在所有系统上,并且通常不需要考虑依赖,而 libgo 有大约 8MB 的额外的包。作为比较:libstdc++ 小于 1 MB,libc 小于 2MB。公平的说,它们相比 Go 的标准库要少做很多工作。然而,还是有很大差异以及有依赖问题。

6g/8g,原生的 Go 编译器,生成类似的执行文件,但是并不依赖 libc,它们是真正独立的。却也无法实现运行时的动态连接。

这也同样涉及小系统。在我的旁边是古老的 16MB 奔腾-100 笔记本,运行着 X 和 JWM 桌面,正愉快的播放我的音乐收藏。它甚至有 5MB 的内存用于磁盘缓冲。有可能用 Go 编写这样的系统吗?

不公平的权利

这个语言在许多方面存在特权。例如,特别的 make() 函数所做的工作,不能用用户的代码对其进行扩展。这在开始并不像看起来的那么糟糕,例如可以写一些与 make() 作用相同的代码,仅仅是没有办法对这个语言的构造进行加强。同样的问题在其他可能需要扩展的调用和保留字上都存在,例如 range。你或多或少被强制使用 goroutine 和 channel 扩展一个高级的出来。

我不确定这真得是个问题。假设 map、slice、goroutine 和 channel 是可选的实现,这个限制带来的冲击就不存在了。它并不损害代码的清晰程度和可读性,但是如果用过“可以模仿任何东西”的语言如 Perl 或者 Python,就会感觉有点不公平。

没有重载

重载是许多语义歧义的根源。有很好的理由让它滚蛋。但是,同时重载,尤其是运算符重载,是如此方便和可读的,因此我很想念它。Go 没有自动的类型转换,因此事情不会像在 C++ 中那样令人毛骨悚然。

作为例子,重载可能用于的地方,设想大数库、(数字)向量、矩阵,或者有限范文的数据类型。当处理夸平台数据交换,可以对特殊数据类型修改数学语义,会是一个巨大的胜利。对于处理古董机的数据,可以有补充的数字类型,例如,用完全模拟目标平台的运算,来代替依赖当前平台语义上的相同。

有限的鸭子类型

不幸的是,鸭子类型是不完全的。设想一个像这样的接口:

type Arithmetic interface {
    Add(other int) Arithmetic
}

函数 Add 的参数和返回值将会限制自动的类型化。一个有方法 func (this MyObj) Add(other int) MyObj 的对象不匹配 Arithmetic。有许多类似这样的例子,而它们中的一部分很难决定到底应该用鸭子类型覆盖它们,或者当前的规则更好。你可能落入许多不太明显的问题中,因此这是个“保持简单可能会更好”的例子,但我总是觉得不够方便。

Russ Cox,Go 核心的作者之一,说明:

这不能工作的原因是 MyOjb 的内存布局与 Arithmetic 的内存布局不同。即便是内存布局吻合的> 其他语言也在纠结于此。Go 只是说了“不”。

我猜测需要定义 func (this MyObj) Add(other int) Arithmetic 来代替。妥协的方案带来的好处是编译器和生成的机器码更加简单。

指针 vs. 值

我不确定对这个指针/值的事情到底高兴不高兴。Java 的所有都是引用的语义更简单。C++ 引用 vs. 值的语法同样也是不错的。一个可能的方面是你获得了更多对内存布局和使用结构的控制,特别是当包含其他结构时,而值 vs. 引用语义在函数调用的时候很清晰,C++ 却难以预料。

顺便说一下,map 和 slice 是引用类型。在最初的时候,我对它们感到烦恼,但是你可以构造有着类似行为的自己的对象:包含(私有)指针的结构体,这或多或少是 map 和 slice 做的事情。现在只剩下如果有办法钩挂到其 […] 语法中去的话……

逻辑上下文(译注:三元运算符,还记得吗?)

逻辑上下文提供了许多简化代码的可能。不幸的是,虽然 !pointer 还是很清晰的,但指针仍然必须同 nil 相比较。更进一步说,从现在起不再有指针运算。参考上面的期望的第十条。在让代码工整同时有更短。

给每个类型一个零值的观念,这微小的弥补了逻辑上下文的不足。

缺失的东西

有一些东西没有进入 Go 中,而我确实很想念它们。我希望社区能找到将那些东西加入进去的办法,当然得保持 Go 轻量的形式。我期望看到许多不怎么重要的功能,而有些仅仅是愿望,只有一个是我真正想要的,那就是元编程。

这不是在讨论泛型和模板。接口或多或少可以代替这些,就像上面已经提到的那样,现在来说鸭子类型是不完全的替代。

现在讨论的是真正的代码生成代码的那类。设想一种可能,实现一个特定领域语言。如果用 SQL,无论如何 SQL 将会成为代码的一部分。SQL 包可以提供一种编写 SQL 的解决方案,编译时语法和类型检查。这可能会增加编译时间,但是我宁可选择对我所有的代码进行静态检查,而不是在运行时发现 SQL 中有语法错误(还有每次调用额外增加的解析时间,而不是一次的)。

这种灵活的机制同样可以让语言得到改进,让试验性的功能及早得以实现,在考虑将其加入语言核心之前就已经稳定了。当然,得是一个良好设计的实现。没有人想重现 C/C++ 的悲剧。整合在一起,Go 可以获得强大的表达能力。

操作符/方法重载也可能提供类似的便利,因此算上这些。有特别命名方法的 Python 模型作为例子,它有恰当的语义并解决实际问题。

不幸的是,这些已经一次又一次的讨论过了,对于像我这样的新人,已经有太多没有结果的讨论,从而无法看到如何解决。引用其中一个:

欢迎反馈。

搜索列表中关于这些话题的内容,每个话题都能找到 1000 封以上的邮件。

这是麻烦。如果一个确定的话题已经讨论了如此多的次数,为什么官方文档里没有关于这个的任何内容?像这样的争论会阻止潜在的贡献者。同时,已经参与讨论的人也会疲于回复。

最好是社区有一个提议流程(像 Python、Java、Perl6、XMPP 等等那样)可以跟踪这些建议,标识出某个提议需要满足的需求,并认真考虑,同时汇总当前的开发状态。这样,想法就可以量化,可以通过合理的争论予以拒绝,可以用更好的想法来代替,最终使其进入语言而不用向设计目标妥协。从良好的拒绝文档中,潜在的贡献者可以学习到需要避免什么、不期望什么,以及需要做什么。

这不需要民主。对于项目管理来说乱哄哄的做法和独裁的做法成功的一样多,而失败的也一样多。最重要的是有这么一个流程,并且是透明和容易理解的。

总结来说,别落下坏印象。社区不应当是固执与傲慢的。他们会听取意见。这与另一个人在邮件列表上说的类似:

当在添加功能的时候加了麻烦,就永远不能把它拿出来了。

我完全同意这点。我需要元编程,它是如此不可思议的灵活,而它必须在 Go 强大力量控制之中。而不是像不够格的 PHP 做的那样。

开放问题

这些问题现在对于我来说有些不清晰。当我找到问题的答案时,我会更新这个综述。其中大多数是假设,其他是基于兴趣。自己判断。斜体内容是我自己对获得的答案的总结,除非有必要,没有任何引用内容。

  • 内联能否工作?夸包的内联是如何工作的?


    还不行,但是已经在处理编译器了。夸包的内联容易实现,因此 C++ 的“头文件中的代码”的混乱可以终结了。
    我估计 6g/8g 会支持这个。不清除 gccgo 是否也能够有夸包的内联。

  • 让 Go 支持 8 位可行吗?C 可以灵活扩展到 8 位微处理器。最低的 Go(受限的) 运行时硬件环境是怎样的?


    当前编译器不支持。32 位是最低要求。
    我希望有人能在什么时候将 Go 移植到更低要求的机器上。这可能要花点时间,C64 也不是一夜之间就有了 C 支持。_

  • Go 目标文件是否可以在运行时加载?在这些文件中的类型实现能否无缝的与宿主应用整合?


    还不行,但没有任何根本原因表示为什么不行。这些可能已经在线路图上了。

  • 那么优化的限制呢?例如 gccgo,它是否有着与编译 C++ 一致的优化级别,或者还有什么重要的步骤没有支持?


    对于 6g/8g,作为编译器的标准实现,优化器还有许多缺陷,但是已经在改进中。gccgo 没有任何相关信息。
    这其实很好。编译器不是最大的缺陷,但是 Go 的性能仍然很好。Go 的设计使得容易进行静态优化,这也是为什么有意限制功能的原因。看起来,应该会提供这样的功能。

  • 可能检测作为一个众所周知的安全问题的整数溢出吗?


    通过运算符重载,我猜测实现一个保护数据类型的会比较容易。但是,这可能值得作为语言的核心出现,作为一个能够接受的性能下的实现。C# 已经做到了这点。
    还不是时候。Go 明确的定义整数运算是被环绕的(译注:0xffffffff + 1 = ?)。C 是没有这样的定义,因此 C 编译器如果想的话可以做边界检测,而 Go 编译器不能。
    这意味着语言规格定义为了适应这点可能在某些情况下会发生改变。在我看来,默认有保护的整数是值得的,而环绕的整数则作为可选的数学技巧。

  • 接口的内存开销有多少?结构的呢?数组的呢?


    简单来说,就是最理想的情况。对于每个类型都有类型信息,一个接口值需要两个指针,除此之外没有任何开销。

  • 接口方法调度的时候,CPU 时的开销有多少?创建 goroutine 呢?调度 goroutine 呢?


    接口方法的调度与 C++ 虚函数类似。创建 goroutine 仅仅分配一些内存。调度 goroutine 实际上只是简单的循环调度。因此它非常快,但是在某些工作情景下可能工作得不好。

  • 有什么办法向不是定义在当前包的类型增加方法?例如向 int64 增加方法?


    不能。绝对不能。只有定义类型的包可以添加方法。因此,没有混合。这是保证代码用途的清晰的一种内部机制。否则程序的声明周期会依赖于包的导入时机,一旦有动态加载时,就没有什么是确定的了!

  • 是否可能向接口增加方法,也就是说一个有着接口作为接收者的方法,可以用于任何实现这个接口的对象?


    不能,同样的原因。对特定类型编写一个包函数来代替这个操作。这样函数有确定的命名空间,它们不会变成魔鬼。

  • 关于调度 goroutine:一个 goroutine 通过死循环能让其他 goroutine 以及 main 程序饿死吗(译注:不给吃,不给穿,将你们全部都榨干……),或者有什么优先级作为安全保证?


    这里没有安全保证。goroutine 并不是为了替换线程的低延迟的并发/并行进程。


    一方面,这很不幸。另一方面,协作式调度的复杂度更低,并且有更好的吞吐量。看看 Linux 内核有多少个调度中的循环得到了最佳的性能。低延迟,可扩展,不会资源枯竭,并且公平的优先级是难以实现的,而且那样的话就抛弃了操作系统的便利。可能最聪明的做法是增加一些将 goroutine 转化为隐含的线程的办法。

  • 那么关于垃圾回收呢?它对于处理大量的小对象如何?


    关于内存算法与 malloc() 一样有效率。当许多对象只是通过它们内部的指针引用的时候,速度可能会下降(例如,slice 就有类似的情况)。


    主意,当前的是暂停世界的收集器。马上会有替代。没有什么阻止使得可以去掉基础的 GC 。让语言本身尽可能的优化有更高的优先级。

反馈

已经有许多反馈了,所以我增加了这个小节。这记录了一些不适合于我整篇内容的有趣的附注。附注让我原来的文字更清晰,那些让我觉得“是的,当然,我怎么能丢弃这些呢?”的已经整合进了上面的文字中。

三元运算符

一个读者详细介绍了 Python 的三元运算符 if foo else b 的历史。除了这个 python 已经具有高度可读的扩展性,考虑为什么增加这个结构就得往前追溯。

看起来 Python 有很长一段时间没有这个,而人们与富布尔快乐的生活在一起:some_value or “default-value”。直到有人对 condition_to_test and value_if_true or value_if_false 的做法有了主意以后。从此它走了下坡路。这个改造有缺点,于是其他改造被发明出来。每个都比之前一个要丑陋,最终,Python 有了三元运算符。

这对于 Go 有什么意义吗?当然,如果你不能打败它,那就请拥抱它吧。

D(译注:嘿,之前好像有人说:为什么不谈谈D……)

我有不谈论 D 的好理由。我并不是忘记了它,有人没有发现我那关于 D 的几个字(译注:原文是 6 个词,翻译后……)的总结。我宁可不体验它。我不写关于 D 的东西,因为我不用 D 写东西。因此,现在我应大众要求,这是无知的我对于 D 的观点:

当我第一次遇到 D (在 heise.de 新闻里,你可以在这里找到英文版本),我相当惊喜。终于有一种语言尝试与 C/C++/Java 不同了。但是当略读了文档之后,我失去了兴趣。

为什么?很简单,它只是隔靴搔痒。是的,它在一个语言里提供了所有东西,在 C++ 中用模板的实现,还有在 Java 中那些放到另一个抽象层的东西,但是这就是关键:在哪,做啥。在那些语言里可能不怎么有效率,但是映射到 D 以后相当的奇怪。

虽然统一格式的全功能语言很有趣,我却看不到它哪里适合我。它没有尝试精简。也没有提供任何值得探索的新概念。它只是无法满足我的需要,所以为什么我要切换并,并且激战于无法同他人分享的代码;无法作为开源发布,并尽早使其尽早公开使用的代码;无法同他人的代码简单的进行接口的代码?

如果我工作于一个有许多特别之处的大项目,我会想要选择 Java 或者 C++,这基于哪个语言能最好的匹配项目的需求,尤其是外来代码。D 恰好没有准备好这个。如果是小项目,对我来说 D 没有任何特别之处。这没有切换的动力。补充一下,我不喜欢自动引导或将所有东西都加入语言中。这是个企业级的语言,但是 Java 和 C# 也是企业级的。

你知道什么东西有趣?我开始拒绝 Go 的原因就是因为 D。我阅读关于 Go 的时候看到了类似的东西,然后我想“哦,好吧,这又有另外一个……”然后就忽略了新闻。当我再次读的时候,一些事实(我不记得是什么了)激发了我的好奇心,而两周后我写了这个综述。

Go 实际上是不同的。它给出了切换的理由。它切合了我的需要,并且提供了一些其他通用语言无法给与的东西。不,Haskell、Erlang(译注:又见 Erlang……)等等都不行。我喜欢从科学的角度来看待它们,但是我仍然喜欢在疲倦的时候仍然能明白自己的代码,同时我也喜欢让其它人能够明白我的代码。但最重要的是,Go 与外部世界交互很好,这要感谢 gccgo,即便它仍然是一个相对未知的语言,我应当能从使用它中获益。

在互联网上的本文

感谢互联网,为所有我未能留意到的和已经了解的讨论。这里已经有许多不同方面的思考,我发现十分有趣。你们没有让我失望。这不像你将要读的内容,但是也差不多,一些回复并没有涵盖在当前的更新中:

许多人遗漏的一个非常重要的事情是编程语言是一个工具。曾几何时,我沉迷于 Perl 5,花时间学习了所有的奇技淫巧。但是你知道这其中真正的原因吗?不是语言自身的魅力让我着迷(这是一个很小的原因)。部分原因是可以摆弄那些元素,这是来自我黑客之心的呼唤。但是真正的原因是包文件和特定的途径只需要你编写代码。这使得我可以用更少的时间解决实际问题。

这是为什么我用 C 编写 AVR、用 PyQt 处理 GUI、在 GPU 上用 ARB顶点程序汇编代替 GLSL、用 C++/Fltk 处理轻量的 GUI、Lisp 在 Emacs 上、在用 Perl 写了原型后,用 C 编写自制的车载 MP3 播放器、基于 ARM 的 Python 反编译器,当然还有,无须维护的 PHP 的 web 服务。我恨 PHP,但是它对于那个任务来说是个正确的工具。因此,攻克它并且开始为你的任务提供生产力,审查整个构思,并持续的审查下去。

这就是我对 Go 的期望,并且看起来它确实能满足这些。我看过许多事情随着时间的迁移而走向错误的方向,没有谁、没有什么事是有保证的。但是我通常选择早期选择,长期采用。并且做事就像这里这样,做到能做到的最好。我能说的就是那些其他语言不能提供给我的这一切值得继续。

有一个令我相当好奇的更好的语言设计暗示,经常是这样的句子“…除了 20 以上的研究。”模糊的建议仅仅对那些众所周知的概念有效,而不是特定话题。欢迎任何人,谁能给我指出这结果和 Go 之间的关系,以及什么匹配了设计目标的约束。在顶部有我的邮件地址!(译注:Jörg Walter)

如果只对编程和 Go 感兴趣,你可以跳过这段。下面这段是跑题的扯淡。

对于那些觉得我站在你们脚趾上咒骂的人来说:这是咒骂。意味着这不是完全客观的。它使用了讽刺和夸张的手法。一些人理解了,还有一些不理解。通常,那些理解这个的人都与我有着相同的经历。而这些都是一种公开的发泄。我仍然热爱 C,我也仍然在使用 C++ 和 Java,当它们对于任务来说是正确的工具的时候,但这不意味着它们从那以后让我这颗程序员的心快乐(译注:那些传统卫道士快乐吗?那些装逼侠快乐吗?还有那些一无所知的中国特色的老学究快乐吗?他们都不是程序员啊!)。

顺便说一下,“某些东西并不适合另一些事情”与“用某些东西不可能成功做另一些事情”之间应该还是有不同的,即便它们相伴出现了 10000 次。

两个告诫我说不要让 OO 脱离并发的朋友,有什么细节说明吗?我没办法找到这个误解的本质,除了一些简单的说明外。接受过专业训练的读者能很好的理解真正的细节,而没有理论背景的人只能有我说的那个宏观思路。

对于被我关于 Perl6 的开发时间戳到的 Perl6 爱好者看这个:毫无疑问,通常来说我是一个 Perl 的爱好者。在我的机器上装有 Rakudo Star,并为了看看能有什么好处而认真的尝试过用其编写小工具。就像我用 Go 做得那样。然而没有收获,Perl6 还没准备好用于生产环境,不过我得承认,它快达到了。

对于我咒骂的分类和选择的语言吹毛求疵的人看这个:当然我只能讨论我知道的。这里确实有说是咒骂 C 族或者 Algol 后即语言。在介绍中我特别提到若干种其他语言已经是某些系统的主要语言,由于我的认知限制选择了都知道的一些。是的,JavaScript 属于这部分。这是一个广泛应用的语言,比其他都要广泛。

最终,那些由于“it’s”和“its”而指出我不够认真的家伙们:我非常抱歉。你们是对的,在这个 65kB 的文本中确实有地方打错了。已经修正了,现在我希望你们能够读得通顺了。请直接向我指出类似的错误,因为我从来不用任何拼写检查。

总结

任何将 Go 源代码与其钟爱 C 系语言比较都表达了一个基本的观点:Go 到底哪部分是你喜爱的,还有哪部分是你憎恶的?这多少都会让每个人烦恼,取决于你如何让它变得更好一点。它创造了不同的感觉,因此不能将其与其他一些语言做简单的类比。这很好!

最终,得到了一个简单清晰的容易学习但却不同寻常的类型系统。向计算机科学初学者推销 Go,这可能是主要的障碍。他们通常都被教育使用 OO 的思想。然而,这是简单和安全的,这使得 Go 更好的适应了自学成才的程序员。

如果你有传统的 OO 背景,“Effective Go”是值得读的很棒的文档。有这样的文档很重要,但是这并不足够。研究并编写这个综述相比同时在编写的应用来说,教会了我更多关于 Go 的东西。以前做过的多事情用 Go 来做会有不同,但是会同样好(甚至更好一些),我之前并没有意识到这些。总是有种感觉“Go 不能做 X”,而实际上可以做得很好,只是方法不同。

关于 Go 的潜力比较公平的猜想,留意 Go 的年龄吧。第一个发布版不超过 2 年的时间,并且在今年就已经标识为稳定了。看看 Go 今天已经取得的成绩,设想一下 Go 也有类似 Java 或者 JavaScript 那样的商业背景。对于新的语言,一个成功的好例子是 Java,而对比 Go 跟 Java 1.0 的功能。我们已经有了赢家。

但对于推进这样的潜力,这个语言需要一些动力。包括通过开放的、活跃的、正在增长的社区,或者通过企业的回馈。我更喜欢社区,但是对于实际成功的案例,可能必须有一些企业的参与。来为 Oracle 是否会搞砸 Java 的生意下注吧。_

真的,对于当前所有的流行的系统语言的缺陷来说,Go 就是答案。它只是需要证明。

最后要说的是,我已经在互联网上看到了相当数量的对 Go 的批评,这些是不能忽视的,所以得说说:这样做的多数人并没有真正去了解它。Go 是不同的,即便是看起来它就是某种 C。它不是。它不是 C++ 或者 Objective C,也从来没有尝试成为这样的!所以,请不要再说“我们已经有了 C++/Objective C,谁还需要 Go?”这样的话。看看 Go 是如何尝试用完全不同的途径来解决问题的。接受 OO 可以用不同的途径来实现这一现实。你可能选择忽略它,但是如果你使用 JavaScript,你已经在用一些不是基于类的 OO 了。不要只是接受它,却用不同的方式来发挥这种力量。对于其他人,那些认为 Go 还不足够好的:记得这是一个解决实际问题的语言。它就在那。它能工作。用结构优美的语言来解决实际问题,虽然它不稳定,还未完成,并且不够快?对于细节吹毛求疵很容易,但是要做一个真正的产品,需要处理所有约束。这就是 Go 能做到的。

最后的最后:感谢社区。你们反馈了许多有价值的东西。请继续纠正我的错误,偏见和其他任何事情,应该还有。我希望这个文档将帮助那些潜在用户接触 Go。要做到这点,它需要是正确的。请吹毛求疵吧。(译注:对于中文翻译也一样。我囫囵吞枣的翻译,总是错漏百出,悲情……)

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。