2007年9月20日星期四

Binary Choice & reCAPTCHA

有些时候我们面临binary choice,也就是二元选择,的时候我们会认定只能选一个,因此必须牺牲另外一个,如果无法明确区分两者轻重,这将是一个极为痛苦的选择。不过我最近在读李中莹的《重塑心灵》时发现,原来我们应该积极去考虑如何能够两全其美,而不是被迫接受binary choice。

举个例子,为了防止有人使用bot连续请求某些web资源,我们可能引入CAPTCHA,也就是通常所见的验证码,以确认请求是真实的。通常我们也就默认了,越高级别的安全要求,就必须付出越多的代价,然而一个叫做reCAPTCHA的服务却不这样认为,至少它认为为提高安全性所付出的性能代价能做点别的有益的事情。

如果一般的验证码一样,reCAPTCHA也是提供一组处理过的英文字母,要求用户识别并输入。不过reCAPTCHA并不直接以零售模块形式嵌入你的站点,而以web2.0流行的API方式免费提供,你可以使用它的API为你的站点中需要的位置加入验证码。可能你会问,reCAPTCHA这样做有什么好处,因为它提高了你的站点安全性,却自己付出了性能代价,难道它在验证码下面显示一段广告?嗯……这是常人的思维,确实有很多web2.0服务通过附带广告盈利,不过reCAPTCHA可是Carnegie Mellon University的项目,所以不可能有广告的。一所大学完全出于计算机安全的研究提供免费的CAPTCHA服务?也不是,其实Carnegie Mellon University在大量扫描书籍,同时利用CAPTCHA提交者做人肉OCR。这时候就想通了吧,CAPTCHA虽然占用了计算机资源的同时,也占用了人的大脑资源,然而reCAPTCHA这个项目成功的让CAPTCHA提交过程中的资源浪费变成有效的资源消费。

根据reCAPTCHA的介绍,每天有六千万个CAPTCHA被人们识别,其中每一个大概消耗10秒钟时间,然而将这些工作时间聚集起来就非常多了。那么reCAPTCHA到底是如何工作的呢?当后台的扫描服务遇到一个无法识别的单词时,它就会和一个真正的CAPTCHA单词并列提供给用户输入,用户输入之后就仅对比真正的CAPTCHA单词,如果对了就当他输入对了,同时认为他输入的另外一个单词就是识别结果。对于一个无法识别的单词进行多次这样的操作,就能得到一个具备高确信度的结果。

如果你希望帮助该项目识别更多的信息,你可以在自己的网站上放置reCAPTCHA。对于WordPress和MediaWiki都有现成的reCAPTCHA插件,对于PHP、Python、Perl、Ruby等动态语言也能轻松找到reCAPTCHA库。另外reCAPTCHA还有一个MailHide的子项目,能够让你隐藏你的email,用户必须点击链接并输入CAPTCHA后才能看到完整的email地址。没错,就和Google Groups上面的效果一样,不过CAPTCHA用的是reCAPTCHA,并且也提供一堆现成的插件与库,有兴趣的可以去官方网站看看API文档。

2007年9月9日星期日

Minimal Blogging and Micro-Blogging

最近大家都在说micro-blogging,所以我也来说几句。为什么国内开始多人讨论micro-blogging了呢?估计是因为饭否在国内已经热了,能够有所谓的“现象(phenomenon)”了,同时国内其他Twitter的copy cat之间的竞争也越来越激烈了。然而我却一直用Twitter,无论身边都少人用饭否都不转过去。首先因为我比较无视copy cat,对我这样觉得原创很重要的人来说,copy cat服务通常看都不看一眼,知道是那么一回事就是了。每天有什么新的web2.0 startup出现,订阅TechCrunchRead/WriteWeb这样的web2.0观察日志就够了,基本上各种古怪的创意尽收眼底,一个copy cat根本不值得我花时间去看。一个新服务对比其参考服务至少要有自己的创意以及更好的市场定位,我才觉得有必要去了解,否则每天新增的web2.0 startup那么多我怎么可能了解得来。

回到micro-blogging的话题来,事实上我觉得Twitter这样的可以叫做minimal blogging了。minimal blogging这个想法来自minimal design,因为当你第一眼看到Twitter的时候,你肯定会说那是一个minimal design的。所谓的minimal design,也就是最小化设计,历史上曾经有一些人指责CSS仅能实现最小化设计,也就是只能由一些矩形的区域组成整个页面(参考Minimalism),更复杂的设计css就处理不好。而Twitter则被我称之为minimal blogging,因为它只能处理最小的最有限的信息,一篇最多就140个字符,不允许链接,不能是HTML或者别的markup language(例如ubb code)。虽然我一直在用Twitter,甚至还测试过从中国电信发一条短信给Twitter的英国接收号码要¥1,然而其实我并不觉得Twitter对我很有吸引力。一方面,我并不喜欢被flooding的感觉;另一方面,我想要的是micro-blogging不是这么minimal的。

对我而言,micro-blogging意味着我不需要想太多的就能发表,而且发表过程操作简单,然而同时要能够分享我能接触的各种信息。Twitter符合了前面两点,但是不符合最后一点,因为它无法发链接和图片,视频或者音频就更不要说了,不过因为我不是视频和音频爱好者所以这两项就免了。暂时而言,我使用Opera Community进行mobile blogging,也就是Cat in Mobile。它允许我在手机上即拍即发布,文字和照片都有了,对我而言就差一个链接了,不过mobile blogging不需要链接,要链接就通过feed和我的del.icio.us聚合一下好了。至于不需要照片的场合,Twitter也就将就着用了,因为无论如何同样是手机上的Opera Mini,Twitter操作起来还是比Opera Community要方便的,要知道手机上多填写一个表单就增加不少操作复杂度。

我真正向往的是Tumblr,一个tumblelog服务,然而我却从来都不使用它,最近也就仅仅导入了几个feed进去。其实这是很正常的事情,就好像除去Macbook之后VAIO真的异常吸引,然而即使我有钱都不会买VAIO,首先因为它确实烧钱,其次是日本产品的保修服务根本就不和欧美的同一个等级,意味着它的维护成本超高。日本产品,大至汽车小至PDA,停产之后都不会留充足的备件,如果你买的产品在保但是已经没备件了,那就无法修,因此我非常不喜欢购买日本产品,不用考虑保修直接吃进肚子里的除外。Tumblr看起来拥有我想要的功能,同时又保持micro,然而对我而言迁移成本实在高。在使用Twitter之前,我没有micro-blogging的需求,所以当时也没思考我能在我的Tumblr上发什么,然而在写这篇文章的同时,我在极力挖掘Tumblr的潜力,以及思考低成本的迁移方法,或者有一天我就迁移到Tumblr了。

最后一句话,为什么我向往Tumblr?因为国外的web2.0达人们有不少在都在用,例如Digg的共创人之一Kevin Ross

深入理解 ASP.NET 动态控件 (Part 4 - 解决问题)

前言

在开始写这个系列的文章之时,我想着必须深入介绍背后的原理,然后将所有需要的背景知识呈现到读者眼前,不过我现在发觉这并不是好的写作方法,要写下去对我自己来说难度也不少。最近受到Infinities Loop发布TRULY Understanding Dynamic Controls (Part 4)的刺激,我决定继续写这个系列的文章,并且领悟到了更多读者需要的是对问题的一种较为易于理解的解释,而非一种严谨的解释,因为前者更有助于读者解决当前问题并在再次遇到类似问题时自行推导解决。

其实我在本系列的第一篇文章就已经明确了怎样的文章才让读者容易接受,现在是我自己误入歧途了,所以必须纠正过来。第一篇文章的结尾建议读者自己阅读控件开发有关的书籍,之后就能完整理解和解决这个问题。实际上这是一个比较不合理的建议,大多数人并不可能花时间看完一本厚厚的控件开发书籍(ASP.NET 2.0的比ASP.NET 1.x的要厚了不少)。我需要做的不是复述书上的观点,那也不是我想要做的事情,真正需要做的事情是将书里面的观点浓缩为一篇文章,要让读者能解决问题的,推理看起来符合常识以至于容易接受,然而又比严密推理省下一大多文字。好吧,就让我们按照这种思路去看看如何解决一些常见的动态控件问题。

问题分类

这是受到Infinities Loop启发的,我们首先要将问题分类,然后逐个击破。这里的分类将最常见也最容易解决的排到上面来,然后逐步深入讨论。在开发过程中,使ASP.NET程序员想到要用动态控件的情景通常有如下几种:

  1. 你需要呈现不确定数量的控件,但这些控件是同一类型的
  2. 你需要呈现不确定类型的控件
  3. 上述两个问题的综合或嵌套
  4. 你需要开发自己的Web控件

