2009年10月25日 星期日

SD2C 2009 (Part 2 - Session & Forum)

SD2C的第一天晚上,我在「开放平台」和「PPT制作秘诀」两者之间犹豫,最后选择了去听蔡学镛的「尼古丁+咖啡因...不瞌睡的PPT制作秘诀」,原因是我觉得如果蔡学镛能教别人写不瞌睡PPT,那么他自己的PPT至少也应该是不瞌睡的。事后证明我的选择是没错的——别人告诉我「开放平台」论坛成了平台和开发人员互相责骂对方的战场。

蔡学镛把制作PPT比喻为制作生鱼片,要经过选材、处理、装饰这三步。在选材方面,一个PPT讲的内容最好是相当于一章书的内容,时间不能超过90分钟,而讲述的事情不能超过7件。接下来的处理,要注意做好破题,引起受众的兴趣,并且每一小节的结束时都要有总结。一页PPT所表达的意思,有可能相当于一个词、一句话、一段话,我们应该多用表达一句话的PPT页面。最后的装饰会需要用到不少图片,通过使用正确的关键字联想策略,往往都能搜索到你想要的图片。

SD2C的第二天,我原计划要听听周爱民的「实践者思想」和蔡学镛的「DSL设计与实践」的,结果上午听完Gary Bennett的「iPhone SDK简介」之后就被吸引住了,下午的时间都放在iPhone相关课程上了。这说明了,课程的首要条件还是要能把人吸引住,不会让人想要睡觉,也不会让人想要换个课室看看。在满足这个前提条件后,才有资格来讨论内容的知识性如何。我在听了几节iPhone开发的课程后,又开始对iPhone开发充满兴趣了。

晚上的「程序员软技能」论坛是我最期待的,因为这个话题可以讲得非常有震撼性。讲得好的话,这可以让很多程序员发现自身的unknown unknown(不知道自己不知道的事情)来,把这变为known unknown(知道自己不知道的事情)之后,只要努力了就能获得改善。结果主持人刘江自己戴上了嘉宾的帽子就开始不停地说,搞到台下有人举手说「在刚才的30分钟里面您已经讲了20分钟喇」。同时,估计因为刘江是做图书的,所以花了不少时间在读书的话题上,此外再提到了一些企业文化和沟通技能之类的东西,顺便还鄙视了一轮内地的高等教育连幼儿教育都不如(来自台湾清华的蔡学镛很无辜地无法参与到这个话题中来)。可能是我原本对这个话题的期望太高,所以我对这个论坛挺失望的。将来有空我会专门写文章讨论一下程序员常见的unknown unknown。

最后一天的SD2C我没有参加,因为已经没什么让我想要听的了,所以我选择了去参加Windows 7 Community Launch。有微软社区的人说SD2C是「傻蛋2.0大会」,选择了Visual Studio 2010 Beta 2 Launch、Windows 7 Launch、Windows 7 Community Launch这三天来举行,分明就是跟微软过不去。说实在的,我也觉得今年的SD2C日期选择失策了,明知道Windows 7对Microsoft有多么重要,也明知道发布日期是哪一天,还偏要选择一个冲突的日期。希望明年的SD2C能够选择一个更好的时间吧,相信会务今年已经通过举手投票搞清楚大家的喜好了——最好是在星期二三四开会,因为星期一和星期五工作压力小,同时星期六日是用来休息的。

2009年10月23日 星期五

SD2C 2009 (Part 1 - Keynote & Meal)

一年一度的SD2C又来了,今年的时间由去年的两天半扩充到三天,第一天只设主会场,全部都是keynote。

跟去年的情况类似的是,keynote环节基本上就是赞助商专场,每个赞助商都来从技术的角度说一下自己当前最重视的市场。例如说Microsoft开始重视Team Foundation Server的市场占有率了,于是就在Visual Studio 2010的keynote上介绍TFS的特性,所占比例比VS2010自身还要多。当然,keynote里面也有一些是有启发性的,例如IBM讲的大型企业中的敏捷实践,这就属于我认为比较好的keynote。

