Archive for the ‘软件开发’ Category

从HTML 5想到的

2009/06/04

最近发现很多地方在讨论HTML 5。突然感觉HTML标准照此发展下去,最终可能迫使Adobe Flash这样的plug-in退出Web的舞台(还有微软的Silverlight,如果它能真正登上舞台的话)。

HTML 5是HTML标准化力量(很多人相信是Google和Apple在推动)的一种努力,以争取更强大的,不依靠于私有plug-in的表现能力(expressiveness)来支撑复杂的Web应用。HTML 5为标准还有待时日,但Web在过去几年里的表现能力已经有了大幅度的提升。下图是对不同的界面表现技术从其出现至今表现能力的一个大致估计(这不是我的估计,但是我很同意这幅图)。

这幅示意图说明,界面表现技术(甚至推广到所有计算机技术),都具有不可阻挡的收敛趋势。一开始,不同的技术针对不同的应用范围(比如,界面的发布广度决定了是选择表现力较差的HTML瘦客户技术,还是表现力强的OS native技术;硬件的速度——工作站或是嵌入式移动设备,是另一个决定技术选择的因素;等等)。针对不同应用范围的技术不会直接竞争。慢慢地,不同技术独立的发展让它们的各项指标逐渐接近,各自的使用范围开始扩大,重叠最后重合。竞争最终会得出确定的结果。一种技术——最多两三种——会成为主导。这种趋势揭示了一个事实——虽然竞争能够给用户带来好处,但是用户不会容忍不同技术间无休止的竞争(即使它们有互操作接口,无止境的异构并存也不能容忍)。有人认为竞争的优势体现于迫使厂商不断改进产品;我说这是竞争的初期目的,最终目的是让真正优秀的技术脱颖而出,最后获得胜利的技术必定充分走向公众领域(对于软件来说通常是标准化和开源),从而使后续的发展从竞争推动转变为协作推动。

这幅图还说明,如果一个领域的不同技术之间差异太大,无法做到自发收敛,业界就会选择另外的突破口。Plug-in和HTML/CSS即针对不同操作系统之间界面技术差异过大、中长期之内都无法收敛这个现状的突破口。替代品最初作为被替代品的高层封装,提供功能子集。如果低层接口差异过大、拒绝收敛,高层的替代品功能不断增强,最终会边缘化低层接口。另一方面,如果低层接口收敛的速度超过高层替代品功能增强的速度,高层替代品就会成为昙花一现的技术。例如,因为网络技术快速收敛于TCP/IP,CORBA的GIOP被更具体的和TCP/IP绑定的IIOP完全替代;CORBA本身也被更低层的socket接口替代。

因为操作系统界面技术始终差异过大,Flash才作为OS native的替代收敛途径出现。HTML/CSS作为Flash的替代并不是因为现实的plug-in技术无法收敛(事实是Flash仍然在plug-in领域占有绝对主导),而是因为Flash的私有实现性质导致人们对未来plug-in技术的发散存在恐惧。占主导地位私有技术标准被另一种技术夺取绝对主导地位的可能行大大高于一种公有技术标准。也许,面对HTML 5,Adobe最好的策略是更加开放Flash。作为商业公司,不能无限度的自动开放技术,但是当市场开始寻求替代标准的时候,最好不要心存侥幸。如果HTML 5真的替代了Flash,那时也许Adobe保存Flash这一技术财富的方式就只有推出自己的Web浏览器。如果提前做好准备并且幸运的采取了正确的策略,也许Adobe的浏览器在继承Flash的基础上还能取得一些竞争优势,但是那样付出的努力和代价,以及所冒的风险远远高于通过开放Flash保持其主导地位。

界面开发中的DSL与定制控件

2009/06/02

“中等程度的定制化”太含糊,按俺的观点,基于DSL的方案就适合“不需要自己写控件”的场合,理由很简单:a) 控件质量有基本的保证,b) 不会有过于怪异的用法,比如Windows这边最喜欢干的多次重入同一个消息循环之类的。反过来像游戏UI那种基本上要求100%定制的还是免谈了。⋯⋯

——陈甫鸼对《界面开发中的DSL》的评论

