Archive for the ‘Mac OS X’ Category

唯实用主义

2010/04/11

Joel 的 Blog 是个很有意思的关于软件开发的网站。里面有不少睿智的看法,比如『抽象泄漏』。但是有时候 Joel 的实用主义实在有些过度。只要两个方法都能解决同一个问题,就对它们进行无限制的无差别化,这就失去了从更高层次分辨优劣的能力。比如这篇 2003 年的《Biculturalism》,试图和 Eric Raymond 的《Art of UNIX Programming》唱唱反调。

试图和《Art of UNIX Programming》唱反调的 Blog 不止这一篇,比较新的还有这篇微软雇员写的。我觉得,以一篇 Blog 的篇幅想挑战一本书的观点,前者天然的处于下风,除非能一针见血的指出后者根基上的肤浅。可惜,Joel 的 Blog 只是说 Raymond 的观点稍稍过于执着;微软雇员的那篇则是在自设了很多假定的前提下玩逻辑游戏。如果你只是想说对手的观点稍稍有些过激(而不是全然推翻),或者想玩复杂的逻辑游戏,在篇幅和对手差一个数量级的情况下大多是自取其辱 —— 你很难有比对手更细致的分析,也很难把一个逻辑推导所需的前提假设的历史根源阐述的比对手更清楚。

回到 Joel 的这篇《Biculturalism》,开篇头一句『By now, Windows and Unix are functionally more similar than different』。看到这句话,非但没有感到 Windows 和 UNIX 之间的分歧有越来越小的迹象,我脑海里首先想到的就是kernel mode 下的 Windows GDI [1] —— 今天主流操作系统里唯一放在 kernel mode 里的窗口管理和图形界面系统,还有和 GDI 紧紧绑定的 window-based toolkit [2]。也许细心的读者会提醒,人家 Joel 说的是 functionally,你说的差异是 architectural。那么,架构上的差异是否完全对功能透明呢?我看 Joel 自己也不完全这样认为(比如『抽象泄漏』)。就像 Apache Web Server 在 Linux 上缺省采用多进程,在 Windows 上采用多线程 —— 架构上的差异导致的性能差别也会让功能的可用性发生质的改变。

Joel 说,UNIX 的文化在于崇尚为程序员提供便利,Windows 的文化在于崇尚为用户提供便利,我们最终是为用户而不是其它程序员提供产品。说这番话的时候 Joel 一定忽略了 Raymond 书上的引用的 Ken Thompson 的这一句话:

This is a consequence rather than a goal. I abhor a system designed for the “user”, if that word is a coded pejorative meaning “stupid and unsophisticated”.

问题在于,忽略了架构上的简洁的系统,是否还能可以提供完全相同的功能?比如我们提到过的,Windows 把 Toolkit(按钮、下拉框、文本框 ⋯⋯)放到窗口管理的 GDI 架构中,每一个控件都是一个窗口,其中还夹杂了直接访问硬件显存的 Direct Draw 窗口;而 UNIX 的窗口管理器一般采用 Toolkit-Agnostic 方式,窗口管理器只知道窗口的内容是一组二维像素,其中的 Toolkit 完全由应用程序空间的代码绘制。『实用主义者』会说,哪个用户会关心这些?不过,用户总是有设计者原本意想不到的应用场景。比如,今天的应用软件很多时候都加入了平滑动画功能 —— 当窗口的内容改变的时候,软件会显示一系列的中间状态。像图片旋转 90 度这样的操作,往往会短暂的显示中间的旋转状态。通常在旋转的过渡状态矩形图片的角会超出显示区域(因为我们都知道矩形的对角线比边长)。在一个 Toolkit-Agnostic 的窗口管理方式里,因为图片不过是一个像素组,超出的部分会被自动 clip。而在每个控件都是独立窗口的系统中,像 Direct Draw 这样的浮动窗口的显示在这种情况下是很难不显示出丑陋的一面的。如果这个例子只是个美观问题,那么用户还有更为严肃的应用场景。现在使用 VNC 一类的远程控制软件的用户很多。VNC 这类软件在 Linux 和 Mac OS X 上很少出问题。因为 VNC 对于传输基于像素的 Toolkit-Agnostic 方式的显示处理方式单一。而在 Windows 上,VNC 程序遇到不同类型的控件就会有很多问题 [3]。