由于今年的话题集中在云和移动上面,所以keynote大多与这两个领域相关。能够上去讲keynote的人,当然是要有一定水平的,不能讲一些很肤浅的内容,但如果因此就把keynote弄得很高深或者很抽象又弄巧成拙了。高深的内容固然能够表现出演讲者很有水平,问题是这样一个面向上千受众的活动,有多少人能够对如此多个keynote的话题都有深入了解呢?就算你对其中一个话题很有了解,觉得讲得如此有高度很对你胃口,但你也总有n个其它不了解的话题吧,那些话题你听起来也会觉得是云里雾里的。

在我看来,优秀的keynote应该是这样子的——它能够阐述一个很新颖的观点,或者提供一种全新的视觉,使得无论你原本对这个话题了解多少,你都会被震撼到——这个事情我有所了解,但为什么我之前就没有尝试过从这个角度来思考呢?这其实也就是Punch Party的优势,无论受众具备怎么样的背景,都能感受到全新观点带来的冲击力。如果能够打到这样的效果,我并不在意一个keynote是否顺便在推销某个企业或者产品。

接下来要说说吃饭的问题,但在此之前让我先说说我对MVP Program的一些观点,这纯粹是个人观点,也不一定符合事实。我觉得,就拿MVP Summit这样一个全球峰会来说,每年要接待来自全球各地的几千人,为他们提供四天的食宿,平均分摊到每个与会者身上的费用肯定是不少的。如果MVP Summit的作用仅仅在于让大家聊聊技术,我想Microsoft作为一家商业公司是不会愿意花这笔钱的,就算Microsoft想要这样做,股东们也不会同意。因此,MVP Program必然是能够产生价值的,并且分摊下来每位MVP为Microsoft创造的价值应该大于Microsoft为他所作的花费。这些价值可能包括,MVP为用户解答问题,为客户带来技术培训,也就相当于节省了Microsoft的服务成本和市场费用。同时,MVP也会积极反馈关于产品的各种问题,这也属于创造价值的部分。

回到SD2C的事情上来,CSDN其实对热心贡献的用户非常好,例如说提供SD2C赠票。但是CSDN从来都对此事保持非常低调的姿态,生怕激怒那些付钱买票的人,同时也怕更多人去找他们要赠票。我的疑问是,为什么这些对热心用户的正面激励不能好像MVP Program那样光明正大的做呢?如果一位热心用户为CSDN创造的价值能够大于SD2C门票的成本价,那送他一张赠票肯定还是赚了的。

为什么我要说这件事?因为今年的赠票不包括背包和晚餐。不包括背包我觉得可以理解,金融危机嘛,能够让我免费来参加SD2C我已经非常满足了,没有背包也没所谓。但是不包括晚餐就非常不人性化了——这不是逼拿赠票的人不参加晚上的活动提前离场吗?况且,班车还要等晚上活动结束后才有,傍晚离开还是挺不方便的。于是我就在会场的做啥大屏幕发起讨论,问问没晚餐票的人准备如何吃晚餐——我的本意只是想组织落单的赠票使用者一起吃饭,反正拿赠票的大多是CSDN活跃用户,大家也都是认识的。同时我考虑到CSDN不希望公开有赠票这件事,我一开始也刻意回避了这个话题,后来有人公开讨论了我也就跟着讨论了。

结果是什么呢?CSDN会务组单独给我一张晚餐票,并告诉我原因是蒋涛看到做啥大屏幕的内容了。这就让我感觉到很奇怪了,两天的晚餐票值多少钱呢?值得把事情搞到这样子吗?CSDN自己是做媒体的,应该十分清楚媒体的影响力。赠票邀请来的人,都是技术社区上最活跃的人,他们的博客都是具有相当影响力的媒体,给这些人留下这样一个负面印象,CSDN损失的是多少价值呢?这是否已经超过了两张晚餐票的价钱呢?我相信以蒋涛在CSDN这么多年来做媒体的经验,应该在这件事被搬上大屏幕讨论之前就把帐算清楚了吧。

