最近去欧洲玩了一圈。回来之后是一堆工作和生活上的变动。一直没时间写新东西。
今天拿到了 Adobe Creative Suite 5 。
最近看到有人质疑在今天的电子设备上阅读是否还需要保留『页』这个概念。他们认为『页』是印刷品时代的产物,在电子出版时代除了怀旧没有任何意义,电子出版物已经有了『页』的完美替代品:竖直滚动条。
对我来说,竖直滚动条平滑移动功能的直接后果之一是,在阅读的时候,你会情不自禁地试图避免阅读窗口(或者设备屏幕)边缘处的文字 —— 确实,它们太边缘了,因为没有页只有滚动条的内容没有上下留白,而对这种缺乏留白的逃避会延伸到你正在阅读的那四五行以外的一切文字,从而会无时无刻不让你希望把自己正在看的句子调整到窗口(屏幕)的正中间。于是窗口(屏幕)的大小失去了意义,读者的视界实际上被压窄到四五行。阅读的动作成了以句子为频度的『读 —— 滚动 —— 读』(甚至不是有阅读器还发明了自动卷动功能么)。竖直滚动条成了一个分散注意力的障碍而非方便的阅读工具。更糟糕的是,有了滚动条,文章的编辑者更难把握读者会看到什么样的页面布局,所以很多时候他们也就放弃了这样的思考。结果就是我经常在阅读的时候实在搞不好把一幅插图放到窗口的什么位置最协调(通常我发现最好的位置是赶紧把这个图片滚出窗口)。这实在是比『页』糟糕很多的阅读体验。
我很欣赏 Kindle for Mac 的做法:根据当前窗口大小把窗口的内容区域作为一页,不提供竖直滚动条,只提供翻页操作。因为没有滚动条,这是让我分心最少的一款阅读器。同时根据窗口大小动态的定义页的大小,也避免了在电子设备上照搬印刷品布局的古怪体验。我猜想在 iPad 和 iPhone 上的 Kindle 和 iBooks 也是类似的操作,因为没有窗口分割,所以页面的大小能更简单的固定为设备屏幕的大小 —— 但是 Kindle 没有进入 China App Store 所以我没有用过其 iPhone 版,也没有机会深入使用 iPad。
回顾一下,其实『页』这个概念还真的不一定就是印刷品时代仅仅因为技术局限而留下的遗产。古代人类不就使用过所谓的『卷轴』么?倘若假以改进,『卷轴』印刷品的便携性和不考虑到阅读注意力的单纯操作性不一定就输给『页』。所以我想,在印刷品时代,『页』并不是没有竞争者的无奈选择,而是在选择中胜出的更优秀方案。到了电子出版时代,人类历史依然会不断掀开新的一页。
对于词典软件来说,发音是个必需具备的基本功能,而且 dict.cn 的 Web API 提供了 MP3 格式的单词(短语)发音,所以从开始写 Dict Mac 的起,发音就被列入计划加入的功能。0.02 版完成之后,也就是从 5 月 25 日左右开始准备实现发音功能。
最初的问题是找到合适的 API,花了一个晚上,从 blog 和论坛的帖子堆里发现无数名称,逐一在 Apple 的文档里查找印证,最后终于锁定 Audio Queue Service 这个 framework。有了 framework 的确切名称后就不必再流连于 Stackoverflow 之类的网站,开始专心阅读 Apple 的文档。
花了一个晚上草草看了一下《Audio Queue Service Programming Guide》,感觉不是很好。这个 framework 功能比较强大,设计得比较灵活,相对来说也就没有提供傻瓜化的播放 API,即使是播放一个 MP3 文件也要 400 多行代码。好在这些代码在 programming guide 里都已经直接给出。但是一来我不喜欢这种 copy/paste 代码的方式,二来如果这些代码有 bug(当然 programming guide 里的代码本身有 bug 的可能性不大,但是和 Dict Mac 的 code base 集成的时候很可能引入问题),也要花不少时间解决,而且很可能要重读 programming guide 甚至查阅 API reference。
另外的问题是选择把 dict.cn 的 MP3 文件 download 到本地文件系统之后再打开文件播放的方式,还是直接播放网络的流数据的方式。从尽量减小暴露给用户的复杂度的角度说,稍稍倾向后者。但是考虑到今后还要实现本地缓存,以及尽早实现功能,还是决定先写出把 MP3 文件按照单词作为索引存储在本地文件系统的代码。花了两个晚上写好了这些代码后,第二天白天我脑子里突然冒出一个问题:OS X 会不会有现成的播放 MP3 的命令行工具?从以往的经验看,OS X 的命令行工具还是相当强大的。Google 了一下,果然有一个 afplay。这样一下子把实现发音功能的剩余工作从至少两个晚上变成了一个小时 —— 只要 fork 一个新进程运行 afplay 即可。
其实刚开始写 Dict Mac 的时候就考虑过是不是把必要的 UI 之外的其它功能都做成一个后端独立进程。最后决定当前阶段还是写成一个进程。不过 afplay 至少让发音功能成为了一个独立的进程。一开始我还在抱怨为什么 Mac OS X 不能直接提供一个播放 MP3 的傻瓜接口,其实这样的傻瓜功能应该做成工具而不是 API,像 Audio Queue Service 那样灵活的接口才值得作为 API 的形式提供。这个事情上 OS X 继承了 UNIX 的精髓,也是像 Windows 这样的不管底层功能还是傻瓜功能一股脑的做成 API 的非 UNIX 类的 OS 最缺乏的文化。
Diff 是程序员最重要的工具之一。不过,除了在 mail 里发送 patch,我很难忍受传统 diff 的字符输出。在任何系统上开发软件,第一件重要的准备工作就是寻找趁手的 GUI diff 工具。GUI diff 工具的优势在于 side-by-side 的比较方式即能显示变化的部分,又不影响对每个版本的连续阅读。Linux 上比较好的是 KDE 的 kompare,也是我见到的第一种 GUI diff 工具。因为先入为主的看到 kompare 使用曲线来联系变化的对应关系,所以任何使用空行填充或者直线来显示对应关系的 GUI diff 工具对我来说也是比较勉强的 —— 可以暂时使用,但是仍然会不断寻找替代品。
平时工作用 Perforce 管理代码,它有个自带的 GUI diff 工具 P4merge。功能很强大(而且免费,虽然 Perforce 是商业软件,但是它的 client,包括 P4v 和 P4merge 都是免费的,而且 P4merge 可以完全脱离 Perforce server 独立使用),除了普通的比较两个文件(版本)之外还可以 3-way 比较(一般用来做人工的 branch merge,分别比较 branch fork 之前的原始版本,main branch 上的修改版本,和其它 branch 上的修改版本)。和 editor 不同的是,diff 工具操作的不仅仅是 check-out 的 version,而是 version repository 里的任意版本。所以 diff 工具要想和 version control 系统配合,简单的文件操作是不够的,一个成熟的 version control 系统必须在设计中考虑如何与外部的 diff 工具协作。
从上周开始用 Git 管理 Dict Mac 的 code base。用到昨天终于感到缺省的『 git diff 』不好用(类似『 diff -u 』的输出)。简单的 Google 一下就找到了答案:
git difftool …
原本以为只有在 Linux 上才会有缺省配置好的 GUI diff 工具和 Git 协作。在 Mac 上试过发现 OS X 上已经配置好了 opendiff 可以让 Git 直接使用。OS X 的 out-of-box 用户体验确实不是只用来蒙初学者的。连 developer 的工具也能准备齐全。但我还是最习惯 P4merge。于是试着配置:
git config –global diff.tool p4merge
git config –global \
difftool.p4merge.cmd /Applications/p4merge.app/Contents/MacOS/p4merge
然后运行『 git difftool 』,P4merge 是调用起来了,但是没有内容。原来 Git 缺省不会向 diff 工具传递参数,必须写到命令行配置里:
git config –global \
“difftool.p4merge.cmd \
/Applications/p4merge.app/Contents/MacOS/p4merge \”\$LOCAL\” \”\$REMOTE\”"
这里还有一个陷阱,在某些 shell 里,『 $ 』是不用加转义的。不过在 OS X 的 bash 里必须加。