所以,当 Joel 声称 Windows 和 UNIX 在功能上趋同,而且 Windows 更注重最终用户的时候,他既忘记了自己提出的『抽象泄漏』理论,又忘记了用户总会用意想不到的方式来使用你的软件(stupid and unsophisticated)。这也是唯实用主义在嘲笑别人热衷于设计的内部简洁的时候总是犯的两个错误。

脚注

  1. 关于 GDI 处于 kernel mode 这个问题,按照一般的思路人们会归于旧的 PC 硬件的性能遗留的历史问题。有趣的是,事实正好相反。在 NT 4.0 之前 GDI 是在 user mode 的。
  2. Window-based Toolkit,即每个控件是一个独立的窗口。这样的设计下,窗口管理器必然介入控件的层次关系的管理和消息的分派。与之对应的是 Toolkit-Agnostic 窗口管理,即窗口管理程序不了解控件是如何存在的。整个窗口内容是一个像素矩形区,比如在 Mac OS X 里,控件完全由 Cocoa 或者 Carbon 库生成和负责 sub-window 级别的消息分派(这两个库连接到应用程序的进程空间)。UNIX 的 X 是提供一定程度的 window-based toolkit,不过现代的 Linux/UNIX 程序一般建立在 GTK/QT Toolkit 之上,绕过了 X 本身的控件,此时 X 也作为一个 Toolkit-Agnostic 管理器存在。
  3. 当然这不是 VNC 协议本身的问题,而是具体 VNC 程序实现的问题。不过关键在于 Windows 的架构让 VNC 程序的实现和测试比 Toolkit-Agnostic 下的 VNC 程序更复杂也更费时费力。

Mighty Mouse 小球挂掉了

2010/02/25

Mighty Mouse 上的小球是出名的容易脏。今天中午我的无线 Mighty Mouse 的小球是无论如何清洁都不能向下滚动了(办公室的 Mac Pro 的有线鼠几个月之前就已经如此了)。下午赶紧在 Apple Store 的网站上约了一个维修。心里还在盘算会怎么处理。这个鼠标买的比较早,可能已经过保了。也许打开清洁一下,或者干脆不给修了?

晚上去了 Apple Store,被告知只要有在保内的 Mac 就能保修鼠标。说是保修,其实也就是给换了一个新的。先前的那个用了一年多了,平时也没怎么保护(起码没像对 MacBook Pro 那样),使用痕迹还是很重的。想不到能很干脆的被换了一个新的。

只是不知道公司的那个有线鼠怎么才能通过层层官僚享受 Apple 的保修呢?(好多同事的小球不能用的 Mighty Mouse 都被 IT 换成 Dell 的鼠标了。不过似乎有人更喜欢 PC 的鼠标。)

另外,今天在店里用了一下最新的 Magic Mouse,觉得挺好用的。没发现有什么有悖人体工程的地方 —— 有人觉得作为鼠标 Magic Mouse 太扁了,其实 Magic Mouse 更像一个本身会移动的 Touch Pad,转换观点之后就不觉得有什么难用(有人会觉得自己的 Touch Pad 不够隆起吗?)。有人说 Magic Mouse 移动速度太慢,我在店里的 iMac 的配置上也没觉得,和我机器上的 Mighty Mouse 一样快(我机器上的鼠标配置一般都是比较快的)。看来我要攒钱在这个新 Mighty Mouse 的小球挂掉之前买个 Magic Mouse。

大部头与网路短篇

2010/02/20

新年前后开始打算学习 OpenGL。按照惯常的思路:像 API 出了小毛病这样小问题靠上网查查资料;像 OpenGL 这样大的主题要找本厚厚的大部头从头到尾慢慢研究。于是我从家里的书架上取出一本搁置已久的《Interactive Computer Graphics: A Top-Down Approach Using OpenGL》 —— 这是我的另外一个习惯:看到感兴趣的先买下来,不管有没有时间学,也不管当时兴趣能持续多久,于是书架上会有不少这样的『储备』(可谓『高束焉,度藏焉』)。不过很多『储备』也并未一直束之高阁下去,日后一旦兴趣恢复也会阅读。所以坚持想买就买的原则,不怕买错也不怕买了不读。

当初买这本《Interactive Computer Graphics: A Top-Down Approach Using OpenGL》之后发现 Windows 对 OpenGL 的支持日薄西山,一下子没了兴趣。不料多年之后转到了Mac OS X —— 对 OpenGL 优化最好的平台之一。也算验证了书不怕搁置的理论。