定制控件(俗称自己写控件)被很多人——好吧,被我——看成界面开发领域专业水准和业余水平的分水岭。快速应用开发(RAD)的鼓吹者通常回避讨论定制控件。企业开发和原型产品绝少需要定制控件,而大多数用户天天使用的杀手级(killer)通用软件又绝少能避免引入定制控件。

在开发中对定制控件的需求一定程度体现了UI framework本身的功能和一致性的强弱。出于功能实现的目的必须引入一个定制控件意味着framework的内键控件不能满足基本需求。由于一致性原因引入定制控件一般是因为缺省控件的用户体验达不到需要的水准,这种情况下缺省控件的水准——比如控件的观感(look and feel)——一般在原型产品中可以容忍,但是会阻碍软件达到高可用性。由于功能原因引入定制控件不意味着framework本身的功能一定有缺陷,特定的应用需求不一定适合通过framework提供内键控件来满足。经常由于一致性原因引入定制控件往往意味着framework本身的品质不够精致。比如在Windows下无数的应用使用自己定制的滚动条(scroll-bar),而这种情况在Mac OS X里非常罕见。

UI DSL对定制控件并不友好。给定一个DSL,它与不同的定制控件配合的默契程度取决于该控件的定制程度。比如,如果一个定制控件仅仅修改内键控件观感(基本上就是override on-draw的处理)而保留内键控件其它一切行为,那么它还是可以通过DSL来制定位置、大小等大多数属性。在处理DSL的可视化工具里也能正确显示控件的位置、大小(观感还是缺省的,开发者设计的时候自己想像最终的效果吧)。所以如果系统的内键控件如何具有良好的一致性,就能避免很多的控件定制,从而让DSL能更好的发挥作用。而一个从外观到行为都自己实现,不以任何内建控件(除了基本的panel、group或者canvas)为基础的定制控件在DSL及其处理工具中能被处理到什么程度就很不好说了。

更深一层来说,DSL不能很好的处理定制控件是因为声明式(declarative)语言和控件的命令式行为的差异决定的。由于声明式语言的局限,DSL处理控件时通常自然而然的局限在只处理控件的初始状态。而处理DSL的可视化工具在决定控件的初始状态的时候往往依靠的是本身的知识。比如,一个可视化工具在设计时显示一个对话框和它上面的按钮的时候,这个工具通常并不能通过询问UI framework(比如Win32或者Cocoa)来得知这个对话框和这个按钮要如何显示(这样做通常意味着要运行正处在开发阶段的程序,这是昂贵的而且经常是违背因果的),相反,工具本身需要拥有如何显示按钮和对话框基本状态的知识。

这种方式有一些严重的问题。首先是同样的知识要在控件本身和DSL的定义以及处理DSL的工具中重复体现。保持多个部分体现的一致的知识并非是简单的事情。我们经常可以看到,在新版本的操作系统中开发时旧版本的可视化工具显示的控件和程序真正运行的时候显示的风格相差甚远。其次,DSL无法预测晚于其语言定义和处理工具出现的控件有什么样的运行或者初始状态,这让语言本身及其处理工具只能处理有限集合以内的内键控件。

解决这些问题的努力所知不多。Sun的JavaBean标准曾经做出过努力。JavaBean的方案是在控件的发布包中(JavaBean是一组字节码文件,打包成.jar)加入一组设计时信息(design-time information)。设计时信息包括控件的初始外观等信息,也由声明型语言定义。JavaBean标准对于一个JavaBean是否提供设计时信息是不强制的。在实践中JavaBean的方案并不成功。因为组件的粒度问题,软件组件从来没有像建筑零件那样做到预制化,所以设想中的独立组件市场(resuable component market)从来没有成为现实。没有市场的需求,大多数JavaBean都是开发者为自己的应用内部开发的定制控件,也就没有投资于提供设计时信息的动力。绝大多数内部使用甚至很多出售的JavaBean都不包含设计时信息。JavaBean的做法也没有真正解决重复实现的问题,虽然控件的运行时外在特征和设计时特征由统一的发布包提供,但是在包的内部仍然是由分离的代码来实现的。开发者仍然需要额外的保持多处代码的功能同步。