在对问题进行分类之后,我们就容易逐个去分析解决办法了。由于分类是按照难度逐步递增的,所以这对读者来说应该是较容易理解的。

不确定数量的同类型控件

如果你要在页面上显示一个调查问卷,问卷的题目来自数据库,而任何问题都只有“是”与“否”两个选项,你决定使用RadioButton提供选项。这时候动态创建控件的念头应该仅仅是一闪而过的,然后你就决定使用Repeater。

如果你在这时候没有想到Repeater,或者任何的TemplateControl,那么你就需要重新熟悉ASP.NET的内置控件了。很多时候我们用多了GridView,特别是一直都用BoundField的话,就很容易忘记世界上还有TemplateField这么一回事。

那么为什么Repeater在此会是一个好的选择呢?首先,连最近基本的foreach迭代循环它也帮我们做了,我们仅需要指定DataSource,然后执行一下DataBind(),它就帮我们动态为每一个数据项按照模板创建控件。其次,对于PostBack之后数据发生更新的情况它能应付自如。为了说明PostBack更新数据造成的影响,让我们再来看一个例子。

首先,我们直接在Session里存放一个string[],内容为{"apple", "boy", "cat", "dog"},然后我们需要将它们显示出来,每一个项目显示为一个LinkButton,点击之后就在数组中将它删除。我们都知道使用Repeater或者GridView搭配ObjectDataSource做这样简单的事情是绝对没问题的,但如果我们手动编写动态创建控件的过程呢?

按照大多数人所理解的ASP.NET逻辑,首先应该在Load这一阶段遍历数组,然后为每一个数据项创建一个LinkButton,最后把这一切都附加到页面上唯一的那个HtmlForm上面去。删除怎么做呢?LinkButton实现了IButtonControl,所以可以添加CommandArgument属性,我们就把字符串保存进去好了。在OnCommand的时候就通过此属性识别当前需要删除的字符串,然后从数组中删除,并且还要在HtmlForm中搜索对应的LinkButton然后把它移除。

这时候你应该看看OnLoad中的代码是否记得为每一个控件的ID属性赋值,否则就会出问题了。页面一开始生成的结构应该是这样的:(左侧的是控件的ID,右侧是控件显示的字符串)

ctl01 ("apple")
ctl02 ("boy")
ctl03 ("cat")
ctl04 ("dog")

我们点击"boy",页面进行PostBack,然后Load生成同样的控件树,之后OnDelete删除ctl02,所以输出的控件树应该是这样的:

ctl01 ("apple")
ctl03 ("cat")
ctl04 ("dog")

我们这次点击"cat",页面又在PostBack,但接着Load生成的控件树就不同了:

ctl01 ("apple")
ctl02 ("cat")
ctl03 ("dog")

必须留意到控件的ID属性重新编号了,然而ASP.NET仅仅知道我们点击了ctl03,所以触发的ctl03的OnCommand,根据现在的ctl03的CommandArgument属性,删除了"dog"字符串。这就是所谓的问题了,无指定ID的控件会自动按顺序分配ID,因此ID具有了不确定性。

如果在OnCommand的时候,调用HtmlForm的Controls.Clear(),是否就能移除所有控件并且让ID重头开始编号呢?实验结果表明上述删除过程中第一次PostBack后会生成这样的控件树:

ctl05 ("apple")
ctl06 ("cat")
ctl07 ("dog")

也就是说,移除确实是移除了,然而ID编号没有重置,而是继续编号。那么Repeater是怎么做到的呢?为什么直接使用Repeater就没有任何问题呢?这个下一篇文章再说,我们现在专心来把问题逐个击破,现在你记住这种情况选择Repeater或者其他更高级的数据控件就是了。

不确定类型的控件

在面对此类问题的时候,首先问问自己控件的数量,如果数量不多,直接通过设置控件的Visible属性解决问题就是了。这也就是说,把可能要显示的控件都声明为Visible="false",然后在代码中判断当前应该将哪个显示出来。

如果控件比较多,然而还是能分组的,同一时间仅仅显示其中的一组,那么你应该考虑使用MultiView,这样你的工作将会轻松不少。事实上,能够使用MultiView解决的,都应该优先考虑使用MultiView解决,这比起自己控制哪一个控件显示哪一个控件隐藏要方便多了。其实MultiView所做的,也就是帮你控制控件的显示与隐藏。

这样做的性能如何呢?我们关注两方面的问题,一方面是服务器端执行的资源消耗,另一方面是传输的带宽消耗。我们先来看看服务器端执行的资源消耗吧,我们最常见的消耗应该就是数据控件操作数据库时的消耗了。在ASP.NET 1.x时代,我们没有数据源控件,所以必须手动进行DataBind(),这也就是说如果不手动执行DataBind()的话就不会进行任何数据操作,因此只要我们记得在数据控件不显示的时候也不要让它执行DataBind()就是了,那样就不会有性能损失。在ASP.NET 2.0当中,使用数据源控件的话数据控件是会自动DataBind()的,这时候会造成控件隐藏时的资源消耗呢?事实上是不会的,数据控件即使已经定义了DataSourceID属性,它也仅仅在自己第一次可见时才进行自动DataBind()。如果数据控件的状态是隐藏的(包括使用MultiView隐藏),它就不会自动进行DataBind()。因此,在ASP.NET 2.0中使用数据源控件以及MultiView之后其底层过程还是和ASP.NET 1.x手动操作的一样,就是少写一些代码而已。

我们接着来看看带宽消耗如何,因为隐藏的控件不输出任何的HTML,因此带宽消耗就是指ViewState了。控件隐藏后,ViewState是不变的,因此隐藏控件确实比完全不加载控件造成了更多的资源消耗,换取的是该控件的状态得以保存。一般来说,简单控件隐藏后多出来几十字节的ViewState是可以忽略不计的,整个页面中HTML缩进所需的空格也都几十上百字节了;但如果是复杂控件,拥有大量的ViewState,这时候你真的应该考虑动态加载了。

总的来说,面对这类问题时首先判断显示隐藏控件的逻辑是否复杂,控件本身是否复杂。如果是比较简单的情况,则直接使用MultiView解决就是了。如果是复杂的情况,那就应该考虑自己使用控件将此逻辑封装在内,而不是直接在页面上暴露这些复杂性。关于封装控件的问题,在下一篇文章中再讨论,因此我们继续看下一类问题。

既不确定类型也不确定数量的控件

有时候我们面对前面两类问题都有清晰的思路,但是面对复合问题就感觉很混乱了。例如还是一个调查问卷的显示,数据来自XML,问题类型包括单选和多选,每一道问题的选项个数也不确定,这时候怎么办呢?foreach嵌套foreach,外层迭代问题内层迭代选项,逐个CheckBox/RadioButton来生成?

这时候我们需要的是把问题分而治之逐个击破的思想。既然是上述两类问题的嵌套,我们就应该能够通过嵌套对应的解决方案来实现。对于这个调查问卷的例子,我们可以用Repeater来迭代问题,先把这个定下来,再考虑模板里面怎么做。模板里面需要显示的是一个不确定类型的问题,因此模板里面放一个MutliView,把问题类型的表达式绑定到其ActiveViewIndex属性上,例如单选题就是0多选题就是1。然后MultiView里面的两个View各自嵌套一个Repeater,第0个Repeater迭代选项并显示为RadioButton,第1个Repeater迭代选项并显示为CheckBox。就这样就完成了,我们没写任何一行后台代码,也没有动态创建任何控件。

然后我们来分析一下这个解决方案的性能。对比起动态创建控件,它所使用的控件确实是多了一倍,因为一道问题同时创建了两组选项,一组单选一组多选,只不过其中一组被隐藏了。然而隐藏掉的那一组唯一的服务器端资源消耗就是创建以及绑定,它们不输出任何的HTML,因为它们的值不会被改变所以也不会输出任何的ViewState,并且它们也不会触发任何事件,因此在对性能没有特别要求的情况下这样的性能损失还是可以接受的。至少,这比起你自己去研究ASP.NET页面生命周期然后自己写一大段代码来实现动态加载控件要好多了。

问题与实验

本系列上一篇文章的问题与实验一直没有解答,现在给出参考答案如下:

  1. 为Page增加一个ShowCheckBox的属性:
    bool ShowCheckBox {
      get { return (ViewState["ShowCheckBox"] == null) ? false : (bool)ViewState["ShowCheckBox"]; }
      set { ViewState["ShowCheckBox"] = value; }
    }
    在OnLoad的时候检测ShowCheckBox属性,如果为true则添加上该CheckBox控件。在Button的OnClick事件中,设置ShowCheckBox为true,并添加上CheckBox。记得这两处创建的CheckBox必须拥有一致的ID属性。
  2. 这是为了让ICallbackEventHandler的处理模型符合页面生命周期的模型。虽然Callback发生的时候,页面生命周期已经与PostBack不同,然而ICallbackEventHandler还是让Callback模仿了PostBack的页面生命周期。RaiseCallbackEvent相当于PostBack的Raise PostBackEvent阶段,GetCallbackResult相当于PostBack的PreRender阶段。前者负责事件响应,后者负责生成返回客户端的HTML代码。