可惜真正开始读之后发现内容并不理想。这本书是国外大学的教材,有教材的通病 —— 前言和泛泛的介绍罗里罗嗦,而且大部分是以前就了解的知识。好不容易读完了泛泛介绍,后面的内容也不流畅。3D 是一个要求对基础数学和对 API 的了解都很深入的主题。可惜这本书对二者的结合并不理想。讲 API 的时候对需要了解的数学基础一笔带过或者根本不提。也许是因为 3D 实在不是一个很容易在一本书里用一套结构讲述清晰的知识体系。这个主题似乎更需要这样的阅读:分别精读两本书,先完整的学习 3D 的数学工具,比如变换矩阵,homogenous 坐标系,等对数学工具了解透彻再学习一本讲具体 API 的书。

也就是说,当学习的主题越来越脱离具体的产品,学习者就会越来越发现大部头的问题所在。每个大部头都在试图讲解自己的一套体系,但是这套体系会和其它的体系发生很多平行的联系。从根本上说,这是我们需要学校的原因:学校帮助我们组织各个大部头之间的平行联系。另一方面,这也是非计算机专业的人可以通过自学学习计算机科学达到比较高的工作水平的原因,因为计算机科学的各个分支和其它分支的平行联系都比较弱。学习一门计算机科学的分支往往不需要了解其它分支或者需要很少的时间准备一些独立的知识点即可。而像通信、自动化、电力这样的学科分支之间就有比较强的平行联系。

但是无论如何,计算机科学里还是有像 3D 这样与另一门或者几门分支具有较强的复杂平行联系的分支。对于离开学校很久的人来说,学习这样的知识要比学习运用一个数据库或者服务器产品困难很多。

不过我不想再体验学校的学习方式,转而尝试一下应用更多的网络资源 —— 这算是我离开通信行业进入软件业之后养成的一个新习惯。一方面,网络上像 Google 和 Wikipedia 这样的工具在三年前离开通信之后有了很大发展,另一方面,软件行业面临的跨系统的希奇古怪的问题比通信业多出十倍。在通信行业能用官方的文档解决大多数问题。而在软件业里遇到的种种问题,没有 Google 这样的工具是不可能解决的。

结果相当顺利。第一个例子是 OpenGL 的投射矩阵(projection matrix)。《Interactive Computer Graphics: A Top-Down Approach Using OpenGL》对这个概念完全没有任何解释。只是对 gluPerspective() 的效果做了一个简单的介绍。gluPerspective() 只是投射矩阵的一种特例(从它的 glu 前缀就能看出来)。单单介绍这个 API 的作用根本不能理解投射矩阵的意义,而用 Google 搜索『OpenGL transformation』能找到这篇文章,介绍了投射矩阵在整个 pipeline 中的位置;顺藤摸瓜即可找到对投射矩阵的详细解释 —— 而不了解投射矩阵的一个重要相关概念 homogenous 坐标也没有关系,因为这篇详细解释的链接里就指向了这篇。这种超链接的组织是 HTML 的本意,不过整个网站的 wiki 风格说明在 Wikipedia 成功之后,这种知识组织方式才越来越被更多人运用的更为娴熟而超过了书籍。

第二个例子是 OpenGL 的选择模式(Selection Mode)。这次书里很遗憾地有很多基本错误。比如对 gluPickMatrix() 何时调用以及调用前后需要哪些其它 API 的辅助的代码例子都有错误;对 glPushName()glLoadName() 的区别也没有任何解释就直接在代码例子里使用了后者。其实 glLoadName()glPopName()glPushName() 共同使用的一个快捷方式。这种对基本操作不加解释就直接利用快捷方式是很让读者迷惑的。Google 查找『gluPickMatrix』的结果指向这篇,对选择模式的解释很令人满意。

其实,在第一个例子中网上资料比书的解释更详细的时候,我就犹豫是继续按照书的结构学习还是完全采用网上的资料。后来还是决定暂且按照书的组织结构。岂料不过一天就又卡在选择模式这个问题上。经过第二个例子,我倒是发现学习资料的组织结构不像以前认为的那么重要。以前我选择书籍最看重的是组织结构里注重知识的循序渐进,减少或者消除前向引用 —— 就是前面的章节引用后面的章节介绍的概念(哪怕是说明了必要性也很令人不快)。而今天参考网上的资料,不用理会原本的结构如何,遇到不太懂的概念只要在网上顺藤摸瓜即可。