把 Dict Mac 介绍给同事们之后遇到最多的回应是『怎么是 10.6 only 的?』我喜欢用最新的东西,加上在家里测试 10.5 也并不方便,所以开发的时候没多想就选了 10.6 SDK。没想到连公司同事这些 Mac 的重量级用户还有不少没升级到 10.6,于是今天中午花了一个小时把最低平台需求降到了 10.5。其间遇到了几个小问题。
第一是 NSApplication 的 delegate 在 10.5 之下没有类型,在 10.6 下则正式定义为 NSApplicationDelegate 接口。不过真正起作用的 selector 都没有变化。把 delegate 的基类从 NSObject <NSApplicationDelegate> 变成 NSObject 之后就解决了问题。第二个问题是 NSTextView 的背景色设置成 [NSColor clearColor] 在 10.6 下工作的很好,到了 10.5 下就变成了黑色,变成 [NSColor windowBackgroundColor] 才行。虽然效果相同,但是从正常思路来说我是希望让控件的背景透明,而不是让它『碰巧』和窗口同色,而且遇到那些背景色可以在运行的时候定制的窗口,非透明的控件还要多写代码来同步颜色。
上面两个问题解决之后,Dict Mac 就可以正常的用 10.5 SDK 编译了,在 OS X 10.6.2 上运行也一切正常。之后遇到的问题最离奇,程序在 OS X 10.5 上启动后就会崩溃,crash log 报告 selector [DictWindow awakeFromNib] 不存在。Google 之后发现遇到类似问题的人不少,唯一找到的 workaround 是把 [DictWindow awakeFromNib] 里的 [super awakeFromNib] 去掉(完全莫名其妙的改法,这是一个人记得刚刚升级到 Xcode 3.2 的时候并没有这个问题,然后从自己的 version control 找到当时的代码才得到的解决方案)。
降级 SDK 总会遇到一些小麻烦。也能从中看到 SDK 的每次升级都确实给开发工作减轻了不少负担,唯一遗憾的是用户们升级得没有那么快,新 SDK 的推出带来的好处要过上一段不短的时间才能让开发者受益。
很多天没有更新 Blog 了,其间也有曾经想写些话题,不过大部分时间还是用来动手做被一件很久之前就想做的事情 —— 自己写个小工具。第一个完成的工具是我每天都离不开的 dict.cn 的客户端 Dict Mac。目前刚刚放到网上的只是 0.01 版。功能还不是很多。不过我自己已经完全切换到 Dict Mac 而不再打开 dict.cn 了。
从开发的角度说,这次也是我学习 Cocoa 的第一个实践项目。Cocoa 的 Interface Builder,以及控件对 URL 和 RTF 格式的处理都很好用。
如今几乎所有用过笔记本电脑的人都能说出镜面屏的缺点和优点。缺点是反光,如果你的座位正后面有一盏灯,那么使用镜面屏是很痛苦的。优点是色彩艳丽。在顺理成章的接受这个优点的时候,很少有人想到色彩艳丽的根源。前些天看到有人在网上问这个问题,我突然想到原来镜面屏的优点就来自它的缺点。
非镜面屏能很好地应对使用者后方的光源,原因在于把光源发出的光通过漫反射平均的分散到各个方向。反过来说,如果你的侧后方有一个光源,那么非镜面屏就会把这个光源的一部分光反射到你的眼中,不管你如何调整角度,只要这个光源还在你的后方,反射到你眼中的光量都不会变化。这就是漫反射的特点。而镜面反射遵循入射角等于反射角的反射定律,所以镜面屏会把正后方光源的光线几乎 100% 的反射到使用者眼中。但是只要稍稍调整角度,让光源处于侧后方,光源的光线就几乎不会通过屏幕进入使用者眼中。所以镜面屏是通过牺牲对正后方环境的容忍度,降低了整个环境对显示效果的干扰。而非镜面屏以牺牲抗环境干扰的整体能力换取对正后方环境的容忍。
When in doubt, use brute force.
—— Ken Thompson
Keep it simple, stupid!
在『天涯』上看到一个帖子提到米格-25。这个前苏联空军中神秘的家伙当年经常在北约的雷达上以三倍音速一闪而过。在以色列曾经遭遇 F-4 并且被后者占据尾追位置发射了空对空导弹,结果是 F-4 看到米格- 25 在导弹发射之后突然加速离开。以当时北约国家 —— 主要是美国 —— 对航空技术的理解,要达到米格- 25 展示出的能力,苏联非掌握钛合金加工工艺不可 —— 在三马赫速度下产生的高温中,除了钛以外,任何低密度金属都会变成橡皮泥,可是即使是今天人类对钛金属的加工能力仍然很有限,完全无法令其成型为航空器零件。还有,为了完成被赋予的防卫广阔的西伯利亚的任务,西方认为米格- 25 必然拥有精巧的机载电子设备。
当 1976 年一个叛逃的苏联飞行员给西方送来一架米格- 25 的时候,后者迫不及待的要看看心目中尖端的机身工艺和电子技术。结果,发现这个三倍音速飞行的家伙竟然是个大钢锭 —— 几乎完全用不锈钢这种在西方看来连普通飞机都造不了的重金属。整架飞机用可称为『狂暴』的发动机才体现出邪恶的航空特性。那个能蹲下一个成年人的喷口最能说明其狂野,不光巨大,而且固定在最大状态,完全没有任何调节机构 —— 在维持三马赫速度而产生的高温下,任何多余的机构都是巨大的不稳定因素。困扰了西方十几年的关于如何实现三倍音速的机身加工就这样被苏联用暴力一举攻克。
还有西方幻想中先进的机载电子设备,不过是把大量的电子管塞到硕大的机头中。如果在地面打开机头的电子管雷达,能把前方 100 米内的生物烤熟。
北极熊独一无二的风格把技术和暴力美学联系到一起。这是人类在技术有限的前提下追求极至的精神。如果有一天人类认识宇宙的能力走到尽头,还能够依靠这种精神在探索的路上走很远。刘慈欣的《三体II · 黑暗森林》和弗诺 · 文奇的《天渊》幻想的就是这种无奈但又令人神往的境地。只是半个世纪之前那个神秘的红色帝国已经在这个境界肆意驰骋了。
最近一件有意思的事情是 Oracle 质问询问 Open Solaris 社区从一个 Open Solaris 这样的 open source 项目如何盈利。老实说比起 IBM 这种虚伪的家伙,我更喜欢 Oracle 的直率 —— 本来么,这个问题几乎人人心中都有。那么,为什么有的 open source 项目 —— 比如 Linux kernel、WebKit、GCC 可以蒸蒸日上;而另一些,比如 MySQL、Open Solaris 则奄奄一息呢?
其实答案还是在于那句老话,open source 是程序员写给自己的工具,它的生存不取决于盈利,不沿循普通的商品交换的资金流动途径,而在于直接提高其创造者社区的生产力。一个 open source 项目能否生存,在于其用户群能否成为自身的维护者和改进者。所以,第一等天然的 open source 项目是程序开发工具。第二等天然的 open source 项目是各种各样的库。同样的功能,提供 GUI 和提供 API 对于一个开源项目来说是生死攸关的差异。使用 GUI 的人极难成为一个产品的维护者。使用 API 的人不是在真正的『使用』产品,而是在创造自己的产品,同时他们有能力也乐于成为他们所依靠的 open source 项目的维护者和改进者。所以作为开源软件 WebKit 之类的库能够得到比 FireFox 之类的成品浏览器更好的发展。事实上,要求 FireFox 把自身的结构整洁化以便能够剥离出其『核心』功能供其它类型的产品使用一直是业界对 FireFox 的一个要求。
一个 open source 项目提供的能够将被其他开发者自由的集成到其它产品中的紧密度是它能否生存的关键。在这一点上,GPL 许可证的项目都游走在生死线上,因为它们很难把自己作为一个非 GPL 产品的不可分隔的一部分。只有 Linux kernel 可以算一个例外,因为 kernel 的特殊边界保证了它可以集成到其它非 GPL 产品中。而 LGPL 和 BSD 的项目就可以生活的好一些。诸如 MySQL 之类的产品,GPL 版只能作为独立的服务集成到商业产品里,自然不如 SQLite 这样能够被无缝集成的项目发展得好。