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 还是可以成为一门不错的语言的。

在 Markdown 中设置链接由新窗口打开

尽管我对 Markdown 不是那么感冒,但工作和生活中总是难免会使用到一些,比如在向某些开源项目发 PR 的时候。于是对 Markdown 的一些小技巧也有所了解。

Markdown 中的链接语法是这样的:[文本](链接)。它相当于 HTML 中的

但众所周知,HTML 的 a 标签有一个实用的属性 target,当它的值为“_blank”的时候,浏览器会默认为这个链接打开一个新窗口,而不是在当前页面转向新页面。而 Markdown 中没有这个“_blank”选项,于是所有链接都只能在当前页面打开,有些情况会非常不方便。

在多数 Markdown 解释器中(如 GitHub Flavored Markdown),这个情况无解。如果一个 Markdown 解释器同时支持 HTML 语法,则这种情况可以通过直接写 HTML 解决。

于是我又多了一个不喜欢 Markdown 的理由。

HHVM 中的 RepoAuthoritative 模式

HHVM 3.8 中提出了一个 RepoAuthoritative 模式,用于提升 PHP 代码的效率。

PHP 代码是解释型的,也就是每次执行的时候,PHP 引擎都会读取源代码,编译成机器代码之后再执行。这也就是为什么解释型语言比较慢的原因之一,它不能一次编译完成,而是一定要运行到哪里编译到哪里。编译的过程拖慢了整个运行的效率。而这样每次“编译”的过程显得有些多余,因为代码是基本不会变的。于是 PHP-FPM 是提出了 OpCache 来缓存编译的结果,如果代码已经被编译过,且源代码没有改动,则继续使用缓存。

而 HHVM 则更进了一步,在 RepoAuthoritative 开启之后,指定的文件夹会被编译成一个二进制文件,并同时进行优化。官方说法是,这样的优化可以在 HHVM 本身的基础上,再提升 20% 的运行效率。

开启的方法如下,其中 /var/www 是想要编译的文件夹。

关闭的方法如下:

编译的过程使用了 /var/run 做为临时文件夹,实测 WordPress 编译完成之后,要占用 175MB 的空间,如果 /var/run 太小会提示“disk is full”错误。

当然, RepoAuthoritative 也有一定的限制,比如不能使用 eval() 和 create_function() 等动态改变代码的语句。不过 WordPress 中几乎没有用到,说“几乎”是因为实际上有两处用到了,一个是在 wp-includes/pomo/po.php 中,这个文件貌似根本不会被执行到;另一处是在 wp-includes/atomlib.php 中,只要不使用 atom 作为 RSS feed 即可。另外就是编译完成之后,源代码的改动就会“失效”,要再一次编译才会使改动起作用。

然而,由于 HHVM 自身已经相当地高效了,开启 RepoAuthoritative 模式之后没有明显的变化。本站实测下来,TTFB 时间大概减少了 50ms,对于原本只有 700ms 左右的速度来说,看不出效果。我试用了一下之后就把它关闭了。不过至少说明在 WordPress 上使用这个功能是没有问题的。如果你的 WordPress 中有非常慢的功能,不妨试一下 RepoAuthoritative 模式。

在 PHP 中实现 String 的 StartsWith 和 EndsWith

和 Javascript 一样,PHP 中也没有对 String 实现 StartsWith 和 EndsWith 这两个方法。由于这两个方法很常用,我们只好自己来实现一下。

在 PHP 4 中,常见的做法和 Javascript 类似,如下:

上面的方法会使用更多的内存,并且对字符串扫描了两遍,效率不高。这种方法在 PHP 5 中已经过时了,因为 PHP 5 给出了一个 substr_compare 方法,可以直接比较子字符串,如果代码就可以简化成这样:

上述实现的效率至少快了一倍。由于历史遗留的问题,很多现存代码都没有更新,比如 WordPress 的源码中就存在了老版本的实现。如果你已经在使用 PHP 5 或者 HHVM,不妨试试第二种实现。

使用 font-face 定义字体