现在的DSL对定制控件的应用大多数是用占位件(placeholder control)来实现。DSL在定义中加入某些内建组件或者纯粹的占位件(例如,在可视化工具中表现为简单的矩形)。代码在运行时讲控件替换为定制控件的行为。由此,DSL可以定义定制组件非常有限的一些行为。

DSL本身在界面设计中的应用并不完美,对定制控件的处理尤其差。但是一些试图解决这些问题的努力最终因为缺乏动力而失败,从另一方面说明DSL本身的缺陷也许并非界面设计中开发者非常急迫解决的问题。这是我们在软件开发中经常遇到的一种情况:某种问题被提出,人们付出了相当的努力试图解决,但是提出的解决方案最后被废弃,虽然有事是因为解决方案自身的缺陷缺陷,但是更多的是因为使用者缺乏被采用这些方案的动力。这说明问题的提出过于理论化,问题的提出者高估了该问题带来的不便或者低估了人们对该问题的容忍程度,而解决方案本身的不完美又无法被其带来的益处所抵消。(说句题外话,文件系统领域,对于粒度比UNIX的user-group策略更细的permission control的需求就是这样一种问题,当理论者提出并试图用访问列表ACL来解决这个问题的时候,却发现大多数系统管理员都认为user-group策略是足够的而缺乏配置ACL的动力。)

界面开发中的DSL

2009/06/01

没有NIB文件的Cocoa应用是可行的,但是非常困难。⋯⋯在开始接触Cocoa的20年间,我几乎每过3个月就会看到有人讨论这样的问题。

三个阵营欢迎你的加入:
1. IB一开始是令人痛苦的,但是你会渐渐离不开它。
2. IB起码聊胜于无,不要白不要。
3. IB有利有弊,逆来顺受吧。

上面是从一次关于Cocoa XIB(以前称为NIB)的长篇邮件列表讨论中摘出来的两段。尽管这个题为《Writing Cocoa apps w/o using Interface Builder》的问题错误的发到了Xcode列表(本该是Cocoa-dev列表),而且其间也不断的有人指出这一点,但是没有阻止每个回信者(包括第一句就指出问题发错了地方的)都慷慨的发表对XIB的看法。最终有30人次发言,不说Xcode列表本来就没有Cocoa-dev热闹,即使是Cocoa-dev列表也少有这么吸引人的话题。这种情景让我想到专用语言(domain-specific language)在界面设计中应该如何应用仍然是一个未有定论的话题。

我的早期接触

我接触的第一种DSL是Windows的资源文件(.rc文件)。当时看来——现在也多少如此——在纯粹的C/C++环境中引入一个语言异类把开发过程搞得非常难以理解,令人厌恶而恐惧。直到发现资源文件描述的东西用C API也统统能实现,对它的恐惧心理稍减,厌恶感更甚。这种DSL简直是毫无用处,多此一举。

除了SQL和IDL之外,我接触的第二种DSL是北电内部开发的一种把一种类型的Java对象转换成另一种Java对象的语言。当时的研发中心还不正式属于北电,开发过程也不太严格,程序员可以对很多事情自作主张。在发现Java的代码也能提供这个DSL的功能之后,我马上下定决心不使用这个DSL(因为没有办法完全绕过,最终写了一个非常小的不做任何转换的DSL文件)。结果是非常令我满意的,那些用了这个DSL的同事除了无穷无尽的bug,什么也没得到,因为这个DSL不光需要编译,而且要先编译成Java代码,再编译成bytecode,既失去了调试所需的透明度,又没有节省任何编译时间。

这就是DSL在我的早期开发生活中给我留下的印象。严格的说,引起我反感的不是全部的DSL,仅仅是具有下面特征的:首先,这种DSL是声明式(declarative)语言,而不是命令式的(imperative)。它们不是图灵完备的,只能根据预先定义的一些规则(rules)来粗糙的描述使用者要达到的目的,而无法精细的控制每一步(step);其次,它们的所有功能,至少是绝大部分功能,都能被某种命令式语言替代。比如Windows资源文件的功能能够被直接调用Win32 API的C代码完全替代。从这个方面来说,我并不反感像SQL或者IDL这样的DSL。因为它们是为了完成命令式语言无法很好完成的工作而设计的独有接口,并非仅仅是命令式语言的某种速写符。