回顾这几年的学习经历,Google 和 Wikipedia 已经彻底的改变了我的学习方式。相比网络上的资料,大部头有更强的知识集中、系统性和结构性。可是这种结构性是出于作者对读者背景和理解力的主观预测。结果往往是详略和节奏都和读者期望的不同。特别是像 3D 这样把数学和具体 API 结合起来的,在不同分支之间有平行联系主题,如果仅靠阅读大部头就需要从头到尾阅读多部,然后主要凭借记忆自己建立起平行联系。

网络上的知识一般被认为是零乱而且缺乏组织的。不过这些年已经不知不觉发生了巨变。首先是个人 blog 的发展让很多作者把网络资料的水准从 BBS 的帖子提高到了出版物的水平。再有,Wikipedia 这样的网站在本身收集知识的同时,也把组织知识的先进方式公开给了所有知识传播者。让 HTML 在传播知识方面有了高层次的范例。最后,Google 让所有这些知识片段按读者的自我需求动态组织成为一个个较大的主题。这三者缺乏任何一种都不可能让网络知识的水平达到传统书籍的水平。而现在的情形是它们的协作已经至少在工程技术领域让网络化组织的知识已经基本可以取代长篇的出版物。

iPad 的独特用处

2010/01/31

Apple 的 iPad 终于如期公布。不过和同事聊起这个东西,我们的第一感觉不约而同是『没什么用』:携带不方便,在随身负荷方面,MacBook (Pro) 和 iPhone 都没有挑战人们已有的习惯(相对计算能力而言),iPad 可谓又一次挑战(如果 Newton 算是一次失败的话),能成功吗?没有键盘的意义是什么呢?是为了没有键盘而没有键盘吗?键盘在我眼里仍然是人机接口里带宽最高的设备。要想超越键盘,除非对人脑的工作原理有更深一步的了解,让电脑直接越过手指理解用户的意图。

搁下这些问题,先忙其它俗务。今天和久不见面的亲戚聊了一晚上,发现话题格外多。先是聊到太空旅行的一个问题争论不下,用 iPhone 查到了资料然后再展开讨论了一番。然后给他在 iPhone 上看了最近写的几篇 blog。最后是一个智力休闲的 iPhone 游戏,不多的两三关花掉我们今晚最后的半个多小时。开车回家的时候我突然想到,如果 iPhone 的屏幕再大一些,对这次聚会来说绝对是一个完美的工具!只是屏幕再大一些,不要键盘,也不需要复杂的 content-creating 应用,我需要的就是这样一个大号 iPhone。如果有人觉得理由不够充分的话,可以想想是否在咖啡厅见到过两个人并排挤在一台电脑之前的情景,或者几个人把一台笔记本电脑傻里傻气地转来转去。

我和身边的人总是不约而同的说,iPhone 是应急计算的理想工具,比应急计算更复杂的任务则完全可以寻找应用笔记本电脑的工作环境。这是百分之百的真实感受 —— 只不过因为我们太习惯独处了。我们一贯的印象是电子设备和社交是互不相容的 —— 还记得大学的时候在宿舍看别人打游戏是多么傻气的行为,看别人在你面前写短信是多么讨厌。作为一个独处的用户,我们的第一反应是没人需要仅仅是屏幕大四倍的 iPhone。但是当生活不是独处的时候,iPad 让线下社交和网络拥有了一个合理的结合点。

看到电影里的微软 Surface 设备,我们都会觉得用它来进行任务布置是多么流畅,但是也毫不留情的嘲笑在现实生活中谁会花费如此不菲的价格,谁又能保证和朋友的所有聚会都在布置了 Surface 的私人沙龙里进行?iPad 正是 Apple 完美的回应。

所以说,什么人类不应该习惯垂直显示,什么指向设备和屏幕 cursor 分离,在我看来都不是使用平板电脑的迫切理由。而 Apple 的 iPad 发布演示也忽略了线下社交这个情景,可能老乔太喜欢唱独角戏了(不过他演示 iSight 的时候和部下们合作过一次),也可能线下社交的热闹场景拿到演示台上太过风格诡异。总之,我们太喜欢面对屏幕通过网线交流,或者面对面为一个能两秒钟就 Google 出来的问题争论一个小时和因为一个没法记起来的细节而中断话题。互联网要么是我们的隔阂,要么是遥不相及的东西。iPad 是一个改变我们生活方式的机会,是真正让互联网回归帮助而非阻碍人们交流的角色。只是把 iPhone 扩大到四倍,就是改变生活的工具。