最后一点,也就是这样做是否厚道。如果CSDN以媒体身份受邀参加这样一场大会,本来是希望借此机会向自己的读者传递更多更好的技术资讯的,然而却受到大会主办方不公平的对待,搞到连饭都没得吃,你们会喜欢被这样对待吗?如果你们自己也不喜欢被这样对待,那就在如此待人之前先考虑清楚。

2009年10月16日 星期五

国际长途比国内市话便宜

众所周知,在国外买国际长途电话卡打到中国,比国内市话还要便宜。这说明了国内话费之高完全是垄断造成的,其实根本就不值这样的价格,因此我们能够打国际长途就打国际长途吧,尽量别再为国内垄断行业支付额外的话费了。

在国内要通过国际长途拨打国内电话,通过VoIP就行了,Skype和Google Voice都可以,它们的价格都很便宜。就拿移动话费来说,非漫游非长途要¥0.29,漫游或/且长途要¥0.39,但是Skype只要$0.021(每次接通需要$0.039连接费),而Google Voice只要$0.02(无需连接费)。

如何使用Skype拨打国内电话呢?你可以用信用卡通过PayPal购买Skype credit,然后就可以用任何Skype客户端拨打国内号码了。对我来说最方便的是,iPhone上面有Skype客户端,只要在有Wi-Fi的地方我都可以用它来打电话。当然,Skype通话质量会受带宽影响,如果你所在的区域信号不好或者带宽有限,通话质量就会下降。

在没有Wi-Fi的时候怎么办呢?通过Google Voice的回拨服务可以解决问题,当然前提是要通过网络(GPRS/EDGE或3G)访问Google Voice。Google Voice拨号可以拨通对方的国内电话,但回拨只能拨美国电话,怎么办呢?我们可以通过购买Skype Online Number解决这个问题。这时候Google Voice和Skype会同时拨通双方电话,然后建立双方通话,话费也就是$0.041而已,还是比移动话费稍微便宜一点。

可能很多人会怀疑,花那么多美元来拨打国际长途,真的比国内话费要便宜吗?这就要看你的具体使用方式和套餐组合方式喇。我曾经因为每天漫游加长途贡献了不少人民币给中国移动,换成Skype后至少省了一半的钱,所以从此我就决定改为贡献美元给Skype好了。如果你并不介意花$10购买Skype credit试试,可以实际使用一段时间后对比一下烧钱速度后再决定。

2009年9月17日 星期四

你的网站「被兼容」了吗?

一般情况下,我们只会讨论我们的网站如何主动兼容某某浏览器,被动地等待浏览器来兼容我们的网站是不切实际的幻想——哪个浏览器会那么伟大,原意主动为一个不兼容的网站而作出改变呢?IE8就是这样一个伟大的浏览器,Microsoft就是一家这样伟大的企业。

故事是这样的,我们有一小段JavaScript依赖于userAgent属性,同样是用IE8进行浏览,在测试环境上userAgent显示为MSIE 7.0,而在生产环境上userAgent显示为MSIE 8.0。为什么会这样呢?打开Developer Toolbar后,发现原来是Browser Mode这个开关在搞鬼——当Browser Mode是Internet Explorer 8的时候,userAgent就是MSIE 8.0;当Browser Mode是Internet Explorer 8 Compatibility View(兼容性视图)或Internet Explorer 7的时候,userAgent就是MSIE 7.0了。

接下来的问题是,我们并没有刻意去拨动这个开关啊,两个相同的页面怎么在不同的环境中默认显示为不同的Browser Mode呢?我的猜想是,这是由于域名不同而引起的——Microsoft自己维护着一个Compatibility View List,当访问该List中的站点时,IE8会自动启用Compatibility View,也就是将Browser Mode切换到Internet Explorer 8 Compatibility View。接着我在地址栏输入以下地址,检查了一下我本地最近更新的List:

res://iecompat.dll/iecompatdata.xml

事实表明,我们测试用的baidu.com域名确实在上述List中,但部署到baidu.jp后也就脱离了该List。这就很好地解释了我们遇到问题,同时也提醒我们域名已经成为了IE8测试中不可避免的一个紧耦合因素。在过去,我们可以简单地认为,部署在不同URL的相同页面在同一款浏览器中显示出来总是一样的。但现在我们必须修正这句话了,仅当不同URL都基于同一个域名时上述命题仍然成立。

