[转][翻译]Go编程语言,或者:为什么除了它,其他类C语言都是垃圾
原文在这里: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(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 的语法)
- 简单 我期望有一个语言有着优雅的简单。只需要几个概念就可以表达所有的可能。正交性是这个情况的关键。简单也使得语言容易学习。在相同的目的中重用语法结构,并且让用户代码与这些结构对接,这样就可以在其上进行扩充。同样的,不要强迫用户使用复杂的结构。提供类、单元测试框架或者文档注释没有错,但是如果用户不想要的话,不要让它们进入视线。 理想情况,让它们“Do what I mean”。
- 平等权利 如果内建的联合数组算法对于特定的数据集是低效的,我期望可以建立一个能像内建那个一样使用的实现来代替。语言不应该有特权。因此,操作符应当可以被重载。噢,在 Python 里做得那些魔法般的事……同样,内建数据类型应当可以作为对象语法在语言中使用。所有这些无须动态,多数都可以用静态的语法糖来实现。(例如:Perl 的 prototyped subs,Python 的操作符重载)
- 元编程 这里有两个方面。一个是模板以及/或者泛型,这太有用了,以至于不能没有。虽然 C 有一些邪恶的 hack 来模拟这个:预处理程序。每个人都需要这个。更加高级的方面是编译时执行代码生成代码,像 lisp 的宏和 Perl 的代码 filter。额外说明一下,这允许创建新的语言结构或者特定领域的语言。
- 高效的和可预测的 如果语言瞄准的是系统编程,它必须相当高效并且完全可以被预测。必须能够直观的预测确定的操作带来了哪些时间消耗和内存分配。核心的语言功能必须很好的优化。库提供了更高层次的结构使得编码更有效率,而执行却低效一些。对象系统无须昂贵的运行时支持(不论是时间还是空间),静态优化应当是可能的。
理想情况,用几千行代码和几百字节内存编写一个有用的控制程序是可能的。当然,效率和功能通常作用相反,需要取舍,所以语言应当提供这样的机会让我来决定。
- 可用性 最终,语言必须可用。所有想法放到一边,最重要的还是它解决实际问题。用一点实用主义不会有伤害。语言应当与其他语言接近,这样就可以用之前思考的模式去思考它。如果严重的偏离常规,那它就必须是值得的。忠于原则,而避免惊异!
- 模块化的库和代码仓库 我期望在成长过程中使用过的脚本语言内建的或部分标准库的那种精巧。一个包的公共代码仓库和适当的可移植的包管理则更好。通常,包包含网络协议、常用格式的解析、GUI、加密、通用数学算法、数据处理等等。(例如:Perl 5 CPAN)
特别的功能 8. 数据结构 我要我的哈希!一个没有同语言良好集成的联合数组数据类型的语言是一个笑话。显而易见,OO(或者其他便利的封装)也是必须的。布尔类型很好,但是更重要的是各种类型在布尔上下文中的明确的解释。额外的,在标准库中有多种高级数据结构,例如不同的树、列表、集合等等。如果已经有了跳跃链表,那就是上轨道了。
让字符串也是 OO 的,尤其是相似的运算符(如 len())作用于字符串应当像作用在数组或模拟数组的对象上一样。附加的,还要看是否所有原始数据类型支持与其他所有对象相同的 OO 语法。无须像 Ruby 那样。只要有语法糖即可。(例如:Python 和它的标准库)
- 控制结构 听上去显而易见,不过允许一次从多个嵌套的循环中跳出的控制是恰当的。消除 goto,真的。上一次我用到它是……好吧,就是上周。我在奥尔登堡计算机博物馆里的 Commodore C64 上做怀旧程序演示。这不算。所以丢掉 goto 吧,但是给我 foreach。除此之外别无所求。JavaScript 的 with 语句我从来没有用过,不过这也是个好主意,我想这是某种形式的“少即是多”。(例如:所有的脚本语言在这方面都很出色)
事实上,有一些东西在其他地方还从未见过。之前,我遇到过许多循环,每个循环的入口和条件测试/循环退出都不相邻。那么如果有一种方法来表达一个循环从中间某个位置开始,看起来会很酷。否则,就需要复制循环的一些部分。有点类似 Duff 的设备,不是为了优化,仅仅是让代码不要那么冗长。
- 表达式语法 许多人刻意避开,但是 ?: 三元运算符是个好主意,如果使用正确的话。Python 实现了 foo if bar else baz,有点罗嗦,不过也还算 OK。然而,JS 和 Perl 的布尔运算符 AND 和 OR 不是用来计算 true 和 false 的,但是对于特定的值可以被认为是 true。假设赋值 value = cmdline_option || “default”。这需要在所有数据类型上有恰当的布尔含义。
- 表达式的函数性质 如果我想要写 Lisp,我会这么做的。我并不需要一个完整的函数编程语言。但是没有什么比 map() 更好的了。闭包和匿名函数(lambda表达式)也是杀手级的功能。可能“超级复杂”领域是 hyper 运算符(在 Perl6 中是这么叫它们)如 any() 和 all() (在 Python 中是这个名字),但是它们都很棒,并且暗含着并行的含义。欢迎来到新世纪,至少是九十年代吧。
- 对象 已经有若干面向对象的模型,但是至少需要封装和多态。有一些组合类的方法也同样存在,如继承和混合。接口应该有,或者用多重继承来代替(译注:多重继承?梦魇,绝对的梦魇!)重载是很重要的,或者至少给我默认参数。命名参数也是很酷的办法。
- 并行 对于这种情况我是个失败者。generator 和协程在某些情况下适用于此。要点是不要内建的多线程,但是让多个代码方便的在一起工作。它们不需要同时执行,但是应当可以同时工作于数据集的多个部分。将应用构造成事件驱动应当比较容易。这一机制对于真正的多核来说,非常棒。(例如:Perl5 的 POE,python 的 generator)
- 字符串和 Unicode 这是该死的 2011,除了 Unicode 什么都不需要。所以务必包含安全的字符串类型,并且所有都是 Unicode 的,不要有例外。我对额外的双编码的隐式转换感到恶心,也不想再见到,或着手工转换而不是语言支持。顺便说一下,我更喜欢 UTF-8。谁会在意常量字符串的索引呢?在这种特例下用字符数组吧。一般情况使用正则表达式。在原始字符串的 API 上要有正则表达式支持。
- 底层接口 有观点认为,可能会需要手工操作 bit。特别当目标是微控制器或嵌入式 ARM 内核,应当有方法吓到裸设备去。理想情况是用语言直接编写 OS 内核,而无须任何汇编(除了平台特定的启动代码,它们没办法用其他办法完成)。
———————–翻译分割线———————–
这文章实在是太长了,拆分吧。今天的任务到此结束。
To be continue…
继续昨天的:[翻译]Go编程语言,或者:为什么除了它,其他类C语言都是垃圾(1)。 总算切入正题,开始说 Go 了。
———————–翻译分割线———————–
Go编程语言,或者:为什么除了它,其他类C语言都是垃圾
(译注:续[翻译]Go编程语言,或者:为什么除了它,其他类C语言都是垃圾(1))
进入 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 来说都是重大的改进。在许多个快乐的夜晚编写没有对象的过程代码就已经足够了,不是吗? ———————–翻译分割线———————– 一个德国人,用了这么多英文生僻的词语,这么多生僻的用法,这么多俚语和缩略语,他是怎么做到的?怎么做到的呢?求秘籍啊!求秘籍啊!