Snow Leopard 的修正准则

2009/12/22

只要应用程序够复杂,每次操作系统升级都会弄坏几个本来工作得好好的功能,有些是应用程序本身代码的问题,只是在系统升级时才突然现身。这些 bug 在旧的操作系统上深藏不露,系统一升级才兴风作浪,大多因为操作系统的 API 行为变得更『正确』了。

升级到 Mac OS X Snow Leopard 之后我们的应用程序就出现了两个这样的 bug。第一个是应用程序 fork 之后立刻在子进程里调用 File Manager 的函数。跑了几年都没问题的代码,一到 Snow Leopard 上就僵死。第二个是 MPEG 的播放代码,同样是用户使用了很多年都没有问题,一到 Snow Leopard 上播放的视频就不停的闪烁。原因都是应用程序调用 API 的时机不对:File Manager 必须在 exec*() 函数调用之后才能使用;用 Core Video 配合 Quick Time 播放视频,必须在取出后一帧之后才能销毁前一帧。这些限制在 Leopard 版的文档上就有说明,幸运的不幸的是,系统『恰好』缓存了某些数据让不恰当的 API 调用『碰巧』能工作,掩饰了旧代码中的错误。显然,我们可以猜到,程序在 Snow Leopard 上才初次显露问题是因为新的操作系统去掉了那些缓存。

这样看来,OS X 岂不是自寻烦恼?旧的 API 行为在原来的应用程序里工作得好好的,何苦修改呢?大致猜想有这些原因:一是缓存让 API 的实现复杂化了。加上一个缓存,就得再加上一套代码保证缓存和真实数据的同步(或者在无法同步时清空缓存)。二来,因为缓存不能完全替代获取或者计算真实数据的代码,所以缓存、维护缓存同步的代码、计算真实数据的代码要同时占用内存。应用程序的活跃部分的体积会增大,原来能适合 CPU cache 的部分可能必须在主存和 cache 之间换进换出,本来为了提高效率的缓存却适得其反。最后一点,缓存让 API 行为变得不可预测。一段『碰巧』能工作的代码意味着当它碰巧不工作的时候更难被监测到和修正。

这些 bug,或者更广泛的说,所有系统升级中暴露的遗留代码的问题都说明了软件(特别是 API )设计的一个陷阱 —— 性能和 API 的确定性行为哪个更重要。结论是,确定性行为不仅更重要,而且往往带来更简洁的设计和更好的性能。

这是『修正准则』(Rule of Repair)的变形实例。Leopard 里的 API 不完美的修正了应用程序的错误输入(这里的输入是调用时机),引入了更复杂的实现和不确定的行为。Snow Leopard 去掉了缓存,消除了 silent success,但是 failure 不够 noisy。结果是功能被搞坏,新出现的 bug 诊断起来也很困难。所以,不要只在文档里说明你的 API 需要什么样的 precondition。如果在你的 API 文档里出现这样的文字 —— 在某某条件不满足的情况下调用某个 API 的行为是不确定的,那你就得三思。应用程序的作者不可能有时间细细品味文档,他们只会在出问题的时候(或者更窄的范围,程序崩溃或者挂起的时候;甚至没有人会注意你的 API 会有内存泄露,即使瞥一眼 Activity Monitor 就能看到疯狂的增长)才匆匆翻阅一下。所以,你的那些告诫会被人忽视数年。直到有一天你决定清理一下你的 API 实现,让它变得更简洁。这个时候那些告诫才会出来咬人。那么,亡羊补牢的方法是用一个 crash 来表达你的告诫(那样会在 debugger 里清晰的显露出出错点),而不是听任程序出现任何可能的行为(比如僵死和闪烁)。

不论如何,Snow Leopard 毕竟简化了实现,尽管做得不完美,起码比打着向后兼容的口号,为了让几个 killer-app 的 buggy code 正常工作而保留丑陋的实现然后把文档变成让人无法理解的技术诉讼书(或者辩护书)更好。

战术核弹

2009/12/01

这个啤酒挺劲!照片也挺有意思。

Wikipedia Thanks to Me[2]

