Archive for 2010年1月

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 扩大到四倍,就是改变生活的工具。

Chrome OS 为什么不会成功

2010/01/24

与朋友聊起 Chrome OS,一多半认为这个东西不会有什么前途。让我自己觉得 Chrome OS 没有前途的原因不是别的,正是那一小半对齐心存希望的朋友的理由和 Google 高层自己的言论。

觉得这个操作系统大有希望的人一般认为 Chrome OS 的目标不是完全替代主流操作系统,而是在 netbook 等设备上取得主导地位,所以作为一个有特定目标用户的系统有可能成功。Chrome OS 的主要策略就是,把 Google 认为人们不应该不会在 netbook 上使用的应用(也就是非 web app 喽)统统割掉,借此达到安全和简洁。对于 Chrome OS 功能方面的质疑,Matthew Papakipos(Chrome OS 项目的领导人)说:『如果希望用 Windows,你应该购买 Windows 机器;如果希望运行 Photoshop,你应该购买 Windows 工作站。』没错,这句话一点问题都没有。但是,也许 Matthew Papakipos 忘记了一个事实 —— 任何市场的划分都有灰色地带。从 netbook 到 laptop 到 desktop 到 workstation 的划分是一个配制逐渐增高的过程。其中每个机型的高端用户都会有一定的需求运行高一级机型的低端甚至中端应用。Chrome OS 的策略是忽略了这一点,看到 netbook 上 99% 的人都用 web app,就把自己的功能局限在 web app 上,而且这条线是硬性划分的 —— 你想当 Chrome OS 用户吗?好,你的一切运行本地应用的机会都被硬性取消了。这种做法缺乏对用户需求的基本认识。一个产品可以针对特定的用户群,但是永远、永远不能在功能上针对某一个用户群进行一刀切的操作。

可能有人会说,Windows 也有客户版和服务器版,Photoshop 也有标准版和 Extend 版。不错,但是 Windows 的客户版拿掉或者加上一个 feature,本质上还是 Windows。如果 Chrome OS 要加上一个 web app 以外的 feature,还叫 Chrome OS 吗?没有『自我阉割』式的独特性,Chrome OS 不过是另一个普通的 Linux 发行版而已。Chrome OS 把自己的本质建立在否定用户群的模糊划分的事实上。

哦,Matthew Papakipos 还说了,当你用 laptop 进行需要超过两个半小时的工作的时候,你就得插上电源,而 netbook 解决了这个问题。问题是,如果我要在外边晃荡四个多小时的时候,为什么不能找到一个有电源的地方呢?暂且承认 Matthew Papakipos 的说法是对的,那么,在那些环境需要进行两个半小时以内的无外接电源工作的用户和那些环境允许进行四个小时以上的有电源工作的用户之间有一个灰色地带 —— 环境需要进行四个小时左右的无电源操作的用户。所以,netbook 是有用的。好了,看来 Google 的高层们是明白市场有灰色地带的。那么,在 netbook 上运行 web app 的用户和在 desktop 上运行大型应用(Matthew Papakipos 口中的 content-creating 应用)的用户之间,就没有一个灰色地带吗?没有一个希望在高端 netbook 上偶尔运行 content-creating 应用的人群吗?哦,Matthew Papakipos 的理论是不是这些人应该拥有两台电脑?那么,他的理论是人们能忍受在两台电脑上来回切换,而不能忍受多带一个 laptop 充电器和拔插电源。

可以说,Google 或者很多创新者的成功就在于发现以前被忽视的用户群。而 Google 在 Chrome OS 上的做法恰恰是忽视用户群。自以为是地把用户分成只需要运行 web app 和需要运行其它应用的泾渭分明的两群。当然,每个人都可以有多台电脑,但是如果 Chrome OS 解决问题的方式是增加用户的状态切换(而且是在两台机器之间),那么这种方案又有什么优势呢?从另一个方面来说,专用系统是必要的,但是一个专用系统在服务主要用户和主要需求的同时,必需能够应急的服务一些高端用户的应急需求。也就是说,netbook 操作系统必需在需要的时候能够紧急充当短时间的 laptop 操作系统的功能。很多有经验的用户甚至会在自己机器上装 Apache 一类的软件备不时之需。从技术上说,服务灰色用户群的能力就是技术人员常说的『伸缩性(scalability)』。

Chrome OS 把自己展示成专用系统,又把缺少伸缩性作为自己的特征。Google 鼓吹的安全性和简洁易用在我看来并不是简单粗暴的砍掉伸缩性的理由。我不能证明它没有用户,但是不能想像它会有什么意义上的成功。

调试专用代码的最小集

2010/01/01