这次想和大家讨论的问题是,你觉得你是完美主义者吗?面对上面的调查问卷需求,你会选择我所说的Repeater套MultiView再套Repeater的做法,从而避免写任何一行后台代码,还是会选择自己封装一个控件动态创建所有控件,避免任何不必要的性能损失?

最后,如果你喜欢本系列文章,并且不希望错过下一篇关于控件开发的文章,欢迎订阅我的blog:

在下一篇文章中,我将会介绍Repeater等数据控件是如何工作的,为什么它们能够轻松应对动态创建控件的各种情况,我们如何学习这些控件的设计模式并运用到我们的开发当中。

2007年9月6日星期四

Is this cheating?

终于把《The Game》看完了。不知道是不是因为它的写作风格比较pop culture,好像Hollywood大片的台词风格,所以读起来感觉非常好,也让人非常addicted,这点与读英文的技术书不同。这本书对我来说属于少数,我的意思是能够随之而来影响我现实生活的书,那当然是少数了。

首先当然是通过这本书发现了一条self improve的途径,因为the game本身不仅仅是围绕pick up这一个game,而是关于你的self-confidence。有些人天生就很有自信,能够随便地对别人说出自己想要的,或者仅仅是想法,而不怕遭受别人拒绝;另外还有一些人则相反,他们会害怕遭受拒绝,拒绝后也就接受了,而且心里还会不舒服上一会儿,以后再次提出的胆量也就少了。当然,人不是两极分化的,所以大部分的人分布在这两种极端之间,但总的来说还是有不少人对遭受拒绝感到恐惧。

读完这本书之后,就明白了PUA都想获得的一种本能反应——“我才不在乎呢”,反正被拒绝又如何,我又没有损失的。其实很多很理性的人都能够理解到没有损失这一点,但就是心理上不愿意接受被拒绝的可能性。对我而言,要去掉这种恐惧并不难,看完这本书之后我能够很快切换到那种心态,这正的麻烦在于——要降低被拒绝的概率,心态不是主要问题,技术才是主要问题。然而要练习技术就不再是那么容易,你需要花大量时间投入到实践当中,不断摸索,然而暂时对我来说我不觉得我有必要投入那么多的时间到与不同的人交流当中去。我还有书要翻译,我还有TOEFL要准备。

这本书引发的后继事件,包括我推荐了JimRobert来看这本书,然后我又在豆瓣上发现了ePIK也在看这本书。其中Robert和ePIK都和我一样是沉迷型的——这本书正是我想要的,我缺乏的东西里面有说,我渴望这种改变。而且我们这三个人还有一个共同点——technically a virgin。我想这正是我们渴望去改变的原因,因为我们都看到了前面有路可走,我们需要看清楚这条路,以及它能够怎么走下去。然而对于我身边的大多数朋友来说,他们对此都毫无兴趣,他们不是没有这个需求,而是忽视这个需求的存在,认为这不是能够通过一本书所展开的一组技能就能解决的。我们之所以相信这本书是有效的,是因为读过之后很快就开始分析身边亲眼目睹的一些这方面的成功人士的做法,并且发现这些天生拥有此项技能的人正好符合书中的描述。

有一件巧合的事见,我们在读这本书的时候,VH1的真人秀The Pick Up Artist开播了。书中有一些事情的描述,单纯看文本是怎么都看不明白的,但如果看到真人实践那就好理解很多了。另外网上有关的资料也不少,我们都搜集回来看看。有很多的技巧,其实不仅仅适用于pick up,还适用于日常沟通,能够让对方更乐于听你说,被你所吸引。

好了,有人看到这里肯定要说我文不对题了,起了一个完全无关的标题来吸引眼球。其实不是的,题目正是我接下来要讨论的内容。首先我要说明一点,现在我是支持life is all about experience的,你必须有所体验了才能更好地作出选择。就好像我希望能到不同的国家旅游一下,这样我才能更好地决定我到底想去哪里发展,随大流挤去美国并不一定是最好的。女人也一样,你要有充分的经验你才能够更好的选择,一开始就盲目相信one-itis是不行的,必须有重复的依据说明那真的是你的one-itis才值得你去追,然而依据来源于经验,而不是无端端的感觉。

好了,这时候问题就来了,就好像我只能告诉Singzy,"you are part of my experience, but you might not be the one"。这时候就违反了常人对relationship的理解。一般的理解是,因为我喜欢你,所以我喜欢和你在一起。然而我的逻辑是,我不确认我对你的态度,然而我需要和你在一起,因为这样才有助于我确定对你的态度。这就是题目所要问的,"is this cheating?"。

当然,其实我不认识这是cheating,这对我来说不是一个问题。因为我首先要有这样的看法了,我才能继续我的experience,否则认知失调带来的麻烦将更大,至少比选择女人的麻烦要大。The Game里面提到一件事情,就是假如你认为隐瞒着一个情人去发展或维持另外一个或者一些情人的关系是不道德的,那么你就不要这样做了,因为你自己都认为是错误的事情,无论如何都是错的。但是如果你好像穆斯林那样,认为这是对的,直接告诉对方我是一个穆斯林,在穆斯林的信仰当中一个男人要有三四个老婆才好,你是爱我的话就必须接受我的一切,包括我的信仰,这样才可能有出路。因此,我也就选择了把我所知道的以及我的逻辑告诉了Singzy。

2007年8月21日星期二

ASP.NET 3.5 的 ListView 控件与 CSS Friendly

之前在写CSS有关文章的时候,我就想写写如何使用ASP.NET控件能够更加CSS Friendly,更容易实现一些常见的页面布局pattern,然而之后就发现这并非那么容易的。说起来要让ASP.NET控简变得CSS Friendly很容易,直接使用ASP.NET 2.0 CSS Friendly Control Adapters就是了,然而事实并非如此简单。

CSS Friendly Control Adapters的不足

首先请允许我对这个CSS Friendly Control Adapters抱怨一下。我第一眼看到它输出的class名称我就觉得很faint了,举一些例子:AspNet-Menu、AspNet-Menu-WithChildren、AspNet-Menu-Leaf。如果你习惯了客户端代码一律使用camel命名法的话,你看到这样的命名就会觉得无法适从,你是要改变原有的命名法来迁就这些控件呢,还是让多种命名法在你的CSS文件中混排呢。如果需要改变这些默认的class命名呢?不好意思,控件自身的CssClass属性已经没有任何作用,因为控件输出的HTML结构都改变了,那些CssClass也就不再对应哪个HTML元素了。因此,如果你需要改变这些class命名,唯一的办法就是直接更改ControlAdapter的源代码,而class命名是以字符串形式硬编码在源代码中的,就算你用搜索替换你还是会害怕替换多了或者替换少了从而引入了更多的麻烦。

说到源代码,这些ControlAdapter的第二个麻烦也就浮现了——网站必须携带它们的所有源代码,而不仅仅是编译好的dll,而且这些源代码的可修改性并不强。为什么说可修改性不强?如果你有想过自己写一些ControlAdapter的哈,我想你已经参考过现有的那几个ControlAdapter了,你会发现编写ControlAdapter严重依赖于你对该Control本身的理解,不仅仅是对Control公开部分的了解,还需要对Control内在逻辑的深入理解。因此,要么你是Control的作者本身,要么你就细看过Control的源代码,否则不可能写出ControlAdapter,甚至修改已有的都很难。

因此,CSS Friendly Control Adapters是一个非常之鸡肋的选择,我们不如向前看,看看Microsoft在ASP.NET 3.5中为我们提供了什么。

ListView以及全新的TemplateControl形式

ListView是ASP.NET 3.5新引入的一个控件,如果你还没有使用上Orcas,或者没试用过这个控件,那么不妨看看ScottGu的介绍性文章:The asp:ListView control。这篇文章详细说明了如何先设计一个原型页,然后设计LINQ to SQL以便获取数据,在将数据绑定到ListView上面,最后还加上DataPager分页。我们不需要看那么多,看ListView那部分就是了,看看声明ListView的代码。

如果你熟悉之前Atlas提供的Sys.UI.Data.ListView,那么你一定会觉得这两个ListView很相似。与之前的TemplateControl(例如GridView)不同,ListView不再直接输出容器本身的代码,而提供了一个Template给你自定义容器,你可以在这个Template中自由编写你的容器代码,它可以是<table />,也可以是<ul />或<ol />。之后项目的Template也是允许自定义的,对应<table />的自然是<tr />,而对应<ul />与<ol />的则应该是<li />。因为这些都是你手动编写的HTML代码,所以你可以随意地给它们设置class属性,从而让你能在整个网站中保持命名风格一致性。