2009/11/30

给 Wikipedia 捐款还有另外一个源渊。一年半以前完全转向 Mac 的一个重要原因也是因为 Wikipedia 。当时我对技术比较沮丧,每天都沉浸于阅读二战史。不过刨根问底的深度优先式阅读习惯没有改变,遇到不懂的名词都要上 Wikipedia 查查 —— 和技术文章相比,阅读二战史还需要更多的访问它。因为偶尔在公司里阅读或者手头只有 iPod touch,所以会用 Mac 或者 iPod touch 来访问 Wikipedia。结果发现它的页面在 OS X (或者 iPhone OS) 上渲染得比 Windows 上漂亮很多(后来和志岩聊起此时,尽管认为不少网站都是如此,但我们都同意 Wikipedia 的差异更明显一些)。

以当时对技术沮丧的程度,我已经认为自己不会在业余时间研究技术了。加上使用 Wikipedia 的感觉,我决定买一台给『非技术用户』使用的电脑。这算是 Wikipedia 带来的一个小小转折。当然后来 MacBook 一到手,接触到 UNIX、Cocoa、OpenGL 这些东西,对技术的感觉突然似乎又回来了。

顶置菜单

2009/11/11

通常,OS X 应用程序的主菜单位置是让刚刚接触 Mac 的 Windows 用户感到新奇的一个地方。主菜单不从属于任何一个窗口。所有应用程序的主菜单都在整个屏幕的最上方显示,同一时刻不可能有多于一个应用程序的菜单显示。所以 active 程序切换的时候会产生 Windows 上没有的菜单切换现象。

对于顶置主菜单与窗口主菜单的优劣,Apple 的 Bruce Tognazzini 从用户交互的效率和速度为 Mac 的风格做出了辩护。当然也有人进行了针锋相对的驳斥。在我看来,在这场围绕 Fitt’s Law(或者说指向设备 —— 即鼠标的操作速度和效率)的争论中,双方至多算打了一个平手。我自己将近三年的 Mac 开发和使用得到的感觉是:主菜单和窗口的逻辑关系要比鼠标的指向速度重要的多得多。

至少有三个原因让我认为主菜单应该和窗口分离。第一,主菜单重在一个『主』字。基本上应用程序能完成的所有功能都应该在主菜单中能够找到对应项。所以,主菜单中必然包含非针对某个窗口(窗口无关的)或者针对多个窗口的操作。最为典型的是『 Windows 』菜单 —— 列出所有打开的窗口,控制窗口的排列。让主菜单这样的 UI 元素从属于某个窗口 —— 一个后果就是每个窗口都有一个 『 Windows 』菜单 —— 从逻辑上说是十分怪异的。第二,在 93 年左右或者更早接触过电脑的人一定知道当年著名的 MDI 模式。也就是 Word 5.0 那种模式 —— 一个大的窗口包含所有的文档窗口。这似乎是 Microsoft 在不违反 Windows 本身的主菜单必须依附于窗口的前提下,尝试让主菜单脱离文档窗口的一种尝试(当然这不是 MDI 的唯一目的,但是我认为从逻辑上看这种考虑是有的)。遗憾的是,MDI 的大窗口带来的用户体验似乎是很糟糕的,让 Microsoft 不得不放弃尝试让 MDI 作为主流。随之而来的是几种不成功的尝试:Visual Basic 6.0 把主菜单做成单独的窗口(也为 Delphi 和某些版本的 Photoshop 采用);Visual Studio 把所有窗口用 dock tab 来实现(UltraEdit 类似)。前者类似 Mac 的风格,但是在处理主菜单的显示上明显没有 Mac 的置顶方式简洁,基本可以认为是 Mac 风格的胜利。而后者并非完美的方案,因为在有了 tab 的情况下用户往往仍然需要窗口级别的组织才能更好的同时处理更多文档。