不知道别人如何,有段时间我常常想在 code base 里加点儿调试专用的代码(就是那种通常被括在『#if _DEBUG_ ... #endif』之类的宏里面的代码),也许是处于对潜在问题的忧虑,或者纯粹就是让程序看起来高级点儿。不过最后总是发现还是删掉(或者压根不写)它们更整洁。如果善于利用 debugger ,需要额外的调试代码的情况还是不多见的。即使是必需的调试代码,问题解决之后也很少需要用再用到。所以我还是愿意必要时候随手写几个 printf ,然后删掉它们。

把调试专用代码长久留在 code base 里要多留意很多:编码规范、对其它部分的功能和调试的影响,等等。随便写几行日后就删掉的调试用代码则简单的多,不必顾及这些,只要能解决眼前的问题。更重要一点,留在 code base 中的调试代码必须对解决一再被发现的潜在问题真正有价值。只有这类问题,你知道它在未来很可能会被发现,但是现在却属未知。而它被发现的时候,用工具和几行 printf 又无法解决,才值得把调试专用代码留在 code base 中。

迄今为之,考虑到把代码留在 code base 里耗费的精力和调试工具的强大能力,我认为只有一种问题值得事先写一些调试代码:某种内存增长的问题。但是这种内存增长的范围很小,绝大多数内存增长问题都不属这种问题。比如,内存泄漏是内存增长的一个常见原因,但是大多数内存泄漏不需要调试代码。良好的 code review 能去除大多数内存泄漏,统一的内存管理策略(比如普遍的利用局部拷贝和引用计数)也能消除大多数内存泄漏。现代的内存监测工具甚至能把大多数引起内存泄露的代码位置明确标识出来。对大多数内存泄漏来说,良好的设计和强大的工具比调试代码更有用。

真正需要专用调试代码的内存增长问题是进行长时间的操作时内存缓慢但持续地增长,短时间之内幅度不明显,积累起来(比如几十分钟或者几小时之后)不容忽视。这种增长也许不是内存泄露(比如,有的情况里存在问题的长时间操作结束或者被用户中途取消,内存会回复),所以用检测内存泄漏的工具通常无法查明原因。而且,因为是长时间操作,内存检测工具运行的开销经常无法容忍,比如一个运行 20 分钟的 use case ,如果把程序连上内存检测工具,运行时间也许会超过一个小时。只要功能慢慢丰富,code base 复杂度不断增加,一个应用程序迟早会遇到这类问题(一般在 3.0 版本左右吧)。但是当最初写下隐含这类问题的代码的时候,你或者你的前任还在忙于功能测试。就算从一开始就重视性能测试,也很难做到边写大段代码边测试每次耗时 20 分钟的 use case。这种问题用外部工具如同隔靴挠痒,又很难在早期发现,当问题显露的时候,code base 已经相当复杂。因此一开始就得在 code base 里做些未雨绸缪的准备。

准备起来很简单,定义一个 dictionary 类型的数据结构(比如一个 std::map),Key 值是程序里生成的实例比较多的类(或者 struct,如果是 C 语言)名,value 是类的实例个数。维护这个 dictionary 的方法就是在对象的 constructor 和 destructor 里对相应的 value 值加一和减一(C 语言要写一组能接受 struct 名称的 debug 版本的 malloc/free)。然后设计一个操作,让你能在长时间的操作中随时打印这个 dictionary 的内容,比如一个随时可见的按钮或者一个菜单项。而且别忘了把所有这些对用户需求来说多余的代码都用条件编译括起来,别影响 release 版本的性能和外观。

这些调试代码纪录的东西不多,但足以解决问题。如果没有这些信息,解决内存增长就如同大海捞针。一旦有了这些信息,大部分情况下通过显示的实例个数的变化都能找到是哪个对象造成的内存增长,找到这个就如同有了金钥匙,接下来顺藤摸瓜用些简单的断点调试和代码查找就能找到病根。某些工具 —— 特别是在 Java 之类的动态语言领域,可以自动收集这些信息。不过『土办法』有独特的优点。比如,程序里会有很多类或者对象的创建的销毁(或者说『生命周期』)完全被其它类控制,这种类或者对象的实例个数不必被纪录,只需纪录控制它们生命周期的类的实例个数。但是外部工具往往不能区分这点。『土方法』需要你自己动手写点代码,所以很容易区分哪些实例个数是真正需要纪录的而哪些又可以忽略。土方法另一个不可替代的好处是消耗的资源非常少,所以能研究一些本身就消耗资源很多的长时间操作。而那些本身就消耗很多资源的工具遇到这些问题往往没一会儿就让系统僵死了。最后,这个办法对程序设计有一点点(也许是有益的)要求:用 C 或者 C++ 的时候不要直接把 char* 或者 byte* 类型的大块内存到处直接分配和引用,给这些 buffer 封装一个 struct,这样才能在调试中得到更有意义和启发性的名字。

所以,我暂时不打算在下一个项目中写『#if _DEBUG_ ... #endif』,除了上面说的纪录实例个数的代码。