最近在看中文字体,顺便也研究了一下 CSS 中关于字体的内容。

在 CSS 中设置文字的字体,需要使用 font-family 属性,比如下面的代码指定了段落(p)中使用 Helvetia 字体,如果系统中没有 Helvetia,则退一步使用 Arial 字体,再没有的话,则使用系统默认的无衬线(sans-serif)字体。

如果你想加载一种新的字体,则需要使用 @font-face 指令。@font-face 用于定义一个新的 font-family,并指定它的源文件。如下:

上面这段代码定义了 Genericons 字体,它的源文件有 4 种可选,eot、woff、ttf、svg。其中 eot 是为了兼容 IE 6 而单独存在的,其它三种则广泛被现代的浏览器支持。实际上,如果不考虑 IE 6,单 woff 一种格式就足够了,没有必要提供其它三种。

定义完之后,就可以在 font-family 中使用 Genericons 了。

@font-face 的另一个作用是统一同一字体的不同名称,比如本站所用的 Noto Sans CJK SC,在 Adobe 那边的定义为 source-han-sans-simplified-c,如果不想每次都在 font-family 中写两个,可以把它们写进 @font-face,如下:

这样定义了之后,在 font-family 中只要写 Noto Sans CJK SC 就可以了,系统会自动在本地查找两种字体,找到任何一种,都可被用来渲染页面。

另外在 @font-face 中还可以指定字重(weight)和样式(style),具体可以参考 MDN 的相关介绍

投靠 Ubuntu

经过一番纠结之后,我还是把博客换到了 Ubuntu 之上。

之前提到过,从 AWS 转到 GCE 的时候,被迫选择了 RHEL 7(Redhat Enterprise Linux),因为只是 RHEL 7 是完全兼容 AmazonLinux 的。虽然 RHEL 需要另外付费,但为了平滑过渡,我还是以谨慎为先。

RHEL 有非常完善的商业支持,文档很齐全,有什么问题一查就可以明白。唯一的问题是,它的收费太高了,GCE 上面用一个月需要 50 刀以上,且不像主机一样用的时间长会打折,这样算下来,年费和一个 Radhat 的订阅差不多价格。但是我并不需要什么客户支持,技术问题都可以靠 Stackoverflow 搞定,这个订阅费用就白白浪费了。这也是我转向 Ubuntu 的主要原因。

还有一个原因是 HHVM。HHVM 是 Facebook 的 PHP 引擎实现,完全兼容 PHP 5.5 (及以上)。WordPress 从 3.9 开始,完整兼容 HHVM。

HHVM 的响应时间,据说是 PHP-FPM 的一半,也就是提升了一倍的吞吐量。原生 PHP 要做到这个效果,需要等 PHP 7 发布才行。看着各种发行版对于新软件的保守程度,不知道 PHP 7 要到什么时候才能成为主流。另一方面,Facebook 的研发能力肯定要比 Zend 来得强,等 PHP 7 发布了,HHVM 的性能肯定会更强。于是我就抛弃了原生 PHP,转投 HHVM。

当然,我也可以在 RHEL 上自己编译安装 HHVM。我试过,GCE 的机器编译了一个小时,我都快睡着了才编译完成,但安装(make install)的时候,告诉我某个文件不存在 -_-。我还是老老实实用官方的安装包吧,而 HHVM 官方只提供了 Debian 系列的安装包。

为什么没有选 Debian?Debian 保守到现在还只提供了 Apache 2.2,无语。

那也可以转投 Nginx 呀?我不高兴重写那一堆的 RewriteRule,并且 PageSpeed 在 Nginx 上也需要自己编译安装,不想再重蹈 HHVM 的覆辙。

由于 HHVM 和 PHP-FPM 的用法几乎一模一样,转换过程没出现任何问题,我做的唯一的工作就是重新设置了 Apache。花了几个小时,不过没什么难度,转换过程很顺利。

现在博客已经运行在 Ubuntu 14.04 + Apache 2.4 + HHVM 3.5 + MySQL 5.6 之上,欢迎测试,有问题请留言。