Web Form的屈服?

ASP进化到ASP.NET的时候,好像Win Form那样的拖放控件支持成为了最大的特色,然而现在Web Form的编写方式又变回和其它服务器端脚本语言(例如VBScript)差不多了。以前ASP的时候,不就是自己写容器的HTML咯,然后用<%For ... Next%>把项目HTML圈起来,现在改为叫做模板其实没什么差别啊,况且其他服务器端脚本语言都有类似的写法,不过可能是helper函数或者别的称呼,都差不多。

因此,事实证明除非放弃对HTML细节的控制权(而这又难以做到CSS Friendly),否则对于大多数服务器端语言来说声明数据表现模板的方式都是类似的,没有更便捷的方式了。能够省事的是数据访问方法,从ADO进化到ADO.NET,从Typed DataSet到LINQ to SQL。将来Microsoft是否会发布更多类似的TemplateControl还很难说,因为ListView已经有非常高的可定制性,原来用来表示二维表数据结构的DataControl都可以用它作为替代品,同领域的控件已经没意义了,不像以前要分开几个DataControl了。我觉得接下来最好能看到一个取代Menu的CSS Friendly Control,因为Menu所表现的数据结构不是二维表,而是树,有必要为这种数据结构提供一个能准确声明HTML细节的控件。

最后,如果你对我的blog中关于ASP.NET与CSS的文章感兴趣的话,可以考虑订阅:

2007年8月13日星期一

买了两本 Photoshop 的书

两本都是《选择的艺术》这个系列的,其中一本是《Photoshop CS图像处理深度剖析》,另一本是《Photoshop CS图层通道深度剖析》。买Photoshop的书,是因为工作中要用到,作为一位web设计人员,单纯需要编写代码的工作暂时还不存在(虽然Microsoft的产品线设计尝试让设计师与程序员能够低耦合度合作),因此学好一些基础的设计思想与设计工具运用知识还是有必要的。

例如Google的Webmaster职位,要求就包括Adobe Photoshop操作知识。我暂时只能用Fireworks做一些简单的mockup,mockup的素材没办法自己用Photoshop处理,这就大大限制创意的发挥。另外我还想去学学摄影,因为在我还不能什么都用Photoshop画出来之前,素材的获取有时候就必须依赖于实物,看来要补的知识还真是一大堆啊。

2007年8月8日星期三

为什么 Flixster 比豆瓣更好

虽然我主要适用豆瓣记录书、电影、音乐,然而用过Flixster之后就为它所吸引,希望豆瓣能够加上Flixter那样的功能。

在我认真使用豆瓣之前,Piggest跟我说过豆瓣的优点:在你注册并添加少量已经看过的电影后,你会发现你想继续添加的电影通常已经出现在“豆瓣猜你会喜欢”的列表上,这时候添加就方便很多了,因为无需再按名字搜索,直接点击就是了。然而Flixter更先进,它根据大家看过电影的历史分析出所谓的经典电影,也就是人人都看过的,没看过至少听说过的,在你注册后就提供这些经典电影给你评分。假如我没看过一部电影,而且也不想看,怎么办?Flixster比豆瓣多了一个“不感兴趣”的选项,这应该算是一个更体贴用户的设计,而这个设计豆瓣后来也加上了,不过不是一个直接的选项,而是你可以选择隐藏“豆瓣猜你会喜欢”列表上的推荐项目,那也相当于是不感兴趣吧。

对于一个Web2.0的网站来说,数据是很重要的,掌握的数据越多,就能越有效的服务现有用户,对新用户的吸引力也越大。在获取数据方面,Flixster的做法显然比豆瓣更好,对经典电影的评价几乎人人都有,而此时以推的形式主动要求用户评分并不会让用户觉得觉得不喜欢,反而提供了便捷,即使有用户不喜欢这样,他也可以选择之际跳过这个步骤。类似的,其实很多Web2.0网站都可以在用户注册的过程最后加上一个可选步骤,要求用户提供更多信息,而网站要设计好获取信息的方式,让用户觉得不用动脑筋就能填写,并且提醒用户在这里填写能够更便利更快得到全面的服务。

2007年8月1日星期三

今天开始翻译 Prototype and Scriptaculous in Action

感谢Dflying Chen的联系与推荐,让我有机会为图灵公司翻译Prototype and Scriptaculous in Action的简体中文版。这本书的作者包括Ajax in Action的作者Dave Crane,同时作为引进大陆的首本关于Prototype的书,因此估计将会热卖。

基于上述因素,翻译此书时我必须特别小心,避免任何翻译错误甚至仅仅是可能引起争议的地方,因为一旦这书上市了必定有不少读过英文版的人来读中文版,接着就可能指出他们认为翻译得不够好的地方,即使那个地方是纯粹文学性的并且与书中讨论的技术细节没任何联系。现在是Web2.0时代,你犯的任何错误都将被别人永久的记录在Internet上,如果第一本翻译的书出问题了那么将来就很难再被信任以担任同类工作,因为读者认为你的翻译不好的话出版社就敢让你翻译。实际上不仅仅翻译工作如此,现在的Internet逼着你我以及任何一个人做事时都必须加倍小心,面对公众所犯下的错误都将被一大堆人记录到他们的blog上,这种负面影响难以用你的个人之力以消除。当然,事情也有好的一面的,如果翻译做好了将能得到相当的赞誉,这种来自公众的赞誉也不是别人轻易能够克隆的,因此也有相当的含金量。

最后,如果你希望对我的翻译工作提供任何的建议或支持,或者你有兴趣和我合作翻译,欢迎留言或联系我。直接在blog回复留下联系方式的话,我将会主动联系你。

2007年7月31日星期二

理想的 ASP.NET AJAX (Part 2 - Server Centric)

使用ASP.NET的话……

ASP.NET的最大优势就是组件化,在UI上更明确地说就是控件化,但这却为AJAX带来了不少问题。

首要问题是输出HTML不由我们控制。复杂的GridView不说,我们就来看简单的CheckBox,在你不对它设置任何样式属性和文本时,它是一个单纯的<input />,加上文本的话文本会被放在<label />中以便点击文本与点击<input />等效,如果再加上样式属性的话外围就再多一个<span />用于设置样式。如果你获得了一个CheckBox的ClientID,那么通过$get()(指document.getElementById操作)获取到的是<span />还是<input />呢?答案是那个<input />。

这时候真正的问题就出现了,假如我有一组CheckBox放在一个大的<span />容器里面,当我在客户端选中一个CheckBox的时候它就从这个容器里把自己删除掉(然后转移到另一个<div />容器中)。我们关注的只是这个<span />容器内发生的事情,当我们通过$get()获取到CheckBox对应的<input />后如何知道它的parentNode的<span />是属于CheckBox的呢还是作为容器的呢?这个问题可以通过对作为容器的<span />添加id解决掉,然而我们仍需要手动判断<input />的parentNode是不是CheckBox的一部分以便知道是否要将它也一起删除掉。当然,你会说我的CheckBox不设置任何样式属性,不存在此问题。然而你能够确保这个CheckBox永远不会被设置样式属行吗?甚至可能是通过Skin而被“意外”的设置了样式属性。总而言之,一个CheckBox是否有<span />以及是否有<label />都是不确定的,而且是不正交的设计选项——有很多不同的因素可能导致它们出现。

这类问题在我们仅仅针对ASP.NET服务器端编程时可以完全不管,而且还很爽的样子——看看,我们可以全然不知道客户端发生什么事的前提下,甚至连输出什么样的HTML也不用管,也照样能设计交互式的Web。然而当我们需要考虑客户端交互的时候,这就很不爽了,ASP.NET控件这种完全隐藏客户端细节的做法让我们难以对输出的HTML进行DOM编程。

通过ASP.NET AJAX解决?

在Atlas出来的时候,我确实希望过能够在一定程度上解决上述问题,最多我就由server centric改到client centric好了,并且我也一直尝试着用Atlas进行client centric的实验性项目,然而得出的结果一直都不怎么好。

我们继续以CheckBox为例吧,不过这次改变对象为客户端的CheckBox,也就是由<input />初始化而来的Sys.UI.CheckBox。Atlas在设计的初期为这些控件考虑了很多,虽然当时控件仅有简单的几个,这里面最强大的设计思想应该就是绑定(bind)了,允许用户自行定义绑定的实现方式,而不仅仅是如同ASP.NET服务器端那样仅仅支持简单的双向绑定及自定义的单向绑定。