通过这个案例,希望能让大家了解到在开发与测试过程中保持域名一致的重要性。如果你开发的页面要部署到example.com,你最好在develop.example.com上开发,在test.example.com上测试,然后再部署。如果你需要在本机进行开发测试,也要通过改hosts模拟一个localhost.example.com来进行测试与调试,以确保代码在最终部署后能执行在相同的环境下。

2009年8月23日 星期日

Twitter Suspension

我的Twitter帐号,也就是@CatChen,经历了一次长达半个月的suspension。

Twitter Suspension

8月5日早上醒来,发现我的Tweetie不能再更新,打开Web看看,发现如下提示:

This account is currently suspended and is being investigated due to strange activity. If we have suspended your account mistakenly, please let us know. See Suspended Accounts for more information.


这时候我觉得很无辜,竟然被误判为发垃圾信息的用户了。当然,这可能和最近Twitter系统不稳定有关,因为有些人的following被清空了,自然会导致另外一些人的followers数量暴跌,由此触发垃圾用户判别标准也是有可能的。无论如何,我只能按照它给出的Suspended Account帮助链接寻求帮助了。

打开帮助页后,发现了一条令人极度无奈的信息——“帐号有可能要被封禁至少30天以备研究之用”。你Twitter要研究和改进反垃圾信息策略可以啊,但你不应该为了自己的研究就封禁我的帐号30天吧!不过我还是先提交请求好了,反正现在也没什么更好的解决方案了。

第一次在Web上提交请求,系统发了一封自动回复给我,我大概看了一下,就存档了。结果请求第二天就关闭了,问题没有得到解决。第二次提交请求,质询为什么请求关闭了但封禁没结束,系统再发了一封同样的自动回复给我,我仔细阅读后才发现,信的末端说明要回复此信解释为什么我觉得这个封禁是错误的。回信后,系统指派了人负责跟踪这个问题,但一直没有任何进展。

后来我在Twitter上看到有人说,要联系suspended@twitter.com,我就再发信去质询进度,这相当于创建了第三个请求。系统继续发回自动回复,我又继续回信解释,接着问题第二天就解决了。虽然我并不能确定,是不是联系suspended@twitter.com确实帮助我快速解决了这个问题,但我建议不幸被封禁的人都应该试一试这个方法,然后告诉我这是不是一个有效的方法。

最后,这次事件引起了我的一个想法——Twitter如此错误封禁我的帐号半个月,和GFW随意封禁一些无关紧要的网站随后又解封,有什么本质上的区别吗?对我造成的影响其实是没有区别的,这两者都只会提高我日常生活的成本。

一直以来,我们依赖于网络从不同的网站获取信息,而GFW有能力随时终止网络服务的能力,所以GFW让人觉得讨厌。最近,Twitter也成为了我们获取信息必不可缺的一个途径,它自身同样具备随时终止服务的能力,因此Twitter并不可爱。与Twitter类似的其它核心服务也如此,如果你的Gmail帐号有一天被封禁了,你使用的所有Google服务随之而终止,Google无论如何都坚持它封禁你的帐号是没有违反服务条款的,这时候Google本质上和GFW还有任何区别吗?

2009年8月16日 星期日

jQuery is DSL (Part 2 - jQuery)

jQuery的Internal DSL形式

在上一篇文章里面,我们了解到了Internal DSL的具体形式,形如:

/* Method Chaining */
computer()
  .processor()
    .cores(2)
    .i386()
  .disk()
    .size(150)
  .disk()
    .size(75)
    .speed(7200)
    .sata()
  .end();


然后我们在看看一段典型的jQuery代码:

$("ul#contacts li.item")
  .find("span.name")
    .click(function(e) { $(e.target).siblings(".more").toggle(); })
    .end()
  .find("input.delete")
    .click(function(e) { $(e.target).parents(".item").remove(); })
    .end()
  .find("div.more")
    .hide()
    .end();


