2007年6月12日星期二

探索 ASP.NET Futures (Part 3 - Client Diagnostics)

貌似ASP.NET 2.0新增的诊断相关服务没多少人关注,更没多少人用,不过对于正在使用此类服务分析站点的人来说,肯定非常期望ASP.NET AJAX中的客户端代码错误也能记录到诊断日志中,这样就能获取更丰富的数据来分析站点不稳定因素的来源。

ASP.NET Futures中已经引入了客户端诊断服务,在解释此服务之前不妨先思考一下假如你要自己写一个客户端诊断服务会怎么做。如果我来写一个客户端诊断服务,我会设计一个Web Service负责收集错误信息,然后记录到数据库中,然后写一个js负责拦截客户端异常并且发送到该Web Service。ASP.NET Futures (May CTP)中引入的客户端诊断服务也差不多就这样子,只不过设计得更加通用更具扩展性。

使用客户端诊断时你首先要在页面上放置一个Diagnostics控件,它负责的就是向客户段输出一段js以便拦截客户端异常。熟悉ASP.NET AJAX控件设计的人应该能猜到Diagnostics控件肯定是一个ScriptControl,不熟悉ASP.NET AJAX控件设计的话也可以通过这个例子了解到,如果你有一个客户端组件要以控件方式在Page上部署那就应该考虑继承自IScriptControl接口了。

接下来我们要部署接收诊断信息的web service了。新建一个名为DiagnosticsService.asmx的web service,更改其code-behind声明如下:
<%@ WebService Language="C#" Class="Microsoft.Web.Preview.Diagnostics.DiagnosticsService,Microsoft.Web.Preview" %>
这样做的意思是,此web service不是使用你写的code-behind代码,它的code-behind类直接使用上述的DiagnosticsService类,所需的函数都在里面了,你不需要再写任何代码。那么我们到底在哪写代码记录客户段发送来的诊断信息?先别急,接下来我会解释此事。需要说明的是,ASP.NET Futures现在这个做法的风格和原来的ASP.NET AJAX有点格格不入,因为它没有直接采用HttpHandler的方式直接暴露此web service,而必须要开发者自己部属一个asmx文件,将来此做法可能改变,届时将不再需要手动部署asmx文件。

仔细留意DiagnosticsService,你会发现它有一个OnClientException的事件,这才是你真正需要关注的地方。为了确保将所有客户端异常都记录下来,你可以在Global.asax的Application_Start中绑定此事件,在HttpModule中绑定也是可以的。在事件的处理函数中,你将通过EventArgs获取到一个ExceptionServerInfo的类型,在里面将会记录如下一些与客户端异常有关的属性:

  • Data
  • FileName
  • LineNumber
  • Message
  • IpAddress
  • Timestamp
  • UserAgent

你可以直接访问这些属性,以你喜欢的方式进行日志记录,例如记录到你自己的数据库,或者是ASP.NET 2.0的health monitor。

好了,这样就解释完如何使用客户端诊断服务了。嗯……等等……还要一样重要的事情没有说,那就是在web.config中启用此项服务。首先要声明config section:
<configSections>
  <sectionGroup name="microsoft.web.preview" type="Microsoft.Web.Preview.Configuration.PreviewSectionGroup, Microsoft.Web.Preview">
    <section name="diagnostics" type="Microsoft.Web.Preview.Configuration.DiagnosticsSection, Microsoft.Web.Preview"/>
  </sectionGroup>
</configSections>

然后声明启用此服务:
<microsoft.web.preview>
  <diagnostics enabled="true"/>
</microsoft.web.preview>

最后,如果你有兴趣继续关注有关ASP.NET Futures新功能的介绍,可以考虑订阅我的博客:

2007年6月11日星期一

Apple 发布 Safari 的 Windows 版了

以前一直因为没有Windows平台上的Safari,所以设计CSS Template的时候就没办法测试对Safari的兼容性,或者只能使用browsershots.org这样的服务来查看CSS Template在Safari中的视觉效果。现在Apple最新发布的Safari 3 Public Beta已经有Windows版本了,那就能很方便的测试CSS Template的兼容性了。

Safari的Windows版本有一个重要的战略意义,那就是让你可以测试你的应用是否能在iPhone上面跑起来,包括AJAX应用。由于iPhone暂时还不会有任何第三方软件,所以它上面能够有什么第三方服务就依赖于Web了,而Safari对AJAX的支持则成为了亮点。如果你是服务提供商,并且希望你的应用将来能被iPhone用户所使用,那么使用PC上的Safari将能让你提早进入网站兼容性测试的环节。

探索 ASP.NET Futures (Part 2 - Search Enabled)

在本系列的上一篇文章中,我们探索了ASP.NET Futures (May CTP)的SearchSiteMap功能,说明了如何将ASP.NET的SiteMap影射为符合Sitemaps协议的XML以便搜索引擎更好的抓取我们的站点。然而让搜索引擎更好的抓取我们的站点了,这部分的优化却仅仅对来自于搜索引擎的访客有用,这是否有点浪费?我们是否可以选择站内的搜索也通过Internet搜索引擎(例如Google)来实现,从而避免开发自己的站内搜索引擎?从ASP.NET Futures (May CTP)开始,这也便得可能了,需要使用的仅仅是SearchDataSource控件以及简单的几句配置。

首先说说SearchDataSource是干什么的吧,它其实是一个ObjectDataSource的派生类,只不过设置了基类的TypeName为"Microsoft.Web.Preview.Search.SearchService",而SelectMethod则是"Search"。事实上你放置一个普通的ObjectDataSource然后设置这两个属性也就可以获得SearchDataSource的效果。之后你就可以像使用普通DataSource控件那样使用搜索返回的数据,绑定到Repeater或者GridView,又或者绑定到你自己的数据控件都是可以的。

SearchDataSource返回的貌似是一个IEnumerable,而事实上是一个List<SearchResult>,通过Reflector阅读SearchService.Search方法的代码就知道了。然后我们来看看SearchResult类型包含什么属性。它拥有3个string属性,分别是:Url、Title、Description。在使用SearchDataSource的时候,数据控件绑定这3个属性就够了。那么之后我们还需要做什么呢?接下来就是配置provider了,因为SearchService.Search方法正是通过轮询各SearchProviderBase派生类来获取搜索结果的。

这听上去很复杂的样子哦,远超过了放置一个SearchDataSource的难度。其实不是的,ASP.NET Futures (May CTP)中已经包含一个SearchProviderBase派生类了,那就是WindowsLiveSearchProvider。此provider的配置方法如下:
<microsoft.web.preview>
  <search enabled="true">
    <providers>
      <add name="WindowsLiveSearchProvider"
        type="Microsoft.Web.Preview.Search.WindowsLiveSearchProvider, Microsoft.Web.Preview"
        appID=""
        siteDomainName="" />
    </providers>
  </search>
</microsoft.web.preview>

其中的appID是你在Windows Live Search申请的Application ID,还没有Application ID的话可以到Windows Live Search的Developer站点申请一个。siteDomainName就是你的网站的域名,例如我的网站就是CatChen.biz。另外,你还要确认Windows Live Search已经抓取了你的网站的页面,通过大部分搜索引擎都支持的site:指令你就可以知道一个搜索引擎对你的网站的抓取情况,例如我可以通过site:CatChen.biz搜索来了解Windows Live Search对我的网站的抓取情况。完整配置并且确保搜索引擎对你的站点的抓取后,你就可以通过SearchDataSource获得期望的搜索结果了。

如果你不满足于WindowsLiveSearchProvider提供的功能,你可以实现自己的SearchProviderBase派生类。在官方文档中Enabling Search页上,最后一个例子提供了两个自定义的provider,一个是用于Yahoo的,另一个是用于Index Server的。另外不要把目光局限于某个特定的搜索引擎,如果你觉得Google、Yahoo、Windows Live都不够好,那么你可以考虑做一个自己的meta-search(源搜索)引擎。所谓的meta-search是指通过调用若干个不同的搜索引擎,根据它们的返回结果再次进行信息的分析与提取,最后生成自己的搜索结果。

在你实现了自己的SearchProviderBase派生类后,有一点需要注意的,那就是SearchService是不知道返回结果的排名的。一般我们都会认为搜索结果越靠前的,当然应该是越匹配的,这在配置一个provider时是没什么问题的。然而配置多个provider后,SearchService仅仅是将多个provider的结果拼接成一个更大的List<SearchResult>,这时候就毫无排序而言了。因此,如果你需要使用多个provider,我还是建议直接写一个SearchProviderBase派生类,使用meta-search的方式自己去合并以及重排结果。

最后,如果你有兴趣继续关注有关ASP.NET Futures新功能的介绍,可以考虑订阅我的博客:

2007年6月7日星期四

Languages Differentiate Thinking?

其实很早的时候我就意识到可能使用中文思考相对于使用英文思考来说,对思考者某些特定领域的思维能力会带来一定的正面或负面影响,不过我不很确定有什么例子能论证这个话题,也不知道哪个领域的思维能力造成的影响可能是最明显的,所以也就没有继续想它。

上个星期新东方有个老师说,使用英语思考能够让人的逻辑思维能力更加严谨,因为没有哪一个在中国的华人能够拿到诺贝尔奖,然而华人在美国改用英语思考就能拿到诺贝尔奖了。或者这根本不是一个有价值的例证,反正新东方老师上课吹水是必须的,然而这却让我重新感到问题本身确实值得思考。

首先我们来看看程序设计语言而不是自然语言之间的区别。我最近阅读到一篇文章讨论了程序设计语言与解决方案之间的关系:On Semantic Distance and Computer Languages。文章中认为,使用的程序设计语言的语义和解决方案的距离越近,实现起来就越容易,因为所谓的实现就是搭桥跨越程序设计语言以及描述解决方案的语言。然而文章最后说道,世界上有5000种语言(包括方言),你是不可能找到一个等价语义集合的,因此也就不存在一种语言总是最贴近任何一个解决方案,那意味着也不可能存在所谓的最好的程序设计语言。

可能这样说比较空泛,那么我们通过一些实际的例子来说明计算机语言与描述解决方案的语言之前的距离为何可以相去甚远,当然后者暂时指代的就是英语为主的描述性语言。假如你需要取一个值,它代表当前时间的20分钟前的时间,用Java写的话代码如下:
new Date(new Date().getTime() - 20 * 60 * 1000)
然而用Ruby写的话代码如下:
20.minutes.ago
上述例子来自Sometimes less is more,阅读该篇文章你可以看到更多这种Java和Ruby的比较。在此我无心说Java与Ruby之间哪个更好,我只是想找一个例子证明不同语言与解决方案语言间的距离是可以有很大差别的。

解释完程序设计语言有关的事情,那么我们可否直接拿上例来推导,并由此得出一个类似的结论,那就是不存在一种自然语言总是最贴近任何一种思维模式。正如如果你想写一段代码获取代表20分钟前的时间值有两种思维方式以分别适应Java与Ruby一样,中文与英文也有描述一件事情的两种不同思维方式,而随着你使用特定一种语言的时间积累,这种方式将反过来对你的某些领域的思维能力造成影响。

然而到底中文与英文训练人的思维能力的结果如何呢?我也不敢乱下什么结论,但从表象上看来,貌似英文适合逻辑推理而中文适合艺术创作。然而到底事实是如何的,我们可否遮盖其他文化因素的影响单纯探讨使用一种特定语言对人的思维能力的影响,这就是我能力范围之外的事情了,应该由社会学家以及语言学家来解决。

2007年6月3日星期日

Broaden Choices before Making Decision

继续发展《还有新鲜感吗?》那次的话题……最近真的在考虑MacBook哦,漂亮的MacOS为什么不能成为选择呢,反正Vista也是在模仿它的。

其实在有充足的选项之前,干什么那么早选定一个固定的方向呢,提前缩窄搜索范围,这在遗传算法里面叫做“早熟”哦。这个所谓的“早熟”会导致之后都搜索都限制在这个小范围内,即使让你求得了这个小范围内最优解,在全局范围内也不一定怎么优。所以遗传算法通常要避免“早熟”,在开始的时候要确保搜索解集尽可能在全局里分散。当然,还有一些更高级的方法避免类聚,然而这部在我们的讨论范围之内。

另外一种要防止出现的情况是不收敛,或者叫做“迟熟”,假如你一定要找一个和“早熟”对应的说法的话。不收敛意味着你在一个很广泛的范围内搜索来搜索去,貌似搜索了很多的解,但是你所能得到的最优解看起来总合全局最优解有一定的距离。

不过对我现在的情况而言,应该控制好避免“早熟”,过早收敛是不好的。找mm这事情也一样,如果不想“早熟”或者“迟熟”就应该做好范围控制,不能让它过早收敛,但又要避免不收敛的情况。

2007年6月2日星期六

Google 的赞助商链接在搜索结果之上!

Google向来声称广告在右边,明确标出,不会打扰用户看搜索结果,搜索结果放的位置才使人视觉焦点的主要位置。只有相关度本身就非常高的广告能够出现在搜索结果之上,并且也会标明为广告。

这个声明在PC上是成立的,即使把CSS给禁用掉仍然成立。CSS禁用掉不是两栏布局就变成一栏了吗?哦……别就只知道CSS布局,我们还有原始的table布局呢,Google的搜索结果页就通过使用table确保广告再右侧。

那么什么情况下广告会跑到上面了呢?在用Opera Mini的时候,因为Opera的服务器会对页面进行优化后再发送给客户端的Opera Mini进行呈现,这过程中不知道什么优化规则就让广告显示到前面了。不过先说明,这样的结果是必然的,因为在HTML中广告出现在搜索结果之前,Opera对内容按顺序进行分段并且优化时先处理广告自然先输出广告。

我觉得这种特殊情况对于Google来说是非常讽刺的,Google一直大力宣称搜索结果上层的位置你无法买到,然而在Opera Mini上显示时显然就是买的结果先出现。并且因为Opera Mini上面能显示的字体只有一种,所以“赞助商链接”这几个字就不那么明显,不留意看你就以为第一条广告是第一条结果。即使你知道那些使广告,要寻觅哪条才是第一条结果也许要费些时间仔细看。

2007年5月30日星期三

Microsoft Surface 发布啦!

非常cool的Microsoft Surface技术终于发布了,之前在YouTube上看过一些demo显示这是Microsoft正在研究的技术,以为要多少年之后才能变为实用,因为实在太Sci-Fi了。

值得注意的是,Microsoft Surface官方网站演示中所用到的UI技术正是WPF以及其基于的.NET Framework,这在ScottGu的blog中有所提及。

另外,Microsoft考虑优先将这台售价五千到一万美元的机器卖给商业合作伙伴,例如高档的餐厅、赌场、影院等,并且可能将于2007年底就开始投入使用,所以这东西是一点都不Sci-Fi的。

Popular Mechanics有另外一段视频深入解释了一些技术细节,可以在这篇名为Microsoft Surface: Behind-the-Scenes First Look的文章里看到。

Less Coke and Chocolate

昨天去补了很多只牙,估计都是可乐和朱古力惹的祸。平时经常吃完甜食,没有清理口腔的习惯,所以糖分就留在牙上面导致蛀牙了,而且这次还蛀得很深了,哎……

另外感觉省口腔医院很废,看上去设施和服务态度一流,不过总是治不好就是了。大半年前我已经开始有一点牙肉痛了,但中间去过几次省口腔医院他们都没告诉我有蛀牙,搞到拖了那么久。

2007年5月28日星期一

探索 ASP.NET Futures (Part 1 - Search & Sitemaps)

如果你在使用ASP.NET站点,同时又希望它Search Engine Friendly一些,很可能你就希望它有一个Sitemaps。在这里我们说的不是ASP.NET的SiteMap,而是Sitemaps.org定义的基于XML的Sitemaps协议,注意这两个名字的大小写以及单复数,之后我都会这样区分它们。Sitemaps协议有点类似RSS或者Atom,只不过它描述的不是最近的内容更新,而是整个站点的地图,主要用来描述特定URL的重要程度、更新时间及频率等。搜索引擎如Google是支持Sitemaps的,通过Google Webmaster Tools(以前叫做Google Sitemaps)你可以提交你的站点的Sitemaps,以便Google更好地索引你的网站。

简单调用

在ASP.NET Futures (May CTP)之前,如果你想要为你的ASP.NET站点增加Sitemaps支持,恐怕必须自己实现一个特殊的页面(或者HttpHandler)用于读取ASP.NET SiteMap并输出为Sitemaps协议。而现在这工作可以交给ASP.NET Futures的AspNetSiteMapSearchSiteMapProvider来做了,你需要做的仅仅是在web.config中写上几句。由于这个功能属于ASP.NET Futures中SearchSiteMap这个类别,所以需要在web.config中对该节进行配置:
<microsoft.web.preview>
  <searchSiteMap enabled="true">
    <providers>
      <add name="Navigation" type="Microsoft.Web.Preview.Search.AspNetSiteMapSearchSiteMapProvider, Microsoft.Web.Preview"/>
    </providers>
  </searchSiteMap>
</microsoft.web.preview>

在这个配置里面,我们启用了SearchSiteMap,然后配置了一个名为"Navigation"的Provider,此Provider使用AspNetSiteMapSearchSiteMapProvider类,就这么简单,和配置任何其他Provider的形式完全一致。之后你还需要确保一下有关的HttpHandler配置好了,如果你新建网站时使用的模板是ASP.NET Futures的,那么HttpHandler就应该配置好的了,配置信息如下:
<add verb="*" path="SearchSiteMaps.axd" type="Microsoft.Web.Preview.Search.SearchSiteMapHandler" validate="True"/>

这时候,如果你的网站已经正常启用ASP.NET自身的SiteMap功能,例如使用静态的Web.sitemap,那么访问SearchSiteMap.axd就应该能看到按照Sitemaps协议输出的结果。这时候或许你会很奇怪,为什么结果只有一条记录呢?这就是Sitemaps的递归调用了,这个主Sitemaps仅仅声名了我们之前配置的那个名为"Navigation"的Sitemaps的地址,也就是SearchSiteMaps.axd?sitemap=Navigation。打开这个地址,你会发现仍然是一个Sitemaps,它里面包含的就是ASP.NET SiteMap提供的数据了。

深入看看

接下来,我们用Reflector来看看Microsoft.Web.Preview.Search下面的一些类的实现方式。我不准备详细分析代码了,因为代码都很简单,直接说说看完的结果吧。如果你之前浏览根据SiteMap生成的Sitemaps时发现少了些东西,在这里你就知道如何把这些项目补充上去了。Sitemaps协议中关于一个URL能够包括以下几样信息:

  • 地址:也就是URL本身
  • 最后更新时间
  • 更新频率:此URL的内容多久更新一次
  • 重要程度:一个0到1的值,默认值为0.5,搜索引擎并不一定根据这个值来判断URL的真正重要程度

然而自动生成的Sitemaps仅仅包括前两项信息,如果我们需要后两项信息就需要手动增加。怎样手动增加呢?因为SiteMapNode类似于字典,能够访问this [string key],所以只要SiteMapNode[]存在"lastModified"/"changeFrequency"/"priority"这几个值就能自动输出到Sitemaps中,而且"lastModified"会覆盖对应Page的aspx文件的真实最后更新时间。

简单举例说明这功能怎么用,假设你使用的是静态的Web.sitemap,我们已经习惯这样定义一个SiteMapNode:
<siteMapNode url="Default.aspx" title="Welcome" description="" />

而增加特定的属性只需要这样定义:
<siteMapNode url="Default.aspx" title="Welcome" description="" changeFrequency="daily" priority="0.8" />

支持Dynamic Data

上面说了那么多,也就仅仅能做到支持系统自带的SiteMap,而实际上SearchSiteMap还能够对Dynamic Data提供特殊的支持。Dynamic Data简单易用,好像Ruby on Rails那样支持scaffolding,预览了ASP.NET将来在敏捷方面的发展。有关Dynamic Data Control的详细信息,请参考Dflying的文章,我们这里仅讨论SearchSiteMap的支持:

需要支持Dynamic Data的话,首先你要实现自己的DynamicDataSearchSiteMapProvider。大家不要一看到要继承自系统类实现自己的类就觉得是非常复杂的事情,其实这里我们仅需要override掉一个函数,也就是DynamicDataSearchSiteMapProvider.DataQuery()。在这个函数中,我们需要返回一个IEnumerable,其中的元素需要具有主键列名属性以及以下属性:

  • SiteMapLastModified
  • SiteMapChangeFrequency
  • SiteMapPriority

你很可能会问,为什么要是不确定类型的IEnumerable而不是确定类型的List<>呢?想想.NET Framework的什么部分用IEnumerable用得最多吧,那就是LINQ。如果你在QueryData()中直接使用LINQ来筛选数据,你就不需要创建自定义类型并且自己填充IEnumerable了。况且,主键列名也不是确定的,如果用一个属性记录其名称用另外一个属性记录其值那就很麻烦了,所以ASP.NET Futures选择了上述充分发挥LINQ优势的做法。

最后,我个人感觉SearchSiteMapProviderBase的设计有点问题,它作为AspNetSiteMapSearchSiteMapProvider与DynamicDataSearchSiteMapProvider的基类,其中包括QueryData()方法,然而此方法只有DynamicDataSearchSiteMapProvider用到,很显然就应该将它放置到DynamicDataSearchSiteMapProvider里面。

2007年5月24日星期四

被急 call 去参加 Google 面试

早上起来后,本来是计划帮一位老师把他的个人网站的模板设计好的,10:30突然收到电话通知叫我去面试。

开头我说大学城出市区起码一个多小时,很难赶上,但对方没有改时间的意思,说“那就只能另外约时间了”。这时候我当然不会选择让电话挂掉然后等下一次通知啦,所以马上问她下午是否可以,这时候才知道原来下午的时间都排满了,才一定要我上午去,然后我就答应上午赶过去咯,她说超过12:00到也没问题。

跑到大学城北部枢纽的时候,发现刚刚走了一部3线,而且最近的司机都很守规则离站了就不开门,所以想着可能还要很久才开下一班车,结果发现10分钟左右就有下一部3线准备开出了。出市区用了我半个小时,然后打的去东方宾馆又用了半个小时,去到刚刚过了12:00。

至于面试,就是签了NDA再进行的,所以就没什么好说的啦,自我感觉不怎么样,没有很突出的表现。之前听说Google的面试都是一个早上几轮面试一起来的,如果你被通知可以走了然后就没消息了,那么你也不知道是在哪一轮面试被刷下来的。不过今天我面试完临走时问了一下,才知道过了的话会有二面通知,希望自己能顺利提供第一轮面试吧。

2007年5月22日星期二

Google SE 笔试通不过