第三,今天的程序往往很多功能并不体现在窗口上,所以很多程序有了一个特殊的状态 —— 没有任何窗口打开,但是程序仍然保持后台功能的运行。这个状态可以叫做 stealth 模式。MSN、Adobe Bridge、很多浏览器、播放器都有这种状态。但是如何从 stealth 模式恢复成普通模式?当主菜单依附于窗口的时候,进入了没有窗口的 stealth 模式时主菜单对于返回普通模式就无能为力了。在 Windows 下,开发者必须编写支持托盘图标(Tray Icon)的代码来支持 stealth mode。而在 Mac 上,几乎任何程序都天然的拥有 stealth 模式,无需一行特别代码来实现 —— 没有编写的代码就是 bug 最少的代码,也是最快的代码。Mac 程序在 stealth 模式下仍然可以拥有一个全功能的菜单(当然不合理的菜单项会 disable),而不是托盘图标提供的一个粗陋的菜单。Mac 的菜单切换也变成了在 stealth 模式下更好的隐藏程序从而不影响用户使用其它应用的一个优势,这是前面说的那种 Visual Basic 风格的独立窗口主菜单不具备的。

而且,今天的应用程序更多的把主菜单作为一种完备性的体现而不是用户体验的主要组成部分。快捷键,工具条,拖放,图标,右键菜单(context menu),乃至于 multi-touch pad 的 gesture;这些才是用户体验的集中体现。所以,主菜单的设置合理更多的在于逻辑关系的正确和清晰,主菜单是用户发现功能的场所,而不是使用功能的主要场所。不是 Fitt’s Law 体现的速度和效率的主要舞台。所以,即使在 Fitt’s Law 方面各执一词,在菜单切换现象上略有劣势,我仍然有理由认为 Mac 的菜单风格胜出。

Universal Binary

2009/11/09

最近 Ryan Gordon 终止了在 Linux 上实现 Fat Binary 技术的努力。这让我和甫鸼展开了一场对 Universal Binary 技术的讨论。

Universal Binary 的鼓吹者一般认为在连接器和操作系统底层直接加入对多 CPU 架构的支持( multi-arch support )可以大大简化开发者的负担和提高最终用户感受到的易用性。反对者则可以反驳说把能放在 build 脚本和 installer 中的 policy 不必要地塞到底层违反了 Unix 的原则。

抵触最大的恐怕是 Open Source 社区。毕竟这次 Ryan Gordon 放弃的直接原因就是 Linux 社区对其努力的冷落。Open Source 的发布方式可以戏称为 Universal Source —— 程序的大部分由 CPU 架构无关的源代码构成,少部分 CPU 构架相关的代码由条件编译构成或者由 build 脚本根据执行 build 的平台条件来引用不同的源文件。经由 Open Source 的安装三部曲(configure-make-make install),就能安装好一个适合目标硬件的精炼的无冗余的程序。这样的发布是完美的 —— 除了不能兼容非 Open Source 的商业模式。

要应对多 CPU 架构,就必需在程序的发布或者运行中实现应对这种架构的智能。Open Source 模式的优势在于它的发布形式能够完整的体现开发者在编写代码时就考虑到的一切应对 multi-arch 的智能,然后在安装过程中(也就是 build 过程中)由编译器、连接器和 build 脚本的强大智能来完成部署。非 Open Source 的发布模式则必须另辟蹊径。在所有面对 multi-arch 这个难题的企业中,Apple 当年的问题最为紧迫。由于要完成从 Power PC 到 Intel x86 的迁移,Apple 必须在一个时期之内能给开发者和用户一个足够简单(甚至傻瓜化)的解决方案。尽管如 Linux 社区大力主张把 multi-arch 的智能放到 build 脚本和 installer(Linux 的 package manager)为正途,而 Mac OS X 在很多方面也确实遵循 Unix 的文化,最终 Apple 还是选择了 Universal Binary 。因为我每日都在 OS X 上开发,已经习惯 Universal Binary,到了有所偏爱的程度。

为了比较各种方案的差异,设定一个例子。有一个工程,在单一 CPU 架构的时候生成四个可执行文件,六个数据文件,一共十个文件,为其增加 multi-arch 支持。假设我们拥有一个不支持 Universal Binary 的操作系统。当然相应的编译器和连接器也就不会支持 Universal Binary。但是我们仍然假设编译器和连接器的交叉编译功能很方便,只要一个 Option 就能切换目标文件的 CPU 种类。也就是说支持不同 arch 的基础能力具备,只是没有对 Universal Binary 的支持。那么,为了服务使用不同 CPU 架构的用户,我们就必须面临几种选择。第一是我们使用一套标准的工程文件,针对需要支持的每种 CPU 架构,用相应的编译器 Option 编译一次。假设有三个 CPU 构架。因为是采用完全相同的工程文件,仅仅是一个编译目标的 Option 不同,所以我们会生成 30 个文件(三个版本,每个版本十个文件)。使用不同 CPU 的用户可以选择不同的版本安装。这样的方案,build 过程中对六个数据文件(假设是平台无关的格式)的生成就是冗余的。对于拥有多于一种 CPU 架构的机器的用户来说,需要下载相应的多个版本,所以重复下载了六个数据文件(重复的次数由用户拥有的 CPU 种类决定)。