然而很快Atlas进入了Beta阶段,为了1.0的及时发布中心转移到了UpdatePanel等的服务器端控件上,客户端控件再也得不到重视,功能不再更新,甚至为了兼容服务器端控件的更新而被迫放弃部分原有的客户端功能。这时候使用ASP.NET AJAX进行client centric开发就变得越来越不可能了。

另外,使用ASP.NET AJAX进行client centric开发还面临一个不够search engine friendly的问题,因为数据都是输出到客户端再展示的,因此HTML本身并不包含多少有语义的内容。我曾经尝试做一个类似WebPart效果的门户页,完全不使用WebPart而使用Atlas自带的拖放支持实现是没问题,虽然其自带的拖放并不完美而必须手工添加不少东西,然而我真正面临的问题是页面第一次输出应该输出什么。

方案1是真正的client centric,好像GMail那样,一出来是空白页,显示loading,这时候通过XHR去和服务器沟通然后再添加内容到页面上,这显然是完全不照顾搜索引擎的做法,对于GMail这样本来就不应该被索引的页面当然没问题,但是大众网站这样做就不好了。

方案2是服务器端先根据用户的Profile生成用户最后一次拖放保存的页面,之后的拖放及保存工作才由客户端负责处理。然而这意味着同一套逻辑我需要实现两次,可拖放的部件在服务器端要是一个Control,在客户端同样要是一个Sys.UI.Control,同事都具备串行化为Profile已经从Profile并行化还原的能力,这样工作量就大大增加了。

之后,从ASP.NET Control Toolkit我们可以看到,客户端Control的地位已经逐步被Extender取代了,因为Extender可以被看作是一组由Behavior、Web Service等元素组成的部件,它不再关心DOM元素在客户端作为一个Control的完整性和独立性,而仅仅是考虑这些DOM元素应该表现出来的行为从而将这些行为附加到DOM元素上面去。这样思想让我们面对上面二选一的情况时有了更好的选择——在服务器端应该注重Control的整体,而在客户端则仅关注Control输出的DOM元素应该具备的Behavior。因此,在上例中我不再需要管一个可拖放部件如何作为一个客户端Control存在,我仅需要知道这个部件是一个<div />元素并且它具有可拖放的Behavior就够了,而无论何时代表整个部件包括其内容的只有服务器端Control。

这样看来,如今的ASP.NET AJAX已经有相当的价值,前提是你有能力针对你的应用开发相应的Control和Behavior。至于未来的ASP.NET AJAX能够为开发者再提供多少便利,这已经很难说了,因为继续在ASP.NET AJAX上面增加client centric的支持,倒不如把精力投入到Silverlight的研发上面去,ASP.NET AJAX很可能不会再有重大更新版本,能把ASP.NET Futures中有价值的AJAX功能都RTM就已经算好了。

最后,如果大家觉得这个系列的文章好的话,欢迎订阅Cat in Chinese(feed)或Cat in dotNET(feed)。将来话题可能将转移到Silverlight,一个我认为比ASP.NET AJAX更具潜力的框架上。

2007年7月30日星期一

理想的 ASP.NET AJAX (Part 1 - Client Centric)

怎样的AJAX才算是理想?

要说什么是理想的ASP.NET AJAX,就要先说说什么是理想的AJAX。事实上AJAX最不理想的地方在于search engine friendly以及bookmarkable,这两个问题有一定的相似性,要解决并不难,只是每一个系统中实现起来都不一样,因此难以提出一个统一的patterns来解决。

首先说说search engine friendly这一点吧,实际上使用了AJAX的站点有很多信息是搜索引擎无法索引到的,因为页面上部分的信息是在用户进行操作后才显示的,显示的这些信息有可能是固定的也有可能是经过XHR(XMLHttpRequest)查询服务器后动态显示的。如果是固定信息,那么在生成页面时就要考虑这些信息是否也应该输出为静态HTML。那么什么情况下该输出为静态HTML而什么情况下不该输出为静态HTML呢?我觉得应该看隐藏的内容是否就是页面语义的一部分。例如页面用于显示一张集体照,当鼠标指向照片上不同人物时将显示他们的姓名和有关的介绍,那么这些信息就应该输出为静态HTML了,因为它们是页面信息的重要组成部分,并且这些信息应该被搜索引擎索引到。

当然,事情并非任何时候都那么简单,有时候我们并不能简单判断某些信息是否应该属于一个页面,或者说一个URL。例如你使用一个页面来展示你的gallery,但并非一下子显示所有的照片,开头显示随机的10张图片,然后允许用户输入/选择tag来显示对应的照片,如果当前显示的照片超过10张还会自动分页。这时候怎么办呢?整个gallery输出的一个静态页包括所有的照片以及描述?这看起来不太可行。假如你的gallery有100个tag和1000张照片,同时这一个页面成功被搜索引擎索引了,用户通过搜索引擎来到gallery页,他怎么找到搜索引擎上匹配的图片以及对应的描述?他根本不可能知道他要找的信息藏在哪里了,100个tag中可能只有几个能让他想找的信息显示出来。

这时候比较好的做法就是将tag分离出来变成独立的页面,或者说是URL。gallery首页上的tag对于用户来说是交互式按钮,在不刷新的情况下直接筛选对应的照片;但对于搜索引擎来说那是链接,链接到代表该筛选状态的页面,并且在该页面上才索引到照片和描述。这样当用户点击特定的搜索结果是,进入的是已经筛选了的gallery结果页,也就必然能看到他搜索命中的信息。同时这也增加了bookmarkable的可能性,因为一个URL已经不仅仅代表一个页面,而代表一个页面的特定状态,因此状态变得bookmarkable。然而这时候普通的筛选结果还不是bookmarkable的,因为在进行根据tag筛选后URL是不会被改变的,因此我们必须通过JavaScript改变URL才能让页面变得bookmarkable。

与理想的距离有多远?

我们面临最大的问题就是URL与状态的对应,以及有关的存取。我们希望每一个indexable & bookmarkable的状态都对应一个URL,而且最好是meaningful的URL。好吧,我们先舍弃meaningful这一点来探讨状态问题,实际上我们已经有一些方法来保存状态。例如ASP.NET提供的ViewState就是一个很好的例子。很多ASP.NET的新手可能会觉得要理解如何正确使用ViewState并不容易,其实它就是用于保存view的state的。整台计算机其实也就是一个state machine,不过我们当前所关注的是与view有关的state,所以称之为ViewState。ASP.NET开头的策略是在服务器端处理一切,所以发明了ViewState并尝试让状态在客户端仅作持久不作修改。然而我们需要的正好是相反的,我们需要一个在客户端能修改的状态,并且以URL作为持久的方式。

首先,我们不可能好像ASP.NET保存ViewState那样保存数据到URL当中。即使我们舍弃了meaningful,ViewState的容量还是会超过URL的长度限制,因此希望好像ASP.NET那样每一个控件各自汇报自己的ViewState然后页面负责统一持久到URL是难以实现的。(当然,在ASP.NET AJAX中尝试一下这样的实现也未尝不可,虽然其提供的控件少之又少。)

既然我们不能够好像ASP.NET保存ViewState那样“放肆”地利用URL进行持久,那么我们就必须针对特定的应用来考虑持久的方式,这正是无法开发一个通用框架实现此功能的原因。例如上面所说的gallery,我们要人为的考虑使用tag作为区分view的一个状态参数,接着我们可能还要考虑当用户选中多个view之后会发生什么事,是不是简单的在URL里面叠加多个tag呢?例如gallery.html#fun+event的样子?那么它和gallery.html#event+fun是等效的,而两个URL会不会导致PageRank的分散呢?之后又如何加上分页参数呢?这一切问题不是没有答案,而是它们的答案都太具针对性了,因此不具有通用性,难以做成一个可复用的框架实现。

因此我们在这里能做到的最多就如ASP.NET Futures或者别的框架提供的history功能那样,提供一个有限容量的string空间给你,你要自己决定如何把状态转换为string然后提交给它保存,之后它保你实现跨浏览器的Permanent URL和history支持。

到此为止,我们站在client centric的角度讨论了AJAX现在面临的一些重要障碍,以及潜在的解决方案。下一part我们将从server centric的角度来继续研究这个问题,并探讨ASP.NET是否能在AJAX方面做得更好,又或者是别的Microsoft技术。继续关注本文章系列,欢迎订阅Cat in Chinese(feed)或Cat in dotNET(feed)。

2007年7月25日星期三

Hong Kong Trip

刚刚从4日3夜Hong Kong trip回来。事实上期末考的时候我就计划要去HK看看,一方面是去参观几所大学,例如科大、港大、中大啦,另一方面是去了解一下香港是一个怎样的城市,例如市民生活如何之类的。所以一放假我就去办通行证了,上个周末终于去了一次HK。

Day 1

第一天我们选择了不搭直通车而搭到罗湖,然后过关后转东铁到大学站,参观香港中文大学。中大座落在山上,面积超大,而且那天没有人带着游览,所以也不知道看什么好,很多区域也不能进去看,只是感觉的学校真的很大。很多教学楼都是傍山而建的,因此两边的出口在不同的楼层,高度差可以导致两个入口相差5层之多。

中大游览之后就去了尖沙咀看科技馆,正好碰到了“飞龙在天”的恐龙展览,于是就马上买了半价的学生票冲进去。有寄存服务真好,可以把书包扔下自由自在的看科技馆里面有趣的展品。展品有很多都是可交互的,虽然弱智了一些,因此很多小朋友在那里玩。

在科技馆中,和Lucy约好了7点到中环站,于是看完我们就搭地铁过去啦,本来想去ferry一下的也没时间了,结果去到Lucy才说最好等埋她同事然后带我们去吃好野,早知道就去埋ferry啦……

晚餐是去吃牛扒,好好味哦!然后回Lucy住酒店Poker,并且Lucy人品暴涨说可以让我和Singzy睡她的房间然后她睡sofa。说到Poker嘛,我发现自己不擅长这类要求下注的游戏,因为我总是没办法在短时间内算准可能出现的情况,我还是适合玩长线投资的game多一些,咔咔……

Day 2

第二天起来是Lucy还在睡觉中,貌似正中了“Hongkongese没有星期六早上”这个说法,因为大家星期五晚上都是用来娱乐的,那么星期六早上就是在睡觉中度过的。我和Singzy独自跑去Hollywood Rd,发现原来半山行人电梯早上是下行的,非常幸运,那就搭行人电梯下去了,结果原来Hollywood Rd也在“睡眠”当中——基本上没有开铺的。

既然Hollywood Rd没东西看,就搭车去香港大学咯,感谢昨天那么多人告诉我们23能到港大,就去坚道搭23了。去到港大东闸下车,发现10:00会有guided tour喔,而当时是9:00,面对的就是港大美术博物馆,当然是选择进去走一走并且等guide的出现啦。美术馆的叔叔很nice,后来出现的那位guide也很nice,于是我们就跟随着guide和一大堆同行的小朋友们游览了港大。

港大之后就是去深水埗看电脑,看到3款macbook的售价分别是8600HK$/10200HK$/11700HK$,也就和macx的代购价差不多,因此估计macx买得多所以买入价比单台的零售价还要低吧,这样它才有得赚。另外看到一本台湾翻译的Transcending CSS,译作《超越式CSS》,要180HK$,没有狠心买下来,回来发现这本2006年底出的书竟然emule上还没有pdf下载,极度后悔中……

午餐吃的是McDonald's,发现mid-size的薯条包装虽然和广州的差不多,但分量多很多啊,简直要撑死了。之后下午就去了黄大仙和科大。

香港科技大学的超级无敌大海景就是在吸引死人了,教学楼也是傍山而建,不过是直接面朝大海,而且整体规划所以效果很好,每一栋楼都能望海。可能因为科大已经考虑都会有相当多的游客来参观,所以有专门的纪念品商店、展厅等等,图书馆还在搞校史展览,新建的学校果然思想也新潮很多,很重视publicity。

在科大的图书馆看校史展览时很郁闷,每次一站定手机就开始震,然后冲出门口接电话,讲完又走回去。其中包括我姨妈的电话,约定了晚上到上环,于是就离开了科大。由于时间还有多,所以就没有搭地铁回香港,而是去ferry了——从尖沙咀的天星码头到中环码头。在渡轮上吹海风感觉很爽,可惜就是很快就到对面了。

Day 3

在罗湖过关的时候看到有书展的广告,所以第三天就去会展中心看书展了。超多人,龙尾排到很远,不过进场很有效率。实际上对比香港和广州,香港很多事情都要比广州高校很多,例如交通就如此,香港的街道上不会有“散步车”,能开的就很快地开过去,路上的行人能通行的时候也走得很快,总体来说应该是整个城市的节奏都很快吧。在广州的话,大家都习惯了慢慢来,不要那么紧张和冲动,所以效率也就完全不同了。

书展总共有5个hall,走完花费了5个小时。香港人看的书和我们看的书是不同的,貌似他们更多看书是为了休闲,至少他们平日工作已经够紧张的了,所以书一定要有相当趣味性才能看进去,太过单调的理论书是没有人买的。我不是说数学书,而是说例如基金书之类的。现在大陆够多人买基金书啦,很理论的书都有人买,但这在香港可能就是行不通的。

在书展当中我什么书都没买到,走了5个钟头啊……感觉比较可惜,真应该下狠心买一两本大陆绝对买不到的书回来看看,虽然价格肯定是大陆版的2~3倍,假如大陆会出版的话。promotion做得最厉害的一本书就是Harry Potter啦,最后一本哦,而且就在星期六全球同步发行,在走道一路看过去都是Harry Potter的标价,而且一个比一个低。另外一本很鬼死多promotion而且题材也很有趣的书就是台湾翻译的《把妹达人(The Game)》,内容看起来也很有趣,不过基于我不舍得花钱……所以还是选择了回来再找pdf。

书展之后超级疲劳的再次来到McDonald's,又是超大分量的薯条啊……已经吃到闻到薯条味就想呕了,一个月内不要再吃薯条。这餐是下午吃的,都不知道算午餐还是晚餐了,中午仅仅就在会展了面吃了几个墨鱼丸啊。

在McDonald's有很多菲佣,其实满街都是,因为是星期天,她们都出来聚会。忍受够吵闹的McDonald's之后我们决定跑去山顶看看,由于第一天碰到两个台湾来香港旅行的人问我们中环站如何走到缆车总站,因此我们步行到金钟站然后打一个站的地铁到中环站,打开guidebook一看才发现——原来金钟站和中环站步行到缆车总站的距离都是15分钟左右,晕掉了……走到缆车总站开始排队,我们开头想着去山顶看日落的,结果上到山顶看到的是“日落ed”——也就是说太阳刚好下山了,不过能吹吹风也不错。

在山顶也没有吃什么正经的晚餐,Starbucks喝了两杯东西就又要开始排队等缆车下山了,然后还是搭23回到坚道然后经半山行人电梯回hotel。貌似我们只知道23这条线,哈哈……

Day 4

最后一天,其实已经没有任何listed的行程了,于是大白天都在hotel里睡觉。Lucy说过中午请我们吃饭,所以11:30才起床去找Lucy。Lucy说搭12能够去到她上班的遮打大厦,我们从hotel下来连站牌都还没看清楚就有12停在身边了,那就上咯,看到哪个站合适就在哪个站下吧。HK的bus都当你是local,根本不报站,除非你主动问过司机你应该在哪下然后请他提醒你。作为一个旅游城市,HK路面的标示都超明显而且对游客有帮助,就是bus不报站这一点对游客很不friendly。

事实上Lucy也不知道去吃什么好,所以在兰桂坊随便挑了一家店就坐下来了,发现要一个4人套餐分量充足(废话,我们才3个人),而且每一款都很好吃。吃饱之后就和Lucy say goodbye然后去香港历史博物馆了——我们离开香港前的最后一战啊,因为博物馆可以寄存所以很适合背着行李去。

历史博物馆里面也碰上了guided tour,虽然整个tour并不让你有多少时间看清楚展品,不过guide的讲解也很精彩,能让你听到很多展品街市上没有说的事情,可惜我们时间有限,完成1个半钟的tour之后就没时间返回开头细看展品和演出了。很有趣的一件事情就是,两次积存行李时都被别人说“怎么你们的书包这么重啊”,其实就装了4天旅行的一些必需品啊,而且我们觉得也就是acceptable的程度,而非overloaded,看来HK的小朋友们的书包确实也不重啊,否则他们的daddy、mommy怎么会没见识过overloaded的书包呢。

17:55那班直通车,我们从红勘站离开了香港返回广州,不过因为列车晚点实际上我们18:30才上车。回到广州发现整个pace又慢下来了,所有事情又回到了我们习惯的速度。

2007年7月24日星期二

MVP 购物 coupon

使用Company Store的coupon购买的礼品收到了,我买了$90的硬件,包括webcam、headset和keyboard,剩下的$60用来买一些小礼品。小礼品大多数都是$10一件吧,买了一个大杯子给我爸,一个水壶给我妈,一个水瓶给Singzy,一个笔记本给Piggest,还有一件衣服是我自己的,之后剩下几$就买些钥匙扣之类的琐碎物。

我觉得这算是一种分享happiness的途径吧,而且这是一种保持自己happy的有效方法。当你拥有一些resource的时候,可以考虑不是把它们独占着,而是分享给更多人,同时又不引起什么竞争或者冲突,那就是最好的了。

2007年7月16日星期一

分享或索取新成立网站的邀请

如果你属于关注Web2.0的人,常常希望了解一下startup(新成立网站)的创意以及实现效果,又或者你仅仅是非常geek非常喜欢试新,你都经常需要各式各样的邀请以加入到所谓的private beta当中去。正所谓private beta,一开始的邀请当然只有开发者身边的朋友能拥有,而你和他们的距离是未知的,所以只能选择等待。

之前曾经有一些网站尝试去做邀请共享的服务,当时最热门的当然是GMail这样的邀请啦,于是大家就都把自己的GMail邀请链接投放到共享网站上,需要的人就直接获取。现在的大热变成Pownce了,甚至有人在eBay上出售邀请,不过也有免费的获取方法,那就是InviteShare。这个网站不仅仅共享Pownce邀请,还共享非常多其它网站或服务的邀请,例如JoostWeeWar的。你需要做的,仅仅是在InviteShare上面注个册,然后找到你想索取申请的网站,把自己添加到等候列表上,之后就等email通知吧。如果你觉得排队的速度太慢了,可以通过发送邀请来提高自己的优先级——从你手上成功发送出去的邀请越多,你排队的优先级就越高。

之所以说InviteShare,是因为我觉得它是一个做下游服务做得非常成功的例子。当某一种服务的数量越来越多的时候,例如需要邀请的Web2.0服务,那么它们的共性就会表现出来,这时候也就可能形成一个新的下游服务市场。这种情况往往是可预见的,例如Web2.0的private beta成为一种流行的服务推广方式,那么在恰当的时机推出下游服务也就是可行的。当然,事情的发展往往都是很快的,恰当的时刻一眨眼就过去了,因此我们需要一种相当敏捷的开发方式来实现我们能够提供的服务。

2007年7月14日星期六

今天收到 MVP 礼包了

礼包的寄送过程有点混乱,不过最终还是送到我手上了。拆开DHL的包装后,看到的是漂亮的MVP礼包盒子,诺大的纸盒打开以后发现包括一张证书、一枚别针、一本说明书和一个皮革质地的小盒子。

证书是很正式很传统的样子,有点像英文的毕业证书那样子吧,看字体就很有中世纪的feel。别针是MVP的徽标,比团徽重多了,针也粗多了,所以别在衣服上貌似不太合适,也不知道可以别在什么东西上。说明书则包括很多关于MVP身份的介绍性材料,当然还包括提醒你去消费那$150的Microsoft Company Store购物卷。事实上,真正有价值的东西都藏在皮盒里面,包括参加会议时可挂在脖子上的namecard(当然是注明MVP的了)、一个有MVP印记的皮质名片夹、一叠有MVP水印的memo纸、一支带激光的圆珠笔、一只皮质外壳的1G U盘。

对我而言,最有价值的应该就是那支笔了,以后做presentation就可以用它的激光在屏幕上随便指,这点比较爽。U盘是其次,我现在有个80G的移动硬盘,也有一张1G的SD卡可搭配读卡器使用,所以U盘并不显得是什么必须品,不过1G的空间以及漂亮的外表,还是很让人喜欢的。

差点忘记了说,在礼盒的底部有一个信封,装着的就是一式两份的NDA(Non-Disclosure Agreement),也就是保密协议。签完之后放到附送的信封里,直接投到邮筒里就是了,因为信封上说明了由收信方付费的。

2007年7月2日星期一

Microsoft MVP Award received

昨天晚上收到了邮件,告诉我获得了Microsoft MVP奖励,然后异常兴奋。要做的第一件事情当然是告诉身边的各位好友啦,特别是感谢那些在此事上曾经支持我的人。事情有时候很奇怪,至前几年热衷于上CSDN回答问题,去年开始将.NET有关的帖子集中到博客园并提升更新频率,这些都是比较“无心插柳”的事情,最后反而有结果了。

或许应该这样说吧,一些事情虽然我想要得到结果,然后过程却不是我真正想做的,在学校里压力不大,所以做事的时候不专心,总是很容易就进入了wandering的状态,结果当然不好。而另外一些事情,我做的时候很enjoy,虽然看不到什么结果,但积少成多最终还是会有结果。因此,在我的毅力得到充分的提升之前,或许我还是应该选择多做一些喜欢做的事情,而如果一件事情我发现无法强迫自己专心做下去那就应该放弃了。

其实人是否应该选择做一些自己不喜欢的事情,然后积累资源(例如金钱)再去做一些自己喜欢做的事情,这一直是个争论。有些成功人士认为这是应该的,但另一些则认为你必须从一开始就尽量选择自己真正喜欢的事情,那样才能把你的生命效率最大化,从而无论做出来是什么都是最成功的。Anyway,反正成功的定义是很personal的事情,你觉得自己活着怎样好就怎样好,没必要考虑太多社会评价,所以这个讨论上存在分歧也是很正常的。

2007年6月28日星期四

IQ 测试

今天早上的数字图像处理期末考试完全变成了IQ测试,非常有趣。作为专业选修课,很多人都是上课但不怎么听,书也不怎么看,然后就去考试了。考试是开卷的,说明了题目来自平时作业,不过数据会有所有不同,于是大家就把老师给的作业答案带去了。

这里先说明一件事,就是其实大多数人的作业也不是自己做的,都是参考着一份别人做好的“作业模板”做的,最好的情况下这份模板需要你填的就只剩下姓名和学号了。因此,考试的时候即使仅仅考作业题,也未必是懂的,那么怎么办呢?就只有临场学习咯。

IQ题是怎样的?例如3*3共9个图案,其中缺了一个,要你从4个里面选一个补上去让它最符合规律。这就是今天我们考试所做的事情。我们看着那份作业答案,里面只有题目和答案但没有中间过程,于是我们就要想尽办法去找规律——到底如何从题目推导出答案,然后模仿着这个推导过程去做试卷。整个过程真的和考IQ差不多,不过题目要难度了,因为规律并不简单。

2007年6月26日星期二

I love Microformat

在我制作自己的在线简历以及作品时,就想到是否能设计一种microformat用于描述resume。事实上,相信我们很多人都有过在网上投递简历的经验,每次大多数相同的项目都要重新填写,极之麻烦,所以我就希望这些重复工作能够一次搞定。相对于制作一个客户端的helper来帮忙填写简历,我更希望是一种描述简历用的microformat,因为我自己正在制作在线简历,而且希望这是一个有潜力的市场。

每天都有很多人网投,特别是毕业生,如果制作一个helper确实能够帮上忙的,然而这个工具无论被多少人使用,最终就是一个小软件,别人极容易模仿着做一个更好的。这样的客户端软件没有任何的粘连性,用户觉得另外一个更好的话随时可以换,所以做成服务很有必要。Web2.0时代,SaaS了,这才是真正的发展方向,而microformat能够和这个沾上边。

开头我们当然只有描述resume的一种microformat,制作在线简历的人少之又少,就好像回到了1998年之前,能够拥有一个美观而且有内容的个人网站是非常值得让人羡慕的事情。1999年,Blogger出现了,为当时那些拥有个人网站的精英群体实现了一个梦想——简单易用的日志管理方式。然而一开始确实也就只有高端群体在使用Blogger这样的工具,直到被称之为blogger年的2003年,使用各种blogging工具的人数开始激增。我期望这种描述resume的microformat的发展也如此,在经历了只有少数高端用户使用的阶段后,能够逐步大众化,变得好像blogging一样容易。

这背后潜在的市场也就是在线简历的空间与管理服务了。就如BSP一样,如果提供一定的存储空间,并且有相对简单易用的编辑功能,若干可以选择的模板,相信谁都可以马上制作一份在线简历,然后或许再购买一个域名搭配上。就如同Blogger提供的服务一样,中低端用户或许只会简单地从模板中选择一个合适的,并且将简历放在服务提供商的空间上,就好像Blogger发布到Blog*Spot上一样简单而且免费;高端用户会购买自己的域名和空间,将简历发布到自己的空间,或者关联到自己的域名,设置更加精美的模板,以便和自己原有的个人网站融为一体。

发展这个市场所面对的问题是,假如多遍填写简历已经够麻烦,为什么还要多维护一份简历,这份可是要保持更新的哦。这就是背后microformat所需要去推动的事情,我希望以后那些专门做网投的网站支持这种microformat,如果你有一份在线简历了就可以直接输入其地址,网投网站能够自动抓取你的简历页面并且完成信息提取工作,之后你就仅需要填写一些与当前申请工作有关的特性信息就行了。

令我感到吃惊的事情是,最近一次查阅microformats.org的时候发现已经存在一种基于hCardhResume格式了,虽然仍然在草案状态,支持的简历栏目也仅包括最基本的,然而却已经得到广泛的应用。已经有一些人在自己的在线简历上使用了hResume,也有专门生成hResume代码的工具,甚至连WordPress插件也已经有了。那么看来高端用户要采用hResume已经很容易了,只要他现在使用的是WordPress,马上就可以通过该插件在自己的个人网站上添加简历。

虽然我前面所说的潜在市场或许已经有充足的供应了,新竞争者要进入市场必须有相当的吸引力,然而我还是觉得能有microformat支持是一件很好的事情。microformat的最大好处是无需丢弃任何已经写好的HTML代码,需要的仅仅是添加新的附加信息上去而已,只要你觉得一种信息有对应的良好组织方式,你就可以提出一种microformat去描述它,以便更多的浏览器、搜索引擎更好的理解这类信息。

2007年6月22日星期五

Adobe Fireworks CS3 很容易上手哦

我以前一直不喜欢Macromedia的产品,因为觉得它们用起来都太像堆砌积木了。堆砌积木不正是编程的理想模式吗?低耦合度的话就是,然而Macromedia自动生成的代码却不是。

就拿DreamWeaver来说,如果一个页面的源代码一看就发现全是MM开头的JavaScript函数,那么我就不会看下去。使用DreamWeaver生成JavaScript是不对的,至少我认为是不对的。《程序员修炼之道》里面说明了你不应该用一个生成代码的向导,假如你无法理解它生成的代码是干什么的话。而用DreamWeaver的人很多都在干着事情,感谢51js之类的网站,以及非常丰富的DreamWeaver脚本生成扩展,中国有大量的站长在使用那些他自己完全不知道在干什么的JavaScript。

然而堆砌积木也有好处的,特别是对于非专业人员。例如我不是专业的平面设计人员,我无法驾驭Illustrator这样的东西,这时候简单堆砌图形的Fireworks就非常适合我了。我主要使用Fireworks来设计页面的mockup,设计好就slice几下把必要的图片输出,接着就可以在Expression Web Design里面写XHTML+CSS了。Fireworks将矩形、圆形等常用的简单构图元素突出出来,而页面设计需要的正是大量基于矩形的元素,因此使用Fireworks设计mockup的效率真的很高。

2007年6月21日星期四

Becoming More Spiritual

人生总有突然为某些事情顿悟的若干次,我不知道这次算不算,但我发现自己更加spiritual了。

关于如何确定人生目标的方法看过一些,不过我觉得没必要试,反正我是一个很清楚自己想要什么的人,不过现在发现那些我很清楚的人生目标都是比较material或者低层次的spiritual的。李开复的那本《做最好的自己》中提到确定人生目标的时候,提到了假如你剩余若干时间的生命的时候会做什么,以及你死了之后别人的评价会是如何。对于前一个问题,我还算清楚,其实我不觉得有什么很急迫的事情必须完成的,所以我会放松地去完成我觉得可以完成的事情,就算完成不了也没什么所谓。至于后面那个问题,在高中的时候我对它的想法是比较material而且tragedy的。

我之前认为,即使我拥有一家庞大的国际化企业,有一个健康的家庭,我死掉之后事情将被很sophisticated地处理掉——幻想一下我有个office,而且至少是在中信那样能称之为tower的建筑物的顶层,然后有一天我就从那个office跳下去了,这一跳纯粹是“好奇心能够杀死一只猫”的结果,也就是我想知道之后会发生什么事,假如我还有能力知道的话。在大型城市中,救护人员会快速赶到现场,我的意思是帮忙确定我的死亡,好让那些记者回去有东西可写。然而在记者把文章发表出来之前,董事会的各位已经在思考自己的下一步棋怎么走了,因为接下来可能有不少的因素会显著影响股价,持住一大堆股票的人当然要先想清楚自己该买还是该卖,然后选择正确的对外立场。与此同时,企业良好的管理机制开始运作,适当的人选自然会被放到适当的位置,用来弥补缺少我的作用力以后所造成的影响,生态环境过一段时间自然能够重新平衡。至于家庭,他们——也就是妻子和孩子——是否好像企业一样找个人来替补我的空缺就是他们的事情了,反正我也没有发言权,然而只要他们有相当的股票在手,并且上面的事情能够按照预期的平稳处理掉,那么他们的生活也能平稳过渡。总之,这些如此sophisticated的事情总有人能负责的,无论我是否希望有人负责。

反正呢,material的东西你是无法带走的,而这个社会现在已经能够非常高效地把你遗留下来的东西瓜分干净,之后也或许就不会有人想要去悼念你。因为说到material了,如果你真的有相当多的股票,即使你的所有器官都捐赠出来,和这些股票的价值相比也就是冰山一角,除非你能在股票上印上你的头像吧,否则这个注重效益最大化的社会真的没什么必要去惦记你。因此,你留下的material价值越多,你的身体或者是贴身物品的价值比例就越低,别人就越没有理由去想着这仅占一点点比例的价值,这就是所谓的tragedy了。

现在,我突然间感觉到spiritual的重要性了,因为这才是你真正能够带走的东西。你死掉后别人没办法从你的大脑里把你的思想挖出来瓜分掉,至少暂时没办法吧,那么人们就会真心地感觉到他们失去了你。如果你的spiritual有相当的价值,例如你是画家或者诗人之类的,那么这个社会就会感觉到它真的失去了巨大的财富了。因此,在考虑人生目标的时候,spiritual是一个很重要的衡量角度,忽视了这个角度就可能导致不那么理想的结局,我的意思是假如你在乎别人对你的评价,又或者你死掉以后还有能力去评价结局是否理想的话。

另外最近看到一篇名为Life After Death的文章,如果你之前有看过我那篇《小睡 / Polyphasic Sleep》的帖子,或者你有关注过“小睡”的概念,就肯定知道其作者Steve Pavlina。这家伙的文章貌似挺注重与生活质量的方面,例如之前介绍过小睡,最近又在写如何能做到早起床;同时也有关注精神领域的题材,例如如何发现你的人生目标。我们把话题回归Life After Death这篇文章上,它的中文翻译版本名为《死后的生活》,主要讲你死掉后可能的精神状态。对于死后的生活来说,最不幸的,或者对某些人来说是最幸运的,也就是你的精神和肉体一起byebye了,那么你也就没办法为死亡做什么准备了,好好享受现有的生活吧;另外一种情况就是你的精神仍然存在,或许被冻结了,或许能继续发展,无论是哪种情况,假如你在死掉之前积累了充分的spiritual,那都肯定是好事情。

因此,我开始感觉到积累自己spiritual方面的感悟是生活中总要的一部分,必须投放相当的时间与精力去修炼,以便将来无论发生什么事情都至少能spiritual的生存下去。最坏的情况下,不是直接死亡,而是被关进纳粹集中营那样肉体上名存实亡,有相当的spiritual生存能力或许还能让你的肉体坚持下去。

2007年6月18日星期一

从 Dynamic Data Control 回归普通的 Data Control

我尝试在自己的软件工程项目中使用DDC,希望它能带来一些比较敏捷的特性,虽然在那么正统的软件工程课程上搞敏捷貌似有些不妥,不过其实也没有几个小组是完全按照流程来做的,反正最终所有文档齐全了演示也能让老师觉得效果不错那就行了。事实上很多小组都是随便分析一下就开始写代码,东西做出来了再补文档,哈哈……

新建了一个ASP.NET Futures的网站后,我先尝试使用DynamicDataAuto控件,发现确实如demo显示的那样“功能齐全”——所有字段都能在GridView中列出来,支持图片的直接显示,也支持外键的直接链接,一个页面上集成了所有的CRUD操作。然后我开始想细致定义这个页面,发现这个scaffold不能好像RoR的scaffold那样自动分解为低级代码,于是就尝试手工转换。首先把DyanamicDataAuto删掉,按照我的需要换上特定的Dynamic Data Control,然后发现可定制性不强,继续换……最终,页面上的控件降级到GridView了,Dynamic Data Page的特性完全没有用上,于是干脆换回普通的Page。

实际上现在的Dynamic Data Control一点都不好用,就如同ASP.NET AJAX的拖放支持一样——现成做好的控件限制太多,自定义需要实现的也太多,难以快速开发一个支持拖放特性的自定义控件。如果要使用Dynamic Data Page,那意味着Page本身为你提供了统一的Data Source,所以Page上面的Dynamic Data Control就不用设置Data Source了,然而这个特性并不能为传统Data Control所用,这就意味着一个Page上要么只用Dynamic Data Control要么只用传统Data Control。然而Dynamic Data Control的定制性实在有限,所以面对上述binary choice只能选择传统Data Control了。

暂时来说,我们也不能用Dynamic Data Control做些什么,只能等下一个版本的ASP.NET Futures看看是否有什么改善。现在的Dynamic Data Control就如同Atlas的第一个CTP那样(那时候还有<atlas:TextBox />这玩意儿),或许到最后整个模式都改掉了。