之前浪费了那么多时间wandering,该报应的时间终于来了,Google SE笔试通不过,收不到面试通知。总结出来就两点:

  1. 改拓展与利用人脉的时候一定要利用好。人脉一方面要拓展,另一方面更要利用,就如钱一样花得值了的才是你真正得到的。其实早些时候就有人大家帮我做Google的推荐,当时一心想着去申ID,就不怎么重视SE笔试,也就没在这之前作推荐,笔势完才发现原来身边那么多人是找人做了推荐的,也就是说他们来参加笔试只是玩下,有推荐可以直接进入面试的。
  2. 该做好的事情一定要全方面重视。其实对于SE笔试我也是很认真的,提前咨询了别的学校举行笔试的方式等等,然而却疏忽了推荐着一个重要的项目。

Move on. Next Target. 继续努力,由于最近对Product Manager的职位比较花痴,所以决定目标是早日有足够的能力获得一个PM职位。

Keywords: cnblogs, profit, future and more

上个星期六下午参加完博客园广州的聚会,讨论议题是博客园的盈利与发展,那么我也来说说这事情。

您为什么愿意使用博客园

要探讨这件事,我觉得首要问题应该是“您为什么愿意使用博客园”,这个问题决定了博客园当前的价值曲线何在,然后你才能确定博客园是否应该迁移到新的价值曲线上面去。如果你问我为什么愿意使用博客园的话,我的回答会很简单:因为用它来写blog简单易用咯。

看到这个答案,你是不是已经笑到肚子痛了?好,那么你先捂着肚子,回想1999年Blogger刚刚创立时候的样子。那时候还没有免费的Blog*Spot,如果你是Blogger的用户你必须有自己的空间,而且要是支持FTP上传的,Blogger所负责的仅仅是根据你设计的HTML模板套用你保存在他们数据库中的数据,然后生成静态HTML并且上传到你的FTP。免费的域名和空间?没有,你自己想办法买去。漂亮的模板?没有,除非你自己精通设计与HTML。看到这里我想你的痛症已经从腹部转移到头部了,这么辛苦才能弄一个blog难道不是一件非常头痛的事情?

我们再来看一个例子,VeryCD与传统的ed2k network有什么不同?理论上整个ed2k network是连通的,资源是完全共享的,当然有些客户端mod设计为同类mod之间优先传输,不过我们暂时忽略掉这类情况。VeryCD的成功就在于它让资源下载变成了非常便利的事情,甚至不仅仅是资源下载,而应该说资源的消费(consume),包括搜索、预览、下载、使用全过程。很早很早之前就在Dash的blog看到一张brain storm的图,画着用户现在消费VeryCD资源时都会遇到什么障碍,其中包括例如播放DVDRip需要安装codec和字幕插件,不是专业用户不知道什么是codec更不知道codec存在版本冲突等问题。之后VeryCD是有致力于解决这些问题的,例如通过制作Storm Player来方便用户播放DVDRip,并且保证此播放器是无捆绑的,这就让用户摆脱了既想装codec又害怕惹上捆绑软件的困境。

总结一下,是否简单易用对产品能否进入大众市场起到关键作用。相信博客园不是想做1999年的Blogger那样的高端用户服务平台,所以易用性就成为了关键。星期六的讨论有人说到了开源项目,有人说到了出书,这些好主意最终能否让大众受惠也就看是否便利(convenience)了。

什么是便利

那么到底便利是什么?我们拿开源项目来说说吧。如果一个开源项目必须要你下载源代码后自行配置和编译,你会不会用?如果好像Community Server那样下载后就是一个Installer可以直接安装使用,你又会不会用?无论对于开源项目的开发者还是使用者来说,是否便利都是很重要的一个因素。例如我想参与到博客园主持的一个开源项目吧,我该如何做呢?先装个SVN吧,这谁不知道,说起来可真简单。但对于没经验的人来说,选择什么SVN客户端,装完之后如何用,这就是个大问题。先说说题外话,知道什么方法能够最有效增加你的feed订阅量吗?那就是在订阅链接旁边加上一个帮助链接,用户打开后能看到一篇文章详细解释什么是feed以及如何使用feed订阅。因此,如果想要有更多人参与到开源项目中来,第一件事应该是普及SVN的使用方法,这就减少了用户自己摸索恰当的SVN使用方式所需要的时间成本。

事实上,我觉得4C理论可以合并为3C,因为convenience也就是cost,不过不是你的cost,而是你的服务对象的cost。星期六讨论时大家的思维方向都是如何能让博客园的写作者能够从写作本身受惠,从而增加粘滞度以及吸引新人。实际上我们可以尝试反过来思考,就是如何降低写作者的成本,从而达到同样的效果。这种成本可以是任何东西,而不仅限于便利性。而且这种策略还可以推广到写作者以外的群体,例如读者、广告商,甚至其他潜在客户。

价值与成本的问题

服务业本身就不在于创造实物商品,而在于创造服务,如果你多观察美国的Web2.0网站创业模式,你就会发现他们有时候会用到一种思维——“如果我能够帮你减少一百万的成本,你肯定乐意从这一百万中分一部分给我”。然而中国人的思维不是这样的,中国人就想着“我如何能赚一百万”。这样想的话,第一条答案肯定是去银行抢,第二条答案可能是坑别人一百二十万扣除成本自己获利一百万,这种思维再想下去就肯定都是和“假冒伪劣”有关的事情,做出来的结果肯定是零和博弈——你所有的收益都基于其他人的损失。因此,如果你很诚实,你想要一个双赢(win-win)的盈利方案,你必须优先考虑如何帮别人赚一百万,而不是自己赚到一百万。

博客园要盈利,思维的重点就不应该在于自身如何盈利上面,而应该在于当前客户甚至是潜在客户如何盈利的问题上面。因此,我们无论讨论出书还是悬赏求技术求人才都好,第一个关注点是客户而不是博客园本身。例如悬赏求技术吧,想着别人一个题目悬赏¥1000那么博客园能收益多少是没有任何意义的,正确的思维应该是想想客户乐意为一个问题悬赏多少钱。例如一个问题,客户估计要自己解决的话要¥1000的成本,那么他的悬赏肯定在¥1000以下,例如他估计来竞标的人肯定不少,那么悬赏可能就是¥900,这时候我们就去思考博客园是否可能使用不超过¥900的成本找到一个合适的人把这个问题解决掉。

总结起来,就是以下步骤:

  1. 博客园能够为客户提供什么样的价值
  2. 博客园能否在特定的成本下完成该价值

产品化——前方200米处

是不是两步就能完事呢?实际上这和最终产品化还有一定距离。

我们来说说出书的事情,其实现在我们都知道有博客园出书团队,但到底找博客园出书的途径是如何的呢,对比其他出书途径有什么pros&cons呢,这些暂时都无法在方便的在博客园或者出书团队的首页上找到,使用搜索引擎也找不到,这就让出书看起来更像是一个内测(private beta)的服务。这时候我觉得博客园就需要一位PM(Product Manager)来把出书服务变为正式版了,至少是公测(public beta)吧,这样才能让更多的人接触到这项服务。虽然出书的人仍然只占一个非常小的比例,但完善的服务总能给需要出书的人一个更好的体验,同时对于读者来说也更有影响力。

因此,最终我们还要加上第三个步骤,从而变成:

  1. 能够为客户提供什么样的价值
  2. 能否在特定的成本下完成该价值
  3. 是否有人能负责产品化

最后,还是照例推介一下我自己的blog,喜欢的话可以进行订阅哦:

2007年5月21日星期一

Google Recruiting Event

今晚去听了宣讲会、交了简历、做了笔试。如果成功获得面试机会的话,明天下午就会有通知,紧张的等待中……

题目当然如传说中的那么基础,但是和其他学校考过的题你全无关系,甚至你不可能通过一份题考的技术范围来猜另外一份题考的技术范围。做完之后,也不知道自己到底算是怎样,大家都是大部分做对了然后错了一点点吧,但到底哪题是关键,错了就死定了,那是不知道的。所以现在除了等,也没办法预测什么,或者作出什么抉择。

比较奇怪的一件事情是,实习职位列表上竟然没有Interactive Design Intern一项,然而网站和海报上是有的,难道因为招的人少而且不属于需要笔试的职位所以在宣讲中以及笔试的职位列表上都去掉了?另外一件事情就是,我越来越觉得Associate Produt Manager职位有吸引力了,可惜Intern不要本科的,Full-Time才收本科毕业生。APM职位给我的良好印象是它什么领域都能接触到,Technology、Design、Marketing、Legal等等,而且Google招收毕业生为APM后两年就能转为PM,其他企业的PM一般都是SE(Software Engineer)凭借多年经验晋升而来的。如果真的有机会进入Google,就一定要试试自己是否有足够的实力获得APM的位置。

2007年5月17日星期四

Cat2 模板系列开始预览啦!

什么是Cat2?

Cat2 = Cat * Cat,两位Cat合作的意思,也就是我Cat Chen和猫窝的猫影组成的小团队。

什么是Cat2模板?

这是一个XHTML+CSS+JavaScript的模板系列,暂时只包括blog模板,并且优先提供Blogger与WordPress立即可用的模板,同时也会考虑为其他常见的blog平台(例如DotText)提供立即可用的模板。

如何获取Cat2模板?

我们的模板存放在Google Code的Project Hosting,地址为:http://code.google.com/p/cat2/。您可以直接在Cat2的SVN上下载模板,或者预览模板的效果。暂时我们仅提供Silver和Black两个模板的预览,这两个模板都还没有100%完成XHTML+CSS的设计,大家可以根据预览的效果为这两个模板提出一些建议。

如何使用Cat2模板?

在我们的模板正式发布之后,您可以将我们的模板直接应用于您当前使用的blog,也可以修改后应用于您的blog或者其他形式的网站。Cat2模板使用的许可是MIT License。

2007年5月16日星期三

What turns me on?

CC说我是一个非常清楚自己想要什么的人,这应该和我小时候经常努力争取到一样东西然后发现并非真正想要有关。现在我无论决定要获取什么,我都必须先确定我是真的想要,不是一时刺激让我对此有占有欲而已。

不过对于what turns me on这个问题呢,我却没有任何确认的答案,或许因为还没有足够多的like/dislike test呱,所以暂时处于untrained状态,无法预知我对一样东西最终的态度是like还是dislike,也就无法确认what turns me on。

2007年5月15日星期二

更新了 Blog Roll

大家如果看到“榜上有名”的话,可以考虑在你的blog上也增加我的链接哦。如果发现自己“下榜”了,则可能是因为你的blog已经长期没更新了,所以我就把链接给去掉了。

花了一整天终于把 Universal Resume 呕出来了

参考着几份不同人的简历,从去年8月份那份Google Camp简历上面把信息复制到新简历上来,同时又做了一些增删,最终做好了一份最新版本的通用型简历。另外还完成了简历翻译工作,现在有中英对照两个版本,要感谢的是Google Translate,很多时候不知道怎么翻译好的时候就参考它给出的翻译,能够获得很好的灵感的哦。

然后开始要做portfolio,因为申请designer类的职位都要portfolio啦,而且就算申请engineer类职位有个portfolio也没什么坏处。

2007年5月13日星期日

No Way Back

要学Piggest那样做到“谷底反弹”只有一个选择,就是告诉自己没有任何的退路。在大学那么久了,不是没有进步,而是总觉得自己可以吃老本,总是有后备选择,“最多不久怎样怎样”,这样下去就只能一直选后备方案。虽然后备选择也在plan中,所以看起来还是It was planned,但其实都是worst plan。

以前我们说起沟女的时候,说到“我们做你的坚实后盾”何解,意思并不是在你有危机的时候我们会如何出手帮你,而是说我们在后面顶着你把你向前推,让你毫无退路,如果你不奋力向前就会给我们压扁。现在我觉得我需要一个“坚实后盾”了,至少是一个reminder,在我想选择backup plan的时候提醒我不能再向后退了,必须全力奋斗才能survive。

Keep Busy

发现自己太过闲了,太多时间做一些wandering的事情。

在你的生活太专注于一个项目的时候,wandering一下还是好的,往往能够有不少意外收获,随机获取的信息总能激发一些创意,有助于当前项目的进展。但如果生活本身就是wandering,而且做的任何小项目都是wandering性质,那就死人了,出来的结果总是没有集中的价值,也就没什么实际用途。

从现在开始,我要设置一些目标然后keep busy,拒绝wandering,提高时间利用率。

2007年5月5日星期六

一个事件三种解释

最近读过或者在读的书包括:《魔鬼经济学/Freakonomics》、《蓝海战略/Blue Ocean Strategy》、《引爆流行/The Tipping Point》。这三本书都说到了纽约犯罪率持续上升30年后在90年代突然下降这回事,并且尝试用自己的观点来解释。

第一本书说,这是因为30年前的堕胎合法化,导致很多不应该出生的人没有出生,而这部分人如果出生了,因为他们来自未婚或低龄母亲,不可能接受良好的家庭教育,所以成为罪犯的可能性很高,现在这批人没有出现在世上,犯罪率就下降了。书中希望突出的是,“揭示隐藏在表象之下的真实世界”,传统人士认为正确的所谓“传统智慧”往往是错的,只不过更符合人们直观的逻辑思维,更容易被接受,所以例如加强警力导致此次犯罪率下降就不是真的,数据表明其他城市加强警力也不可能达到此次犯罪率下降那么明显的效果。

第二本书尝试强调执行蓝海战略时如何排除合作方的障碍,让大家都接受新战略规划。因此,书中认为加强警力是有效的,并且叙述了当时的纽约警察局长如何在这件事上实现了蓝海战略,排除障碍,让市民获得了更高的安全保障。

第三本书的副标题是“How Little Things Can Make a Big Difference”,所以它尝试把此次犯罪率下降解释为某一个little thing的结果,从而引爆的一次流行,让潜在的犯罪分子不再想着要犯罪。

其实这是非常好笑的一件事,同一个事件被用作素材而用于论证不同的问题,差别只在于论证的目标不同,所以引述的数据也不同。曾经在Google Sitemaps上面见到一句话,说使用相同的数据推导出不同的结论正是统计学家们的工作,看来这次真的一个明显的例子。

2007年4月28日星期六

No Junk Food Month

我现在决定把接下来的5月份定为我的No Junk Food Month,一个月内不吃任何垃圾进肚子里。

寒假的时候由于做手术,连续n个星期只能吃清淡的东西,看起来真的会让身体调理好的哦,至少脸上的皮肤会好一些。现在下决心一个月做成这样一件事情,以后才好下决心做其它的事情,咔咔……

明天是4月最后一天哦,是不是应该再趁机吃一次呢?

2007年4月27日星期五

放过一个,放倒一批

很久没写blog了,因为我的laptop坏了……现在还没有修好,只能暂时找台机来发发blog。

看了一下今年大四的毕业论文选题列表,不明白为什么有些摆明就是让无所事事的学生放过的,反正就是研究目标不明确,随便乱写也算是写了。而且听说别人完全通过Internet能够3天搞定一篇毕业论文,无需动手研究,这也实在厉害……

难道就没有人想过,每放过多一个不那么优秀的学生,对接收他们的组织来说整体质量就下降了一分,最终结果就是把整批人都放倒了?

以前在华附对这些事情是有明确规定的,例如保送生同一时间最多有两所高校同时在waiting,避免有些学校答应要你了而你又不去了。曾经有一年,华附有两个学生申请南大通过了,后来又不去了,结果当年高考南大马上做出报复性行为——有两个华附考生上了投档线但不录取,这在以前从来没发生过。类似的事情还有不少,所以华附吸取教训,确保保送生工作不会有损两所学校之间的关系。

为了保障更多人的利益,特别是后人的利益,限制个人的行为是理所当然的。不过中大这里好像很少强调集体或者集体荣誉之类的事情,所以每一个人都仅仅着眼于自己的利益,而没考虑到自己行为的外部性,从而陷入了一种恶性竞争中。学校本身也处于无意识状态,自然没有任何管制策略,甚至没有任何宣传策略,结果该管得多严就完全看老师的了,老师不想为难学生自然就松一些,至少很多人会被放过,结果是整批人被放倒,在外面得不到良好的声誉。

2007年4月9日星期一

根本不存在 DIV + CSS 布局这回事

在《欲练 CSS ,必先宫 IE》和《你有 <table /> 强迫症吗?》这两篇文章中,看到有不少评论用到div+CSS布局这个说法,用来和table布局比较。实际上div不是用来布局的,div只是用来表示一个其它元素都无法准确表达语意的一个块区,只有CSS是用于布局的,所以根本就不存在div+CSS布局这回事。反过来,table布局的时候经常依赖于CSS定义一个单元格的布局属性,所以可以说是table+CSS布局。也就是说,我们讨论的两种主流布局方法应该是纯CSS布局和table+CSS布局,如果你觉得你在用的是div+CSS布局,那么有可能你也有强迫症了。

接下来我们说说如何进行纯CSS布局,因为CSS布局依赖于XHTML,所以我们先要说说如何书写一个CSS无关的XHTML。其实书写CSS无关的XHTML并不难,虽然你不能再好像书写table布局代码那样集中精力于最重的视觉效果上,但其难度也不过是中学生写作文那样。

中学生写作文如何写呢?首先看看题目,然后想想整篇文章分为哪几个大的段落,每个大的段落说些什么,能够把你要说的东西说清楚。对于XHTML来说,这相当于用div把文档切割为几大块。这时候你不要想着这些div将构建一个怎样的DOM啊、CSS如何选择DOM中元素设置规则实现布局之类的事情,就大概划分一下文档的大区域就好了。

然后当然是用一些常用的手法来表现感情或者论证问题,这在XHTML中就是用特定的元素来完成一些常见的信息组织。下面就是信息组织形式与元素的对应列表。

img

作为内容的图片是一定要放到img里面的,这没有更好的选择了。然而如果图片不是作为内容,而是作为修饰性的,则千万不要用img。对于非内容的图片,应该在CSS中引用,而不在XHTML中出现。例如每一个导航链接有一个前导的箭头指示,那么这些箭头就应该通过CSS的background-image属性加上去,而不是直接作为img出现。

a

这也是一个非常准确定义的元素,链接都需要使用它。或许已经有很多人忘记了a的本意是锚点,其实这是一个十分有用的语义,你可以用它来标记文档中一些重要的引用位置。

ul, ol

ul和ol分别是什么意思呢?如果你回答不上来,却知道它们可以用来干什么,那证明你是被可视化工具宠坏了,要转换过来编写符合语义的XHTML需要先补充基础知识,这时候你最好先找一些看起来非常基础非常全面的XHTML书籍看看,因为没有扎实的基础你在上面构建更多的知识都是不牢固的。ul和ol其实分别代表unordered list和ordered list,也就是无序列表和有序列表。在语义上,它们都用于表示一类并列关系的内容,例如我们去商店购物之前列一张shopping list,上面要买的东西就是并列关系,在中文可以用顿号隔开那种。它们的差别在于是否有顺序,例如shopping list是没顺序的,先买什么后买什么是没关系的,但是一份旅游行程安排上面的景点列表却是有游览的先后顺序的。

ul常用于导航栏,因为导航元素符合上面所说的并列关系,树状导航结构还可以通过嵌套ul来表述。在这里,导航可以是我们常见的水平或竖直导航栏,甚至可以是地图导航,例如在中国地图上不同的省份热区其实是不同的li。如果我说,在主流浏览器上用户看到了中国地图和可以直接点击省份热区,在不支持CSS的浏览器上用户能看到一份纯文本的省份名称列表,使用的是同一份XHTML,而这完全通过CSS实现,甚至不依赖于JavaScript,你相信吗?

另外,如果你要显示一个图库的缩略图,这些图片也可以放在ul中哦,因为这些图片也是并列关系。它们可以自动先横排,排满一行就自动排第二行,CSS可以让他们乖乖排队,而不需好像table那样把图片定死在一个格子里。其实table用于布局就如同用监狱关押内容一样,把内容锁死在一个格子里不让它到处乱跑;符合语义的XHTML就如同一个开放的舞台,你只要懂得利用CSS的规则,内容就自然会找一个适合表现自己的地方站着。

dl

没有听说过dl吗?因为那些可视化工具生成的代码中从来不会出现dl?dl的意思是definition list,也就是定义列表。它包含的子元素不是li,而是dt和dd,也就是definition term和definition description。dl本身设计为字典单词与解释列表这样的语义,例如:
<dl>
<dt>Apple</dt>
<dd>苹果</dd>
<dt>Boy</dt>
<dd>男孩</dd>
</dl>

然而,如果你需要表示的的语义也是类似的,一个列表既包含定义也包含解释,那么也可以考虑用dl。

form, input

form也就是表单啦,这没什么好说的,就算再不顾及语义的人在书写XHTML时也会考虑到它与各种input对提交数据的影响,从而小心谨慎。

table

table自然是用来表示表格的,这不废话!如果是数据表,当然可以用table来表示,但如果不是,就最好别用table了。

人名列表呢?例如一个3行4列的人名列表。如果这12个人名是并列关系,我建议你用ul和12个li来表示,再通过CSS来让它们在一行内并列显示多个。名片表呢?也就是3行8列,每两列中左侧一列显示人名右侧一列显示电话地址等联系方式。我觉得dl在一定程度上能满足此需求,dt放人名,dd放联系方式,不过这时候就涉及了dl滥用的争论,因为人名与联系方式当作定义与解释有点牵强。

接下来还有一个关于你是否系统学习过XHTML的小提问,那就是你是否知道table下面的caption、col、colgroup、thead、tbody、tfoot元素及summary属性分别用于定义什么,还有就是你书写table时是否会使用thead、tbody。

div, span

再次审阅上面的列表,如果你需要表示一个块区却无法在上面找到更适合的元素,那么你就可以考虑使用div和span这两个最没有语义的元素了。div与span的区别,历史上的不说了,现在通常大块的区域用div,行内的小文本片段就用span。在上面我已经说了div一般用于全局划分为几个大的区域,所以一般不需要使用了。span其实也很少使用,因为行内的强调通常可以用语义更强的元素例如strong和em。

在理解上上述那么多常用元素后,写一个XHTML就真的如同中学生写作文一样容易啦,还是搭积木那样,其实和以前使用可使化工具搭积木没什么不同,唯一不同是现在你理解了你在搭的是什么,而以前你只在乎搭出你想要的视觉效果来。写代码与写作文所类似的地方,就在于你写得越多就越熟练,也就越能写出好东西来。在写好XHTML后我们就要开始考虑如何写CSS了,或许还需要在XHTML中略作修改以方便CSS中规则的选择与匹配,不过这是以后再说的内容了,今天就说到这里。

最后,如果你有兴趣阅读我以后发表的有关CSS的文章,可以考虑订阅我的blog:

2007年3月31日星期六

Imagine Cup 2007 Web Development 第一轮出线啦!

200多支队伍参加比赛,第一轮有30队能够出线,我们队出线啦,好开心哦!第一轮比的只是策划,把策划推销出去就行了,接下来要努力把实现做好,才可能在第二轮出线。如果真能第二轮出线进入决赛就好啦,能够去韩国哦。

2007年3月24日星期六

