Go 语言中奇怪的 if 语句

上篇文章提到过 Go 语言中带有强烈的设计者的主观想法,Go 的 if 语句就是其中一例

常见的 if 语句大约是这个样子的(C++):

这样有一个问题:变量 event 定义在了 if 语句的外面,也就是说,在 if 语句之后,也可以继续使用 event 变量;而如果后续的操作中不需要 event 变量了,它实际上就造成了命名空间的污染。这并不是一个严重的问题,多数情况下不会造成任何问题,而如果一定要解决的话,在 C++ 中可以在 event 的定义之外套一层大括号来限定它的作用域。虽然代码看上去有一些奇怪,但无伤大雅。比如这样:

但是 Go 的设计者不知是出于什么原因,非要从语法上解决这个问题。于是 Go 中的 if 语句可以写成这个样子:

对,你没有看错。虽然 event 是定义在了 if 中,但它在 else 中也是可以用的。也就是说,这种写法实际上是上述的 C++ 写法的语法糖。

并不清楚 Go 的设计者添加这个语法糖的目的是什么。它仅仅是为了解决变量的作用域问题而提出,却牺牲了代码的可读性。if 语句可能会变得过长而不易阅读;在后续重构的过程中,拆分 if 语句也会变得困难。为了解决一个小问题而增加一种有问题的语法,看上去有点得不尝失。

吐槽一下 Go 语言

由于公司一直都在大力推广 Go 语言,我也有幸接触了一下。虽然 Google 内部已有三大语言(C++、Java、Python),Go 在公司内部的政治意义大于它的实际使用,但它的设计却有着很多亮点。比如 Go 自带了代码格式化工具(gofmt)和自动化测试工具(go test),这些标准化的工作在其它语言中一直都存在着多方争议,一直都不能统一,而 Go 从一开始就搞定了这些琐碎问题,让广大程序员可以把重心都放在开发新程序上,而不是争一些有的没的。

当然,这篇文章并不想表扬 Go 语言,而是要吐槽一下它的缺点。

Go 的设计者有着强烈的 Unix 背景,有一些平台依赖的东西都默认甚至是强制成 Unix 的。比如 Go 语言中有一个名为“path”的包,用来操作路径,而它强制路径分隔符为“/”,而无视 Windows 中的“\”。后来估计是开发中遇到一些问题,又做了一个叫“filepath”的包,来兼容 Windows 的使用。又比如 Go 语言中对环境变量的解析,强制环境变量的标识符为“$”,而不视 Windows 中的“%”,甚至整个标准库中都找不到对“%”的处理方法。这些基本的区别都不做处理,很难想象 Go 语言在跨平台设计中有哪些更为隐藏的坑。

这还不是主要的问题。更严重的问题是,Go 语言中有过多的“私有”特性,对使用者不公平。

先来举一个 C++ 的例子,C++ 中有一些运算符,比如加减乘除,这些运算符可以使用于 C++ 的基础类型(如 int)上。基础类型是 C++ 语言本身提供的,如果运算符只能作用于基础类型,那么 C++ 也可被视为“自私”的。然而 C++ 的设计者并没有这么做,这些运算符,不仅可以用于基础类型,也可以在自定义类型中,通过重载的方式来使用,使得 C++ 中不仅两个 int 可以相加,两个 class 的实例也可以相加。这样一来,运算符对于使用者就是公平的。

而 Go 语言中,则有着很多反例。比如它的泛型:Go 的官方说法是“我们不支持泛型”,具体原因就不深入讨论了。但是,Go 语言的自有类型 map 和 channel 却是支持泛型的。这就成了一个典型的“自私”的例子。类似的例子还有 Go 语言中的 internal 包有着特殊的作用域;Go 的官方库有着极短的包名,而第三方库通常是以“github”开头的很长一串文本。

这些“私有”特性,说明 Go 语言的设计者并没有为开发人员考虑过,自己设计得爽了,却徒增了学习曲线,不利于 Go 语言的推广。如果能把这些问题解决好,Go 还是可以成为一门不错的语言的。