另一种选择是我们写一套特殊的工程文件。一次生成支持三个 CPU 的 12 个可执行文件(每个架构四个),而对于平台无关的六个数据文件只生成一套。所有的结果都打入一个 package。相应的,installer(或者说 unpackager)必须针对用户安装时的平台提取正确的可执行文件版本和数据文件。这种方案的缺点是必须自己在 build 脚本和 installer 里编写处理 multi-arch 的智能。Universal Binary 的反对者会说这些智能并不复杂,Build 脚本和 installer 正是实现它们的好地方。但是,实际上每个工程实现这些智能的方式都会有些微的区别。这就意味着,用户从一个产品得到的知识并不能用到另一个产品里 —— 比如检查该产品支持的所有架构,如果是 Universal Binary 的话可以用统一的命令查询。开发者从一个工程转向另一个工程也必须通过文档或者别人的描述才能熟悉这个工程特有的 multi-arch 处理。

对于上述的工程例子,支持 Universal Binary 的系统则是一次生成十个文件,只在四个可执行文件中同时包含三种 CPU 架构的代码(用 ELF 的术语来说是三个 section )。这种方式为反对者诟病之处在于,安装好的系统存在冗余 —— 磁盘上会有用不到的机器码,提高了连接器和操作系统的复杂度,违反了 Unix 的 Mechanism, not policy 的原则。不过,任何稍稍违背这一原则的做法都会带来一个好处 —— 标准化带来的统一性。在《C++与垃圾回收》一文中我曾经说过,正是因为 C++ 把各种高级内存管理策略放到了库而不是语言级别,才让这些高级策略不能得到强制统一而形同虚设。所以,mechanism-not-policy 需要灵活的「道」来处理而不能一味的极端化。

Universal Binary 其实可以看作一种次优的方案。它的冗余度没有达到最优(与定制编写 build 脚本和 instaler 相比),但是大大低于第一种方案。即使存在冗余度,冗余的粒度也很小,属于次文件级别(ELF 格式为例,即 section 级别)。而且这个冗余度也带来了一些智能 installer 方案不具备的好处,就是安装之后的程序可以在不同架构的 CPU 的机器之间自由拷贝(前提是程序具备这种在任意位置运行的能力,而 Mac OS X 的 Single-File Application 恰恰是这种风格)。Universal Binary 还是一个非强制性使用的功能。连接器和操作系统只是提供支持 multi-arch 的能力,选择打入哪些 CPU 架构的执行码完全由开发者自己决定。从这个角度来说,Universal Binary 并不是真的违背 mechanism-not-policy 的原则。Universal Binary 产生于 Mac 的特殊历史,也与其文化相辅相成 —— Single-File Application,也就是 installer 完全零智能;无中央化的包管理(通过 Guide 来保证用户体验的一致);相当数量的用户(由于 transition 阶段的存在)拥有或者在一段时间拥有基于不同 CPU 架构的多台机器,需要程序能够在机器之间自由拷贝并保持工作(由于 Single-File Application 的文化)。

Universal Binary 是一个能提供有别其它方案的独特优势的方案,当然也引入了自身的代价。综合考虑,这些代价在 Mac 特有的文化中是完全可以接受的,也就是说 Universal Binary 在 Mac 的发展中是最好的选择。至于这种收益代价的比例在其它操作系统上如何还有待考验。不过我认为其它系统应该保持一种开放的态度。

MacBook Pro 图标

2009/10/10

这几天实在是太忙了,这种状态还要持续一段时间,不过也不会很长。没时间写长的东西。

今天无意发现 OS X 里面 MacBook Pro 的图标很精细(也就是在我的 MacBook Pro 上表示本机的图标)。似乎是严格按照 15 寸的 MacBook Pro 外观设计的。不知道在 17 寸的机器上表示本机(或者在网络上表示 17 寸 MacBook Pro)的图标是否会体现出机型之间的细微差异(还有在网络上能否识别对方是 17 寸的机器)。