Adobe Apollo vs Joyeur Slingshot

如果觉得这世界上有Microsoft WPF/E vs Adobe Apollo还不够刺激的话,那么我们可以看看刚刚加入这竞技场的一位新选手:Joyeur Slingshot。Joyeur Slingshot是谁?我想你应该看看它背后那个阵营标记,没错,就是Rails这5个字母!

Slingshot有什么明显的好处吗?使用Microsoft WPF/E和Adobe Apollo都要将思维模式由B/S改为C/S,将设计重点转移到运行在客户端的代码上来,不再是考虑服务器端代码如何处理客户端请求,而是考虑客户端任务如何通过请求服务器端服务完成。然而Slingshot的思路不是这样,它希望Ruby on Rails的程序通过小规模的重写就能实现offline模式的支持,就如通过小规模的重写就能将整页提交改为AJAX操作。如果你不是很清楚这在RoR中有多少工作量,就想像一下ASP.NET用上UpdatePanel和Extender所需要作出的修改吧。

Slingshot是怎么实现的?其实大部分RoR程序员都是用“offline”的模式来开发的,也就是在自己机器上装一个RoR的平台。要在一般的客户端机器上装一个RoR并不难,也就和装一个.NET Framework差不多。Slingshot提供的就是客户端和服务器端同步数据的能力,至于何时何地同步什么数据,这就由开发者决定了。同时Slingsot还强调文件的拖放传输,你不在需要一个一个选择文件然后点击上传,在Slingshot附带的客户端浏览器中你可以直接将文件拖放到页面上实现文件选择与上传下载。

使用了Slingshot的框架之后,网站还能用不同浏览器访问吗?这显然是可以的,用户看到的还是老样子的RoR网站,只不过无法使用Slingshot提供的一些功能而已。Slingshot将于4月底发布,最终和WPF/E以及Apollo的对抗将会如何呢,让我们拭目以待吧,或许Rails将继续以它的敏捷性在Web2.0领域获得较大的市场份额。

2007年3月23日星期五

更新中……

之前一段时间只看技术书,有些麻木了,所以现在改为开始看小说,更新一下大脑里面的信息结构,否则我真的没创意了。

另外最近Blog*Spot又无法从大陆访问了,大家要访问我的Blogger最好通过Feed吧。

2007年3月19日星期一

Adobe Apollo Public Alpha

Adobe Apollo Public Alpha出来啦!一直尝试去学ActionScript,却一直都学不进去,现在机会又来了,直接学习AS3,到底我能否用Apollo做点什么东西出来呢?嗯……暂时还不知道呢。

2007年3月18日星期日

你有 < table / > 强迫症吗?

上次讲到“欲练 CSS ,必先宫 IE”,如果你宫了IE然而还是觉得不得要领,那就该怀疑自己是不是有传说中的table强迫症了。

在CSDN社区上,时不时能够看到一些页面整体布局的问题,要求用div做一些table才能做到的,否则就以此为把柄说XHTML+CSS布局方法不好。其实,首先要做的是改变思维,以适应XHTML+CSS的布局。

面向页面设计而非面向浏览器设计

XHTML+CSS能够实现的是一种流布局,也就是随着内容的长度自动增长区域,并且最终导致整个页面增长,这时候浏览器就必须显示滚动条。table强迫症的一个征兆就是极力避免流布局,希望以浏览器的可视区域为布局目标,要求在可视区域中划分内容区域而不是在页面上划分内容区域。实际上XHTML是无法针对浏览器设计的,因为它仅仅包含语义,或者说是内容,而浏览器如何去表现这些内容是我们无法确定的。CSS提供了我们控制表现方式的一种途径,但这仅仅是针对主流浏览器的,而且浏览器支持的“指令集”还有稍为的差别(说到这,我真希望能够为一个浏览器写CSS然后编译为全平台兼容代码),最后这些指令暂时还仅仅支持针对页面的流式布局控制。因此,如果你决定要开始写符合语义的XHTML并且仅仅用CSS控制布局,首先就要把思路转变为面向页面(或者说是文档)的布局控制,而非面向浏览器可视区域的布局控制。

接下来肯定有人要说,“那你就是承认了有些布局老方法很容易做到,但新方法很难做到啦”。这是当然的,然而这不成为我们继续使用table的理由。这时候要反过来探讨原始目标了,我们是为什么要控制布局?低层次的需求是为了美观,谁都希望同样的内容能够以更好的视觉效果展示在用户眼前;高层次的需求是为了控制受众的浏览方式,让他们能够按我们预先设计好的方式来区分页面内容的轻重点,按我们的期望优先浏览某些内容,同时也帮助他们更快的找到他们想要的内容,而不会在我们的网站内感到沮丧。既然我们确定了这时控制布局的目标,那么我们再来看看CSS是不是“没办法把事情做好”。首先,CSS也能做出美观的页面,虽然某些布局做不到,但是在CSS的限制下做到同等美观程度的页面是肯定没问题的。其次,CSS也能让设计变得友善,不会说CSS的设计就肯定是“干净”到用户无法一眼找到他想要的功能。因此,虽然CSS无法实现某些特定的布局效果,但对于设计师来说它能够达到老方法所能达到的同等效果,这就足够了。

从XHTML中去掉内容无关的视觉元素

另一个table强迫症的征兆就是,习惯为每一个视觉上的元素对应一个XHTML元素。在table中,无论视觉效果有多复杂,我们总能不停的切割table,甚至table套table,直到准确定位每一个特定的元素。然而应用了CSS之后,这就是不必要的,甚至会给设计师带来麻烦,因为XHTML+CSS就是为了内容和布局分离,所以如果一个视觉元素与内容无关,那么它就不应当出现在XHTML中,自然也就不会对应一个XHTML元素。

例如有一个网站当前栏目的徽标,这个徽标没有任何的语义,而XHTML中也有文字内容描述当前栏目了,那么这个徽标就并不一定要对应一个<img />元素。如何让徽标显示出来呢?它可以是当前栏目文字描述区域的background-image,同时通过一些定位技巧让它显示出来。如果你认为有这个徽标就不需要文字描述时,你还可以通过定位技巧将文字隐藏掉,这样单纯看XHTML或者在不支持CSS的浏览器上就只见文字描述,而在支持CSS的浏览器中则看见会标。从这个例子,我们可以看得到一个视觉元素不一定要对应XHTML中一个实实在在的内容元素,或者对应一个文本元素而非图形元素。XHTML包含的是内容,那就不应该包含与内容无关的视觉元素描述,而通过CSS你可以事后增加有关的视觉元素。

又例如:before和:after这两个伪选择器,允许你创建插入在匹配元素前后的元素,这样就能够实现非内容视觉效果仅在CSS中插入。常见的用法包括,插入clear到浮动元素之后以确保父元素的完整包含,又或者是引用语句的前后自动加上引号。事实表明,CSS是很适合于将非内容的元素从XHTML中分离出来的,因此我们在设计XHTML时就不能够总想着要有什么效果,而应该单纯想着信息的组织形式。

最后,如果要我为table强迫症开处方的话,我还是会选择《CSS Mastery/精通CSS》。看完之后,你自然能够解除上述的烦恼,理解到CSS布局带来的便利,从而选择开始用纯CSS的思维来进行设计。如果你关注CSS方面的内容,可以考虑订阅我的blog:

将来我会和大家分享更多关于CSS的思考,以及使用CSS的技巧。

2007年3月16日星期五

国内 Web 2.0 网站缺少的一个按钮

无论是web2.0网站,还是一些比较奇特的blog,都缺少一个about按钮。

明白我在说什么吗?你的网站太创新了,我都不知道是做什么服务的,又不在显眼的位置放个about,那我就真的不知道自己是否应该注册了,或者说是值得注册。有些网站还好,在页面底部的导航栏有“帮助”或者“规则”这样的东西,但这显然也不如直接在页面顶部导航栏放一个about好,因为谁会注意到底部导航栏呢?

最近我们学校有一个学院书记就大胆说出了中国学校门户的不好,“都是那些给当官的人看的新闻”,他认为学校门户就应该学习国外的,应该是服务导向的,也就是说学生和教师应该能够很方便的通过网站获取到自己需要的服务。举个例子,如果你是新生,登录你们学校门户,你知道该从哪里查询成绩吗?相信大部分人都不知道,必须有人告诉过你,你才知道哪个链接哪个链接走下来就是查询成绩的地方了。

这就是中国网站的缺陷,中国人就是特爱面子,就是纯粹做show,完全不理会网站必须服务了目标人群才算是成功的网站。

2007年3月13日星期二

什么样的 Code 更像是 Configuration

在Code is Configuration这篇文章中,说到了我对Ruby on Rails优点的理解,那就是RoR的代码相当于是配置,所以能做到习惯优于配置。有人说,这是动态语言的优点,然后把动态语言和静态语言区分开来讨论各自的优劣,然而我觉得这是不能绝对划分的,语言的动态与静态是一个过渡。

举个例子,virtual函数的override也算是一种动态,因为程序是运行时查表寻找最顶层的override函数然后执行的,和所有的动态特性一样,这需要牺牲一些效率。然而我们没有把C#称之为动态语言,因为它还是要经过静态编译才能执行,但这不妨碍它拥有部分的动态特性。因此,语言不能简单的话分为动态与静态两类,这之间是可以平滑过渡的。

既然语言没有动态与静态的绝对划分,那么代码就是配置的概念也应该没有绝对的划分,我们只能讨论怎么样的代码更像是配置。为了说明为什么代码可以说是“更像配置的”,这里就再举一个例子。同样是创建一个复杂的Toolbar,在MFC里面大部分的代码都是用来“实现”这个Toolbar的,虽然MFC已经对Win32API进行了封装,但使用起来还是很像调用API的风格。而在WinForm创建复杂Toolbar的话,大部分代码都是创建控件然后设置属性,这样看起来就更像是“配置”。所以使用WinForm的代码比调用MFC的代码更像是配置。注意我这里说的是“更”,既然存在B比A更好,那就还可能存在C比B更好。比WinForm更好的是什么呢?WPF和C# 3.0,前者能够隐藏更多的实现细节,后者能够让属性设置代码更简洁,让代码看起来更像是在配置控件。

如果抛开执行效率不说,给你设计一门全新的语言,应该如何做到Code is Configuration呢?这先要看看单纯的Configuration是怎样的,例如XML,抛开背后的实现细节,单纯用XML来写程序,或者说是“配置程序”,你又愿意吗?肯定不愿意,因为虽然它的层次结构好,然而写起来很麻烦,和自然语言的差距太大了。因此,理想中的语言应该尽可能贴近自然语言的情况下,同时也能够尽量隐藏实现细节提供高度抽象的可配置性,而后者通常越是动态语言就越容易做好。说到这两点特性,你是否突然想起一门正走向消亡的语言呢?贴近自然语言一直是BASIC所强调的,而同时又是动态语言的,那就是VBScript了。然而最大量使用VBScript的应该就是ASP了,而ASP正在逐步被ASP.NET取代。或许将来有一天,VB.NET能够重新加入动态语言的特性,配合非编译的ASP.NET,能够打造一个好像ASP那样适合RAD的ASP.NET。

2007年3月2日星期五

Vista + GeForce 6600GT = 不给我玩游戏

游戏过程中,甚至是Vista自身的3D屏保,都可能出现问题,接着黑屏。幸运的话,过一会儿能够恢复显示,并且提示“显示器驱动程序 nvlddmkm 已停止响应,并且已恢复”;不幸的话,整台机死掉,只能reboot。

用Google搜索"nvlddmkm",发现问题出在NVIDIA的显卡驱动上,而且出问题最多的就是6600,哎……怎么这么不幸。而有些人通过重装驱动,或者停止超频甚至是降频解决了问题,说明这可能和主板、内存的稳定性有关,可惜我暂时找不到有效的解决办法,只能怪主板太cheap。

2007年2月25日星期日

还有新鲜感吗?

ZDNet有个家伙,用了19个月的Vista,说至今仍然感觉到Vista有新鲜感,大家可以去看看他的文章:Windows Vista - 19 months of usage and counting。

在跟别人聊起这新鲜感的事情的时候,我说这19个月的测试版是当然可以让人保持新鲜感的,因为不断的有upgrade和fix——原来有问题的功能变得没问题了,原来没问题的功能变得更好了,这当然让人能保持新鲜感。然而如果连续用19个月的正式版呢?正式版发布后,或者准确来说你给钱购买后,就不会再增加什么新功能,因为你以前给钱了,出新功能不可能赚到更多的钱,这时候新鲜感就会自然下滑。当然,XP SP2是个例外情况,因为XP的安全问题严重,必须增加安全中心这个新功能去巩固用户信心。

说完这段话,突然感觉到这是个类似GF和Wife的隐喻,在你最终结帐之前可以不断更新功能,这样你也就有持续的新鲜感,但结账之后就……

不过现在Web2.0啊,大家都标称自己是永远beta,永远在改进,其实和mm的关系应该也能这样啊。有很多人的思想被限制在当今的一夫一妻婚姻制度里面,对以前的一夫多妻不敢恭维,对将来的制度也不敢想象,就如此固步自封,其实是不对的。我们应该尝试去探索一种新的人与人之间的关系,例如好像我这样,寻求一个永远beta的mm,咔咔!

现在的软件已经开始进化,Software as a Service(SaaS)慢慢会流行起来,甚至连Vista也被设计为如此——将来Vista的升级就会如同一种服务一般设计,你了一付费使用这种服务了,就能得到升级,这种升级是能够确实让系统功能能够得到明显更新的。购买费用足够高的Ultimate版本,其实也算是包括了购买服务的费用,因为Ultimate版本在Windows Update时能够下载所谓的Ultimate Extras更新,属于能够明显增加系统功能的更新。看来突出长期的服务长期的交易将成为潮流……

Anyway,其实没必要将那么多SaaS之类的东西,我在说的是新鲜感。我不会随便换操作系统,安装任何一个操作系统都经我细致调教以保证在我通常的使用情况下能够有best performance,但我希望它好像Vista那样能够有不停的增值服务,甚至更多的更新以保持新鲜感。嗯……没错,我说的就是这个东西,我需要一个类似这样的mm,我不需要结婚。如果可以,我需要一种新的license来维护这种关系双方的利益,但不要婚姻法。

2007年2月23日星期五

Code is Configuration

Ruby on Rails强调Convention over Configuration,也就是习惯优于配置,这对我来说是一个很有吸引力的特性。.NET是配置优先的(据说Java也是),最好什么都不是硬编码而是可配置的,开发出来的产品最终可以在部署时根据实际情况配置,或者再被调用时按照调用者的需求配置。为什么RoR可以是习惯优于配置呢?如果什么都硬编码了,遇到需要改动的情况怎么办?按照我现在对RoR的理解,思考了一下,寻找到一种可能的解释——RoR的代码也就是配置!

在.NET里面,函数或者是指令,都是描述行为的。一组指令组成一个函数,代表着一组顺序执行的行为;之后一组函数组成一个类,代表着一类行为的聚合。而RoR指令给人的感觉却不是这样,RoR中有些指令完全可以认为是描述性的,就好像配置一样,而且是顺序无关的。

举个例子,创建数据表的migration,你可以认为它是ruby语法写的CREATE TABLE,而且它做的也就是CREATE TABLE,但实际上它是schema!它已经描述了数据表是怎样的了,而不仅仅是生成数据表,生成数据表仅仅是migration执行时瞬时的行为,生成数据表之后Rails自动提供ORM功能,不再需要另外的schema或者自动生成的code来重复描述schema。这正是其符合DRY原则的地方,在整个RoR应用当中,只有migration这一处描述了schema,其它地方均无再次描述schema的地方。然后再对比一下我们在ASP.NET中的做法,重复描述数据表schema的地方实在不少,无论使用哪种ORM工具都如此,希望以后大家都能用上DLinq从而避免这个问题。

再举个例子,model中可以直接用helper函数指定每一个属性要符合的业务逻辑,从而提供验证功能。还记得我们在ASP.NET中这有多麻烦吗?首先,根据决不信任客户端提交原则,在View中要验证数据,验证一方面要能抵御恶意编写的提交,另一方面要对不符合业务逻辑的提交给出反馈,这辛苦了Validator控件,而且我们自己也要写不少代码(如果部分输入设计复杂验证逻辑的话)。接着,业务逻辑层再来一次验证,这是为了业务逻辑层的完备性,在脱离原有表现层而被第三方调用时也能确保数据的合法性。然而在RoR中却不用那么麻烦,在model中一次声明规则就行了,view中输入的数据如果不符合model的规则自然会在view中得到反馈,这种逻辑既是声明性的,又符合DRY原则。

最后,说说ASP.NET可以从RoR学习的规则。首先,MVC分离是有必要的,仅仅垂直分3层是不够的。我之前也有想过,为什么要在表现层提供众多的Validator控件,而不是在业务逻辑层提供验证相关的Helper类呢?这些Helper类,加上业务逻辑层反馈到表现层的通道,就能够做到只需要实现一次验证。或许Validator控件,也是简化表现层设计的一个做法吧,为了吸引别人学习ASP.NET,MS做了很多东西让它看起来很容易用,却让人难以全局协调。其次,一个类象征现实中一类事物的隐语仅仅是针对model的一个说法,其它时候我们更需要函数,类就仅仅是用来聚合函数。例如一条验证规则,这是一条配置,同时也是一个函数调用,需要完成验证这一顺序操作的是函数,而不是类。如果你要用类去表示规则,例如config文件映射出各个ConfigurationSection那样,这就增加了处理步骤,因为最终你还不是把类里面的属性读取出来交给函数处理这条规则。

2007年2月18日星期日

难得写影评

从来没写过影评,难得写了点相关的东西,原本发在TLF上面,得了原创精华,现在贴过来。

The Prestige 中的“执着(obsession)”

好像有不少人提问为什么Tesla不拿复制机去复制钱,为什么Angier要淹死自己,之类的问题。还记得Tesla关于obsession所说的吗(TLF版字幕翻译为“执着”)?这部电影里的人可以认为都为obsession所困,只有Cutter是清醒和实在的。

obsession可以翻译为“执着”,但显然有一定的贬义,因为这种执着首先强调的是你无法拒绝去想一样东西,你总是想着总是想着,没办法再专心干其他事情。其次,你想着的这个事情通常都是所谓的妄想,根本无法做到的,甚至你自己都清楚这一点,这显然导致所谓的“认知失调”,也就是你的两种认知互相矛盾。

你自己经历过obsession吗?如果你经历过,那么你很容易理解这些人的行为方式。如果你是一个很实在的人,不曾有过obsession,没有过例如患得患失之类的感觉,那就不那么容易理解了。

Tesla到死的时候都是被定为疯癫的物理学家,后来才追加那么多荣誉给他,可见他生时的obsession是多么的强烈——他在做很多常人认为常识之下根本不可能做得到的事情。Tesla发明了交流电相关的很多专利,还是无线电之父。想一下Tesla的那个true magic,无需导线都能点亮的灯泡,现实中的Tesla确实有研究过无线供电,不过电影里假设他已经研究成功。无线电我们已经用上了一个世纪了,然而无线供电却依然没有实现,可以想象无线供电在当时是多么天方夜谭的事情。无线电本来就可以用来赚去足够多的钱,交流电也是。Tesla完全不去赚钱,去研究无线供电,而电影中的版本则是在实现无线供电之后就帮Angier做那机器,事实上是可以理解的,这就是所谓的obsession。

Angier的也是obsession,他相信他所说的“一切魔术皆可复制”,另一方面Borden的魔术他复制不了,这是显然的认知失调。所以无论付出什么代价,他都想要超越Borden,这不是为了超越本身的个人感受,而是不再让自己所相信的“一切魔是皆可复制”受到自己所理解的现实的质疑。这种自己对自己的质疑其实是痛苦的。解释认知失调的常用例子是喜欢吸烟的人也知道吸烟有害,这时候一个人的两种认知——“我喜欢吸烟”和“吸烟对我身体有害”都无法辩倒对方,这两条都是正确的,这时候就会让人很不爽。而在Angier这个问题上,就不是不爽的程度,而是痛苦。

Angier还有另外一个obsession,就是她妻子死的时候的感觉。还记得他在洗手池憋气的情景吗?他没办法理解淹死到底是好的感觉还是坏的感觉,大多数人的常识,包括他自己憋气的结果,应该都是觉得被淹死是痛苦的事情,然而这时候非常不幸被Cutter说了一句“就像回家的感觉”,这就出事了……这同样是一个认知失调,两种理论都无法证明对方的错误,然而有第三方的办法可以证明,那就是把自己淹死。Angier选择了稍微更相信Cutter的说法,所以Cutter告诉他真相的时候也就再次出事了……

最后,Borden也不幸染上了obsession,这可能与他说过“我就有一个魔术你无法复制”有关,所以才一定要跑去后台看,这最终导致了他的死刑。当然也可能由于他确信自己的牺牲已经足够大,并且他看不出Angier有什么所谓的牺牲,所以他认定Angier做出一个更好的魔术来是不可能的事情。

个人认为,就算这部电影叫做"Obsession of The Prestige"也不过分,因为讲的就是obsession有关的故事。一个魔术,Cutter之所以说常人不能看穿它,是因为不愿意去往能看穿它的方向想,就在于常人都不希望自己是认知失调的,希望自己当前的认知是非常可信的,例如同情心驱动你尽量不去想笼里的鸟被夹死了这个可能。

为什么写“执着(obsession)”

写这篇评论,是因为在TLF看到很多很多人都会提问为什么某人不按照常规的利益最大化的思维来考虑,这是不是意味着只有少数人经历过明显的obsession?

有些事情曾经让我挺obsession的,所以我想知道这是怎么回事,是不是有另外一些人则能永远保持简单思维,不仅仅不会obsession,也不可能理解别人的obsession。

2007年2月16日星期五

学习 Ruby on Rails 真的很爽!

最近开始看《Agile Web Development with Rails 2nd Edition》,发觉感觉真的非常爽。虽然至今连Ruby语法都没完全懂,懂了的也没记住多少,但在看书过程中你会乐意模仿书中所有的例子,一个一个完成看看结果是怎样的,观察这个神奇的框架如何将你所书写的一点点代码转变为使用ASP.NET要不少代码才能实现的功能。

我不知道它是怎么实现的,但暂时我还不觉得我有必要知道它是怎么实现的,因为那种流畅的感觉太好了,所谓的"Convention over Configuration",让我完全可以凭感觉去写,而不是好像用ASP.NET的时候那样想着“你这buggy又trappy的家伙,幸好我知道你底层是怎样实现的,我可不会随便踩进你的陷阱”。既然我不用了解它,就能流畅用好,为什么我还急着去了解那么多呢?

至于"Don't Repeat Yourself"的好处,暂时没体会到。ASP.NET中要坚持DRY原则的话,要在设计上下点功夫,而我现在用RoR才写那么几行代码,根本谈不上设计。

