说说我以为的RIA与Rich client

中午徐X和米高讲了一下Rich client的架构。其中徐X讲的是如何从单机分层系统到Rich client。

实际上最早的单机分层系统的UI部分激发了OO作为界面的编程模型。然后分层模型为了C/S结构发生了一些变化,目的是共享数据和通信,但是由于OO在远程调用上面的失败应用(Corba,EJB,Dcom),所以让人对OO产生了怀疑(实际上只是用错了地方)。而后又发生了B/S的变化,是一种完全的中心共享方式,原因是HTTP的无状态性造成客户端很难保存state,所以就有了完全中心共享状态的架构。而后通过通讯的增强(Ajax),客户端的状态保持逻辑通过异步通信来增强,所以产生了更好的用户体验。但是对状态同步的进一步要求和对会话状态保持的进一步要求让Ajaxian了的应用还是有点难以承受,所以Rich client又回归了。当然回归的时候同时带来的还有新的编程模型,如基于标记的声明式编程模型,还有更方便View-Model同步(通知)的数据binding机制,布局管理器,绘图支持能力,多线程能力,内嵌的视频编解码能力。其实WPF作为Windows上的新型UI编程模型他的确从Mozzila的XUL还有Adobe的mxml吸取了一些经验。上面这些是徐X阐述的主要内容,很精彩(最后的编程模型是我加的注释)。

而后米高做了一些技术层面的对比,主要是对比了Web和rich client的区别,不过我比较失望^___^,因为对比有失偏颇,原因是米高只用了5分钟准备ppt。

最后是我的意见。我现在已经不想割裂的分开Rich client和Web上的RIA,实际上目前他们已经有走向统一模型的趋势。

去年在InfoQ写文章的时候我就表达过这个意见。今天徐X也强调了,经典的MVC实际上很重要的是解决了数据共享(同步通知问题)与状态(会话)保持的问题,所有的架构问题其实都围绕了这个问题。首先RIA里面已经开始了layout数据分离的加强过程,比较明显的就是声名式的组件组合配置,还有数据绑定模型,这个在Flash和Silverlight还有JavaFx都有着重的解决,而且方向都很类似。其中Silverlight其实是一个减缩版的WPF。然后我们从架构方面来思考,解决状态共享和传递是通过增强的双向通信能力来完成的,很多RIA框架在开始提供web socket模型,这样让通讯超过无状态的且单向的HTTP,包括HTML5(目的是扩展Web上常用的一些Object,增强Web的编程能力,且让很多元素得到正确的语义,这个与XHTML2的关注点不同)的草案里面也有Web socket(类似socket的编程对象,可以实现二进制协议的面向连接的通讯)的提案。当然些努力就是让实现消息传递的开销更小,时效性更高,配合线程概念的支持,就可以实现复杂的基于消息的异步界面逻辑(这会极大的扩展RIA应用的能力)。因为通讯其实是解决状态共享的一个方向,通过高效的消息通知达到多个消费端的状态共享。另外一种解决Browser端状态同步(这里主要指客户端与服务器的数据库同步)的方法就是离线存储能力,这样削弱客户端对服务器的依赖。这种解决方案的代表就是各种Gears,Google gears,dojo offline等等,他们在浏览器里面嵌入sql lite一类的数据库,让客户端有自己的结构化存储能力,对于没有多客户端数据同步要求的应用来说离线方式可以让客户端形成完整的编程模型,通过sync机制在连线的时候进行数据同步是一种非常帮的RIA发展方向,从这个角度它已经是Rich client了。

那么可以扩展一下。我们知道Lotus Notes有服务器端replication的模式,离线会存在本地,连线的时候再同步。而对于另一些应用,极端地如Skype,他对实时的同步要求很高(当然它属于通讯类应用,也就是3C中的Communicate,而不是Content system),Skype的解决方案就是p2p。如果RIA有了socket(当然还有跨域支持),有了多线程,那么p2p是不是也不算难事了呢?状态同步通过p2p来实现,虽然不是可靠的通讯方式,但是却符合Internet的最大努力原则,所以我觉得这两种技术的结合的确很容易让RIA和Rich client不在有明显的界限,未来的目的就是融合。所以,要注意的是为什么微软拼了命在推Silverlight,而且拼了命的公布了Mac和Linux版本的Silverlight,其重要原因就是让WPF的模型渗透到RIA,用Rich client围攻RIA,来解决Adobe用超级NB的Air这个RIA衍生来围攻Rich client的困难。