在接触DSL很久之后才了解DSL,decalratvie和imperative这样的名词。一开始的概念就是“只有程序代码最好”。

DSL与UI

Java Swing是众多UI framework里面完全不使用DSL的一个罕见的例子。这是Sun追求100%纯Java的副作用。这样做的好处在于程序员不用再学习专门的DSL。 程序的编译和分发也更加简单统一。带来的问题是定义UI的代码十分冗长。特别是命令式语言和UI定义的对应方式不直观,这样用可视化工具产生的UI代码除 了本身比手工编写的代码更为冗长之外,每种可视化工具产生代码的方式也不相同。用命令式的步骤来理解产生的UI效果,让程序的可读性很差。

用命令式语言表示UI的问题,也就是引入UI DSL的初衷。尽管有类似北电那种情况的特例,DSL的绝大部分都是在图形界面设计方面的。这是由图形界面设计的本质决定的。UI的布局部分很适合声明式语言。

Windows的资源文件有了20多年的历史。作为UI开发还不为人们熟悉的时代的产物,当它出现的时候,人们把使用它作为一种新奇的技能,还来不及权衡其利弊。20多年之后,资源文件已经很少使用。它的语法和功能都过于陈旧,只能用来偶尔声明一些比较简单的对话框。对于复杂的国际化和本地化需求,资源文件也是疲于应付。资源文件无法提供具有很强动态特性的UI。后来的framwork一般都拥有运行时的自动化布局功能。自动化布局让纯粹代码表示的UI变得勉强可以接受,不致可读性非常差。这也是Java能够完全放弃DSL的一个必要条件。

现代的UI framework基本都使用DSL,大多采用基于XML的语法。比如GTK和Cocoa。这些DSL从本质上说仍然是代码的速记符——完全脱离这些DSL用纯代码实现的UI是理论上可行的。但是就像一开头说的,完全脱离DSL是不建议的,实际上非常困难的。

DSL的利弊

DSL让程序的代码量,尤其是大体类似的冗余代码量大大减少。减少代码量就意味着减少引入错误的机会和减少维护量。但是同时,DSL让程序失去了一部分透明度。由于失去透明度带来的维护开销是否能够被代码量减少带来的收益抵消是一个很难衡量的问题。

DSL对于版本控制也不是很友好。UI DSL一般由可视化工具生成而很少手工编写。通过可视化工具做出的很小的改动往往造成DSL文件的很大变化。这种“雪崩效应”给版本控制和历史改动的跟踪带来了很大的麻烦。两个方面可以解决这个问题。第一,可视化工具尽可能消除“雪崩效应”;第二,可视化工具支持DSL的双向修改,即经过手工修改的合法的DSL文件可以被可视化工具正常操作,这样开发者在做出很小的改动时可以直接手工修改,出现“雪崩效应”时也可以用手工补救。

DSL对于跨平台开发非常不利。跨平台开发使用的语言绝大多数是命令式的。其一般的策略是把各个平台特有的API封装起来,对上提供统一的接口。由于跨平台部分采用命令式语言,接口本身和支持接口的平台特有API也最好是命令式的才能让整个架构的集成更为自然。所以跨平台开发中对于跨平台接口以下层次中DSL的使用总是极力避免。像Cocoa这样的framework并没有成为跨平台的接口,通常作为平台特有的API处于跨平台接口之下,这种情景中对绕过XIB的需求最为强烈。而对于GTK这种本身支持多平台的framework,这种需求就小得多。

DSL适合中等程度UI定制化。对于UI控制度极高的场合,比如游戏设计,UI DSL没有什么市场,整体UI都是用命令式语言编写。对于一些prototype UI,人们往往习惯用命令式语言调用预设定的alert popup或者简单可控的对话框。

对DSL是否利大于弊我仍然持怀疑态度。但是Java Swing遭到了失败(尽管有其它原因),越来越多的framework采用DSL,可视化工具的更多应用,人们不可避免的越来越习惯和无法脱离UI DSL。