从结构上来说,是不是跟上面那一段Internal DSL的例子很相似?就算我们不看对应的HTML,我们也能猜到这段jQuery代码的含义:
  • 遍历<ul id="contacts">中的每一个<li class="item">
    (这看起来是个联系人列表)
    • 对于里面的<span class="name">
      • 绑定click事件,操作是显示/隐藏class="more"兄弟节点
        (这是估计联系人姓名,点击后切换详细信息的显示/隐藏)
    • 对于里面的<input class="delete">
      • 绑定click事件,操作是把class="item"父节点删除
        (这应该是用来删除联系人的)
    • 对于里面的<div class="more">
      • 隐藏这个div
        (默认隐藏详细信息?)
从这里我们已经能够看出jQuery的Internal DSL形式带来的好处——编写代码时,让代码更贴近作者的思维模式;阅读代码时,让读者更容易理解代码的含义。不信?我们看看与jQuery拥有相似功能的Prototype是如何实现上述逻辑:

$$("ul#contacts li.item span.name")
  .invoke("observe", "click",
    function(e) { $(e.target).next(".more").toggle(); });
$$("ul#contacts li.item input.delete")
  .invoke("observe", "click",
    function(e) { $(e.target).up(".item").remove(); });
$$("ul#contacts li.item div.more")
  .invoke("hide");


这是我用Prototype所能写出的最贴近Internal DSL的形式了。(如果你能够写出一个更自然的版本,欢迎分享。)在Prototype里面,能够返回一组元素的操作就只有$$(),并且它只能作用于全局,缺乏jQuery中find()或者filter()的功能,所以这一组描述联系人列表行为的语句无法组合在一起,必须逐一定义每类元素的行为。此外,此例子中每类元素都仅仅指定了一个行为,因此Prototype的invoke()写法看起来还是和jQuery的click()写法很相近的。但如果一类元素拥有多个行为,Prototype的invoke()就不能好像jQuery那样链式调用下去了,必须每一个行为重头写一个$$(),或者把invoke()改成each()加匿名函数。无论是那种做法,都只会降低代码的可读性。

jQuery的语法分析器

我们都知道,Internal DSL的实现依赖于对语法分析器的封装,对Internal DSL的调用其实都是对语法分析器的调用,经过语法分析后再构造出对底层API的调用。例如jQuery当中的click(),它依赖于当前的状态,也就是前面$()筛选出来的节点集合,把click()解释为要为这一组节点绑定DOM的click事件,最后再调用DOM API完成任务。在这个例子当中,DOM API相对jQuery API而言就是底层API了。

jQuery可以说是挑了一个最容易实现的语法模型来做,永远只有一种token,因此永远也只有一种状态,这种状态当然也是永远有效的,你根本不可能给jQuery输入一个当前状态无效的token。jQuery的唯一状态就是一个jQuery对象实例,其本质就是一个元素集合。读入的token可能是各种针对这个元素集合的操作,但它的返回一定还是一个元素集合。这使得jQuery的语法分析器不会进入无效状态,也就无需判断无效状态,因此大大简化了Internal DSL实现中常见的一个难题。

小结

通过拿jQuery和Prototype做对比,我们可以发现jQuery用非常低的成本实现了Internal DSL,同时带来了Prototype所没有的明显好处。这可以看作是一个很好的范例——如果你需要描述的业务逻辑能够归纳为简单的语言模式,为此实现一门Internal DSL的性价比将会是很高的。你需要做的仅仅是为这个简单的语言模型实现一个简单的解释器,接着你就可以享受贴近人类思维模式的接口了。

最后,如果你喜欢我的文章,可以考虑订阅我的博客:

2009年8月10日 星期一

jQuery is DSL (Part 1 - DSL)

jQuery刚刚出来的时候,我没有太多关注它,觉得这不过是Yet Another JavaScript Library。早期的jQuery专注于DOM节点的筛选与操作,不提供众多的基础类扩展,更不提供UI组件,因此体积能够做到很小。然而,我实在看不出它和我熟悉的Prototype比有什么明显的优势——jQuery能做的各项独立的操作,Prototype都能做。