这样,我们知道2年前开始声音渐强的Offline storage和越来越强的绘图,data binding的意图了吧,融合已经开始了,目标当然就是吃下这个大平台,然后成为最大的赢家!

关于Javascript的入门图书

犀牛书绝对的过时,也绝对的不友好。在开始的时候,作为一本系统讲解javascript的书,它非常注重阐述规范,所以在那个时代它是最好的图书。但是现在,作为js技术的入门图书,我们有一些更好的从javascript的精髓和基本理念入手的图书,读完他们你就可以体会到规范为什么是这个样子,从记忆角度来说理解后的记忆要强过死记硬背,所以用这些图书入门是更好的选择。

Update: 有位Sina的同事说道犀牛书还是要有一本的,目的是作为参考书。我倒是觉得这个完全可以依靠网络,因为大部分时候我们就是想索引一下方法,或者看看方法签名和解释,这个时候通过在线服务绝对要好过翻书。服务里面gotapi是个不错的选择!

在国内看翻译的推荐看:

1、大辫子nicholas写的Javascript高级程序设计

2、John Resig写的精通Javascript (Pro javascript)

ppk on javascript这本书也不错,比较实用,但是个人感觉没有精通Javascriptt这本讲的清楚。

如果看原文可以期待:

1、John Resig的Javascript Ninja,未出版,但是有样章。

2、Js大神Douglas Crockford的Javascript The good parts.

上周和钱钱讨论Programming Ruby这本书的时候我们也持这个观点,它曾经是一本非常棒的入门书,因为那个时候它是第一本非日文的Ruby图书。但是这本经典书到了现在已经有了很多不一样的竞争者,像钱钱同学推荐的Every day scripting with ruby就更偏实践,更容易让你入ruby的门。

短小精悍的Javascript模板引擎

今天看到Jonh Resig的博客里面提到了他在他的Secrets of the JavaScript Ninja这本书里面介绍的一种简单的模板,代码如下:

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {};

this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :

// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +

// Introduce the data as local variables using with(){}
"with(obj){p.push('" +

// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");

// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
})();

这段代码非常简单,但是却有不少trick。而且这些trick十分的优雅(John resig一向擅长写优雅的javascript)。非常值得读一下。思路就是把一段模板引擎里面的求值代码标记出来,将其余代码用单引号包裹起来,所以代码会分为文本和求值两种部分,压入一个Array,最后join。把这个逻辑用string的形式初始化一个Function对象,然后cache起来就成为一个高效的javascript模板引擎了。不过这个引擎只有求值赋值的功能,没有更加复杂的逻辑控制如if, else, for这些循环,但是在求值部分增加一些正则判断来处理这部分逻辑也是可行的。我非常喜欢的Trimpath Javascript Template估计也是这个思路(虽然经常用,但是由于代码可读性的问题一直没有仔细阅读它的源码)。

这段代码里面的闭包用法也很不错,可以防止命名冲突和全局变量的污染,不过这个写法估计大家都比较熟悉了。

模板的用法就不赘述了,如果你想用这个微型模板可以看看John Resig的JavaScript Micro-Templating

解决Hg在MacOSX leopard上的locale问题

简短说,因为重新在新mac上装mercurial,没有装macports也没有fink,这次也不想自己编,所以选择了预编译的package。但是后来发现报错!
我用的是http://mercurial.berkwood.com/这里的包,1.0.1的mercurial package,是08-05-25出的那个包。

line 373, in _parse_localename
raise ValueError, 'unknown locale: %s' % localename
ValueError: unknown locale: UTF-8

那么,如何解决呢?这里找到了答案:
http://www.selenic.com/pipermail/mercurial/2007-October/015296.html
解决的方法就是在你的.profile加入下面这样的声明,如果你用的是bash的话。

export LC_ALL=es_ES.UTF-8
export LANG=es_ES.UTF-8

然后就工作正常了,如果想知道为什么,可以看看这里有更详细的原理介绍,LC_ALL是给字体字符集使用的环境变量。
http://www.madboa.com/geek/utf8/

结绳记事,希望有帮助。

重构我人生

我是一名程序员,但是其实我从来没有规划过我会成为一名程序员。从小我的爸爸就培养我的各种兴趣,其实所有的兴趣对于我都是一样,兴趣本身不是目的,收获的是一种态度,这种态度会指导我的生活。所以我有了现在,我是一名程序员了。

那么这个引子的目的是想说我的程序员中最大的收获,这个收获就是方法和态度。这个名字就是敏捷,敏捷就是这几年以来我接受的最有用的一个态度。敏捷是一个基本的不能基本的概念,但是我在这里不想展开,但是敏捷的所有方法中都提到的迭代与反馈的方法。而人生也是这样的,年复一年人生在迭代,每年我们都会回顾这一年,给这一年的生活一个反馈。所以说基本上我们的生活也可以是一个敏捷过程,那么我们还没有做的是通过改变自己的生活提升自己生活的质量。这个过程我们可以用程序员的一个术语,重构来描述,当然这是片面的。重构就是在不改变程序结果的情况下重新调整逻辑体,为程序的改变或者提升可读或者正确性等尽行的改变工作。那么人生也需要自己从自己的生活中找到可以提升效率或者改进行为方法的地方,为未来的变化做好准备。

扯多了,但是这个问题其实简单,所有的自发的变化都有可能产生重构的效果。那么这一段时间我进行了什么重构呢?

  • 1、开始消灭我的个人电脑的硬盘分区。从很就以前我的Windows机器的硬盘都是有很多分区,里面力图放相应的内容。但是后来发现这样一点也不好,因为最后数据的无序造成分区的最早企图的失败。那么后来我发现Unix的树型管理很科学,你也可以把不同硬盘/分区挂载到树的不通节点,很灵活。而且Windows的盘符其实和目录没有本质区别,但是灵活性奇差。硬盘的第二个变化就是买大硬盘替换多个小硬盘。我有80G+120G X2+160G+250G+320G这么多硬盘,放到机器里面实在费电,所以应该做的就是买500/750/1T这样的硬盘替换它们,这样省电省事。同时,随着关键数据的量的增大,逐渐我也要开始使用Time Machine备份来提高安全性。
  • 2、使用好Mac,这个的确提升了我的效率,而且也有机会接触更多Unix哲学。不是说Unix哲学怎样,而是多学习很有好处。
  • 3、读书。我在包里面放一本技术书、一本文学/哲学书、一本杂志,分不同场合查看。这个是好看簿的大野狼告诉我的一个方法,后来我发现真的很好。原因就是如果专注技术而忘记了人文知识的补充,那么人生就会暗淡下去了,所以这种方法引诱我丰富自己,感觉很好。
  • 4、重新思考自己的选择。看着我去年写的wishlist,关于一些男人的玩具,如大液晶,好手机,昂贵的镜头,这些愿望在去年没有实现,但是去年的目标都是一些“超值”的选择,但是经过一年的思考,从《身份的焦虑》这本书里面我知道对物品的期待如果画一条满足度的曲线,那么得到那个物品以后这条曲线会急速下降。所以其实我能够享受的很多就是拥有前的期待,尤其对于这些男人的“玩具”也许逐渐提高你的期待才是更有意义的。比如,现在如果我再期待拥有的单反已经是D300+17-55 DX+105VR+S 10-20+SB800这样的配置了,而长焦我基本上放弃了。而去年购入的LX-2给我带来的满足非常之大,这半年来我也的确照到了不少的好照片,记忆满满。提高的需求可以延缓无谓的投资带来的资金损失,在头脑清醒后可以增加准确度。
  • 5、经常整理你的物品/文件,删除或者扔掉没用的东西,这样可以减少下次整理它们带来的麻烦。减少没有必要的需求,让你能够享受的需求的质量更加高一些。

等我写到这里的时候我知道题目说大了,上面的这些还和敏捷搭不上边际。但是在实施和回顾的过程中,我发现我的生活变化了,变好了,这就够了。

在Mac下启动多个Firefox实例方便JsUnit运行

项目中的JsUnit是使用ant脚本运行的,里面需要设置BROWSER_PATH的环境变量来启动浏览器。在本地check in代码的时候,我们会运行一下测试来减少愚蠢错误被提交到代码控制系统。但是在我的mac下Firefox只能启动一个实例,在运行JsUnit test的时候会提醒我已经打开了Firefox,不能打开另外一个实例,这样我必须关闭正在运行的Firefox。而且由于我比较喜欢打开非常多的Tabs来保持浏览状态,所以关闭Firefox让我很不爽,再说,因为重新启动的Firefox里面带了很多的Tabs,所以经常造成实际运行的JsUnit test发生随机性的超时错误,这个就不能容忍了,因为这无法保证我们的信心。

那么,为什么FF不能启动多个实例呢?原因是它们共享同一个Firefox的profile,所以没法多个实例并发访问。但是通过命令行参数是可以创建多个profile给firefox的,简单了。不过遇到的问题是JsUnit的ant任务会检测BROWSER_PATH是否存在,所以如果我把带参数的命令行写到环境变量里面Ant无法检测到这个文件就会报错。那么如果关闭检测可以么?还是不行。因为JsUnit的StandaloneTest里面实际最后会调用DefaultProcessStarter的execute方法,这个方法调用Runtime.getRuntime().exec(command),这个实现非常直接,不过因为parameters如果直接写到命令行里会发生文件无法找到的问题(应该用数组将命令和参数传入)所以没有办法传入,还是无法运行。

放弃hack吧,我可以修改Ant task和JsUnit的方法,但是绝对不好,因为这个hack没有提交回去的意义。
所以换个思路,这样做:我们去写个shell来解决它。

先在终端运行/Applications/Firefox.app/Contents/MacOS/firefox-bin -CreateProfile jsunit,这时候会弹出窗口让你确认创建这个profile,选择一下不使用extensions和各种工具条,这样减少这些设置对测试的不良影响。
然后在你的home目录创建一个firefox.sh,里面写上:

/Applications/Firefox.app/Contents/MacOS/firefox-bin -P jsunit $1

前提是你的Mac使用的是默认的bash,否则修改$1为对应的引用字符。然后chmod firefox.sh 555,让它可以运行。

下面就是修改你的~/.profile:

export BROWSER_PATH=/Users/[User path]/firefox.sh

source ~/.profile让修改生效再运行JsUnit就OK啦。如法炮制想开几个Firefox实例都可以啦。同样方法也适用于让Firefox2和Firefox3共同运行!非常简单。还可以做到开发和浏览分开……以此类推。

回顾一下JsUnit的代码写的不好,如果像Selenium一样能够自动创建一个profile就好了,因为那样可以减少测试之间的影响,还可以让Selenium并行执行。我想,如果有空我可以做一下这个工作:D

写javascript单元测试是挺爽的事,可惜不要在safari上?

对于javascript来说,通过单元测试,你也可以实现TDD。对你非常有好处,一是减少了js变动带来的代码退化问题,另外一方面是TDD可以改变你设计程序的方式。
举个简单的例子,写javascript很多情况下是和BOM(也就是文档模型)和DOM打交道的,这样可以说javascript程序很容易与dom高度耦合,这样的程序运行起来没有问题,但是应对需求变化的能力会比较地。但是根据人的思维方式,javascript很多时候是先出页面,然后根据页面逐渐调试着写出javascript,对于开发者来说,脑子里并不是真正的清楚自己要什么,而是在想界面的结果……这样产生的高耦合代码不容易测试,也难以面对多变的界面。
所以,换一种方式思考。如果用单元测试的方式去写,你就需要考虑程序的可测试性,这会细化你的程序的模块粒度,因为细粒度的抽象容易单元测试。同时由于js单元测试的页面是mock出来的,所以一般都会尽量的简单,这样会减少程序远对界面的依赖。同时由于界面的可测是性问题,也许会减少对element的style的修改,转而使用语义话的css。例如如果一个元素高亮,你可以element.style[‘font-size’]=’bold’;element.style.color=’red’l….,当然你也可以element.addClassName(‘highlight’),然后那些不好验证的界面的约束条件可以放到css里面去,放给可用性和用户验收测试去验证。这样的单元测试的验证条件(assertion)会简单很多,如果写过单元测试的朋友肯定会有感触的。
那么,常见的Javascript的单元测试框架有JsUnit和scrit.aculo.us的单元测试框架两个。前者方便用ant调用和分析结果,适合使用了ant的项目。而后者的优点就是界面好看,直接运行产生的报告清楚漂亮。所以小型项目我倾向后者,而大型项目我倾向前者。当然,由于js的动态特性,其实做个单元测试框架非常简单,所以自己动手也无妨。关键是要写,而且争取做到测试先行。

前面是个引子,其实写这个的原因是今天上午的一个郁闷的事。
前面的blog entry说道我升级了Firefox3,结果遇到了getElementsByClassName问题。但是今天换到另外一个项目组,没有用那个方法,程序也正常。可是我TDD的写一个新的feature的时候却发现可爱的JsUnit的testRunner在Firefox3里面无法工作,从firebug里面看到了一堆安全性问题。估计是firefox3的新安全模型造成的吧。那么,由于firefox3覆盖了firefox2,所以难道我没法写程序了?当然不可以,还有safari嘛。马上开始去写测试了,写好测试执行测试,发现红条。嗯,很满意,因为TDD的红-绿-红-绿的节奏就是这样的。然后我开始去写实现来满足这个测试……结果忙活了一上午就是不行……而且发现一些原来的测试也无法通过了……我仔细寻找问题,diff修改的内容,可是最后实在没有发现任何让它不能通过的原因,因为手工在firebug里面都已经验证了写的实现是没有问题的呀……崩溃。此时我突然想起来我们的持续集成服务器里面没有跑safari的JsUnit测试……也就是说不能确定在safari下全绿(此时的背景是我们的持续集成显示全部绿色,也就是说所有的测试都可以同过,包括windows和linux平台还有IE及Firefox),那么我可能衰了。马上开动camino(靠,一上午都忘记用它执行JsUnit了,因为我的习惯是Camino里面保存to read list),运行一下全绿。很兴奋,但是感觉刚才寻找问题的1个多小时被无辜的浪费了,心疼呀。

那么,请注意啦,我只是想提醒,jsUnit可能不能在safari下正常工作(大部分测试没有问题,少量在其它浏览器正常的测试在safari下无法工作),我用的是safari3.0.4……

Firefox3 beta2带来的getElementsByClassName问题

Firefox3解决了一些内存泄露问题,或者说主要的是解决了一些长时间运行以后的内存占用问题。由于使用mac的习惯是休眠不关机,所还是在正式版出来之前选择了升级到firefox beta2。
那么作为开发人员肯定遇到firebug不能用的问题,还好,我们可以用firebug的beta版本来解决这个问题,当然这个firebug还是会出现莫名其妙的问题,无所谓啦,不久就会发布正式版本了。
这些都是废话,问题集中在firefox beta2提供了原生的getElementsByClassName方法,而我们大家都很愿意使用的prototype库正巧也给Element封装过getElementsByClassName方法,看起来两者的作用是一样的,但是不巧……其实两者还是有很大差别的,所以造成了如果你的项目正巧用了getElementsByClassName方法,而后你又用习惯的prototype方式便利了它……那么你的程序就不能在Firefox3 beta2工作了……
我们以前的习惯就是不用考虑新浏览器的javascript兼容行问题,只需要考虑向后兼容,但是这次问题还是出现了变化。
其实原因是简单的:
1、Firefox3实现的原生getElementsByClassName方法返回的不是javascript数组(Array),而是html element collection,这个东西可以用标准的方式遍历,但是却与Array没有共同的prototype(这个指javascript里面的原型继承的prototype)。所以,很不幸,如果你用了Prototype库的那个返回Array的方法写程序,然后使用了Array增强方法里面的first()等方法或者Emmerable里面的each()等方法,那么程序一定会出错。此时你可以$A一下这个html element collection,但是这不是好方法。因为其实Prototype在1.6以后就不推荐使用getElementsByClassName方法了。
2、那么解决的方法就是使用select方法代替原来的getElementsByClassName方法,不过记得要给参数的前面加个”.”。比如oneElement.getElementsByClassName(‘someClass’)应该修改为oneElement.select(‘.someClass’)。而其实select方法由于接受css selector语法,可以实现更强大的选择操作。

嗯,非常废话。
那么简单的说:如果你在Firefox3里面发现你的基于Prototype库的js程序的getElementsByClassName以后出现了method null的错误,那么你需要用select方法代替它。且考虑到长时间的不兼容状态,最好完全消除你的程序中的getElementsByClassName同名方法调用,不关你用什么js库。