已经很久没有一本书让我阅读过程之中有如此的兴奋感了。之前看《Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics/ASP.NET 2.0高级编程》,虽然也很有feel,但那种feel不是学到新东西的兴奋,而是自己辛苦用reflector看代码查资料领会到的知识得到印证的感觉,觉得有这样一本系统讲述这些底层知识的书在手边,快速看一遍,以后就知道如何查找了,比用reflector慢慢最终慢慢理解要好多了。这种感觉准确来说,只是所谓的“松一口气”,ASP.NET给入门者的印象是很简单,然后你发现很复杂,接着你松一口气因为有人帮你将复杂的知识归纳好印成一本书方便你查阅。

看现在这本700多页的RoR的书,我能够一个晚上看50多页,因为一旦开始看了就真的不想停手。曾经有人评价RoR能让程序员惊叹“这不就是我梦寐以求的Web开发方式”,而阅读这本书的过程就让我信服了这个评价,并且推动着我一直想看下去,因为我总会想着“还有什么我梦寐以求的事情还没被我发现呢”。

2007年2月14日星期三

欲练 CSS ,必先宫 IE

“Win国天下,欲练CSS之人不在少数,大多不得要领,又或是走火入魔,全为IE所累。故曰:欲练CSS,必先功IE。”

曾经,我也属于为IE所累的行列,如今见到很多人仍然不愿意对自己的宝贝IE下手,所以决定特异写篇文章说说此事,以明辨IE到底是宝贝还是累赘。

好了,funny部分结束,按回我的习惯直入正题。之所以说IE不好,是因为IE会误导了你对CSS模型的理解,让你以为IE的理解是对的,之后无论如何你都无法用你的IE模型理论去为你那个无法在FF正常显示的CSS提供fix。更加坏的事情是,即使你仅仅针对IE设计,不考虑其它浏览器,由于IE模型绝对可以说是一只让人难以捉摸其脾气的怪物,所以你单纯为IE设计也会遇到众多难题,发现很多的效果总是绕来绕去都难以实现。

我们都知道,XHTML+CSS的目标就是实现内容与表现分离,理论上对于任何特定一份内容,我们都可以通过CSS实现任何我们想要的表现形式,或者细致地说是布局形式。虽然现实与这个目标有一定差距,但是CSS已经能够满足大多数常见的布局需求,这有CSS Zen Garden为证。然而如果你用的是IE,因为它难以捉摸,所以如果你想用一种简单优雅的CSS去让IE能够实现“任何你想要的布局形式”,那是不可能的,只有复杂繁缛的CSS才能够在IE上满足你的需求。我曾经提到过一种理论,“一个人对一个研究方向是否感兴趣很可能是完全靠偶然事件决定的,这就好像人第一次打羽毛球,如果你赢了几盘你就会感兴趣,如果你一直都赢不了你就会没兴趣”。IE在需要复杂繁缛的CSS这一点上,就足以令大多数的入门者却步。你总感觉到不得要领,你自然没兴趣学下去。

举一个例子说明这个问题,例如你不知道IE有hasLayout这回事,一个元素是否hasLayout对它的布局方式有重大影响,于是你肯定用最简单的思维去思考CSS,认为不同的CSS规则之间应该是松耦合的。“CSS应该被设计为简单优雅的”,你肯定会这样想,没错,它确实被设计为这样,不过IE不是这样去实现CSS罢了。我们用下面的代码去证明IE在quirks mode与standards mode之间的区别:
<div style="background-color: red; height: 30px">
  <div>Hello</div>
  <img style="float: left; width: 200px; height: 160px" src="blank.gif" />
  <div>Hello</div>
</div>

首先,我们用quirks mode看看结果如何,并且一个初学者看到这样的结果会去如何理解CSS规则。在quirks mode中,我们可以看到背景为红色的<div />包含了上面1行的文本,以及下面向左浮动的<img />(自然也就包括在浮动块右边的文本),在这里,我们可以建立两种认识:

  1. 容器是完整包含内容的,当内容的总高度比容器大的时候,容器就会自然伸展以确保容纳内容。
  2. 浮动块也属于上述条件所要求通过伸展以确保容纳内容。

以上规则是完全错误的,一个懂得标准CSS以及理解quirks mode的设计师将会如此解释他的理解:

  1. 因为IE在quirks mode中会将height理解为min-height,所以它认为<div />的高度不小于height指定的30px即可。而根据CSS标准,当height设置为30px时,高度就一定是30px,超出部分如何处理则由专门的CSS规则决定。
  2. 因为<div />被设置了height属性,在IE中这就让它hasLayout了,这就导致它一定要包含所有的内容,包括浮动块。而根据CSS标准,浮动块是无需被完全包含的,它就浮动在那里,除非遇到设置了clear属性的元素,否则后继内容只会侧移避让。

好了,相信这个对比足以说明问题的严重性了,通过IE的效果去理解CSS,最终只会让你的理解与真实的CSS相差甚远。详细的standards mode与quirks mode带来的标准执行差别,可以参考这篇文章:CSS Quirks mode and strict mode。

然后肯定有人要问我,如果通过doctype确保使用的是standards mode,那是不是就没问题了呢?standards mode确实会让IE对CSS的解释合理很多,但事情并没有那么简单,这你可以通过实践去慢慢体会。你可以尝试在standards mode中设计CSS,并且尽力保持它们在IE/FF/Opera/Safari这4大主流浏览器中显示一致,随着设计的进行,你会发现这不是那么容易做到的。或许你不乐意花时间去fix其中的一些小问题,宁愿任由其中一些浏览器的用户看到比较丑陋的布局,但至少你已经了解到一个和上面例子类似的道理:不同浏览器即使同样在standards mode,其对CSS的理解仍然有所差异,而差异当中最多只可能有一个是正确的,甚至可能全部都是错误的。这篇CSS contents and browser compatibility就列举了众多浏览器对CSS支持的差异,一份CSS总会因为其中有一些规则在某些浏览器上是不支持的或者是buggy的,而导致你难以保持它们在不同浏览器上显示一致。

接下来可能还有人会问我,既然IE的市场份额最大(特别是在入门级的用户当中),又或者说我的客户指定使用IE作为客户端,仅仅针对IE设计CSS不好吗?为什么要针对FF之类的标准浏览器设计CSS然后再为IE进行fix?因为IE难以捉摸的脾气,让你无法将它的行为理解为一种简单优雅的规则,然后让你陷入CSS规则高度耦合的困境中。请看下面的例子:
<div style="background-color: red; border: 2px black solid">
  <img style="float: left; width: 200px; height: 160px" src="blank.gif" />
  <div>Hello</div>
</div>
<div>Hello</div>

现在,你在IE中看到的效果应该是左边出现<img />,然后两个<div />内的Hello都向右偏移以避让<img />这个浮动块了,其中上面的<div />仅仅占用移行的高度,因为它没有声明高度,所以就是自然高度,也就是一样,这些都很好理解,所有规则都是解耦的。然后向例子中增加对第一个<div />的width属性复制,看看结果会如何:
<div style="background-color: red; border: 2px black solid; width: 600px">
  <img style="float: left; width: 200px; height: 160px" src="blank.gif" />
  <div>Hello</div>
</div>
<div>Hello</div>

这时候第一个<div />完全容纳了<img />,把第二个<div />挤到下面了。这该怎么解释呢?我们可没有设置它的height属性哦,难道又犯之前例子所说的因为hasLayout而必须容纳所有内容?正解,这就是IE难以驯服的地方,一个应该是完全独立的width属性,设置之后引起了高度以外的其它影响,这让人无法尝试以一种简单优雅的方式去理解IE的行为。这就证明了,如果你要学习如何为IE设计CSS,就先要学习标准CSS,再加上对IE怪异行为的理解,比仅仅学习如何为一个标准浏览器设计要难多了。这时候你是不是想说,“如果客户愿意放弃IE,甚至全世界都愿意放弃IE,那就实在太美好了”,没错,这才是正确的想法,一心想着仅针对IE设计以求方便只会让你走火入魔。

最后,如果你已经有了一定的CSS基础,对CSS规则都理解无偏差,却缺乏组合CSS规则的想象力,无法做到所谓的“实现任何你想要的布局效果”,这也就是说,你的内功已练成,仅仅差一些表面的套路,这时候我推荐你去看《CSS Mastery/精通CSS》。看完这本书,相信你只会觉得自己缺乏布局的创造能力,而不会有布局却不知道如何实现。另外,如果你关注CSS方面的内容,可以考虑订阅我的blog:

过年之后,我可能会写一些与ASP.NET+CSS有关的文章,因为现在ASP.NET+CSS的开发并不方便,即使用了ASP.NET 2.0 CSS Friendly Control Adapters也如此,因此需要根据自己的实际情况定制配对的Control Adapter才能解决问题,这就是我接下来要研究的事情。

2007年2月12日星期一

IIS7 会重用那些不该重用 HttpHandler

首先,实现IHttpHandler时要实现一个IsReusable的属性,这个属性告诉ASP.NET此HttpHandler是否可重用。如果一个HttpHandler是可重用的,那么多次请求都有可能用同一个HttpHandler实例;而如果一个HttpHandler是不可重用的,那么ASP.NET应该确保每次请求使用的都是一个新构造的HttpHandler实例。

Page是设计为不可重用,所以每次请求都会导致构造一个新的Page实例,这是因为Page的生命周期不能恢复到初始状态,一个Page经历完生命周期后就不能用于处理下一次的请求。类似的,如果我们有一个HttpHandler有类似的性质,处理一次请求后其状态就难以恢复到适合于处理下一次请求,或者说恢复还不如构造一个新的,那么我们就应该设计为不可重用。

我在做一个通过IFrame提交的无刷新上传控件,这东西包括一些HttpHandler,为的是能够直接关联到axd后缀而用于路径无关的场合。其中有一个HttpHandler我直接继承自Page,并且写得好像aspx+cs编译出来的代码那样,在OnInit阶段构建完整的控件树。这个HttpHandler以前在XP的IIS5上一直没问题的,但到了Vista的IIS7就出问题了。

先说明,在IIS7我采用其新的配置模式,将<httpHandlers />配置在<system.webServer />节,而不是<system.web />节,这是模仿着ASP.NET AJAX的web.config做的。做好之后就发现问题了,这个用作HttpHandler的Page竟然时候抛出Exception,说Page上面有多于一个的HtmlForm。仔细检查后确认,我的Page代码确实仅仅添加了一个HtmlForm,并且这个Exception不会出现在编译后的第一次请求,于是我就怀疑Page被重用了,所以OnInit被多次执行,这才可能导致它有多于一个HtmlForm。我启用了Page的Trace,在Render中注释掉base.Render,并且用Trace输出Page上的HtmlForm数量,发现真的是每次请求都会导致多一个HtmlForm,这基本上可以肯定是因为Page被重用了。

然后我就用Google搜索,结果发现forums.asp.net上有人提出了完全一样的问题,他也是用Page来做HttpHandler。我想只有拿Page来做HttpHandler的人才会遇到这样的问题,因为一般自己写的HttpHandler都是无状态的,所以都是可以重用的。而那张帖子只有管理员回复了一句,“你最好去forums.iis.net问吧”。于是我就去iis.net搜索,结果发现没有人提到过这个问题,于是只好自己去论坛提问,可惜等了一天都没有人回答,看来IIS7普及之前iis.net的人气都不会上升(IIS7的默认欢迎页面链接到iis.net)。

最后,我选择了先采用Jeffrey Zhao建议的work around,就是再制作一个HttpHandlerFactory,它负责每次返回Page的新实例,然后在配置中改用该HttpHandlerFactory。这个解决方案实验证明是可行的,就是多一个类而已,不知道性能损失有多少。如果有人知道这个问题的官方解决方案的话,或者有一个更好的work around,请告诉我,谢谢。

2007年2月5日星期一

Microsoft AJAX Library + ADODB = ?

最近做了一个基于Web的纯桌面端数据库应用,非常轻量级的,在挑选库的时候最后还是选择了自己熟悉的Microsoft AJAX Library,而没有使用prototype、dojo、YUI之类的。一方面,是因为Microsoft AJAX Library比较贴近我熟悉的控件模型,另一方面要做的东西真的轻量级得只需要普通的控件,不需要拖放和效果,不需要封装新控件或widget。

过程

整个制作流程大概是这样的:

首先,我设计了一套CSS模板,使用Sharepoint Designer来做,配色方案是临摹着其他漂亮的table排版模板做的。在这个过程中,我真的觉得Sharepoint Designer非常好用,你需要做的仅仅是用XHTML将模板所需要用到的语义全部写好,然后开始在<style />内部加CSS,只要CSS技术过关,很快就能把整个页面格式化为你想要的效果,而Sharepoint Designer的IntelliSense则能够自动提醒你哪个class下面有哪个id或者有哪些element。在格式化好页面后,把CSS从<style />移动到独立的CSS文件,并添加对一些特定的浏览器的修正,那就搞定了。

接着,开始根据程序的需要,在XHTML上设计Page、Form等的一些常用结构,并且写了一些基本的函数来实现Page直接的导航,Form内的常见操作。我用的仅仅是函数,这些函数根据性质以类来聚集,但没有做一些代表实体的类(例如Page类),因为我相信这个东西足够简单,通过函数完成简单操作就行了,没必要将XHTML元素映射为类(控件)。在搞定了如此简单Page划分之后,Page之间就完全解耦了,然后我就可以独立设计每一个Page里面的逻辑,而Page之间的数据传递则通过切换Page的函数负责,这有点类似QueryString的做法。不过我这里Page的概念仅仅是一个<div class="page" />的元素,而传递的可以任意JavaScript对象。

数据库的操作,我反而没有做任何的封装,直接new ActiveX("ADODB.Connection"),然后就拿来用。很raw的形式,写起来的代码有点点像ASP的feel,不过ASP是直接输出XHTML,而我是构建DOM元素。实际上所有的CRUD操作都是以最直接的形式执行的,几乎不存在任何业务规则,所以也是直接把CRUD的操作封到函数里就行了。有趣的地方在于,Sharepoint Designer在一个对象赋值为ADODB.Connection后,竟然能在IntelliSense提示Open等方法,不知道这是对于ADODB等常用对象的支持,还是对于任何COM对象都提供支持。

最后需要做的,就是在XHTML上把整个界面填充完整,把函数钩上事件,这就完成了。没有任何数据控件,不曾用到ITemplate,用得最多的无非是TextBox和Button。

总结

即使完全不用于AJAX开发,甚至是没有服务器端的开发,Microsoft AJAX Library也是一个很好用的库。如果你习惯MS那套控件设计的思想与风格,那么Microsoft AJAX Library会显得很容易适应,不过前提是你已经有很好的JavaScript基础。

现在国外有一个争论,就是一个JavaScript的入门者是否可以使用库,还是说有能力自己写一个库的人才应该使用库。这个争论的来源,我觉得是因为现在的库还是太过像手脚架了,能够帮熟练的使用者提高开发效率,提高代码质量和软件健壮程度,然而对于入门者来说却会因为对基础问题的不理解或者理解偏差而到职他们在使用库的途中犯下更多的错误。

在这个问题上,我使用MFC时就有切身体会,我就觉得熟练使用Win32API的人使用MFC才能获得最大的便利,而入门者使用MFC则对着MSDN左翻右翻也不得要领。我不懂Win32API,曾经学过一点点MFC,站在VB Form或Win Form的角度来理解,总是觉得MFC设计很怪异,为什么如此多地方设计得如此命令式而不封装起来呢?在使用MFC过程中碰到自己无法解决的问题时就更麻烦了,然而懂Win32API和熟悉MFC的人却能很快告诉我——例如这是因为MFC自身的一个设计缺陷,或者是它迁就Win32API使用方法或风格的地方。

MFC就是一个如此的手脚架,让懂Win32API的人能够更高效地做一些事情,而不懂的人却无法得到抽象带来的便利,因为它并没有怎样抽象,只是将函数式API封装为对象。现在的多数JavaScript库也就是做到了这个程度,将最初的DHTML到如今标准化的DOM API进行封装,却没有太多的抽象,所以用起来还是要清楚它到底干了什么。这是因为遇到了问题有可能无法在高层解决,而必须回到低层解决。

回到争论上面来,我个人支持库的使用者必须有能力完全理解库这一立场。考虑一个不太了解JavaScript的ASP.NET开发者,他使用Microsoft AJAX Library,无论是使用XmlScript还是JavaScript,他的“活动范围”也就是sample的附近,一旦超出了这个范围,他就在“凭运气写代码”了,这时候代码质量可想而知。如果他开发出来的东西还有给其他开发者调用,那就更危险了。这种现象其实也普遍存在于ASP.NET的控件开发者中,对ASP.NET理解有限的开发人员制作出来的控件就有可能和给使用者带来麻烦。

至于什么时候能够有高度抽象的Web开发框架……想一想,MFC诞生于1992,而Win Form以来的.NET Framework诞生于2002,这估计要10年吧。或者乐观一点,VB Form的抽象也很好了,因此Win Form才那么多参考其设计,拿比较成熟的VB6来说吧,诞生于1998,也要6年。这样说来,可能也要到2012才会有高度抽象的Web开发框架出现。况且,抽象也依赖于硬件发展,当我们不再为“小小一段”ViewState占用多少流量与带宽而烦恼时,抽象才有可能实现。

2007年1月29日星期一

“都几唔痛下㗎喔”

做完手术啦!做嗰时有麻药,就真係唔痛嘅,之后慢慢开始痛,越嚟越痛……都係上床瞓觉好过啦,反正瞓着觉就唔知痛。

2007年1月27日星期六

非常好用的 Microsoft Expression Web Designer

我暂时不知道Microsoft的Expression Web Designer和Office Sharepoint Designer有什么不同,对比过它们的安装选项看起来是一样的,所以暂时当它们一样吧,我现在正在使用的其实是Office Sharepoint Designer。

我最近做了一些小的CSS Template设计,就是设计出来的结果是一整个demo页面,里面已经包含各式常用的HTML元素与元素组合,并且是styled好的,让人一看上去就知道使用这套CSS Template制作出来的网站会是什么风格,同时使用者还可以根据自己的需要添加和覆盖一些CSS规则,简单来说就是类似Mollio那样的CSS Template。对我来说,Office Sharepoint Designer极为方便的地方包括:

  • 元素margin与padding的可视化,让你无需专门改动CSS就能方便看到页面上元素所占据的margin和padding。
  • CSS继承链的可视化,选择一个元素后CSS窗口选择“摘要”模式,就能显示它一路继承下来的属性,其中已经被覆盖掉的属性会用删除线标记。

有了这两项支持,设计就变得极为方便了,设计一个CSS Template只需要以下几步:

  • 用完全只考虑语义的方式,将需要styling的HTML Template写好,通常这个HTML Template包括常用的元素组合。
  • 自顶而下地进行CSS定义,在Office Sharepoint Designer中可以直接看到效果,用一个比较符合标准的浏览器测试也行。
  • 当整个CSS都定义完了,再考虑用不太标准的浏览器进行测试,并添加相应的修正项。

值得注意的是,Office Sharepoint Designer自身显示HTML可视化设计界面的解释器与IE6或IE7都是不同的(其实以前Frontpage也是独立解释器),它比IE6更符合标准,支持CSS也非常好,但实际上它解释出来的效果有可能会和IE7略有差异,所以千万不要以可视化界面中的效果为准。

ASP.NET 无法确保在注册的 JavaScript 内不存在重复定义

在ASP.NET 2.0中,我们使用Page.ClientScript属性(也就是一个ClientScriptManager对象)的一些名字以Register开头的方法注册客户端脚本,这是大家都知道的。

理论上应该如何避免冲突

先说说为什么要这样注册脚本,而不用Response.Write直接输出。举个例子,你用3个DropDownList做了一个输入日期的区域,分别代表年/月/日,然后你为了防止用户输入2007/02/31,所以你决定把这3个DropDownList做成级联的,也就是随着年和月的输入改变,日的可选项跟着改变。这时候你可以通过写一些JavaScript来实现级联,例如定义一个名为updateDateRange()的JavaScript函数负责更新DropDownList,然后直接把这些JavaScript放到C#的字符串里,并且使用Response.Write输出。这些代码用起来会很正常,直到有一次你的页面需要输入两个日期。

在需要输入两个日期的那个页面上,你把3个DropDownList复制粘贴了一遍,也把输出的JavaScript的那段代码复制粘贴了一遍,接着根据两处ID的不同做了相应的修改,结果有一组级联无法正常运行起来。你查看服务器端输出的HTML,接着恍然大悟——原来有两个updateDateRange()函数。于是你把updateDateRange()改为updateDateRange(yearControlClientId, monthControlClientId, dateControlClientId),同时把JavaScript删减为仅输出一遍,这时候无论哪组级联都使用同一个函数,它们根据调用时输入的DropDownList.ClientID来区分。

又有一天,你决定把这组级联封装为一个UserControl,做起来当然还是复制粘贴大法,也就是把3个DropDownList和JavaScript复制进UserControl,然后把UserControl的引用复制回原本的调用处。忙完之后,发现那个有两个日期输入的页面又出错了,原来updateDateRange(yearControlClientId, monthControlClientId, dateControlClientId)又被重复输出了,因为页面上放入了两个UserControl所以JavaScript被输出了两遍,并且没办法减少输出次数。

这时候你可以使用Page.ClientScript.RegisterClientScriptBlock解决问题,它通过type和key这两个参数确定脚本是否被重复注册,而被重复注册的脚本仅会输出一次。为什么要type和key两个参数呢?以前ASP.NET 1.x的同类函数只有key一个参数,这带来的问题是可能两个不同的控件设计时都使用了同一个key来注册自己的脚本,结果其中一个控件脚本的成功输出必然会抑制另一个控件脚本的输出。加上了type参数,各控件都用自己的类型作为标识,这样就能有效避免注册时冲突。

为何无法真正避免冲突

关于这个问题,我们先看看ASP.NET内部定义的JavaScript是以什么方式命名的。通常,private的全局函数或变量,命名都以双下划线开头,例如大家熟悉的__doPostBack,或者是WebPartManager在客户端使用的__wpm。而public或protected的全局函数或变量,一般就好像C#那样使用Pascal命名法。具体的例子,大家可以用Reflector看看System.Web.UI的资源中的那些js文件。

我们暂时就假设这种命名法是正确的,然后模仿着去在自己开发的控件中实践。事实上很多控件开发者也确实这样做了,比较多的专业控件中你都能看到双下划线开头命名的函数或变量,这至少可以避免和控件使用者在页面上注册的函数或变量冲突,因为在页面上注册的函数或变量通常都采用比较简单的命名法。

假如现在我们要做一个浮动上下文菜单,也就是当你的鼠标移动到某个HTML元素上时该浮动菜单自动出现,当鼠标离开元素并且也不在菜单上时,菜单自动消失。为了方便用户操作,我们允许用户鼠标移动过程中稍微离开菜单区域,所以定义当鼠标离开菜单区域若干时间后才让菜单消失,而这个时间在客户端保存在__disappearAfter变量中。这个控件看起来什么问题都没有,直到你把它和ASP.NET 2.0自带的Menu控件放在同一个页面上,因为Menu控件也有类似的功能,而且和我们的控件一样Menu控件选择了将时间变量保存在一个名为__disappearAfter的变量中。

现然,作为ASP.NET框架的使用者,框架没有声明这个变量的名字不允许使用,我用了有问题当然就可以认为是框架的错。Brad Abrams写了一本《.NET设计规范/Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries》,里面却完全没有提及JavaScript和CSS的规范,好像在ASP.NET中使用到的JavaScript与CSS都是琐碎的不能在琐碎的事情,所以完全不值得一提。

事实上,既然ASP.NET允许一个页面上不同的控件设计者引入不同的JavaScript和CSS,就必须提供一种方法去管理潜在的命名冲突。如果是JavaScript,我们可以考虑使用ASP.NET AJAX的namespace来避免冲突,下一代代号为Orcas的Visual Studio和ASP.NET将内置ASP.NET AJAX支持,所以其内置控件所使用的JavaScript应该也会有namespace,这样就有有效降低冲突概率。至于CSS命名冲突,暂时没有好的解决放案,只能依赖控件设计者的习惯了,你可以考虑为你的控件根元素附上一个namespace以示区分,这样也算是降低冲突概率的一个办法。

最后,如果你希望阅读更多类似的ASP.NET主题文章的话,欢迎订阅Cat in dotNET (Feed: http://feeds.feedburner.com/CatChen/dotNET)或Cat in Chinese (Feed: http://feeds.feedburner.com/CatChen/Chinese)。

2007年1月25日星期四

放假了,要去做个小手术

又放假啦,又不知道做什么好了,暂时没有任何计划。

唯一问题是下星期一要做个小手术,有人愿意之后来探望我的话,可以联系我哦。

2007年1月14日星期日

开始学习 Cantonese Typing

对住份粤语字打法大全(2007贺岁版)慢慢睇,都几恶顶下,考虑係咪开翻个Cat in Cantonese玩下先。

Microsoft 应该学学 Sony 的“产品皆无完美”设计思想

MS现在推广Vista的最大障碍应该就是XP。虽然XP+SP2已经比较臃肿,但还是很够用的,XP潜在安全性问题虽然不少,不过既然SP2通过安全中心加上了那么多安全方面的提醒,也就够了。

Vista推广自己足够安全,然而却拿不出什么实际的例子来说明它比XP更安全——你总不能通过揭露自己一个产品的短处来推销自己的另一个产品吧。例如Vista的那个Virgin Stack,投入了去写一个全新的TCP协议栈,而放弃从原有系统的成熟代码修改而来,这点就应该拿出来卖卖广告啦,可惜至今只有安全领域的专业人士知道这个实事,大众用户对此一无所知。

Sony的聪明之处在于产品从开始设计时就注定是不完美的。Sony只有非常少数的经典产品可以称之为完美,大多数产品就算能够成为经典,也一定留下一个巨大的不足,或者说是瓶颈,让用户感到不够用,从而乐意掏钱买下一代的产品。

最明显的例子是Sony CLIE的TH55拔牙事件。在TH55上市之前,Sony官方的页面都是暗示这台PDA将成为完美机型的——蓝牙、Wifi、480*320长屏、摄像头、CLIE升级过的Organizer软件、Sony诱人的工业设计。然而就在TH55正式发布的前一个晚上,原来的官方页面被撤了下来,新换上去的页面公布这台机没有蓝牙,这导致大批的CLIE Fans愤愤,最终调解的结果是只有欧版的TH55是有蓝牙的。

TH55拔牙事件说明了Sony的产品设计思想,它就是不愿意看到有一个机型能够满足大多数用户的所有要求,然后人们买到这台机后就不再升级了。即使到了发布前的最后一个晚上,Sony也要改变主意把TH55的蓝牙拔掉,从而让买了TH55的人以后为了蓝牙功能而更新换代。

XP现在的问题就在于它已经太好用了,这无法促成用户去更新换代。或许MS也在祈求多一些XP有而Vista没有的安全漏洞出现吧,这样它就可以暗示大家为了安全所需而升级到Vista了。

2007年1月13日星期六

我给 Google China 出主意

看到这个活动,我又参加了,嘻嘻……先说明,我觉得这个活动的题目应该标明是Google China,而非Google,它们不是同一回事,而是从属关系的两个实体。

2007年的Google China,是时候推出一些收费服务啦,要在中国从个人口袋里挖得到钱的服务。这是不是个非常违反常理的建议?正所谓“索取比给予还要好”(参考《硬球/Hardball》),如果你的用户已经愿意为你的服务而掏钱,那么还有什么他不愿意为你干的?

分享价值是Google价值观的一部分,然而分享方式确实非常美式的——我放在这里啦,你要的话你可以来拿啊,我不会强迫你要的。但这在中国是行不通的,中国人习惯了作客时主人会拿一大堆好吃的出来——你吃这个啦,这个好吃啊。当然,给你吃不是白吃的,如此热情好客的背后,当然是希望拉拢关系。甚至好像婚礼这样的,给你请贴了你不能不去,去吃了不能不封利事。这听起来很强买强卖,但中国人就习惯这样,因为不是这样半推半就的接受而是主动请求获取,反而是显得不礼貌的。

这样说来,流氓式的软件或者服务才真正体现了中国文化的精髓(或者是糟粕)。你想要,但又不好意思说,是不是?那我就硬塞给你,给个充分的理由让你接受,如此你就不拥顾虑别人在你背后怎么说你了。Google要学的就是这个,给个充足的理由用户去接受你的服务,这个理由不一定是经济上的,可以是道德上的。

“你看人家Google都把服务送到你嘴前了,你不张一张嘴,这不太合乎人情吧?”大多数人都会选择张嘴,而不是带着“不近人情”这顶帽子。好像Google这样优秀的服务,只要成功让你的用户吃上一口,后面他自然会主动要。而如果好像Tencent那样的烂服务,就每一口都要这样强迫用户去吃。

用户乐意吃了,这时候就应该收钱了。收多少无所谓,反正Google不缺这些钱,需要的就是获得用户的投票——如果你能让一个中国用户自愿给钱你,也就得到了他对你足够的信任和肯定。之后,你无论推出什么服务,这些信任你的用户总会乐意去用的。

2007年1月12日星期五

回归 Blog*Spot

现在Blog*Spot已经有一段时间没被封了,而地震之后Sitesled一直上不了,现在连Blogger也无法发布到Sitesled了,所以只好转回Blog*Spot。

转会去之后,自然考虑使用新的Layout模式,而放弃旧的Template,所以界面看起来会不同了,有些以前有的功能也暂时取消了。不过大家可以放心,这些功能很快会恢复,我考完试了就会把Layout改好,让它支持更多更好的功能。

2007年1月11日星期四

Windows 的换代与在线社区的兴衰

这看起来是两个没太大关系的主题,但实际上却是相关的。最近在看《魔鬼经济学/Freakonomics》,两个周末就看完整本书的6章了,然后就喜欢把一些没关系的东西联系起来看,看看是不是其中一个因素的变化会导致另一个因素跟随变化。可惜我没办法获得那么多的数据,很多事情只能观察其表象。

先说说操作系统与应用软件之间的联系,通常一个操作系统上面的应用软件越多这个操作系统的用户也就越多,所以Microsoft就想尽办法让Windows上的开发变得简单甚至可以说是弱智,就为了让更多人可以写Windows平台上的应用程序。当然,这种作用效果有时候也会反过来,如果一个操作系统越多人使用,开发人员也就越乐意在上面开发软件,因为面对的市场也就越广。

在线的软件技术讨论社区就如软件一样,和操作系统存在着上述联系。如果一个在线社区讨论一个操作系统及其软件的人多,就会让这社区上越来越多人选择该操作系统,当然也会让使用该操作系统的用户喜欢来此社区讨论。PDA就是一个明显的例子,有些比较倾向于Palm的论坛,就越来越倾向于Palm,例如以前的PalmStamp那样,Windows CE的用户就只有个“蜥蜴自然保护区”的板块。而另外一些论坛则倾向于Windows Mobile,Palm用户在上面就只有找电子书和漫画等资源的份了。

当年Windows XP出来的时候,就带旺了一些论坛,例如NewsFan。当然这些论坛都有一个共同的特点,就是专门为XP开一个专版,甚至几个专版。有些非常积极的XP Fans长期在上面,每次有一个新的build出来都会马上安装来看看的,然后和其他人分享一些hacks & tricks。其中的积极分子,后来成为了中国的第一批MSMVP(微软最有价值专家),技术特长当然是Windows XP,而且他们对大众用户也非常有价值——及时从MSDN Subscription下载Windows XP SP1的VLK版并与大家分享。

今年Windows Vista出来了,新一轮的受惠者中站在首位的肯定是VistaFans。VistaFans现在有多达8个Vista专版,讨论相关的各种hacks & tricks,还包括资源下载和界面美化,甚至连Windows PE 2.0也有专版。如果你是新来到VistaFans的访客,首先,你可以下载到一个Vista的RTM镜像,x86和x64版本都有;其次,你可以找到破解有关的讨论,例如现在比较多人用的TimerStop破解法(指暂停了激活倒计时);最后,你还可以参与到软硬件升级与兼容性相关的讨论来,例如支持Vista的杀毒软件和显卡驱动。即使你原本完全没考虑过要装一个Vista,到了VistaFans发现如此便捷的路径之后你也很可能会想装一个。

有受惠者自然有受害者。VeryCD向来允许发布MS的资源,却在2006年底突然禁止发布MS的OS,包括原版或修改版,其后还扩大到禁止发布任何MS的商业软件。以前TLF不允许发MS的商业软件,所以VeryCD成了一个优秀的替补,现在正是Vista发布前夕VeryCD却宣布禁止发布MS的商业软件,导致想用Vista的用户就流失到VistaFan之类的地方,留下来的用户就变得不想用Vista了,因为论坛内并不提供使用Vista所需要的便利。

2007年1月10日星期三

HP Pavilion tx1000

在今年CES上,发布了HP的第一台娱乐型的Tablet PC——Pavilion tx1000,并成为我当前的心水Tablet。

1.8G的AMD Turion™ 64 X2,加上1G的内存,还有NVIDIA GeForce Go 6150,绝对能满足现在大多数应用与娱乐的需求(玩游戏除外)。屏幕是触摸屏,好处是可以直接用手指按,坏处是不能用笔悬浮在表面画手势。

价格暂时没公布,不同的细节配置大概是$1300到$1700的范围,在Tablet中算得较低的了。不过HP好像没把它宣传为Tablet,只是当作新一代的可旋转屏幕的娱乐型notebook,并且是带有触摸屏的,只不过触摸屏加上Windows Vista Home Premium自带的Tablet支持这台机自然就成了传统意义上的Tablet了。

2006年12月31日星期日

Irreversible Writing

Blog,被我称之为Irreversible Writing,因为一旦点下了Publish按钮就没得回头。

当然,你会说之后可以删可以改之类的,但其实不是的。一旦搜索引擎爬过了你的blog,它所引的副本你就无法删除;一旦某个你不期望看到你blog的人看过了你的blog,他的记忆你也无法删除。而且现在不仅仅文字会被搜索引擎索引,图片也会。研究脸谱识别技术的网站最近也开始增多,如果你的脸谱特征被记录下来了,以后有你出现的照片也能自动识别到你的存在。未来音频与视频的索引技术肯定也会出现,那就是说发表在Internet上关于你的一切,无论是你发的还是别人发的,都难以删除。因此,在Internet上留下任何信息之前,记得先确认“无论谁看到这端信息都无所谓”,如果你想着“某人应该不会看到吧”,那最好还是不要发布到Internet上。

2006年12月30日星期六

2007,我对Google的期望

在猫窝看到了这个活动,所以我也来写写对Google的期望吧。以前tag人的游戏我都没参与过,现在有礼品我反而愿意主动参加,咔咔!

  • 要有原产自Google China的产品——否则都不知道Google China是做什么的,拿着印有“谷歌”的礼品感觉是次品。
  • Google Docs & Spreadsheets plug-in for Microsoft Office——无非就是用C++或C#写一些代码,通过GData协议访问Google的数据,然后就可以在Office中实现好像访问Sharepoint那样上传下载版本控制。有了这个东西,相信Google Docs & Spreadsheets能够很快普及。
  • 培养好开发人员社区——一个操作系统要拉拢用户,先要有足够多的运行在该操作系统上的软件;要拉拢软件,先要有足够多的开发人员。无论Google是否会做Google OS,对于它现在拥有的那么多产品线来说,培养一个好的开发人员社区是很重要的。多一些人用GData开发,也就多一些支持Google服务的网站或客户端,也就多一些人可能间接使用到Google的服务。
  • Google Checkout支持中国结账——现在想去Google Apps买个域名,因为附送Google Apps不用另外申请,可惜结帐要用Google Checkout,billing adress支持HK和TW都不支持CN,麻烦死了。
  • 更多面向小型组织的免费服务——现在只有个Google Apps算是面向小型组织的,其他都是面向个人的服务,而且Google Apps本身也没什么协作功能,这与37Signals的差距太大啦。在抓住个人用户后,Google就应该开始抓小型组织。

一本好的教材要先让读者有赢的感觉

我们的数值计算老师说,一个人对一个研究方向是否感兴趣很可能是完全靠偶然事件决定的,这就好像人第一次打羽毛球,如果你赢了几盘你就会感兴趣,如果你一直都赢不了你就会没兴趣。

一本好的教材如果要让读者有兴趣读下去,也必须开头先让读者有赢的感觉,一旦如果读者感到frustrated了这本书的命运也就肯定是“束之高阁”了。正如我之前在《我喜欢的教材与我讨厌的教材》中说到的,讲矩阵开篇应该先说说方程组可以用矩阵来求解,这也正是让读者先有赢的感觉。

如果日后我有机会自己写本书,也一定要确定有一个好的开头,确保让读者尝到甜头并且乐意读下去。

2006年12月26日星期二

地震导致海底光缆断开

上网好慢啊,国内网站还可以,国外网站都不用上了,还要几天才能修复。这个样子,我的Blogger怎么维护啊,我怎么获取技术资料啊!

2006年12月20日星期三

每一个女生都曾经是非常有灵气的

“每一个女生都曾经是非常有灵气的”,这句话是Piggest说的,在我某次赞她省实小才女时。

经过了一些事情后,我对这句话思考的结果是:When a baby comes to a new life onto this planet, he's sitting on a rocket that flying high in the sky. Then, most of them lands.

大部分人最终都会放弃他们的才华选择着陆,这就是我今天要说的事情。着陆有可能是因为诱惑,随着人的成长,你的才华发挥出来了,你就发觉你可以放弃掉部分才华去换取一些非常有诱惑力的东西,至少是看起来有诱惑力的东西吧。这种情况通常发生在20岁左右,甚至20岁之前,因为在初中或者高中就发现生活充满诱惑也是时常有人遇到的。着陆也有可能是因为责任,来自对家庭的责任会让你自愿的放弃部分原有的自由发挥空间。这种情况可能会发生在30岁左右吧,不知道呢,或许也有因为家庭压力大很小的时候就放弃了的。

在我高三的时候,尝试在拿到保送后将我的成绩给软着陆了,结果不那么成功,有些东西的运动轨迹变成了平抛甚至垂直下落。无论怎么说,我算是着陆了,但发现在这拥挤的地面上我难以生存——这里的人太多了,想舒服的伸个懒腰都不行。所以我决定不能再这样生活下去,一定要回到三维空间上去,不能留恋这个二维平面上的诱惑。我要做我自己爱做的事情,而不是在一个粒子密度高的环境下让别人的布朗运动影响着我的前进路经。

其实着陆往往和成熟挂钩,你能够在其他人的布朗运动影响下前进,甚至反作用于他人,那叫做有EQ,那样可以teamwork。然而teamwork又如何?我不是反对teamwork,我曾经知道自己的个人能力有限所以才积极拥抱teamwork,因为我发现好的teamwork能够发挥1+1>2的效能,然而这不代表我盲目支持teamwork。盲目地根据partnet限制自己的发挥,希望达成teamwork,然后换取更大的总发挥效率,很有可能导致的效果是1+1+...+1<1,最终整个team的效率比一个人自己发挥的还要低。

地面上没有什么生物能够轻易杀死一只飞鸟,即使成群结队。所以如果你有能力保持着在天空飞,就别跑到地面上结队,反正来自地面的威胁不可能真正伤害到你,除非你已经在濒死状态。当然,只要求你耐得住寂寞,至少在你成功加入一个飞行梯队之前。诚然,能保持在天上飞的动物是少数,所以要成功加入一个梯队也比在地面上结队要难得多,因此做好寂寞的心理准备是必须的。地面上所谓的诱惑,无非是看起来的“人才济济”——是不是人才的都可以先来挤一挤,然而你自己能否挤得过别人,就要看天赋了,任何人生下来都有天赋,不过又各不相同。

当然,不着陆的话往往会给人看做不成熟,就如某些大师级人物不受什么物质诱惑也没有什么责任观念。然而要问这样的人物所创造的价值,放到与地面上的团队比较,或许也在前20%。如果能够把他放置到适合飞行梯队中,那么整个梯队发挥的效能就肯定是整个星球上的前1%。

2006年12月17日星期日

Microsoft WPF/E vs Adobe Apollo

整个.NET社区都在庆祝WPF/E开始CTP,且慢,看看河对面的Flash社区好像也在举行隆重的庆典哦。

AVM2开放源代码

这几个星期发生在Flash社区的震撼事件,包括Adobe将ActionScript Virtual Machine 2(AVM2)的核心源代码捐献给Mozilla组织,变成了一个叫做Tamarin的开源项目。Tamarin的目标是实现一个高效的ECMAScript 4th edition(ES4)引擎,它会成为现在Firefox中代号为SpiderMonkey的JavaScript引擎的新核心,同时也用于运行ActionScript3的AVM2。

如果你想知道Tamarin有多震撼,先来看Tamarin和现有SpiderMonkey的执行效率对比,其中强类型代码(Typed Code)模式是指将JavaScript转换为ActionScript3,并且将function与prototype转换为class:

(摘自:Tamarin vs. javascript Performance)

看到这样的执行效率比较,再想一想IE那个执行效率比FF还要低得多的JavaScript引擎,或许将来Apollo应用的配置要求这样写:四核处理器加Internet Explorer或双核处理器加Mozilla Firefox,哈哈……我的意思是,如果你一定要用IE你就要多投资金到处理器上换取等效的JavaScript执行效率。

Tamarin开放源代码的战略意义是很实在的,这样Adobe就可以让开源社区服务于它的Flash Player了。Tamarin作为一个标准的ES4引擎,虽然现今只有AVM2和SpiderMonkey基于它,但这也足够形成一个强大的战略同盟——Adobe或Mozilla社区对Tamarin的改进都会让双方同时受惠。将来可能有更多软件考虑引入基于ES4的脚本语言支持,如果选用Tamarin的话将会让其开发者社区便得越来越壮大。哪天再出来一个神通把它tune up一下的话,其执行效率将可能远远抛离IE,这时候同样的脚本应用只在Flash或FF中流畅运行,你想不放弃IE都不行。

Adobe Apollo

另一个震撼的事件是Adobe的Apollo即将来临,这家伙将有十足的实力在全平台上与WPF/E对抗。

首先Apollo支持Just In Time(JIT)编译,这样其跨硬件平台能力就可以和Java/.NET比了。.NET所谓的跨平台是狭隘的,JIT主要是指跨硬件平台,软件平台则直到WPF/E才真正肯跨出了第一步,提供对MacOSX的支持。Apollo在挑选浏览器引擎时却费尽了心思,就为了选一个将来容易跨越更多软件平台的。

Apollo最终选中的浏览器引擎是开源的Webkit。Adobe官方宣布明年上半年正式发布的Apollo 1.0将支持Windows和MacOSX,但如果你了解一下Webkit这东西就发现它其实有足够的潜力实现大小平台通吃。Webkit本身作为Safari的核心,所以确保了MacOSX平台的支持;其次它也是KDE上KHTML浏览器的核心,进军Linux也应该没问题;最后它连Symbian Series 60(S60)也都支持,Adobe只需要去和Nokia握握手或许就能让Apollo进驻S60的智能手机,要知道在智能手机上S60的市场占有率可比Windows Mobile(WM)多得多。

反过来看看WPF/E,估计也是2007年上半年能够发布1.0正式版,官方支持的平台也是Windows和MacOSX,但MS已经支持其Linux的支持将依靠第三方来实现,也就是不会用官方的WPF/E for Linux支持。至于移动设备,将来就算能支持估计还是仅支持WM,这样适用范围还是受到了很大的限制。

2006年12月13日星期三

重新开始做做算法设计的竞赛题

期末考前要先提交20题,这对我来说不难,不过做起来也挺有feel的。或许以后有时间还是要多做做题,重新找回感觉,把思维速度调回去。

2006年12月10日星期日

我不想仅仅做一个coder!

“我不想仅仅做一个coder”,这是最近才发现的事实,算是发现得早还是晚?

我不是一个优秀的Coder

“我不是一个优秀的coder”,这是一个摆在眼前的事实。跟很多其他同年龄的人对比起来,或许我真的已经coded了不少东西,而且结果也有一定的质量,但当我见到过真正优秀的coder后我明白到了自己不是一个优秀的coder。

在我的高中里,有一位比我低三个年级的同学,他接手了我创立的网络社团的负责人位置,让我见识到了什么才是真正高效的coding。我与第二任负责人的风格都是设计型甚至是幻想型的,也就是说我们从来不愿意通过coding去实现一个别人已经做过的东西,我们必须要有一个非常原创性的idea我才会开始coding,如果在coding的过程中发现这东西的原创性不足,又或者够原创性但缺乏实用价值,那就会停下来思考和等待新的idea,直到新的idea又有原创性又有价值了才继续coding。然而这位第四任却是一个100%的coder风格,他不会发时间去想这东西是否原创,只要有需求他就去coding,然后再考虑向其中加入一些原创性的或者是实验性的东西。

他一个学期下来就做了一套学生自助建站系统。当学生申请个人网站时,他仅仅需要人手审核申请信息,之后系统就会自动创建目录分配空间,将该网站选择了的模块(例如论坛、blog、wiki等)复制到该目录,修改好这些模块的配置让它们能够正常运作。整个系统中唯一要老师帮忙的就是配置域名泛解释。这东西你可以说没太多原创性,都是辛苦活,他要做的就是纯粹的coding,然而一个在重点中学读书的高中生能够在一个学期内完成确实了不起。而且在这个学期中他还做了其他小网站,都是属于功能单一仅仅为了满足学校某方面需求的那种。

这位同学手上有一张MCSE认证,开发主要用ASP/ASP.NET,有时候疯狂到用C#自己写一个基于模板返回内容的HTTP服务器,“这就是一个纯粹的Microsoft Coder”,我们的指导老师是这样说的。如果一个“纯粹的Microsoft Coder”是这个样子的,那么以我现在掌握的技术和我coding的速度来看,我又差多远呢?看起来非常非常远……

Farmer, Worker and Coder

最近看了很多文章,然后才有了文章开头的想法。我看到有人在讨论coder的命运,我觉得farmer和worker是怎么过来的,coder也就将会怎样。

在国外coder只需要高中学历,因为这是一门学历和经验参半的手艺,太高的学历没用,还要补上相当的经验才有价值。在国内coder显得有价值,这是国情造成的虚高,也就是你本来就不值那个价位只不过看起来像是那个价位而已。将来这个虚高不存在了,你就摔下来。所以今天拿着学士学位的coder要明白到,自己的真实身价与高中生等同,虚高的消失是不可逃避的,你唯一能做的就是在这个时期内让自己的实值上升,这样虚高完全消失后你还能有回原来的身价。coder怎么做也是coder,除非你确信你有能力做coder中的#1,否则就永远是那个身价,也就和worker、farmer一样,要逃出这种困境只有一条路可以选择,那就是让自己真正的增值。只有增值为designer或者architect了,才能确保你不再是在coder的race中,而进入了另一场race。

挑战你的极限

我们的数值计算老师,一开学就学就说:你们计算机系的学生只有两条出路,要么赶紧转去学金融,要么考研,本科毕业出来工作就是浪费了你。其实浪费所指的应该就是毕业后你最多就当个coder,享受不知道何时会突然间没掉的身价虚高。数值计算课有1/3的时间给他用来吹水了,吹的就是学习方式方面的事情,不过也挺有价值的。

他后来讲到,他在浙大读博士时获得了公费去香港城市大学的机会,去到后才发现要拿到学位非常难,但是公费去了拿不到学位回大陆是非常大的罪过,所以就拼了命地去学习合做研究。在香港城市大学拿到博士学位的那一刻,他才明白到为什么国外的学生抛博士帽的那一刻是如此兴奋,因为这真的是一个relief,地狱式的生活终于过去了。然而只有经过了那样的地狱式训练,你尝试过挑战自己的能力极限,你才能够真正学到一些东西,好像现在大陆的学校那样轻松及格拿学位是学不到什么的。

在他讲这番话之前,我一直都想不通自己在这4年里要干什么,因为你有无限的东西可以去干,但每一项的结果好象都没什么价值,这个问题困扰到我有点点obsessed,因为我想不到什么东西有价值去做我就不去做了。现在我终于明白了,我所比较的那些选项之所以没什么价值,是因为我把那些艰巨的、挑战我极限的选项都一早剔出了,留下一些可以保持现在安逸生活的选项,这些当然不可能有什么价值。

我要想实现自己的目标,就必须挑战自己的极限,这是不能逃避的事情。之前虽然轻轻松松生活了两年多,浪费了很多时间,但站在这里后悔更浪费时间,不如直接开始努力让自己重新进入那种充满压力的奋斗状态。

The True Obsession

真正的obsession其实在最后,做了非常有价值的事情又如何,那将是自己真正想要的吗?

高中毕业自广东奥林匹克学校的学生,有一部分会保持着原来搞竞赛或者拼高考时的精英主义和拼搏精神,感受不到也拒绝感受任何草根气息,仅需要享受的就是#1的感觉,这部分人不会坠入这个obsession中;而另外一部分人,例如我,则会因为开了眼界而坠入obsession中。

我们看到的景象是,很多同样来自重点中学的同学享受着很好的生活,物质方面不会显得贫乏,而感情方面相对有充足,身边新奇好玩的事情都可以去体验,而个人进步速度也保持在适当的水平。我们中学6年的训练类似体校不过没那么严格,但也曾经为竞赛而放弃了很多东西,当看到天赋与你相当的人能够花更少的力气享受更好的生活的时候,谁会不心动?甚至我们会自信以自己的能力能把事情做得更好。

然而我们的结果都不乐观,在花了两年时间尝试去适应这种生活后,发现这完全是在自己能力之外的事情。我个人已经直接把这种行为称作“邯郸学步”的了,既没有成功转变为新的生活方式,同时也因为脱离了原来的生活方式而导致自己失去竞争力。最终我们还是放弃了追求眼前某些人享受的美好生活,努力做回原本自己所擅长的东西,那就是无尽头的竞争与地位攀爬。

留下来唯一的疑问是,我们那6年经过的真的是one-way-path吗?是不是进入了这个岔道就必须一辈子以这种方式生活下去?我们不能选择停下来并学习如何去组织一段美好的生活吗?提这些问题是因为,顺着这条路走下去我们或许能够获得别人眼中所谓的成就,然而这不一定是我们当中每一个所想要得到的结局,肯定会有一些人希望能再来一个岔道并让他们走回去。

2006年12月7日星期四

随需写作 / On Demand Writing

Cat in dotNET从今天开始提供On Demand Writing服务(beta),如果您希望我在将来的文章讨论某个您感兴趣的主题,您可以到On Demand Writing Wish List写下您的建议,然后我会尽快对该建议给出答复。

在我看到Wish List的建议主题后,我首先会考虑我对该主题是否足够的熟悉,是否已经做了充足的研究。如果我对该主题不熟悉,并且短时间内不准备深入研究,我就不会写该主题的文章了,因为我觉得确保文章质量非常重要,我不熟悉的主体就不能乱写。在这种情况下,我有可能将该建议主题转发给其他可能感兴趣的作者,或许有一位作者能在该主题写出一篇好文章来。

如果建议主题是我熟悉的,但我还没有做充足的实验研究,那么我将答复您该主题的文章可能需要经过比较长的时间才能完成。这是因为我要确保文章中代码的可重复实验性,所以我写到文章上的每一个实验我都要先自己做一遍。

On Demand Writing服务现在是beta阶段,我暂时无法估计读者的反应会是怎样的,有些细节日后可能还会调整,但无论如何我会尽力确保文章质量。如果您现在想到什么好的主题,可以马上到On Demand Writing Wish List上写下来。如果您希望关注On Demand Writing将来发布的文章,您可以考虑订阅Cat in dotNET。

2006年12月6日星期三

深入理解 ASP.NET 与客户端缓存 (Part 2 - ASP.NET 支持)

在上一篇中,我们知道了HTTP属性与客户端缓存的关系,现在就可以着手用ASP.NET来控制这种缓存。需要注意的是,ASP.NET的Cache是用于服务器端缓存的,所以和我们正在讨论的事情完全无关,我们在这里要讨论的是如何通过HTTP属性控制客户端缓存。

页面缓存

在ASP.NET中,如果你需要添加HTTP属性,可以使用HttpResponse.AppendHeader方法,例如在Page的代码中直接执行Response.AppendHeader。HttpResponse.AddHeader方法是与之等效的,不过仅用于与ASP代码兼容,所以我的建议你最好不要使用。通过AppendHeader方法,你可以将上述Last-Modified属性和ETag属性写入返回中。

接着我们考虑如何从请求中读上述属性然后判断如何返回。我们可以使用HttpRequest.ServerVariables读取请求中的属性,然后和当前的值比较,如果比较结果表明内容无变化,我们就可以设置HttpResponse.StatusCode为304,然后返回空内容;如果比较结果表明内容变化了,那就还是按一般的方式完成整个返回。

这很麻烦,对吧?所以ASP.NET内置了HttpCachePolicy类,让我们可以直接控制有关属性,我们可以通过HttpResponse.Cache访问此类的实例,而如果在Page中我们可以直接通过Reponse.Cache访问它。这个类的使用方式在MSDN中有详细的描述,所以我就不再解释了。由于它的实现也依靠上述HTTP属性,所以使用AppendHeader控制上述属性时,就会破坏掉HttpCachePolicy中的设置(如果你设置了的话)。因此直接使用AppendHeader与通过HttpCachePolicy间接控制这两个方法中,同一时间最好仅用其中的一个,如果你需要灵活性就使用前者,如果你需要简单设置就是用后者。

资源缓存

ASP.NET内置了Cache和HttpCachePolicy,这让Page的缓存已经足够方便,所以让我们来看一看非Page该怎么缓存。事实上资源文件(例如js和css)的最大可能请求数量比Page要多得多,因为一个Page通常链接几个资源文件。

编译嵌入资源

我们先来看看编译控件是如何缓存资源的。系统自带的很多控件都是带有资源的,因为他们需要这些小图片、脚本或样式来确保它们的正常运行,这些资源编译时选择为嵌入到dll中,之后无论控件发布到哪都会附带有这些资源。这些嵌入到dll中的资源以特定的形式引用,在控件呈现为HTML代码时就成了WebResource.axd开头链接,例如:
<script
  src="/WebResource.axd?d=7wVzVzBOs3_HEjhM5umRSQ2&amp;t=632962899860156250"
  type="text/javascript">
</script>
WebResource.axd注册为由AssemblyResourceLoader处理,这个IHttpHandler专门负责从dll中将资源文件提取出来,然后返回给客户端。

留意WebResource.axd后面的两个参数,d是资源的标示,它表明了当前请求的是哪个资源;t是该dll最后编译的时间戳,如果dll重新编译了t就会跟着改变,这就让浏览器知道这是一个新的URL,不应该再使用原来的缓存。

需要强调的是,这并非是一个具有兼容性的做法,它只能确保资源更新时缓存过期,但不能确保没更新的资源成功缓存。根据RFC2616,浏览器操作分为安全与不安全两类,GET和HEAD应该是安全的,因为除了获取信息它们不对外界造成任何影响;POST、PUT以及DELETE是不安全的,因为它们对外界造成影响,所以你刷新POST后的页面时浏览器会提示你是否确认再次提交数据。RFC2616中提到,对于安全操作除非服务器端显式声明过期,否则客户端有权直接取缓存来显示,因为无论客户端是从服务期端取还是从缓存取都应该是不对外界造成任何影响的,然而有一种情况除外——就是当URL中存在QueryString时。

当URL中存在QueryString时,这个请求被认为是可能对外界造成影响的,所以当客户端进行这个请求时必须通过服务器端完成,也就是不允许使用缓存。RFC2616如是说了,但并非每一个浏览器都如此做了。IE和Firefox违反RFC2616对有QueryString的URL进行缓存,而Opera和Safari则遵守此规矩每次重新获取内容。也就是说,ASP.NET的这种资源地址在Opera和Safari中是决不会被缓存的,例如你的ASP.NET应用在MasterPage使用了ASP.NET AJAX的ScriptManager,那么打开每个页面时有关的脚本文件都要从新下载。

非编译嵌入资源

如果我们当前在写一个ASP.NET网站,有些资源是直接以文件形式存在的,不是编译嵌入到dll中的,那么我们就没办法享受上述系统提供的便利了,但我们可以自己实现类似的机制,并避免上述某些浏览器不缓存资源的问题。详细的实现方式将在本系列文章的下一篇中讨论,如果你不想错过其中的精彩内容,请订阅Cat in dotNET。

深入理解 ASP.NET 与客户端缓存 (Part 1 - HTTP 协议)

前言

这个系列的文章要讨论的是如何通过ASP.NET服务器端技术来优化客户端缓存策略,而且让这种策略变得可配置和可扩展。我们要了解的知识从HTTP协议中相关属性对客户端缓存的影响,到ASP.NET如何控制这些属性来实现我们的缓存策略。

HTTP基础知识

由于讨论涉及到客户端缓存,所以还是先要简单介绍一下HTTP协议是如何控制客户端缓存的,这里涉及到HTTP头的Last-Modified、ETag等属性。

Last-Modified

  • 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
    Last-Modified: Fri, 12 May 2006 18:53:33 GMT
  • 客户端第二次请求此URL时,会在头部加入一个属性,询问该时间之后文件是否有被修改过:
    If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
  • 如果服务器端的文件没有被修改过,则返回状态是304,内容为空,这样就节省了传输数据量。如果服务器端的文件被修改过,则返回和第一次请求时类似。

ETag

  • 和Last-Modified类似,由于RFC2616(也就是HTTP/1.1)中没有说明ETag该是什么格式的,只要确保用双引号括起来就行了,所以你可以用文件的hash,甚至是直接用Last-Modified,以下是服务器端返回的格式:
    ETag: "50b1c1d4f775c61:df3"
  • 客户端的查询更新格式是这样的:
    If-None-Match: W/"50b1c1d4f775c61:df3"
  • 如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。

Expires

这个属性就如我们在ASP中使用HttpResponse.ExpiresAbsolute一样直接,声明某某时刻过期之后浏览器就应该重新请求该URL,使用格式为:
Expires: Sun, 10 Feb 2002 16:00:00 GMT
注意HttpResponse.ExpiresAbsolute在ASP.NET中是不建议使用的,现在我们应该使用的是HttpResponse.Cache.SetExpires。

Pragma

通常我们用到的值就是no-cache,这和在Cache-Control中使用no-cache值是一样的,Cache-Control在下面讲。Pragma的使用格式如下:
Pragma: no-cache

Cache-Control

这是一个集合型属性,它里面能够包含很多子属性,并且允许用户扩展新的子属性。常见的子属性包括:

  • max-age - 以秒为单位的超时,覆盖Expires属性。
  • public - 允许保存在共享缓存中。
  • private - 只允许保存在私有缓存中。
  • no-cache - 不允许缓存。
  • no-store - 不允许缓存在持久介质中。
  • no-transform - 不允许转换存储系统。

实际应用

了解这些HTTP属性,是为了让我们在ASP.NET中实际应用它们,这将在本系列文章的下一篇中讨论,如果你不想错过其中的精彩内容,请订阅Cat in dotNET。

2006年12月3日星期日

AJAX 在中文社区的“集体信仰动摇”?!

最近好像在中文社区又多了关于AJAX的讨论,很多都是说AJAX仅仅是过渡技术,又或者说AJAX现在有些滥用了,反正就好像一场狂热下来大家又成了怀疑论者了。

Microsoft Expression

看起来最震撼的事情应该是Microsoft Expression系列被越来越多人知道,很多人都在想WPF在如此强大的设计器支持下能否干掉AJAX。首先说一下Microsoft Expression是什么,这是一个和以前的Macromedia Studio非常类似的工具集,其战略目标应该也非常类似。

以前Studio的成功在于3大产品为设计人员组成流水线,Fireworks设计的图可以直接切片放入DreamWeaver使用,又会者提供给Flash作为素材,而Flash设计完后同样可以直接插入DreamWeaver设计的网页中。现在的Expression则分为Graphic Designer、Web Designer、Interactive Designer这3大产品,Graphic Designer设计的图可以直接提供给Web Designer或Interactive Designer使用,Web Designer用于设计符合Web Standards的页面,而Interactive Designer用于WPF的交互界面,这两者的设计结果最终都放入Visual Studio中添加后台代码。

暂时看来,Web Designer和Interactive Designer是平行产品,也就是Web和WPF并不存在哪个更优的说法。Web Designer已经经过Beta1了,估计正在进入RTM阶段,所以应该会最早发布。Expression的出现对于程序员来说可以说是好事情,因为在工具上就已经给程序员和美工划分了明确的艰险,这样就可以避免程序员和美工互相推卸责任,界面上的互动效果说不清应该由谁来负责。同时Web Designer是用于设计符合Web Standards的页面的,这就要求美工不能仅仅懂绘图和切片而不懂XHTML+CSS。

Web Designer的出现,应该是美工的噩梦而不是程序员的噩梦,因为终于有一个理由要强迫美工去学好Web Standards有关的技术了。以往拖着美工去看Macromedia的首页(现在是Adobe的首页),告诉他们这是符合Web Standards的门户页设计的顶峰之作,他们会不信。跟他们说,这并排的3个竖栏都是用嵌套<ul />方式排的,所有圆角效果、阴影效果都是CSS而不是切片,他们会说用老的方式排出来同样的效果也没什么不好,除了在移动设备上不好看。现在塞一个Web Designer给他们,界面上优先显示的就是CSS设计功能,让他们设计一个Adobe那样的首页来,他们自然会明白技术差距之远。

至于Interactive Designer和WPF对Web的直接威胁,暂时还是不大的,因为WPF/E才刚刚开始CTP,其适用范围还很有限,比AJAX的支持范围要窄多了。现在AJAX在主流的PC浏览器上都已经实现,并且Opera和Nokia也正在实现AJAX在移动设备上的支持。WPF/E的真正对手是Flash,Macromedia鼓吹了那么多年的RIA都没多少人响应,Flex也不好卖,再杀进来一个WPF/E逼到Flex 2要贱卖了。但就算对Flash来说,WPF/E其实只算得上一个未来的对手,因为它现在仍显得过于“贵族化”——只有花得起钱享受XP/Vista/MacOSX级别界面的用户才有资格享受WPF/E,相对来说Flash则已经算是个“草根明星”了。

AJAX滥用

说到滥用,我们必须承认我们确实滥用了,而且问题还很严重。说到底这是因为我们在为技术而技术,很多场合程序员说了算,而国内程序员的界面设计能力还有待提高。

首先错误来自大家对AJAX使用上的本末倒置,我们看到AJAX通过减少刷新来达到改善用户体验的效果,所以我们开始追求无刷新,而最终变成了技术服务于无刷新,而功能服务于技术,这正所谓之本末倒置。而实际上,应该是来自界面上的需求推动了AJAX的发展,只有当界面需要避免刷新时才使用AJAX。

在这方面,我也不是专家,所以只能建议大家多去看看《About Face 2.0》之类的界面设计书籍,先学习如何设计界面,再学习如何在界面上使用AJAX。简单来说,不刷新是为了不打断用户的思维,让用户连贯性的完成一系列的操作,不需要每次都等待白屏过去然后重新滚动到他操作的位置。然而这是提高用户的好体验的一方面,同时我们还需要降低用户的坏体验。最坏的情况是用户在网站上受了挫折(frustrated)而离开,例如导航无法让他去到正确的位置或者让他获取到所需的信息,又例如用户更改输入提交了几次但每次都有新的提示告诉用户不符合某某输入规则。

AJAX给了机会我们更好的避免这些问题,例如通过AutoComplete获得更佳的搜索导航效果,或者异步的Validation尽早通知用户输入所需要符合的规则及如何更改去符合这些规则。然而除此之外还有很多其他更好的设计。在我们忙碌着做一个AJAX式的弹出日历式日期选择控件,国外的Calendar服务则已经提供了更好的输入方式——它们能够直接识别"Tomorrow 7:00pm dinner with Kathy at home"或"2nd anniversary on Oct 5th."这样的纯文本输入,然后自动从中提取出事件的时间、地点、人物、主题等信息。

现在我们将AJAX的底层技术拿出来作学院式研究没什么不好,但如果你要做的是一个面向最终用户的产品,那在设计时就应该先将AJAX放一边,优先考虑的是用户角色以及他们的需求,如果他们的需求包括到AJAX就把AJAX放上日程,否则就不要管AJAX。国外很多网站的AJAX使用都非常恰当,例如BaseCamp,用户需要连续执行的操作(例如添加多个todo或者将多个todo标记为完成)就提供AJAX支持,这样用户的思路不会被打断,但是无上下文的可打断的操作则保留整页提交的方式。

最后,如果你希望继续关注我的文章,欢迎订阅Cat in Chinese或订阅Cat in dotNET。

2006年12月2日星期六

装上了 Opera Mini 3.0

一直想尝试mobile blogging,可惜总找不到适合的平台和发送方式,直到装上了Opera Mini 3.0。

Opera Mini 3.0的启动页有一个发送照片到blog,看起来挺好玩,于是我就去Opera Community去注册了一个帐号,然后尝试拍照发送上去。在OM3里面,已经内置了拍照功能,不过限制了分辨率为160*120,拍照后就自动上传到你的blog(当然是指你在Opera Community的那个blog啦),接着提示你可以添加标题和内容,然后点一下发布就搞定了。

现在有聊没聊都喜欢周围乱拍,然后写一点点文字就发上去,哈哈。大家来看看我的移动博客,名字叫做Cat in Mobile(feed)哦,里面会keep住发布一些随手拍随手写的东西。

P.S.原来Opera Community的blog和album支持MMS发布,只需要将MMS发送到指定的email地址就行了,我开头也怀疑cmwap能否成功发过去,结果发现真的能发,那就是说非Opera Mini用户也能享受Opera Community对mobile blogging的支持咯。

2006年11月23日星期四

从 ASP 到 ASP.NET (Part 3 - 后记)

首先要说明,题目原本是《从熟练的ASP程序员到熟练的ASP.NET程序员》,不过我觉得太长了所以删减了。这篇是后记,不再会提及任何技术细节,需要说的只是如何到达“熟练”甚至是“精通”的境界。

使用造就熟练

在ASP.NET 2.0发布一年后才来发表这个系列的文章,距离ASP.NET 1.0发布已经有4年了,可能很多人都会觉得太晚了。其实还不算太晚吧,因为我们真正在讨论的是“熟练”而不是“入门”。或许标题为《从ASP到ASP.NET》的文章和书籍在2002年就已经泛滥了,但在那个年代敢说自己到得了“熟练”水平的人可能非常少。很多人都在2002年开始入门,然而真正坚持了4年下来的人,或许才觉得自己有资格称之为“熟练的ASP.NET程序员”。

实际上有经验的程序员都知道,从学习一门新技术到熟练使用都是需要经过3~5年的,没有捷径可走,无论你是多么的高手,或者这门技术声称多么的简单而且容易上手,你都无法用几个月的时间到达熟练使用的水平。所以,如果想尽快成为一名熟练的ASP.NET程序员,就要多使用ASP.NET,并且避免以使用ASP的方式来使用ASP.NET,这样才能摸索出一套真正适用于自己的ASP.NET使用方法来。

需求推动技术

很多人通过看书跨入了ASP.NET的门槛,然而却不知道下一步该怎么做。各种“高级”技术让入门者眼花缭乱,如果拿一个PetShop或者CommunityServer来解剖的话会发现有太多的东西值得深入了解了,而这些都是无底洞,然而如果都是浅尝则止的话又好像学不到什么。

这时候你需要记住一个原则,是需求推动技术,而不是反过来。这个原则在商业上适用,在学业上也适用。我们来看一个很具体的例子,就是在中国读计算机系的大多数本科生是怎么应付学业内的各种需要编写程序的作业的。作为学生,学习过面向对象程序设计和软件工程这两门课程是一回事,然而怎么做作业却是另外一回事。在做作业时,因为复用的需求很低,所以对象级的复用是几乎不需要的,顶多就是用到函数级的复用,如果一个函数能够多次复用,他们已经欢呼函数级复用的伟大。至于面向对象程序设计和软件工程,那是用来考试的知识,考完也就忘了。

你当然不希望你在学习ASP.NET途中积累的知识好像上面说的那样很快就遗忘了,所以一定要基于需求而去学习,而不是别人说什么有用就把什么拿起来生吞活剥一番。所以,如果你入门之后不知道需要接着学什么,那么先别急着学,而是尝试用现有的知识去做点什么,凭你的个人爱好去发挥,直到你发现你现在的知识无法应付了,你也就知道下一步该学什么了。现实中的每一项技术,都是基于之前的技术无法满足某个需求而发明的,如果你想忽略这个历史过程而直接把历史上各阶段出现的技术都学懂,那么你将无法把知识记牢。只有当你重复这一历史过程的时候,你才能够真正的掌握到你所需要学习的知识,同时因为这些技术已经被发明了,你需要做的仅仅是拿资料来看,所以学习的效率其实并不低。

总结

正如每一个人类胚胎的发育都重新经历了人类进化的过程一样,成为熟练的ASP.NET程序员也必须重复部分前人所走过的路。为什么ASP不够好而需要发明ASP.NET,为什么要为ASP.NET发明那么多分枝技术,这些都是需要了解的,如果你有兴趣和我一起了解这些知识,欢迎订阅Cat in dotNET。

2006年11月20日星期一

Blog Refactoring (Volume 2)

我的blog refactoring差不多完成了,现在持续更新中的blog主要有3个。

Cat in Chinese

原本Cat's Life已经转移地址,并重命名为Cat in Chinese(feed)。如果您以前订阅Cat's Life的feed,那么请更新订阅地址为新的Cat in Chinese的feed。

在这个blog,我会继续按照原来的风格写,发一些古怪的甚至完全不值得别人注意的想法。有些可能是非常短篇的,想到什么自己觉得有价值的东西就马上写下来;有些可能比较长,和现在的深入理解系列文章一样,是写很久很久才release一篇出来的。

和以前一样,这个blog的feed是聚合了我的del.icio.us,如果您关注我的收藏的话请订阅此feed。

Cat in English

Cat in English(feed)已经开张啦,我会开始在里面发一些英文文章,开头可能的一些文章比较无聊,因为我想不到有什么一定要用英文发的,但以后可能逐步会多发一些技术文章。其实这个英语blog的主题还没定下来,要看之后会有些什么读者,以及他们的口味如何吧,可能英文读者的口味和中文读者相去甚远,那我就不能用中文的风格来写作了。

此blog将于近期开通email订阅,如果需要email订阅的话请关注首页的更新。另外现在有一个feed是将Cat in Chinese和Cat in English聚合到一起的,那就是托管在xFruits的Cat's Collection。

Cat in dotNET

Cat in dotNET(feed)本来是我用于专门存档.NET类文章的blog,在Cat in Chinese中所有关于.NET技术的文章都会在此处存档(纯幻想型的除外)。从这个星期起,这个blog将不仅仅是存档了,我会为它引入两个全新的栏目。

在我阅读了Dflying和Jeffrey关于针对读者市场的讨论之后,我决定多发一些针对入门者的文章。这些文章都会是短篇,读者读起来轻松,确保不会让读者在feed reader中滞留着一些长篇而又unread的文章。这些文章会归到两个新增栏目中,每个栏目每个星期会发一篇,栏目简介如下:

  • Most Practical - 最佳实践,这会是一个类似FAQ的栏目,我会帖一个很常见的问题,然后给出一个简单可行的解决方案。在这里我注重的是实在可行的解决方案,而不是其中的原理和奥秘,不过实现方式则会倾向于我个人喜欢的风格,也就是能划分为局部的逻辑尽量封装为控件,避免代码散落在Page中。
  • Random Clippings - 随机剪报,有点类似Dflying的英文技术文章推荐系列,不过我一次只会推荐两篇文章。如果用MSDN WebCast的技术等级来看的话,第一篇文章的技术等级会是100或200的,如果您有一定的基础则文章中所描述的技术肯定会是您已通过中文文章了解过的,通过阅读英文文章可以慢慢锻炼自己的英文阅读能力,这和学校里的阅读练习差不多,只不过练习阅读的文章是专业技术类的。第二篇文章的技术等级会是300或400的,我会选择推荐一些有趣的技术文章,或许看完了您不觉得从其中学到了什么,但可以开阔视野。

Most Practical将于每周二或周三发表,大家工作途中遇到什么问题可以过来碰碰运气看看有没有好的解决方案。Random Clippings将于每周五或周六发表,这是考虑到大家可能要到周末放假了才有时间来读读英文技术文章,或者练习一下英文技术文章的阅读能力。

最后,要感谢Dflying和Jeffrey在聊天中给我的一些建议,以及在他们的文章中给我带来的灵感。还要感谢所有读者的关注,如果您希望长期关注这两个新的栏目,那么请订阅Cat in dotNET。

2006年11月18日星期六

深入理解 ASP.NET 动态控件 (Part 3 - 页面生命周期)

前言

在上一篇文章中,承诺了这一篇开始讲解释器的,不过看来要按着一个大框架来写文章不那么容易,没仔细推研究过就写出来的内容似乎很应付式。所以我决定恢复我原来的写作习惯,我觉得哪部分的内容已经成熟了,那就把它release出来,没成熟的就继续留在我的draft里面。这次要讲的是页面生命周期,动态控件对此关注的当然是动态与静态控件在生命周期中加载的差别。

一般加载

虽然一般加载过程已经被说过很多次了,但我在这里还要说,希望能把每一个阶段的特点描绘出来,让大家加深印象。

一般加载分为以下几个主要阶段(粗体标出的阶段的特殊性后面解释):

  1. Init - 初始化,是否为动态控件就以此为分界,Init之前加入到控件树的控件其处理过程就和ASPX中静态声明的一致,因为静态控件也就是在Init前加入的。
  2. LoadViewState - 加载ViewState。
  3. ProcessPostData - 处理PostData,倒不如说是加载PostData,因为此阶段控件多数仅加载PostData,顺便判断PostData是否有改变,别的处理不在此阶段作。
  4. Load - 加载,让ASP.NET程序员尽情发挥创意的地方,包括如何糟蹋ASP.NET这个框架。
  5. ProcessPostData Second Try - 第二次尝试处理PostData,和第一次所做的一样,不过第一次执行时已在控件树上的控件不会受到第二次打扰。
  6. Raise ChangedEvents - 冒泡Changed类事件,这里指的是由于PostData变更而引起的Changed类事件。
  7. Raise PostBackEvent - 冒泡PostBack类事件,除了Changed类以外的所有事件都在这里引发。
  8. PreRender - 预呈现,这名字不怎么好记,改为“末日审判”或许会好一些,因为作为上帝的程序员在这里判决每一个变量的最终值。
  9. SaveViewState - 保存ViewState,判决执行的阶段,变量最终值在此保存,判入地狱的变量无权进入ViewState这个天堂并从此消失。
  10. Render - 呈现,可能是生命周期中最无法解耦的一个阶段。
  11. Unload - 卸载,有加载自然有卸载,但其实没有多少人知道它的存在。

这11个主要阶段可以简单分为3大步骤:

  1. 加载数据:LoadViewState, ProcessPostData, ProcessPostData Second Try
  2. 处理数据:Raise ChangedEvents, Raise PostBackEvent
  3. 保存数据:SaveViewState

这3大步骤构成了ASP.NET页面处理体系,其中第2步的处理数据是基于事件冒泡的形式,也正是ASP.NET比ASP先进的地方。ASP.NET把是否处理以及如何处理分离开来了:控件内部的逻辑决定是否处理,如果要处理就触发事件;控件外部的逻辑决定如何处理,仅当事件触发时才会被执行。

追赶加载

与其说动态加载,不如说追赶加载,因为动态加载的过程包含追赶加载,这是和静态加载的主要区别。每一个控件内部都保存着它当前的加载进度,也就是它到达了上述的哪一个阶段,当我们执行Control.Controls.Add方法来将一个控件添加到另一个控件中时,父控件就会检查子控件的加载进度,如果子控件的加载进度比自己的慢了,就会要求子控件追赶上来,所以叫做追赶加载。

在上面11个主要阶段中,用粗体标出的阶段就是追赶加载时必须补回执行的阶段,而其他则是追赶加载时错过了就忽略的阶段。正是由于有一些阶段不被包括在追赶加载中,所以如果我们的控件要使用到这些阶段,就必须保证在这些阶段之前加载。也就是说,如果控件要处理PostData,包括加载PostData及根据PostData触发事件,则必须赶上ProcessPostData Second Try,这意味着它必须在Load的时候加载。否则一旦错过ProcessPostData Second Try,一个控件将在PostBack中表现得和非PostBack时一样,完全不知道有PostData这回事。

结论

其实结论已经说了,在此再强调一遍:如果你的控件要能成功触发事件,必须在Load阶段加载,如果在Load阶段之后(例如另一个控件的事件中)加载,那么此控件的事件无法正常触发。

问题与实验

先解答上次的问题与实验:

  1. this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));是正确的做法。ASCX与ASPX的编译方式是类似的,MyUserControl类只是一个中间过程,仅包含C#代码的编译结果,不包含ASCX的逻辑。而使用Page.LoadControl方法获得的类才是一个UserControl的最终编译结果,包含了ASCX的逻辑。
  2. 这个实验我自己也没去做过,有兴趣的朋友可以自己做一下看看结果如何。

然后是本次的问题与实验。

  1. 如果要求页面上有一个Button,点击后出现一个CheckBox,要这个CheckBox能够正常触发CheckChanged事件,应该怎么做?注意,不要使用隐藏控件的方法,因为隐藏控件所生成的HTML和ViewState是要占用空间的,我希望这个CheckBox在Button被点击之后才在页面生命周期里出现。
  2. 为什么ICallbackHandler在Beta2中仅有RaiseCallbackEvent一个事件,而到了正式版中被拆分为RaiseCallbackEvent和GetCallbackResult两个事件?(提示:这和页面生命周期的阶段划分有关)

如果想讨论或者公布答案的,可以直接在文章评论中进行。如果想知道详细的分析,敬请期待本系列文章的下一篇,通过订阅Cat in dotNET将确保你不会错过本系列的后继文章。

2006年11月16日星期四

从 ASP 到 ASP.NET (Part 2 - 忘记什么)

前言

上一边讲到ASP程序员迁移到ASP.NET时,应该顺应Web开发的潮流学习Web Standards,应该为了更好的理解ASP.NET而学习OOP,然而学习这些知识之后并不代表你就是一个合格的ASP.NET程序员了,因为你仍被ASP的思想所束缚,接下来我要告诉你如何解决这些束缚。

比喻

首先我们要看看ASP.NET是如何“确保”你被继续禁锢在ASP的思想内的。假如我把ASP比作洗衣板,而把ASP.NET比作洗衣机,那么ASP.NET这台洗衣机就实在有点太过“多功能”了,因为你可以选择:

  • 把衣服扔进去,然后把水倒进去,接着伸手进去按照老方式把衣服洗干净;
  • 又或者使用洗衣机的自动进水功能取代上述倒水步骤;
  • 还可以从洗衣机内侧把它独有的增强型洗衣板拉出来,以获得比老洗衣板更洁净的效果;
  • 甚至进行手洗机洗混合洗,总有一种混合洗方式能同时满足你洗衣服的欲望与对洁净衣服的需求。

这台洗衣机有一个严重问题,就是想尽办法诱惑你把手伸进去,而你需要做的仅仅是把洗衣机盖上然后管好你自己的双手。

坚持原则

“干净衣服与人手勿进”——这是你应该贴在洗衣机上的标签。

在这里我必须假设你已经把洗衣机的使用手册翻烂了,其实我的意思是你已经将上一篇中说明一个ASP.NET程序员必须学习的东西都学好了。这时候你已经了解了ASP.NET的运行方式,那就必须避免不符合这种运行方式的操作。例如一个ASP.NET处理程序是立体的,那么你就要拒绝去执行那些平板的操作。

一开始你肯定会非常不适应,例如为什么洗衣机洗的衣服不如手洗的干净,又或者为什么要我记着复杂的洗衣编程设定。然而这问题不是出在ASP.NET身上,而是出自于你对它的了解还不够深入,所以你不知道如何让它完美的视线你的目标,同时少费功夫。这个过渡阶段最需要的是坚持,或许一开始你会发现ASP.NET能实现的功能真的和你的目标有一定差距,但只要你不是急功近利的去完成目标,而是仔细摸索ASP.NET其中的奥秘,那么总有一天你会发现无论多古怪的需求你总能提供一个简洁的ASP.NET解决方案。

假装的ASP.NET程序员

这部分内容本来应该属于trouble-shooting的,你有兴趣的话或许可以看看自己是否属于某一类trouble:

  • 坚决不使用WebControl,仅在必要的情况下使用HtmlControl;
  • 在ASP.NET 2.0中坚决不使用DataSource控件,使用手工DataBind的方法;
  • 使用Response.Write输出脚本或调试信息,而不使用ClientScript和Trace;
  • 直接从Request.Form读取数据,而不在LoadPostData时从NameValueCollection中读取。

如果你命中上面任何一类trouble,其原因都是没有好好坚持ASP.NET的使用原则,而尝试用ASP的方式解决问题。解决途径就是拒绝继续使用ASP的方式,然后深入了解ASP.NET的内在运行机制,从而选择一个正确的ASP.NET式解决方案。

长期关注

最后,如果你希望更多的关注ASP.NET运行机制方面的资料更新,你可以直接订阅Cat in dotNET ,这样你将不会错过任何一篇的更新。

2006年11月15日星期三

10 年后我还会用 .NET 吗?

现在看着那些“学院派的老人家”在用MFC,感觉总是怪怪的。明明非常简单的一个东西,用WinForm完全能取代MFC的写法,而且不会由于采用WinForm而显得隐藏了什么底层(要讲解底层的话会用Win32API写),为什么偏偏要用MFC呢?

或许这就是惯性的问题吧,当你对一样东西有了深入的了解后,就好比作了长期的投资,变得很难撤出来。之后即使有一样更好的东西放在你眼前,要放弃原本的投资迁移到新投资项目上也不容易。

其实别人用MFC还是用WinForm与我无关,我关心的是我10年后会怎样,我会变得一样保守吗?会一样抱着.NET不放不乐意接受新事物吗?我觉得这样的事情最好不要发生在我身上,否则我会觉得生活十分没聊的。

平时写写ASP.NET的深入理解系列,属于自己真正花时间去了解一样东西了然后把其中的乐趣分享给大家,但如果真的把自己绑死在ASP.NET上就不好了。现在我忙于手头上的学习任务和一个ASP.NET项目,完结了这个ASP.NET项目后我会去学学Ruby on Rails,看看其中有什么值得学习和思考的地方,或者以后再用ASP.NET的时候就可以把那些优秀的思维方式搬过来用。

2006年11月11日星期六

在 catch 块内进行 throw 的多种方式

参考了throw; vs. throw ex; Here's the difference!我才知道在catch块内的throw;和throw ex;是有区别的,以前都不知道可以直接写throw;呢。

这两者的主要不同在于输出的stack trace上:

  • 如果你直接使用throw;,那么stack trace就和根本不存在这个catch块的时候一样,显示错误根源是真正抛出异常的地方。
  • 如果你使用throw ex;,那么stack trace就认为你catch到的异常已经被处理了,只不过处理过程中又抛出新的异常,这时候stack trace就把throw ex;当作错误根源了。

 显然,后者会让stack trace的信息量少了,增加了追踪错误来源的难度,所以最好不要这样做。如果你要进行catch,然后你又要让异常继续冒泡,除了throw ex;以外你还有另外一个选择:
try
{
  MethodThatThrowsException();
}
catch (Exception ex)
{
  throw new Exception("oops!", ex);
}

这时候,你就将原本的异常封装进了新抛出的异常中,而stack trace会自动认为内部异常是导致当前异常的原因,也就会把内部异常的stack trace也递归显示出来。

2006年11月10日星期五

ASP.NET - 解决一个大难题的同时引入另一个更大的难题

前言

ASP.NET的优点我说过很多次了,也就是各个控件独立负责自己内部的逻辑,这是一个好事情,因为它解决了原本ASP处理逻辑耦合度高的问题。然而这是需要代价的,那就是引入ASP.NET页面生命周期,随着控件的多层嵌套,应用的复杂度增加,我们再次陷入泥潭!

问题

其实这个文章题目我两个月前就写下了,可是一直没想写完它,直到今天我在这个泥潭中泡了几个小时,于是决定先从泥潭中跳出来把文章写完,再跳进去继续解决问题。问题是这样的:

  1. 使用MS AJAX 1.0 Beta2 + 2.0 CTP新建一个项目,同时在Bin中放上Beta2的AjaxControlToolkit.dll。
  2. 扔上一个Accordion,放置几个AccordionPane,设置一下CssClass。
  3. 在Page_Load中使用Page.LoadControl加载一个UserControl,然后添加到页面上。
  4. 接着发现UserControl内的控件无法正常触发事件,陷入泥潭中……

首先要说明,如果仅仅做第3步那个UserControl肯定正常运作,那意味着问题出在ScriptManager或Accordion中出现了问题。

正文

想知道到底是什么出问题了吗?先听我说说这个ASP.NET页面生命周期的问题吧。

由于生命周期按阶段划分,任务在不同阶段按部就班完成,所以我们的每一个操作都是阶段相关的,有些操作仅能在特定的阶段操作,有些操作在不同阶段执行会导致不同的结果。当然,MS希望尽量消除这些阶段间的差异,例如让一个操作在尽可能多的阶段中都能执行,并且尽可能减少在不同阶段中操作引发的不同结果。然而这不可能完全做到,例如我们都知道ViewState读写限制为仅能在某些阶段进行,于是依赖于ViewState的控件属性也就因此受到同样的限制。

控件属性读写受阶段限制,这很好接受,对吧?因为这仅仅是一层依赖关系。顺着依赖关系推广出去,情况会变得越来越复杂,限制的原因埋藏得越来越底层,接着我们发现复杂性这一问题在ASP.NET这种结构良好的体系中出现了,而消灭这种复杂性的银弹还没被发明。

作为控件或组件的开发人员,我们当然有义务消除阶段差异,让下游的开发人员面对更低的复杂性,而且我们也确实尽力去做了。控件的每一层封装,都包含着这种努力,并向上承诺尽可能低的阶段差异。然而为了让控件看起来简单易用,我们不可能将这些差异完整地记录在文档之中,我们尝试去隐瞒细节,控件被层层封装时我们都这样做。底层文档没告诉我的差异,我当然也没必要写到这一层的文档上去;底层文档提及了的差异,我尽力弥补了,即使弥补得不太好,也不写到这一层的文档上去。于是文档就好像神话传说一样随着世代相传而改变,最终没有人知道这个控件依赖于某些底层的阶段差异。

做过控件开发的人都知道,有时候我们必须根据实际情况采用不同的方式构建看起来一样的控件。例如最简单的数据控件都会存在是否PostBack的构建差异,如果是非PostBack,则需要在DataBind时构建并将数据保存到ViewState,如果是PostBack则根据ViewState直接构建,如果PostBack后又遇到了DataBind则需要清除原来的构建并重新根据新数据构建。再复杂一些的控件,还会分步骤构建,默认情况下为了消除使用方的阶段差异,部分构建步骤会尽可能靠前到Init时执行,而另外一部分构建步骤则尽可能推迟到PreRender时执行,中间部分则尽可能减少自己的变化以便使用方操作。然而事情不会那么简单,使用方的某些操作(通常是访问某个属性)如果依赖于某个构建步骤的完成,因此一旦这些操作出现,原本在PreRender才执行的特定构建步骤就要提前执行,当这样的操作在不同阶段进行多几次,构建步骤就已经散落在页面生命周期的各阶段。

构建步骤可能散落于页面生命周期的各阶段对于控件设计师来说是一个严峻的问题,这意味着他要保证任何一个构建步骤在任何一个阶段执行都是无差异的,当然这不可能做到,于是又要引入别的机制来减少这种差异,复杂性就此产生了,接下来随着复杂性的增加控件设计师越来越无法确保较低的阶段差异程度,这就到控件使用者遭殃了,如果控件使用者又再把控件封装,并且依然企图降低阶段差异程度,那么灾难也就发生了……

结果

我花了几个小时在泥潭中泡了几个小时,边泡边写这篇文章,问题当然已经有结果了。

如果Accordion设置了HeaderCssClass或者ContentCssClass,那就会出问题,但如果为AccordionPane都加上以上两个属性,又不会有问题了。这样的情况当然通过用Reflector查看这两个类的代码来解决,结果发现Accordion会检测每一个AccordionPane是否有设置这两个属性,如果没有就把AccordionPane的设置为和自己的一样。在AccordionPane被设置时,会调用this.EnsureChildControls(),这是一个会导致构建步骤提前执行的方法,于是控件构建的顺序就改变了,不仅仅Accordion内部的顺序改变了,整个Page的都改变了。由于控件的ID是按顺序自动分配的,包括我那个UserControl,构建顺序的改变意味着ID的改变,也就相当于整个控件树都改变了,事件当然不能正常触发。

最后的解决方案当然是为我那个UserControl指定ID。我花了那么多个小时才发现自己做了件蠢事,一早打开Trace来看控件树就应该能发觉UniqueID的变化。

总结

虽然这个问题看起来不是一个太好的例子,因为一打开Trace就应该能找到问题的来源,但实际上它却正好揭示了ASP.NET框架内部的“蝴蝶效应(Butterfly Effect)”——随着复杂度的增加,任何一个细微的改变都会导致全局上的巨大变化。在设计ASP.NET的时候,MS可能也在想着解耦,在简单的情况下这东西确实也解耦,然而在复杂的情况下却正好背道而驰,这真的是很讽刺。

2006年11月9日星期四

愿饥饿与你们同在!

上个星期二回去和Benny讨论设计方案,他指出了一个问题——你的这个设计既帮助用户共享信息,同时也提供信息过滤机制,这不很矛盾吗?相当于你一边创造信息,一边销毁信息。当时正在吃饭,于是我想到了一个解释:

人什么时候要去获取信息,这基本上和肚子饿了就回去找东西吃一样,饿的时候会饥不择食,饱的时候你送给他都不要。在这个问题上,Google站对了立场,它不塞信息给你,因为人人都有饱的时候,饱了你就无法逼他吃,在一般情况下你无法知道一个人是否饥饿,所以它不做任何推服务。Google只做拉服务,因为当你饿的时候主动找上它,证明你很需要食物,它塞信息给你,你乐意接受的程度就很高,这时候只要它做得比同类型的其他服务提供商要优秀一点,你自然会找它。甚至有些领域你是只能选择Google的,这就好像本地只有一家Chinese Food Restaurant一样,你不能不选它,除非你做好了思想准备接受更坏的结果。

所以,要做信息供应商,我们的目标要很明确——瞄准饥饿的用户,看看用户哪方面不容易吃饱的就狠攻那个方面。当然,还有另外一种方案,就是在一个未知领域通过创造需求,让过上温饱生活的人把他现在的盘中餐倒掉然后去你那里抢信息。“操纵消费者”,这不是广告公司常做的事情吗?不用管那些客户原本的需求和消费能力如何,只要能操纵他们对你的产品有需求,他们自然会努力赚钱并且努力消费。

2006年11月8日星期三

从 ASP 到 ASP.NET (Part 1 - 学习什么)

前言

首先要告诉大家,文章标题是我“恶意删改”了,原本是《从熟练的ASP程序员到熟练的ASP.NET程序员》。

从ASP迁移到ASP.NET的程序员肯定不少,我就是其中一个,然而要从熟练的ASP程序员转变为熟练的ASP.NET程序员并不容易,这不仅仅要求你学习非常多的新东西,还要求你丢弃非常多的旧东西。对于没学过ASP的人来说,或许这还容易些,因为他们本来就做好了苦学的准备,也没多少需要丢弃;对于熟练的ASP程序员来说则比较痛苦了,因为原本期望自己原来的知识都可以平滑过渡轻松用上ASP.NET,结果发现现实与期望的差距是那么的大。

在发现这个差距之后,没有人应该停下来然后倒退回去ASP的时代,知难而上把ASP.NET用到和ASP一样熟练才是我们的目标,因此才有了这个系列的文章。在系列的第一篇里,我们先来讨论ASP程序员缺了什么,什么是应该优先补上的,只有把这些知识补上了,我们才能够把自己称作ASP.NET程序员。

Web Standards / Web标准

做Web应用首先要懂做Web,现在提倡的是Web Standards,其所涉及的XHTML、CSS、JavaScript是一定要懂的。很多ASP程序员可能已经熟悉老式的表格排版方式,但这是应该被丢弃的东西。很多宣扬Web Standards的文章都给出了不少表格排版的坏处,那些我就不多说了,我要说的是不使用Web Standards对ASP.NET程序员最致命的一个坏处。由于MS也向Web Standards靠拢了,所以ASP.NET 2.0被设计为兼容Web Standards的,这时候所有的控件都被设计为语义与表现分离。如果你不遵守此分离规则去分工,那么随着你和你的美工轮流编织这张Web,最终有一天这张Web就会把你和你的美工给绑死。

要学好XHTML+CSS的设计,不仅仅需要观念上的转变,还需要开发工具上的更换。很多人无法适应Web Standards的设计观念,是因为他们还在用老工具,于是总是觉得用旧观念设计更方便效果更好。因为我相信,只有当你适应了新的工具,体验到新工具带来的便利和高效,你才会乐意接受观念上的转变。

说到开发工具,我假设熟练的ASP程序员都能够完全脱离WYSIWYG的编辑器而以纯文本方式编写XHTML和CSS,因为XHTML+CSS的开发要求你和你的美工都具有这样的能力。以往美工可以安乐地对着Photoshop,这是他们最习惯使用的工具,操作起来有精确性的同时又可视化,他们可能有一半的时间是用眼睛思考的。然而现在改用CSS就没这样的好事情了,能够好像Photoshop那样设计CSS的软件还没有诞生,修改任何一条CSS规则都会应用到所有页面上,至于每一个页面哪些元素会匹配这条CSS规则这需要美工用脑袋记着,不再是可视化。虽然改一下CSS规则然后看一下几个页面的预览这也是一种选择,然而这比Photoshop中调整参数时的即时预览要差多了,所以让美工学会在脑袋里进行预览是很重要的,这样才能写出好的CSS来。

如果要推荐一些工具的话,我会选择Visual Studio 2005 + Expression Web Beta 1,前者开发人员自己用,后者是美工用来设计或修改Web页面用的。

OOP / 面向对象程序设计

OOP可以说是ASP.NET的基础,没有OOP就没有ASP.NET控件这个概念,也就没有了ASP.NET与ASP最巨大的差别。

从最原始的CGI开始,Web应用开发者无非就在设计着这样一种逻辑——根据输入的Request生成输出的Response,大多数情况下两者都是平板的纯文本字符串,除非设计上传/下载文件。ASP引入了Request和Response对象,让处理稍微显得立体了一些,你不再需要手动分析Request文本,它能够帮你将提交上来的Form、QueryString、Cookies等参数提取出来供你使用。Session和Application对象的引入让你在不了解细节的情况下进行特定目的的存储,Server对象的引入则为你提供了很多有用的函数。

ASP遇到的最大问题是,立体的Request提供出来的数据却是平板的,整个处理过程也是平板的。那就说,在处理过程中的任何一个步骤,都可以访问任何一个Request数据项,然后把结果输出到Response中,这导致程序代码的耦合度很高。如果输出的Response有问题,你没办法明确指出处理过程中的哪一段应该对它负直接责任。

ASP.NET尝试通过引入控件的概念来解决这个问题。每一个控件都是一个独立的逻辑单元,它仅仅对自己内部的逻辑负责,并且尽可能减低对外部环境的依赖性。控件不再像普通的ASP逻辑那样它可以乱访问Request和Response,它的能力应该受到限制:

  • 一个控件仅仅应该读取它生成的HTML元素提交回来的数据,否则应该考虑通过其他控件的属性来获取,而不是从Request获取。详细说明如下:
    • IPostBackDataHandler和IPostBackEventHandler就是为控件处理自己生成的HTML有关的参数与事件而设计的。
    • 如果控件要获取的数据来自子控件,则应该通过子控件的属性获取。
    • 如果控件要获取的数据来自外部控件,则应该请求父控件或环境帮忙获取。
  • 一个控件生成的HTML应该是环境无关的,也就是无论其他控件生成怎样的HTML都不会和此控件生成的HTML冲突。详细说明如下:
    • Render用于生成本控件的HTML。
    • 如果控件要生成的HTML存在可能引起冲突的情况,则应该请求父控件或环境处理。例如最常见的生成脚本,为了避免同一段脚本多次输出就应该向ClientScriptManager注册脚本,然后让它来觉得脚本的输出。

当然,上面这些规则你喜欢怎么违反都行,没有人规定你一定要这样做的。但只有遵守了这些规定,你才算得上是一个ASP.NET程序员,否则就仅仅是一个使用着ASP.NET框架的ASP程序员。

要遵守这些规则,首先要把OOP学好,这样你才会明白为什么要遵守以及如何去遵守。因为规则是死的,而我们面对的情况可能是灵活多变的,当面对一个新的情形时应该选择如何设计呢?显然你不一定能够从上面的规则中找到一条来参考,这时候你的OOP思想及价值观就起决定性作用了。

HTTP协议

HTTP协议其实没什么好说的,一个熟练的ASP程序员必须懂的东西,而且可能从你学习ASP的那天起它就没改变过。只不过对于ASP程序员来说,这东西是透明的,因为我们直接使用Request,这和直接处理HTTP协议没太大的区别。但是到了ASP.NET,Request已经被隐藏起来了,你应该避免使用它,这时候你就需要重视HTTP协议了,否则底层通讯发生了什么你完全不知道。

总结

虽然看起来我只列了3个学习要点,但我们的目标是熟练,所以每一样你都至少用上一年半载才算学到点东西,这一点儿都不简单。

本系列的下一篇将讨论“忘记什么”,如果你明白了“学习什么”,却发现学习进度不理想,那就证明你有些包袱没有抛下了。

2006年11月5日星期日

深入理解 ASP.NET 动态控件 (Part 2 - 编译过程)

前言

要深入理解ASP.NET动态控件,首先就要深入理解整个ASP.NET对页面的处理过程,由你书写好一个ASPX文件(可能还有一个code-behind文件)到你在浏览器中看到的HTML页面,这中间到底发生了什么事。这其中的第一步就是解释ASPX文件并进行编译,也就是这篇文章要讨论的内容。

由于ASP.NET编译本身就是一个大话题,所以我决定在本系列文章把这个题目再细分成几篇文章来写。开头第一篇简单叙述编译过程中涉及的各个步骤,让大家了解ASPX中的声明性代码和C#/VB.NET代码如何合并在一起并编译成assembly。在这篇文章之后,再深入了解编译过程中的一些细节,看看一个ASPX中声明性定义的静态控件到底是如何运行起来的。

鸟瞰

开始讲编译过程了,首先大家来看两张图,这张是ASP.NET 1.x的编译流程图:

接下来这张是ASP.NET 2.0的编译流程图:

这两张图来自官方文档ASP.NET 2.0 的内部变化,大家要注意到代码嵌入(code-beside, inline)与代码隐藏(code-behind)的编译模式是不同的:代码嵌入仅进行一次编译,声明性代码与C#/VB.NET代码都一起编译到一个类里面;代码隐藏则将声明性代码与C#/VB.NET代码分开几次进行翻译/编译,这些代码之间是局部与局部(partial)的关系或是基类与派生类的关系。

着陆

我们现在着陆到图上的某一点,来看清一个编译步骤是如何执行的。

ASP.NET 1.x

图上引人关注的地方就是代码隐藏编译时存在两次的“继承自”关系。第一次继承是很好理解的,用过VS2002/2003的人都记得代码中明确声明本页面的类继承自Page类,那么第二次继承又是怎么来的呢?

先把上面的问题放一边,我们换一种思路来思考,重新想一想我们的C#/VB.NET代码有什么。如果我们在ASPX中放上了一个TextBox,那么两边的代码都会出现它的定义,ASPX代码是<asp:TextBox id="myTextBox" runat="server" />,C#代码是TextBox myTextBox = new TextBox();myTextBox.ID = "myTextBox";。然后我们在此TextBox的后面用HTML写上<div>Please write down something</div>,那么这段HTML仅在ASPX中存在定义,而不在C#代码中存在定义。

接下来我们将C#代码给编译了,然后用ASP.NET引擎运行它(确实能够如此运行,但这不是我们当前关心的事),你猜我们能够看到什么?我们应该能够看到一个TextBox。至于后面那段文字呢,聪明的你应该马上想到它没在C#代码中被定义的,所以不可能被看到。

现在我们明白到了,有一部分逻辑是仅仅在ASPX中有所定义,我们需要将它们添加到C#编译结果上。如何添加这部分的逻辑?ASP.NET选择了继承机制,从C#编译结果的那个类继承,然后在派生类中加入仅在ASPX中定义的逻辑。至于作为声明性语言的ASPX如何编译成MSIL,则属于下一篇文章讨论的内容,在这里就不解释了。

需要说明的是,这两次编译中的第一次必须手动进行的,例如在VS2002/2003中执行编译;第二次编译在运行时进行自动进行。因此改动了ASPX无需重新手动编译,而改动了C#/VB.NET代码则需要手动编译。

ASP.NET 2.0

上面我们解释ASP.NET 1.1的代码隐藏编译时也提到了其中的问题,一个TextBox控件要在两边同时声明,这明显违反了DRY(Don't Repeat Yourself)原则。ASP.NET 2.0为了解决这个问题而引入了新的机制。

所谓的新机制就是C#代码中的那个partial关键字,大家可能都习惯了它的存在,但有没有人曾经想过一个这样的Page继承类的其他partial在哪里呢?如果你在VS2005中作一次项目内搜索,就会发现这个类的其它partial是不存在的,这时候你就该去看看官方文档(例如我上面给出那个)。官方文档会告诉你,另外一个partial就是ASPX,它们会好像两个普通的partial文件那样合并编译,所以在ASP.NET 2.0中我们仅需要一次合并编译就解决了所有问题。然后我要告诉你,官方文档所说的是错误的,ASP.NET 2.0的编译还是好像ASP.NET 1.1那样,只不过根据ASPX中的控件定义生成对应C#定义的工作由IDE转交给了ASP.NET编译器,至于细节你可以去参考我之前写的两篇文章:《ASP.NET 2.0 解决了 Code-Behind 需要控件声明同步的问题》与《ASP.NET 2.0 的编译模型并非完全像 MS 说的那样》。

在ASP.NET编译器捡起了定义同步这项工作后,整个编译过程就都在它的职责范围内了,不再好像ASP.NET 1.x那样先由C#/VB.NET编译器负责隐藏代码的编译,再由ASP.NET编译器负责二次编译。既然ASP.NET编译器同时负责两次编译,那就能够省去第一次编译手工进行的麻烦,编译工作都由它在运行时负责就好了。

下一步

现在我们已经对整个编译过程有了了解,大多数编译步骤都很容易理解,无非是叫C#/VB.NET编译器出来做些本职工作,只有一个除外:仅在ASPX中声明的逻辑是如何被编译为MSIL的,因为我们将此作为下一步深入理解的目标,并在下一篇文章中讨论。

问题与实验

这里有一些简单的问题或者是小实验,通过它们可以加深大家对文章的理解,大家可以将答案直接写在文章评论中。

  1. 我在Web应用的根目录新建了一个用户控件MyUserControl.ascx,隐藏文件中定义类名称为MyUserControl,我现在需要在页面上动态加载此用户控件,请问以下哪种方法正确?为什么?(提示:ASCX的编译方式与ASPX类似)
    1. this.Page.Controls.Add(new MyUserControl());
    2. this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));
  2. 在讨论ASP.NET 1.1编译的时候,我说到可以直接运行隐藏代码编译出来的类,并且说应该能看到一个TextBox。事实上这个TextBox可能也无法看到,不过我手上没有VS2002/2003,所以没办法验证。大家有兴趣的话,可以自己去动手做一下实验看看那个TextBox到底是否会出现。在实验之前,让我先说说如何让隐藏代码编译结果直接运行:
    1. 打开MSDN,找到IHttpHandler这个条目,然后看看它的示例代码,以及如何在web.config中配置一个路径使用特定的IHttpHandler。
    2. 由于Page类本身实现了IHttpHandler,所以隐藏代码编译后的Page继承类也一定是IHttpHandler,在web.config中配置一个使用IHttpHandler的路径,并指向你要测试的隐藏代码类。
    3. 在浏览器中访问你配置的路径,你就能够看到纯隐藏代码编译后的执行结果。

2006年11月3日星期五

Microsoft Ajax Beta1 - 边学边用边补充 (Part 4 - $create)

在Beta1之前,我们可以使用Xml-Script定义对象,当然也可以用JavaScript的老方式来定义对象,不过用过后者的人肯定会觉得这种方式不太方便,因为大多数Control和Behavior都需要手动调用initialize后才能正常运作。例如CTP中的Button,不执行initialize就不会绑定DomElement的click事件,它自身的click事件也就不能被正常触发。还有beginUpdate和endUpdate的初次运行也由Xml-Script的解释器负责,在JavaScript中你就需要自己负责了。

在Beta1里面,终于有一样东西可以让你方便地定义对象,同时不需要自己负责这些琐碎的必要执行项目,那就是$create指令,它的参数列表是$create(type, properties, events, references, element)。从名称上看这几个变量都很好解释,但实际上不是那么简单,现在逐个来说:

type

你要创建的对象类型,必须是继承自Sys.Component的,例如Sys.UI.Control或者Sys.UI.Behavior。注意这里说的是继承自,也就是你不能用$create创建一个Sys.Component,这应该是一个bug。

properties

你要为新建对象所提供的初始属性。对于属性名称,设置时会先确定其get方法和set方法是否存在,如果都存在就直接用set方法赋值。如果不是呢?这就要分支判断了,我不想用文字解释代码,所以我用例子解释可能有哪些分支:

  • get方法不存在,要设置的是公开的成员变量,那么{Property:"Value"}的赋值行为就是instance["Property"]="Value"。
  • get方法不存在,则可能是公开子对象深层次赋值。例如处理{Sub:{Property:"Value"}}的赋值行为就是instance.Sub.set_Property("Value")。如果是更多层的公开子对象,程序也能为你递归处理。
  • get方法存在而set方法不存在,传入的是Array,则通过get方法获取原本的Array,然后进行Array覆盖。
  • get方法存在而set方法不存在,则也可能是深层次赋值。同样是{Sub:{Property:"Value"}},这种情况赋值行为就是instance.get_Sub().set_Property("Value")。更深层次的递归调用赋值也是当然的。

这些例子的思路很不严谨,仅仅是为了让大家明白到没有get方法或没有set方法时可能会是什么情况。要严谨的就自己去看Sys$Component$_setProperties函数,参照着上述分析很容易看明白。

events

你要为新建对象提供的初始化事件。这个解释起来就简单多了,$create中仅仅检查事件名称对应的add方法是否存在,存在就添加,不存在或者要添加的不是function就抛出异常。

references

这个参数暂时没有任何资料有提到过,但是从代码看得出是用于设置对其他Component的引用,这些引用实质上也是一种属性。和properties不同,reference传入的属性值不是真正要赋值的实例,而是Component的名称,然后通过$find找到这个Component的实例并用set方法赋值。$find找不到实例或者set方法不存在都会导致抛出异常。如果大家还记得Xml-Script的写法,那就会想到Component之间交叉引用是很正常的,如果你在Application的initialize阶段进行$create,那么$create将享受和Xml-Script等同的待遇,也就是允许交叉引用。你可以按你喜欢的顺序用$create声明Component,即使先声明的在reference中提到后声明的名称没问题,因为Application的initialize阶段结束时这些reference才真正赋值。但是过了这个阶段就没有这种待遇了,reference中提到的名称必须都是已存在的Component,否则将引发异常。

element

你要新建的对象关联的DomElement。如果你创建的是Sys.UI.Control或Sys.UI.Behavior的子类,则必须由element,否则必须没有element。和type一样存在的bug就是,如果你想创建一个Sys.UI.Control,那么你自己会要关联一个DomElement,然后一个异常被抛出来提醒你不应该有element。

说完了这5个属性,是不是发觉这看起来简单的$create其实一点都不简单呢,这就是为了保证$create能够获得和Xml-Script一致的效果。它们的效果真的一致吗?书写起来不是Xml-Script更美观吗?Jeffrey向来支持Xml-Script,有了$create之后我选择支持$create,因为你将发现它的书写方式并不比Xml-Script要丑陋。

大家可以先看看Dflying的使用ASP.NET Atlas实现拖放(Drag & Drop)效果(下),那个声明DragDropList和DraggableItem的Xml-Script看起来不错吧。然后来看看我的使用$create的DragDropList与DraggableItem声明,是不是层次感也不比Xml-Script差?Xml-Script的层次感来自于XML,而$create的层次感来自于JSON,因为那些properties、events、references都是使用JSON表达式书写的。

2006年11月2日星期四

中文“正义”一词可以是矛也可以是盾

在中国人眼里,好像是不会说因为追求正义的处理结果所以要去法院的。去法院通常都是解决问题的一个步骤,因为计算结果显示这步如此走对自己有好处。而这还不是最让人觉得奇怪的,让人觉得奇怪的是在某些时候会有人拉住你说——这事情为什么要去法院解决?过去的事情就过去了,显然这步你如此走对你和对另一方都没有好处。

这意味着其实我们并不需要真正意义上的正义,是吗?“正义必胜”中用到“正义”这个词,有时候可以把它用作矛刺伤别人,有时候也可以把它用作盾用于保护自己,但它永远不可能成为追求,也不可能长期背在肩上,这是因为我们负担不起,当我们需要握手和拍肩膀的时候矛和盾就必须先扔到一边。

把 IE7 装上了,不知道是好是还是坏事

奇怪的事情连续发生,昨天中午在麦当劳要了一对麦辣鸡翅,结果发现两块都是中翅。因为WGA Crack有点过时了,所以跑去9down弄了一个新的,结果发现IE7的WGA Check也能绕过了,成功把IE7装上了。

其实装IE7真的不知道是不是好事情,因为它和IE6一样是quirk兼buggy的,而且quirk和buggy的方面和IE6还不同,还有待大家去探索并寻找解决问题的hacks。装上IE7后,意味着我在IE7中测试的网页在别人的IE6中不一定能够获得的效果,然而我在同一台机上面又不可能有多一个IE6,所以要做好IE6的兼容性就不容易。

2006年11月1日星期三

Microsoft Ajax Beta1 - 边学边用边补充 (Part 3 - ITemplate)

首先,使用ITemplate的例子大家可以在Dflying那里找到一些:

这些例子当中,ITemplate都不是显式声明的。我们仅仅是在xml-script中制定了以某一个DomElement为基础生成一个ITemplate,但是ITemplate不是Control,到底ITemplate是如何生成的呢?

在PreviewScript.js中,我们可以看到唯一一个实现了ITemplate的类:Template(全称为Sys.Preview.UI.Template),它有一个parseFromMarkup的静态方法,这个方法用于解释xml-script中的<layoutTemplate />段落,然后根据其中的layoutElement属性给出的DomElement生成Template的实例。

如果要单纯用JavaScript指定基于某个DomElement生成ITemplate,那该怎么做呢?在官方论坛上有一张名为How to implement the Sys.UI.ITemplate interface programmatically的帖子,里面有人给出了在CTP中可行最简单的答案,放到Beta1中代码应该是这样的:
var layoutElement = $get("layoutElement");
var templateInstance = Function.emptyMethod;
templateInstance.createInstance = function() {
return {instanceElement:layoutElement};
}
这样你就获得了一个名为templateInstance的对象,它基于HTML中一个id为"layoutElement"的节点。templateInstance可以用于DragDropList.set_dropCueTemplate,虽然它连ITemplate都没继承。

给出了这个例子以后,大家肯定就要问为什么可以这样做。其实客户端的ITemplate和服务器端的ITemplate是很类似的,它们的功能都是关联到一个声明性模板,并且在特定时刻将声明性模板的内容转换为树提供给上级容器使用。只不过客户端的ITemplate在createInstance时生成DomElement树;服务器端的ITemplate则在InstantiateIn时生成控件树(这个以后在《深入理解 ASP.NET 动态控件》里再分析)。要生成DomElement树这就再简单不过了,只需要直接从整个DOM中摘取你想要的节点不就是咯,所以就有了上述解决方案。

不过这不是一个太好的解决方案,首先它没做到继承自ITemplate,如果ITemplate的使用方先检查你给的对象是否继承真的自ITemplate,那么templateInstance对象就无法通过检查。其次它直接返回DOM上面的节点,而createInstance会被调用多次,无论是单个调用方还是多个调用方的情形,调用方都会期望每次调用返回一个新的DomElement树,然而templateInstance每次给的都是同一个,这就可能造成一些奇怪的问题。

为了解决这两个问题,我们需要设计一个新的Template类,实现ITemplate并且保证createInstance时每次都返回新的树(使用cloneNode),完整的代码我放在这里:继承自Control的简单Template。

最后就是提问时间,你觉得这个Template类继承自Control是好还是不好?好与不好我都各列举一个原因作为范例:

  • 好的地方是它采用类似Control的构造函数,可以直接用$create来从DomElement创建Template。
  • 不好的地方是它破坏了Template的语意,因为从语意上来说Template和Control应该是互斥的。一个东西是Template就不应该是Control,它的功能是为容器提供DomElement/Control,它的角色应该是provider。

那么你觉得到底是好还是不好呢?