后来用jQuery的人越来越多,并且大家都爱用它的链式方法调用,甚至还把这种写法推广到其它语言中去。例如ASP.NET MVP Omar AL Zabir就把他的服务器端C#组件设计为支持链式方法调用的。这时候我才开始关注jQuery,并且逐渐喜欢上了链式方法调用的写法,也在我自己的JavaScript组件中实现类似的API(参考AsyncOverload)。最后,我突然明白到,这其实就是一种Internal DSL嘛!

在这篇文章里,我准备先讨论Internal DSL,在下一篇文章里面再解释为什么jQuery是Internal DSL。现在我们就从最根本的问题开始吧——

什么是Internal DSL?

DSL是指Domain Specific Language,也就是用于描述和解决特定领域问题的语言。例如说,我们有专门描述字符串特征的正则表达式,有专门描述数据库查询的SQL,有专门描述XML结构的DTD和XSD,甚至有专门描述XML变换的XSLT,这些都是DSL。

当然,并非我们关注的领域都有现成的DSL,这时候我们有三个选择:
  1. 使用通用语言描述该领域的问题(non-DSL)
  2. 发明一门全新的语言描述该领域的问题(External DSL
  3. 在一门现成语言内实现针对领域问题的描述(Internal DSL
例如说,我们现在要描述一个很简单的金融领域问题,“我在花旗银行存款$200”这样一句话对应的三种法写法可能是:(假设已经存在I和CitiBank两个实体实例)
  1. I.DepositTo(new USD(200), CitiBank); /* C# */
  2. I deposit 200USD to CitiBank /* E-DSL */
  3. I.deposit(200.USD()).to(CitiBank); /* I-DSL */
第1种做法的成本最低,你只需要有OO的思想就可以了,你总能把实体类设计出来,但可能和人类描述此领域问题的思维方式有一定偏差(为什么USD可以new?为什么不是deposit [something] to [somewhere]?)。

第2种做法的成本最高,你需要写一个全新的解释器,至少是写一组全新的规则,然后让YACC这类工具帮你生成一个解释器,但这样出来的语法最贴近人类思维方式,甚至就如同自然语言一样流畅。

第3种做法术语上述两者的折中方案,如果语法不太复杂可以使用Builder模式实现语法分析,写出来的语法相当贴近自然语言,但还是有学习门槛。由于脚本语言有相当的灵活性,所以现在很多人倾向于选择在脚本语言内实现Internal DSL。

如何构造Internal DSL?

常见的两种Internal DSL实现方法是Method ChainingFunction Sequence。如果我们需要描述一台机器的硬件组成,两种实现方式的代码分别如下:

/* Method Chaining */
computer()
  .processor()
    .cores(2)
    .i386()
  .disk()
    .size(150)
  .disk()
    .size(75)
    .speed(7200)
    .sata()
  .end();


/* Function Sequence */
computer();
  processor();
    cores(2);
    processorType(i386);
  disk();
    diskSize(150);
  disk();
    diskSize(75);
    diskSpeed(7200);
    diskInterface(SATA);


无论是哪一种写法,中间都必须写一个分析器层。就如同语法分析器需要使用状态机一样,Internal DSL的实现也必须内置一个状态机,以记录当前执行到什么状态了,并且接下来可以转移到哪些有效状态。

由于这不是一篇专门讲语法分析器和状态机实现的文章,所以我们把关注点保持在API层面就可以了,不深入讨论其实现细节和成本。我们知道链式方法调用能够实现Internal DSL就够了,至于jQuery是如何利用好这一点的,我们在下一篇文章里再作讨论。

小结

在这篇文章里,我们了解了Internal DSL与External DSL之间的区别,同时还了解到实现Internal DSL的具体方式,这为我们接下来讨论jQuery的Internal DSL式接口做好了铺垫。在下一篇文章里,我们将深入地来看看为什么jQuery的接口要如此设计,它能为用户带来了怎样的便利,同时它自身的实现上又有什么优势。

如果你不希望错过下一篇文章,你可以考虑订阅我的博客: