2006年12月31日星期日

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月27日星期三

地震导致海底光缆断开

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

2006年12月21日星期四

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

“每一个女生都曾经是非常有灵气的”,这句话是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月14日星期四

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

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

2006年12月11日星期一

我不想仅仅做一个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

深入理解 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

2006年12月6日星期三

深入理解 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月24日星期五

从 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将不仅仅是存档了,我会为它引入两个全新的栏目。

在我阅读了DflyingJeffrey关于针对读者市场的讨论之后,我决定多发一些针对入门者的文章。这些文章都会是短篇,读者读起来轻松,确保不会让读者在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月19日星期日

深入理解 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月17日星期五

从 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月16日星期四

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月12日星期日

在 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月11日星期六

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一样,你不能不选它,除非你做好了思想准备接受更坏的结果。

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

从 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月4日星期六

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。

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

2006年10月31日星期二

深入理解 ASP.NET 动态控件 (Part 1 - 感性认识)

正如我在《我喜欢的教材与我讨厌的教材》中所说的,我讨厌那种标题之后直入理论部分并开始写“定理1、定理2、定理3”的做法,所以在我自己的文章也绝对不会这样写。我认为感性认识是理性认识不可缺乏的基础条件,所以在很理论性的解释ASP.NET页面生命周期之前,先通过一些大家可能都遇到过的例子给大家一个感性认识。

动态控件遇到的第一类问题就是跨页面生命周期时无法自动保存,你必须每次手动创建。举个简单的例子,例如现在我有一个DropDownList,有三个ListItem,值分别是"0", "1", "2",在我设置了AutoPostBack之后,我希望SelectedIndexChanged时根据我选择的ListItem数值动态创建相应数量的TextBox,简单的代码如下:
protected void dropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
for (int i = 0; i < dropDownList.SelectedIndex; i++)
{
TextBox dynamicTextBox = new TextBox();
this.Form.Controls.Add(dynamicTextBox);
}
}
需要解释一下的是,直接用dropDownList.SelectedIndex是为了省事,因为ListItem的值本身也就是从0开始的顺序整数。

测试一下我们这个小小的ASP.NET程序有没有问题,结果当然是没问题的,你选择了哪个数值就真的会有相应数量的TextBox出现,好简单哦!我们再扔一个Button到页面上看看又会怎样,这时候你就会发现如果通过点击Button导致PostBack,那么动态创建的TextBox就没掉了,看起来事情并不如我们期望的那么简单。

“我们已经知道这个问题啦,快点给出解决方案啦”——如果你急需要一个解决方案,请直接看本篇文章的最后几段。我知道很多人是因为当前有一个棘手的问题才来翻看这类文章的,但我也不能因此而忽视了另外一部分人的需求——他们希望由浅入深地了解这个问题,并且得到解决方案的同时得到完整解释。

接下来我们继续来看第二类问题,动态创建控件的事件触发不正常。我们又来写一段简单代码:
protected void Page_Load(object sender, EventArgs e)
{
TextBox dynamicTextBox = new TestingTextBox();
dynamicTextBox.ID = "DynamicTextBox"
dynamicTextBox.Text = "InitData"
dynamicTextBox.TextChanged += new EventHandler(dynamicTextBox_TextChanged);
this.Form.Controls.Add(dynamicTextBox);
}
void dynamicTextBox_TextChanged(object sender, EventArgs e)
{
this.Trace.Write("DynamicTextBox", "TextChanged");
}
由于用到了Trace,测试的时候别忘记把Trace打开哦。

我们再扔一个LinkButton到页面上,目的仅仅是为了触发PostBack,然后看看事件是否正常。奇怪的事情发生了,在修改TextBox的值之前,无论怎么点那个LinkButton,一切都非常正常,TextChanged事件确实不发生。修改了TextBox的值之后点LinkButton,事情也还正常,TextChanged事件发生了。但之后就出问题了,无论你是否修改了TextBox的值,TextChanged总是在每一次PostBack时都被触发。

这个问题很怪异对吗?事件既非完全不触发,也非总是触发。其实答案隐藏在我之前那篇《深入理解 ViewState》里面,去读一读那篇文章,或许你自己也能够解释为什么会这样。

动态创建的控件或许还存在第三类、第四类问题,在此就不一一列举了。我相信被动态控件问题困扰过的ASP.NET程序员绝对不少,而未遇到过此类问题的程序员看到上述两个问题也未必能给出解决方案和正确解释。

在提供问题的解决方案之前首先要说明一点,作为ASP.NET程序员的你需要在某一时刻某一地方让控件动态出现时,就立即在该处写代码动态创建并添加控件,这往往都是错误的做法。正确的做法是向后退三步再抬头看,这时候你看到的就不是你要让控件动态出现的那一个准确的时刻和地方,你应该看到ASP.NET页面生命周期的全貌,接着你就应该清楚你的代码该加去哪里了。

好了,是时候给出最直接的解决方案了,唯一的解决方案就是让你看清楚ASP.NET页面生命周期的全貌,而其中最佳的入门方式就是学习控件设计。虽然上面把动态控件说成一个复杂的问题,然而大家天天都在用动态控件,只不过动态控件已经被封装到一个静态控件里了。例如复杂的GridView控件,它会自动根据每一列的性质来生成对应控件,如果是模板列还要分析模板中的内容来生成模板中定义的控件,这些控件都算是动态控件,为什么PostBack不会让他们自动消失,为什么为它们添加的事件从来不会错误触发,在你学习完控件设计之后就会一清二楚。

关于控件设计,我推荐大家买Wrox(乐思)的书来看,是以控件设计为主题的那两本,不会很厚,很快能看完。如果你在使用的是ASP.NET 1.x,或者你一定要看中文版的书,那么ASP.NET服务器控件高级编程将是一本很适合你的书。至于ASP.NET 2.0的则有Professional ASP.NET 2.0 Server Control and Component Development,英文版今年8月才发布,根据清华出版社的惯例至少要等半年才可能有对应中文版。

既然连解决方案都给出了,这个系列的文章继续写下去还有什么意义吗?书上能给你的只是一个临摹着去做就不会出错的模式,以及一个听起来很合理的解释。到底为什么临摹这种模式去做就符合ASP.NET的大模式(主要是编译模型和页面生命周期),ASP.NET的大模式到底是怎样的,这就是我接下来要写的东西。

2006年10月30日星期一

扩展 Atlas 的客户端 Web Service 调用功能

Jeffrey Zhao最近在写客户端调用WebService的有关内容,那么我也来说说。Jeffrey Zhao说到了,Beta1中只有一个onComplete,onTimeout、onError、onAborted都没有了,这3个事件都整合到onComplete中,使用者需要自己在onComplete中手动判断到底属于哪个情况。另外Jeffery Zhao最近在讨论继承WebRequestExecutor中说到,XMLHttpExecute._onReadyStateChanged无法重写,所以要跟踪返回就要在WebRequestManager.completeRequest事件中进行。

这样看来,Beta1的WebService基础代码确实不太友好,我虽然还没有在Beta1用过WebService,但看样子有必要改善它。这次又到讨论继承哪个类?这个不一定要用继承嘛,除了我们熟悉的继承,以及JavaScript天生就支持的prototype模式,我们还可以考虑decorator模式。

由于我还没开始弄Beta1的WebService扩展,所以在这里仅能提供一段以前写下的用于Atlas June CTP的Web Service调用封装,调用方式为:
Cattism.BodyMaintainer.Web.Services.call(method, arg1, arg2, ..., configuration);
其中,method是带调用WebService的代理方法,之后arg1, arg2, ..., configuration就和直接调用WebService代理方法时使用的参数列表一致。该封装提供4大回调函数的一致性处理,例如如果你需要onError时一律将Exception信息通过debug.trace输出,那么你可以在Cattism.BodyMaintainer.Web.Services._onError中添加相应代码。你甚至还可以要求仅当调用时没指定onError回调时才将Exception信息通过debug.trace输出。

有人会想说我提供的代码看起来不太像decorator模式,对吗?其实我写的时候是完全根据自己的需求写的,写完才想起来这应该做成一个标准的decorator模式,不过也懒得改它了。在这里我只是希望给大家提供一个思路,就是当你觉得Atlas还不够好的时候你应该主动去添加一些代码让它能够满足你的需求,同时思维不一定要限制在基于Atlas的修修补补,你完全可以对Atlas进行二次封装制作一套适合你的个人工具库或者企业类库。

2006年10月29日星期日

深入理解 ViewState

上个星期写了一篇《控件 ViewState 属性的值保存去哪里了》,解释了Control.ViewState最终还是通过Control.SaveViewState和Control.LoadViewState这两个方法存取的。文章中有一句话可能会让大家感到疑惑的:“我们在OnInit之后使用this.ViewState[key]读写时该属性都为true”,其中“该属性”指StateItem.IsDirty。到底为什么IsDirty属性在OnInit之后总是为true呢?参考了TRULY Understanding ViewState,我终于明白到其实它并非总是为true,详细原因请听我慢慢说。

首先要让大家来看的是StateBag.TrackViewState方法,这个方法在控件OnInit时就会被调用,而它的作用就是让StateBag开始跟踪StateItem的变化,任何变化都将导致该StateItem的IsDirty属性变为true。也就是说,在OnInit之前,IsDirty属性是false的,并且无论你如何设置Value属性的值都不会改变IsDirty属性。在OnInit之后,IsDirty属性也保持着false,直到你第一次改变Value属性的值(指通过this.ViewState[key]的方法改变)。到了SaveViewState的阶段,只有IsDirty属性为true的StateItem才会被保存下来。

为什么要如此设计呢?例如一个通过声明性定义的Label的Text属性,在ASPX中它被赋了初值,然后该初值自然通过ViewState["Text"]来持久。在下一个页面生命周期,首先OnInit时这个Label的Text属性会加载ASPX中声明性定义的初值,然后LoadViewState时会用ViewState中读取到的ViewState["Text"]来覆盖它。然而除非你在上一个页面生命周期以编程的方式改变了Text属性,否则ViewState["Text"]还是初值,那么你就是用ViewState["Text"]保存初值去覆盖声明性定义的初值,同一个值这样覆盖完全没意义,而且还浪费了ViewState的空间。为了解决这个资源浪费的问题,凡是声明性定义之后没改变到的值就不应该使用ViewState来持久,而详细的实现就是上面说的TrackViewState机制了。

说到这里,Control.ViewState已经解释完毕,如果你是控件设计者你可以放心地按以下方式把控件属性存放到ViewState中:
public string Text
{
get {return this.ViewState["Text"] as string;}
set {this.ViewState["Text"] = value;}
}
它的内部机制会懂得区分你存进去的值是不是ASPX上声明性定义的初值,然后决定是否持久该值。同时,如果你在任何阶段想改变一个ViewState值是否持久的决定,可以通过ViewState.SetItemDirty(key, dirty)来改变,这基本上已经满足了所有控件开发人员的需求。

2006年10月27日星期五

Microsoft Ajax Beta1 - 边学边用边补充 (Part 2 - DragDropList)

由于我在做一个类似Live.com的东西,所以需要类似WebPart的功能。我不清楚Atlas扩展的那个所谓的Cross Browser WebPart到底是什么,支持如何,所以不敢去尝试,从而决定用DragDropList。

首先,关于如何使用DragDropList,可以参考Dflying的以下两篇文章:

参照第二篇文章轻松制作出WebPark拖放效果来是完全没问题,接下来要做的就是让用户拖放修改后的界面保存下来。这时候ASP.NET 2.0新增的Profile Service就是一个很好的选择,而且Atlas也包括对Profile读写的支持。接着问题就来了,何时能够获取到拖放结束的事件然后更新Profile呢?DragDropList和DraggableListItem没有对外公开任何事件,所以在使用它们快速实现类似WebPart的功能时仅仅能够获得视觉上的效果,无法对它们编程以实现任何有实际意义的东西。

这时候我选择了继承DragDropList来增加对外的事件触发能力,需要做的仅仅是重写onDragEnd方法。感谢Beta1的prototype书写方式,所有函数都是virtual的,这给重写带来了方便,不需要再用registerBaseMethod覆盖掉基类的函数。重写如下:
function MyDragDropList$onDragEnd(cancelled)
{
  debug.trace(cancelled);
  MyDragDropList.callBaseMethod(this, "onDragEnd", [cancelled]);
  this._dragVisual.style.width = null;
  this._dragVisual.style.height = null;
  this._dragVisual.style.zIndex = null;
}

debug.trace(cancelled)在这里是做演示作用,真正放在此位置的应该是一条触发事件的指令,甚至可以根据cancelled值决定是否触发,因为若它为true则实际上拖放没造成任何变化。

后面对样式属性的删除又是什么呢?这是对DragDropList某一个bug的work around。在拖动过程中,由于拖动的DomElement(也就是this._dragVisual)脱离了它原本的上下文,如果它原本的样式属性是width:100%那就会出问题,所以DragDropList取了它的offsetWidth属性来作为它的width,这样width的单位就一定是px。然而这却造成了另外一个问题,拖动后DomElement的width变成了一个单位为px的值,用户缩放浏览器时它就不能跟随缩放,这不符合我在CSS文件中定义width的单位为%时所期望的。于是我就在拖放结束后删除DragDropList对此DomElement样式的影响,让CSS文件定义的样式重新成为当前样式。

最后,提供一篇我觉得比较有价值的文章,方便大家实现自己的IDragSource和IDropTarget:Drag and Drop with Atlas: interfaces。文章中详细解释了这两个接口的各个方法,比起你自己去研究DragDropList的代码要简单一些。

Microsoft Ajax Beta1 - 边学边用边补充 (Part 1 - Debug)

这Beta1的更新多就不再强调了,关键就是它对以客户端为中心的开发人员不太友善。客户端框架正在作大幅度修改,这个我不反对,但是将明显一个修改到一半的东西扔出来显然就不太好。Beta1里面的客户端框架是没问题的,不过Preview中的就衔接不上了,有些CTP原有的功能没有在Preview中保留,有些则看得出是修改中的,例如debug版的代码不太工整,enclosure和prototype书写方式混合。

将老项目由July CTP迁移到Beta1 + Preview,第一件事当然是测试更新后原有功能是否正常,不正常的地方是由什么引起的,但我马上就发现可爱的debug.dump和debug.trace都不见了,这样我可没办法做项目迁移啊,因为连基本的debug功能都没有。搜索了一下Beta1的代码,发现只有debug.assert和debug.fail;而在Preview中则有如下一段代码:
if(!debug.trace) {
  debug.trace = function debug$trace(message) {}
}
这段代码的用意很显然,debug.trace已经被去掉了,然而CTP迁移而来的代码中可能有用到debug.trace的,这样做能避免对debug.trace的调用引发脚本错误。

为了完成迁移,首先要把debug.dump和debug.trace补上,尝试的就是把July CTP的代码借过来用。代码借过来后,debug.trace基本没问题,就是第一行那个Debug.writeln不知道用来干什么的;debug.dump的问题就一大堆,这是由于一些CTP中存在的类改名了或消失了,例如Sys.IArray、Sys.ITypeDescriptorProvider、Type.Event。我开头的做法是把debug.dump中不能用的代码段注释掉,后来Jeffrey Zhao为ASP.NET AJAX 1.0 Beta补充trace和dump功能一文中提供了一个更好的版本,我也就参考着那个作了修改,制作出我自己的版本:用于MS AJAX Beta1 + Preview的debug补充包

由于我还没有仔细看过Beta1的原代码,所以采用了保守修改的原则,也就是尽可能少的改动。因此,我仅仅作了如下替换:

  • Sys.IArray.isImplementedBy -> Array.isInstanceOfType
  • Sys.ITypeDescriptorProvider -> Sys.Preview.ITypeDescriptorProvider
  • Type.Event-> Sys.UI.DomElement

 

需要说明的是,Jeffrey Zhao那个适用于不加载PreviewScript.js的场合,而我的这个必须和PreviewScript.js一起使用并且在其之后加载。另外为了方便我直接在浏览器地址栏调用debug.dump和debug.trace,我为它们提供了更简短的别名:$dump和$trace。

2006年10月23日星期一

博客重构 / Blog Refactoring

之前曾经我的Blogger上说过将技术贴存档到一个专门的地方,同时开一个英语blog。现在第1步已经完成了,我在cnblogs开了一个新的blog,名称为Cat in dotNet,所有纯技术贴都已经迁移到那边去了。以后我的技术贴,在Blogger发的时候同样会在cnblogs发,不过对于技术贴的定义我需要做一个详细的说明:

  • 主要是以讨论.NET Framework、C#、ASP.NET、Atlas相关技术为中心的帖子。
  • 以揭示运行机制及undocumented功能为主,同时也有FAQ类型的帖子。
  • 纯猜想性的帖子不包括在内。

 

cnblogs的好处是能够贴代码,这是Blogger所不能的。不过既然我已经习惯了只贴小段代码,并且自己格式化,那以后也坚持这样做,因为贴大段代码要读者自己找出其中关键部分也不是一个好的做法。

至于英文blog,我准在各大英文BSP中挑一个然后开始好好写,目标是能够和英文社区的native speaker混在一起,哈哈。

2006年10月21日星期六

ASP.NET AJAX Beta1 发布

Atlas在7月的CTP之后就几个月没有更新了,当然就意味着之后会来一个大更新,这就是ASP.NET AJAX Beta1。ASP.NET AJAX Beta1将原来的CTP拆分为两个部分:1.0核心增值CTP,同时原来的Atlas Control Toolkit也改名为ASP.NET AJAX Control Toolkit了。上述3个下载都已经更新了,然而要把老的Atlas项目迁移到新版本则不那么容易,需要看着迁移向导来做一系列的修改。

作为一个大更新,更新项目当然不少,这个可以参考Changes between the ASP.NET AJAX (“Atlas”) CTP and the v1.0 Beta/RTM Release。这份参考是在巨大的可怕(打印版本为49页),红字绿字标出了大量RTM和CTP的不同之处,我猜这足以让老的Atlas程序员放弃更新他们的老项目。当然,也不要为了这些巨大的改动而停滞不前,因为这些改动中的大部分都是响应来自社区的要求,将Atlas的代码结构尽量简单化,让更多人能够更容易的理解Atlas。如果你想看一份能让你开心看完的更改列表,不妨来看看Scott GuthrieASP.NET AJAX Beta 1 Released,这些新特性也足以鼓励你在新项目中采用新的版本及使用新的代码书写方法。

2006年10月18日星期三

.NET 里 String 的特性

String是指System.String,同时string关键字也是System.String的别名。这里要说的特性都是一些让不理解的人容易犯错的特性。

String的第一个特性就是它是引用类型,但很多时候表现起来却像值类型,这是第一个让人容易犯错的地方。String有一个不可破坏的特性(immutable),或者简单叫做只读特性,这意味任何改变String的操作其实都没有改变原本那个String,而是创建了一个新的String实例同时让变量的引用(指针)指向了新String。这个特性让String在某些方面表现得像值类型,例如:

  • 作为函数的传入参数时表现得像值类型,也就是如果你传入了一个String,然后在函数内改变了它的值,并不会对函数外面该值原本的引用有任何的影响。
  • 把一个String赋值给另一个String,改变其中任何一个String另外一个都不会受影响。

然而这不影响String作为一个引用类型的本质,例如它可以是null,也可以用于lock。

String的第二个特性就是字符串池(String Pool),也叫做拘留池。程序运行时所有的String其实都存放在一个池中,任何一个特定内容的String在池中仅会有一个副本,所以多个内容相同的String对象其实都是引用同一个字符串副本。这特性意味着你操作两个String如果内容可能相同你就要格外小心了,例如:

  • 在执行lock的时候,如果放进去的是一个String,那么当两个线程中该String内容一致时其中一个就会阻塞,因为这两个String其实引用同一个字符串副本。

 

在了解到这些String特性后,编写程序时遇到String的传递和比较就要格外小心咯,想清楚String的行为会是怎么样的,再决定如何写代码。

控件 ViewState 属性的值保存去哪里了

看过MSDN的都知道,存取ViewState有两种方法:

  • 直接操作控件的ViewState属性,通过this.ViewState[key]就可以直接进行读写。
  • 重写控件的LoadViewState和SaveViewState方法。在LoadViewState中系统会将此控件以ViewState保存的信息作为一个object类型参数传入,控件需要自己将信息unboxing出来。在SaveViewState中,控件需要自己将想通过ViewState保存的信息boxing到一个object里面,然后return给系统。

那么到底这两种方法读写ViewState有什么不同呢?

使用Reflector看看Control的LoadViewState与SaveViewState方法你就会发现,其实控件的ViewState属性也就是一个特殊的控件属性,类型为StateBag,由于Control已经为你写好了将StateBag存取到真正的ViewState的方法,所以只要你继承Control控件你就可以放心地把值存到StateBag里面去,而这些值最终会保存到真正的ViewState中。

就这么简单?还差一点,就是StateBag这个字典的每一个项目类型为StateItem,而StateItem有一个IsDirty的属性。只有这个属性为true的StateItem才会被保存到ViewState中。我们在OnInit之后使用this.ViewState[key]读写时该属性都为true,所以StateItem都会被保存。但如果你想要某个StateItem临时不保存到ViewState,那就可以执行this.ViewState.SetItemDirty(key, false)。例如我们熟悉的TextBox,在它的TextBoxMode为Password时它就会通过上述方式让this.ViewState["Text"]值不保存到真正的ViewState中,也就确保了你无法通过ViewState窃取密码,同时也导致了该TextBox的OnTextChanged事件无法正常触发。

那还有什么要说的吗?要说明的就是StateBag的存取所受到的限制。StateBag的存取,与你手动重写LoadViewState/SaveViewState保存的其它值一样,在LoadViewState之前StateBag并没有任何值,在SaveViewState后的任何更新不保存到ViewState中。

2006年10月15日星期日

别把 identity 都放一个 website 里

俗话说别把鸡蛋都放一个篮子里,identity也不能都放一个website里。

现在很多web 2.0网站都希望用户在上面慢慢建立自己的identity,通过这方面粘住用户,这确实是个好办法,然而对于用户来说是有风险的。将自己的identity都放在一个网站,将自己的创意与作品都放上去,如果这个网站倒掉了,或者改变其策略变得对你不利或者你不喜欢了,这时候你就麻烦了。因此,将自己的identity分布于不同的网站是有必要的,这是一种基本的安全策略,关键就是如何分布才能为自己和读者带来便利。

我现在正准备重新设计我的identity,首先要解决的就是blog文章的归属问题。现在我主要利用这个blog发表技术文章和思考,也用mywallop发表一些无关紧要的东西。未来我打算将纯技术文章分离出来,或者专门找一个地方进行存档,这是因为我的纯技术文章中有部分是起着FAQ作用的,方便浏览者查阅很重要。接着我要把我的wallop.com也启用了,SNS的好处是邻近的人能够很容易就留意到你的更新,而不邻近的人可能永远都不会注意到你在写什么,所以我会写一些和我的social network有关的东西。最后就是我计划开一个英文的技术blog,是时候练习一下用英文写些实实在在的东西里,这样也有利于我在英文技术社区里建立identity。

2006年10月10日星期二

看看英文 blog 写写中文 summary

在之前的《中国IT从业人员学习障碍:心态大于语言》中说到,接触前沿技术的人都是在英文技术社区里泡着的,而中文技术社区的保守心态会让人不愿意将英文资料翻译为中文。最近又碰到一些人问我哪里有比较多某某技术的中文资料,回想起我以前也时常对某技术感兴趣却因找不到中文资料而直接放弃,所以直接回复求中文资料的人:这样的东西在国内算是“前沿”,还是老老实实学好英语在Google上搜索英文资料吧,想在中文领域淘金最后通常只落得两手空空。如果你在此领域有一定了解后,多多翻译资料造福中文社区,后来者才有金可淘。

现在我也慢慢习惯了去英文社区泡了,虽然多数是潜水,看一些PageRank高的技术blog也不错。这是因为英文社区才能找到一些比较geek的用户——喜欢弄懂一样东西的运作机理、喜欢探索undocumented功能的使用方法,而这些也正是我喜欢的东西。在中文社区,是很难找得到人和你沟通与分享这方面的知识的,最多就是为一些实际问题互相帮下忙,你提一个探索未知领域的问题往往得不到任何回复。

接下来我准备做的事情,就是在看完英文资料后用中文写写摘要,算是对中文社区的一点点贡献吧。翻译我是不会做的了,首先懒得去跟原作者沟通,其次我通常不会直接buy某一篇文章的idea除非我自己亲自做一些小research来证明,所以写出来的东西也就肯定和我参考的文章有很大的不同,在文章中对参考添加链接也就算了。

2006年10月9日星期一

AJAX - 服务器端也用 JavaScript 不好吗?

现在ASP.NET要实现AJAX已经不难了,可以用同时有服务器端扩展和客户端框架的Atlas,也可以选一个Atlas之外的服务器端JSON串并转换器和支持JSON-RPC的客户端框架。类似的,PHP等现在热门的语言都有服务器端JSON串并转换器,唯独已被人放弃的ASP没有,所以很多人都为如何在ASP上实现AJAX而感到困惑。

要在ASP实现AJAX,首先考虑的是用什么数据格式进行传输,主流的选择就是XML和JSON:

  • XML - 服务器端没有ASP.NET Web Service,只能采用Microsoft SOAP Toolkit来提供Web Service,这个Toolkit不能够直接将ASP代码暴露为Web Service,只能将VB6编译的dll暴露为Web Service。
  • JSON - 服务器端缺少转换器。VBScript没有JSON转换器,同时由于这是过时的语言,也就不可能有人为它开发JSON转换器。

说到这里,细心的人肯定会指出ASP不仅仅可以用VBScript写,用JScript写也可以。服务器端使用JScript其实就是使用JSON的突破口,因为JScript类似于JavaScript,JavaScript的JSON转换器在JScript中可以直接使用,根本不需要另外编写。

其实现在已经有用JScript编写的ASP实现了AJAX,使用ASP+JSON搜索一下就能得到一堆结果。例如Simple AJAX (with JSON) Chat Application for ASP 3.0,这是一个用ASP实现的聊天程序,语言选择是JavaScript/JScript,数据传输采用JSON。

说到这里,就要回顾一下我之前那篇《Script# - 把 C# 编译为 JavaScript》。将C#编译为JavaScript是一个好主意,而且Script#的实现也很出色,让C#成为核心语言也无可厚非因为它确实是很好的语言。不过JavaScript也是也门很好的语言哦,为什么不能让它成为核心语言呢?

现存的JavaScript引擎不少,在标准的实现上略有些不同,而各自的bug当然也有多不同,但是这不应该成为JavaScript成为服务器端语言的障碍。使用JavaScript作为服务器端语言的好处是,现在已有的客户端框架都能直接拿过来用,XML和JSON都可以使用了。JavaScript最缺的应该是一些执行服务器端专有任务的框架,例如数据库和文件读写,同时也缺乏一些低层操作的能力例如Stream和Thread。然而不能够因此而歧视一门脚本语言,RoR不就是一个成功的例子吗?如果有一个JavaScript引擎能够用于服务器端任务,相信技术社区就能出产不少有用的框架来让其功能变得强大,或许能够比RoR更快热门起来——因为现在懂JavaScript的程序员肯定比懂Ruby的多。

2006年10月7日星期六

XNA - Microsoft 平台的新游戏框架

最近突然对DirectX又有点兴趣(之前有过n次兴趣,都是轻轻碰一下让DirectDraw绘下图就不想弄了),于是就下载了最新的DirectX SDK下来,想看看现在的Managed DirectX(MDX)有什么好入手的地方。结果发现最新的DX SDK也只有MDX 1.1,那么MDX 2.0在哪呢?上网以搜索才发现,原来MDX 2.0的开发计划已经终止而技术支持也将中止,MS推出了XNA这个新项目,是类似MDX是托管的游戏开发框架,并且部分MDX 2.0支持的功能都迁移到XNA中了,看来XNA就是MDX 2.0的后继版本了。

其实之前就知道有XNA这东东,但是没深入了解,和大多数人一样觉得这是一个肤浅的游戏开发引擎,想着用XNA也就是拖动放入简单的游戏元素然后用C#写几句脚本,所以没花时间去了解。现在发觉它是MDX 2.0的后继版本,想着MDX并不是一个肤浅的东西,所以深入了解也好吧,于是搜索了一些专注于XNA有关技术的站点来看看,发现它的创意确实不错,而且目标也很好,只不过很多人误解罢了。

我们来看看XNA官方blog的第一张帖子XNA Then and Now Part One,里面解释了为什么MS要提出XNA这个项目,以及项目的终极目标是什么。XNA开发队伍调查了现在的游戏开发者开发过程中遇到的问题,然后列了一些:

  • 游戏开发团队中的大部分都是美工和编剧,但真正有话事权的却是那些宁愿专注于引擎开发的程序员。
  • 由于惧怕财务风险而毫无创新。
  • 由于长期开发团队精疲力尽兴趣下降(称之为burnout),同时没有足够的毕业生填补空缺。
  • 制作游戏如此困难——指编写代码的技术性感觉方面。美工素材如此复杂和混乱让你的C++指针在此毫无发挥的余地。
  • 设计一个游戏并不容易。API、开发工具等如此多代码都是大师级程序员在脑袋里写成的。然而很不幸,最好的游戏设计师通常不是最好的程序员。

站在MS的角度来说,如此多的问题实在太不幸了,游戏开发商都保保守守开发一些成功游戏的仿制品,因为这样做财务风险低,收入多少比较容易预测。这样下去MS的XBox360肯定没办法卖,因为没有真正有好创意的游戏。为了解决此困境,MS推出了XNA,期望能够让创意重新变成游戏开发商的关注点,降低创意带来的风险,让MS的平台(Windows和XBox360)上有创意的游戏更多一些。

2006年9月30日星期六

Windows 程序设计 - 该教 MFC 还是 WPF

上个学期我们上“Windows程序设计”这门课程,主要讲的是MFC,当然也包括GDI/GDI+绘图,同时普及了C++历史和STL使用等知识。我当时是比较反对讲MFC的,任课老师以前曾经教过VC6和VB6,现在为什么选择VC2005而不选择VB.NET呢?如果觉得VB.NET显得“幼稚”,那么可以选择C#。现在MFC已经是过时的技术,易用性比不上WinForm,等Windows Vista出来又有WPF了。

我反对讲MFC的理由是,培养一个熟练的MFC程序员和一个熟练的WPF程序员同样需要5年,然而从现在算起5年之后MFC肯定已经在收缩而对WPF程序员的需求将会增加。任课老师反对讲WPF的理由是不能够将Beta阶段的技术拿到课堂上来讲。

现在我反思过这个问题,对于还有2年就毕业的学生来说,他们是否拥有“成为熟练WPF程序员所需要的5年”是不确定的,因为2年后别人对他们的选择会在一定程度上决定了他之后的3年怎么过。对比一个不太熟练的MFC程序员和一个不太熟练的WPF程序员,显然2年后前者会比较受欢迎,或者称之为“更容易被接受和选择”。

站在学生的角度,一些人会希望学习MFC而另一些人则会希望学习WPF,这时候学校应该视乎学生的需求程度而开课,任何一门课程只要学生的需求相当学校都应该尽力去开。而“Windows程序设计”这是一门选修课,所以老师是完全有权自己选择讲授内容的,况且MFC的学习需求也不低。至于为什么不开WPF相关课程,就只能解释为需求不足或者学校无法满足此需求了。

2006年9月28日星期四

ASP.NET 2.0 中的 DataSource 系列控件

在ASP.NET 2.0中,引入了DataSource系列控件,扔一个到页面上并且选择性的配置好SELECT/UPDATE/INSERT/DELETE对应的操作,它就能够和数据空间无缝合作自动处理查询与更新,并且提供分页、排序等支持。

有一些ASP.NET 1.x的程序员,并不喜欢使用DataSource控件,觉得还是按找1.x的写法在Page_Load里面设定数据控件的DataSource属性然后执行DataBind好,但实际上2.0的数据控件为DataSource控件做了不少优化所以应该尽量使用DataSource控件。我在使用2.0的过程中DataSource控件和手动DataBind都尝试过了,并且借助Reflector分析.NET自带控件的代码,发现了其中的一些差异。

首先要说的是,数据控件对于是否使用DataSource控件是非常敏感的,很多操作数据控件都会检查自己的DataSourceID属性是否为空,如果不为空则很多事情都能够配合DataSource控件自动化完成,如果为空则通过事件通知用户需要手动完成一些操作。

例如GridView的分页,在使用DataSource控件的时候是能够自动运作的,在翻页时GridView会自动将PageIndex设置为GridViewPageEventArgs.NewPageIndex,同时设置RequiresDataBinding为true,接下来的事情就如魔术般自动发生——GridView会重新执行DataBind,这时候它会懂得自己去找DataSource控件获取DataSourceView,然后从DataSourceView中获取本页数据并进入创建子控件环节。如果你提供给DataSource控件的SELECT方法本身就支持通过传递参数选择仅获取某一页的数据,那么DataSource控件就懂得仅获取GridView所需页的数据以及总页数。

但如果没有DataSource控件,只是指定DataSource并且DataBind,那就会提示GridView未设置OnPageIndexChanging事件,其实意思就是你必须自己手动处理这个事件。对于DataSource从来都是整个GridView显示的数据这种情况来说,自己添加一个OnPageIndexChanging事件并在其中手动更改PageIndex属性和重新DataBind就可以解决问题了。但如果你想优化SELECT方法为仅获取当前页的数据,那就没办法了,因为你无法手动设置GridView的PageCount属性。

2006年9月25日星期一

Atlas 正式命名发布

发一则晚了两个星期的新闻,Altas已经有了正式命名,详细可以看这篇"Atlas" 1.0 Naming and Roadmap。大概包括以下几个变更:

  1. Atlas 1.0正式版将于2006年底发布,作为MS官方发布的产品将受到MS技术支持。无论是企业还是个人都可以放心的将正式版的Atlas应用于重要的场合,因为如果用户反馈bug存在则MS将会提供相应的hotfix。
  2. Altas 1.0将是一个“核心”版本,包括现在Altas的一些核心功能。部分现有的Atlas功能将在Atlas 1.0“核心”版本中剥离出来,成为独立的下载项目,这样做是为了能够确保“核心”的功能的质量以及能够赶在年底之前发布正式版。
  3. Atlas的客户端JavaScript库将命名为Microsoft AJAX Library,可用于任何AJAX项目而不仅限于ASP.NET;Atlas的基础服务器端控件库将命名为ASP.NET 2.0 AJAX Extensions,这些控件的标签前缀将由<atlas:>改为<asp:>,并且在ASP.NET的之后版本中成为内置控件;Atlas Control Toolkit这个免费的服务器端控件集将命名为ASP.NET AJAX Control Toolkit。

Atlas发布正式版是一件好事情,因为稳定而且受到官方技术支持意味着会被更多人选择,这样就能够有更多的Atlas技术资料也有更多人参与到Altas技术讨论中来。

2006年9月22日星期五

Script# - 把 C# 编译为 JavaScript

原来有一样东西叫做Script#,是Atlas的主要开发者之一的Nikhil Kothari自己编写的一个小框架,目标就是制作一个C#编译器让C#可以直接编译为JavaScript,并且是不经过编译为IL这个步骤。

之前我就说过,以MS的做法如果要想继续宠着那群很RAD的ASP.NET程序员,并且确保他们继续think inside MS's box,那就必须做一个C#到JavaScript的编译器,让ASP.NET程序源可以继续以C#语法和服务器端控件编写方式来制作客户端控件,然后如服务器端拖放控件一个制作Ajax应用。不过我当时的想法是,先编译为IL再编译为JavaScript,这样其他语言也都能兼容近来,不过Script#的做法就是C#直接编译。我当时还想过要有一个控件树和HTML DOM影射的模型,不过在Atlas里面这个应该是xml-script的DOM而非HTML DOM。

回头看Atlas,为什么它设计得那么像C#,让大家可以用C#的对象设计方法来写JavaScript(当然代码要写对一些),同时把C#能实现的都尽量实现了,原来就是为了Script#铺路。其实这个编译器不难做,C#每一条关键所表达的语义要在JavaScript找一个对应的表达方式是肯定做得到的,不过如果对应的JavaScript表达方式如编译为二进制代码一样难读,那就难以编译后修改再用了,所以Script#有一个关键的目标就是编译出来的JavaScript是高可读性的。

其实很好的主意天天都会降临在不同的人身上,只是你会不会愿意抓住它然后做一个大家都看好的东西来,而你的价值取向很大程度上决定了这个东西最后是否被看好。当然,价值取向只是一个决定因素,如果你有适当的价值取向,但是你没有足够的技术来按照这个价值取向实现,最后还是会走弯路,甚至选择放弃。

最后,抢占时机也是很关键的,在Script#正式公布的同时能将Java编译为JavaScript的Google Web Toolkit也出来了,结果Nikhil Kothari有点后悔为什么不提前几周正式公布。

2006年9月21日星期四

Google Camp Kick Off Day

今天是Google Camp的开营仪式,主要包括下午Tech Day的游园式活动和晚上的李开复讲座。由从Google Camp进驻的idea出现到开营仪式仅仅不过40天时间,所以工作看起来都挺仓促的,没什么独家的精心准备。

有趣的是,开营活动竟然是有模板的,所以就算时间仓储,也能按模板完成开营仪式及前期活动。模板是怎样的,就不便公开了,因为那是印着“Confidential”的东西,我只能按照今天的活动情况大致介绍一下。

今天下午是Tech Day活动,也就是6个需要使用Google产品或帮助大家了解Google的小游戏,并且有礼品派发给获胜者,包括笔、笔记本、文件夹之类的小文具,杯子也有一些,非常多人想要的Lava Lamp也来了一只,理所当然地给某获胜队伍抢去了。晚上是Kick Off Day,也就是开营仪式,包括赠旗仪式和李开复演讲等。演讲主题是“21世纪人才需要具备的7种素质”,这算是他讲过多次的题目了,不过由他亲自来讲还是十分精彩的。

Google Camp开营仪式的整个活动,并不是由Google负责所有工作的,而是雇佣PR(公关)公司负责的,所以前期准备工作我们需要分别联系Google和PR公司,导致沟通有点问题,工作看起来也就很赶。可能因为Google刚刚进入中国吧,外国的PR方式不可能照搬,所以在有自己一套PR方式之前还是要雇佣本土的PR公司。MS Club以前的活动都是直接和MS负责高校活动的人直接联系的,PR直接由MS负责,这就省去了和PR公司沟通的麻烦。我们只能期待本土的Google成长起来,做到既能够适应本土环境也为本土环境注入新的力量。

2006年9月20日星期三

大学牢 - 本科的四年有期徒刑还剩两年

最近都没有时间写blog了,因为在忙着Google Camp的开营仪式以及有关的前期活动。南校区Google Camp的几个发起人都是研究生来的,我大三也就成了学历最低的。然后我们在学校BBS上公告招募第一批核心成员,结果东校区投稿的只有大三的,看来BBS在大二的宣传和人气都严重不足,以后可能越来越少人上BBS。其实我不想负责带大三的人,因为同一年级的往往不怎么听你的,很难调动,如果一个组织都是同一年级的话那些苦累活往往没有人干,变成部长级才是真正干活的。

现在大陆的高等教育,其实是在起监狱的作用。为了避免那么多人涌到人才市场上,所以就想方设法把人锁在学校里。正规的监狱囚犯通过劳动自给自足,大学这监狱不要求学生劳动,还为大家提供所谓的知识,当然要通过收钱才能让帐面平衡,所以叫做“教育产业化”。很多人不希望教育产业化,但是社会环境就是给机会学校摆出产业化的态度。以前学校从国家拿钱,是为了好好培养学生,国家可以说艰苦的环境也能培养人才嘛;现在学校从国家拿钱,态度跟监狱一样,就是为了帮国家所住一群奋青,国家可不能够轻易降低监狱的预算,所以也就只能任学校扩招。

总之,我还有两年就完整这四年的本科有期徒刑了,然后绝对不会再尝试在中国的学校里浪费时间。要读书,就算远的不能去也要去香港读。

2006年9月6日星期三

我喜欢的教材与我讨厌的教材

中国的数学教材都是这样写的:
   引理 5.3.14.1
      ...
   引理 5.3.14.2
      ...
   定理 5.3.14
      ...
   推论 5.3.14.1
      ...
   推论 5.3.14.2
      ...
这样写的书我觉得不是教材,而是referrence,作者仅仅是列举了他脑袋和其他参考书上的理论,然后根据依赖关系进行拓扑排序,最后写在一本书上面。例如矩阵,可能你整本书学完了你就会解书上的习题,除此之外你无法把知识和你之前(例如是高中)学过的任何数学问题联系起来,而老师则很可能告诉你这是“专业基础课程”所以这些知识“以后才有用”。

国外教材是不会这样写的。国外教材开头总会用一些你熟悉的而且能引起你兴趣的东西来给予你感性认识,例如通过解方程组的方法的对比来引入矩阵。可能你花了一些时间才弄懂了解方程组这个例子中的矩阵乘法运算是怎么算的,然后又花了一些时间发现要弄懂矩阵的逆怎么求并不容易,但你会乐意把这本书看下去。

我觉得国外的教材对于读者来说显得友善很多,因为在给出理性认识之前,提供一个感性认识是很有必要的,这样才能让读者产生兴趣,并且在读到后面章节遇到任何他无法独立思考理解的问题时,至少他还是可以马上联系开头的感性认识为该问题建立一个感性认识。而如果读中国的教材,一旦有一个问题你无法理解,那就是无法理解,你无法在大脑里面形成一个感性认识“大概就是这样推导的吧”,这时候你会面临两种选择:要么自己摸索着为该问题建立一个感性认识;要么死记硬背。大多数中国人会选择后者,而我个人极度不喜欢后者所以通常选择前者。

至于中国教材的作者,不是读死书的就是懒人,因为他们写书时跳过了引起读者感性认识的章节。读死书的不用说,他们根本不懂得如何写吸引人的东西,他们只能将自己脑袋里已经高度抽象化的概念写出来。而懒人则是大多数,他们有能力写吸引人的东西,可是他们懒,他们也想着节省纸张和读者的钱,所以决定不要写那么多。正确的叙述方式是“描述常见的现实例子 - 归纳出抽象的概念 - 应用概念到现实中”,懒的作者就会认为,这其中的第一部分可以省掉,或者合并到第三部分中,但这样做是违反了人的认知原理的,这样写的教材只会害了读者。

正如一个人类胚胎的发育会重新走一次人类进化的途径一样,一个人对某个概念的认知肯定也要走一次人类历史上对该概念认知的过程,虽然弯路可以少走一些,走的速度也可以快一些,但起点和终点还是一样,不能把起步的感性认识给去掉。

2006年8月29日星期二

Google Apps Hosted 上线

Google Apps Hosted上线了,包括Gmail、Google Talk、Google Calendar、Google Page Creator这4项服务,而且只要成功申请到Beta服务,就完全免费。这样看来,Microsoft Office Live又要加油了,否则又显得一文不值了。

首先从价格上,Google Apps Hosted就有相当的优势。需要使用Google Apps Hosted,你仅仅需要有一个域名,这个可以到Yahoo! SmallBusiness去买,$10/年,比在国内的某些域名提供商买还要便宜,而且服务好,什么子域名都可以设置,还可以设置子域名的范解释。而Microsoft Office Live除了Basic长期免费,其余Beta后就开始收费,而且最低的也要$30/月,实在是贵。而且免费附送的域名还是Microsoft全权管理和配置,你不能再分配下面的子域名。

然后从服务上来看,由于Google Apps Hosted的收费服务还没有开放,所以暂时只能够拿免费服务和免费服务比,也就是和Microsoft Office Live Basic比。Google允许你申请比较多的Gmail帐号,但Basic则限制你只有5个Live Mail帐号;IM双方都提供支持;Google有Calendar,Basic好像不提供Calendar服务;Google的Page Creator和Basic的在线HTML编辑器相差不大。如此类似的服务,从风格上来说,个人或非盈利性组织可能会倾向于Google,而中小型企业会倾向于Microsoft。

2006年8月28日星期一

稍微修改了一些 Post ,为大家增加了 Link

原本帖子中提到的人基本上都没有链接到的,现在都增加了链接了,方便了读者直接点击查看我提到的人。还有我提到的一些网站,也增加了链接了。现在回过头来增加那么多链接,都是想方便大家,也算是为该网站的PageRank作一点贡献,表明我认同该网站。

小睡 / Polyphasic Sleep

小睡,或者翻译为多相睡眠,也就是一天睡眠多次并且时间很短,以此来节省时间,通常也用于一些不能长时间睡眠的情况。严格的小睡安排,应该把一天24小时平均分成6段,每段4小时,每段的开头小睡20~30分钟,然后醒来就开始工作,直到这一个段结束,这样你一天仅需要睡3个小时。关于严格的小睡,大家可以参考Steve Pavlina的Polyphasic Sleep系列文章,他在自己的blog完整记录了自己连续5个半月体验小睡的情况。

其实我在中学的时候就见过有人体验非严格的小睡安排——白天安排每隔4小时一次的小睡,晚上有一次比常人短的长睡眠。已经保送并且有个人爱好要忙的人,通常会选择这种非严格的小睡安排,因为白天小睡导致错过的课程都是无关紧要的,一个白天多占用1.5小时睡觉,但却可以换来午休1小时和晚休1~2小时的不睡觉,而且醒着的时候机敏程度能够保持在比较高的水平。

当时我也喜欢在白天安排一些小睡:早上6:00要起床,应付早操和早餐等等琐碎的事情直到早读完,7:40开始第一节课也就开始我第一段小睡。8:20醒来,开始忙自己喜欢的东西,当然时不时也要应付上面的老师,直到12:00我的精力耗尽了也就去吃饭了。中午1:00到2:30按学校要求睡觉,下午就可以忙到6:00吃饭。晚上7:00开始自习我又开始睡觉,睡到8:00才醒,之后又可以忙到11:30。虽然我的做法没节省到睡眠时间,但是醒着的时候的机敏程度却得到了提高,感觉是很舒服的。我喜欢小睡醒来精力充沛的感觉,然后喜欢干需要消耗脑力的工作,就好像一块质量好的电池以一个恰当的电流放电一样,在使用过程中电压保持着不会掉下去,直到用完了电压突然掉下去了也就拿去充电了。(其实上课时候趴在桌子睡并不会太辛苦,习惯了就很舒服,睡眠质量也不错,不过不知道当时是否有影响到身边的mmm同学,有就惨咯,因为我连续一个学期都那样,希望她不要太介意吧。)

由于最近一段时间睡眠安排得不好,凌晨2:00才睡,睡到中午12:00,为了开学需要调整回来,所以想着干脆就调整为小睡好了,就算不能为自己争取更多醒着的时间,也能确保自己醒着的时候是最佳状态的。

最后就是想问一下,有没有人想和我一起体验小睡。首先小睡是对健康生理心理健康都是无害的,大家可以上网搜索一下有关的解释,最大的困然来自于晚上醒着的时间无法联系到其他人,所以只能干一些独自干的工作。这是让小睡者改回正常睡眠的一个主要理由,因为晚上的工作时间还是和白天的工作时间有所不同的,即使你在一个繁华的大都市所谓的不夜城很多的商业晚上还是会关门,至于凌晨营业的服务又不一定能够引起你的兴趣。如果全世界都采用小睡安排,那么很多小睡者都乐意保持下去。所以我想找多一些人陪我,变换作息时间,然后大家在晚上的工作时间仍然可以保持联系。

2006年8月27日星期日

灌了4瓶王老吉,系统扰乱中~

现在实在痛苦,仅仅是因为连续4天每天1瓶王老吉,哎……

好像我这种喝可乐大的,平时基本上不喝广东凉茶的人,突然间喝那么多王老吉肯定受不了啦。所以呢,要我戒可乐喝王老吉是不那么容易的,虽然可乐可以不喝,但是王老吉就算再好一次喝那么多也还是会死人的。如果可以,应给可乐混合王老吉喝,然后慢慢改变混合比例,哈哈!

无论要作什么改变,都是应该慢慢来的,矫枉过正就不好了。其实“重构”这种思想用于慢慢改变自己也是有效的,不要好像重写那样把一些已经存在的习惯推倒重来,而是保持结构化的慢慢改变,那么你可以保证自己任何时候都是一个“稳定版本”而不会显得脆弱并且无法应对突发的其他事情。

2006年8月26日星期六

中国人才市场暴缺testers?才不需要呢!

报纸上常常刊登这样的消息,看过之后就觉得超好笑,中国很多有软硬件开发能力的企业都可以站出来说自己缺tester,但真的要招tester吗?你去问问老板或者负责成本控制那位,肯定say no。

中国人还是习惯一次性购买商品的使用权,不懂后期服务是什么,既然买方是这样的思维方式,那么卖方就只有跟随。看起来功能相同的商品,一个质量差一个质量好,肯定后者贵而前者多人买的,既然这样要多赚钱就必须作质量差的然后多出货。在这样的背景下,很多开发者都会选择多快好省的开发途径,而不注重质量,特别是在好不需要核心技术的领域。

如何才能多快好省?拿网站开发来说,例如ASP/PHP之类的,就是直接搜索有没有能够满足客户需求的同类应用,最好能够搞到源代码,然后直接打开源代码修改到符合客户需求就交货。但实际上对程序质量有一定了解的人都应该知道,随便找来的源代码本身的质量就无法确定,可能高也可能低,在没有官方文档的情况下直接阅读代码来理解然后动手修改,很容易就会导致稳定性和安全性暴降。关于这个原理的证明,可以以“动网论坛”来举例,有人曾经说过“越是正规的网站,在使用‘动网论坛’时就会作越小的改动”,而看看那些加到满身都是插件的动网论坛,往往都是一些小组织搞出来的“看起来很Cool”的论坛,漏洞满身,要搞热不容易,搞热了就容易受攻击。

多快好省到底省多少?一个网站完全重新设计造价¥6000,找一个次品改一下充给你¥2000;一个网站完全重新设计造价¥2000,找一个次品改一下充给你¥500。只要有一个人开始拿次品出货,市场就会被他的出价扰乱,然后到底一个网站值多少钱就没人知道了。而背后的关键原因就是一个网站的质量是无从评估,至少对于中小型企业来说如此。除非作为买家的一方有专家负责察看源代码来估计质量,否则仅仅看看使用起来的效果如何,买家肯定无法判断为什么看起来类似的东西价格差异能够那么大。

回到tester的问题上来,既然造价¥6000与¥2000的东西客户不容易区分,那么产品是否经过完善测试客户就更无法区分了,这时候雇用tester就显得非常多余。说到这里我就突然想拿Dell来举例,Dell就属于没有核心技术也要冲高端产品的,它能够在高端组件(例如CPU/显卡)出现后立刻发布使用该高端组件的PC,但是该高端组件和PC内的其他组件兼容性如何呢?是否能够稳定运行呢?Dell是不知道的,仅当出现相当数量的问题反馈时,Dell才开始想办法解决此问题。

Dell在PC方面的例子,例如XPS产品线,我不了解无法举例。但是Dell在05年底Handheld市场发布的X51系列却是一个明显的例子。X51v发布时是Dell的最高端机型,拥有VGA屏幕,预装Windows Mobile 5,售价¥4000左右,当时能够拿来对比的是HP的hx4700。hx4700是HP的旗舰级产品,拥有VGA屏幕,比X51v早很多发布,所以安装的是PocketPC 2003 Second Edition,但售价却近¥6000,承诺在Windows Mobile 5发布后提供免费升级。大家能看到的情况就是,X51v简体中文发布后有一段时间了,hx4700还是没有提供简体中文版的Windows Mobile 5免费升级,而英文升级早已出现了。这时候你就应该考虑是以下两种情况中的哪一种:

  1. HP的技术弱,或者服务部门不抓紧,Dell都把使用简体中文版的新产品投放市场了,HP还不能把一个升级放出来。
  2. HP或者是MS在制作简体中文版升级时遇到问题卡住了,而Dell忽略该问题直接把产品投放市场。

结果看起来更像是第2种情况,X51v出现了不少软件bug,官方惟有靠发放补丁来解决问题,而且简体中文版还会出现一些英文版没有的bug。而这就是没有经过完整测试的结果。不过X51v的购买者则还是有机会在那里吹,看看你们花了¥6000买hx4700的还在那里无奈的等待HP发布升级,我花¥4000就用上了顶级的Windows Mobile 5产品咯。

2006年8月23日星期三

HighRing - 成年人听不到的铃声

人听力的有效范围是20Hz~20KHz,而随着年龄所造成的听觉劳损,成年人会逐步丧失对高频声音的听觉。英国的一家企业由此原理开发出一种用于驱逐青少年的工具,通过播放14.4KHz的高频噪音让青少年远离一些不对青少年开放的商店。

然而当青少年明白其中的原理后,他们就想到了一种利用此原理且对自己有利的用法,那就是用高频声音作为手机铃声,也就是所谓的HighRing。HighRing最常见的使用方式就是用作上课时的铃声,学生本人能听到电话响了然后接听,而站在上面讲课的老师却听不到。

想下载HighRing的铃声,可以到HighRing.com这个网站,上面有5秒和15秒两种铃声,并且有wav和mp3版本。

2006年8月20日星期日

xFruits - 玩转RSS

既然说过要说一些大家都能看的东西,那从介绍一些大家都能用而且比较有趣的服务开始吧。今天推荐的是xFruits,一个专门提供RSS相关服务的网站,虽然6月就开始Beta了,不过知道的人应该不多吧。

在xFruits,你可以看到RSS聚合、RSS到网站、RSS到移动网站、贴子到RSS、RSS到PDF等各式转换服务。这些服务看似简单,但结合起来却很好用哦。例如你平时使用RSS阅读器订阅众多的Blog,希望也能在手机上直接浏览这些Blog却暂时找不到实现的方法,那么这时候你就可以先用RSS聚合功能将你订阅的Blog聚合为一个RSS,然后再用RSS到移动网站的转换生成一个适合在手机上看的页面,那就可以享受直接在手机上订阅众多Blog的效果了。

RSS到PDF也是一个不错的功能,例如你多年的个人发表堆积在MSN Spaces,虽然东西很多但是却散落在不同的类别里面,你想将这些东西整理为一本出版物,那就可以尝试RSS到PDF功能了。整理出来的PDF绝对会让你满意,它会帮你自动将每一篇文章按日期排下来,保持原本的图片和链接,同时还会自动按3种索引方式为你添加PDF书签:按日期、按标题和按来源。

2006年8月19日星期六

写大家都能看都能评论的东西?

长时间看着自己的Google Analytics报告,发现回头率极低,大多数人都是通过链接或搜索过来然后看完他要看的技术文章就走了,不回访也不订阅。

嗯……虽然我不是太在乎自己的blog被什么人阅读,不过我觉得养blog有点像养植物,这种方法不好养就换种方法,反正不容易死掉。现在考虑写一些能够让我身边的朋友看一看和评论的东西,因为一般评论过的人之后都会再回头看看其他人的新评论,希望这样能够增加回头率。

另外Title也准备改一下,"Cat's Life"这个名称是当初不明确要一个blog来干什么的情况下起的,现在我不同的blog应该根据内容来定制一些有意义的title。

2006年8月18日星期五

Blog*Spot 解封 & 老牌 Blog 解说 & Blogger Beta

最近Blog*Spot可以直接从大陆访问了,不少人都开始转向用Blogger的服务,结果发现Blogger并没有现在常见的帖子分类/标签功能,而且模板编辑是纯HTML的也不容易上手。Blogger这么好声誉的网站服务就那么差吗?当然不是,这需要从blog的起源说起。

blog的始祖,或者说明是Blogger,开头考虑是给拥有个人网站的人用的。也就是我自己有一个个人网站,拥有静态的HTML,甚至是动态网站例如ASP/PHP。这时候我想写日志(当时还不明确叫做blog,或者刚刚开始叫做blog),那么我需要手动添加日志的页面,然后手动更新首页到各篇日志的链接,日志多了我会考虑每个月的日志都在一个页面集中起来或者集中链接,这叫做存档。但是手动这样做很不方便,就算空间支持动态我也懒得自己写数据库程序管理这一切,怎么办呢?这时候Blogger就来提供这部分服务了。

Blogger能够帮你保存日志的内容,然后自动帮你将内容生成HTML(考虑到多数空间仅支持静态HTML),然后通过FTP自动上传到你个人网站的空间(FTP上传是主流),这确实是一个很好的主意。发表的时候,你可以选择Blogger首页为blog.html,因为可能你自己的网站有真正的index.html,然后还有photos.html和books.html分别用于展示你的照片和阅读的书。那时候还没有flickr和douban,所以后面提到的两个频道是你自己手动更新的。

故事说完了,大家明白了吧?Blogger诞生的时代,面向的用户都是拥有自己的个人网站的,而Blogger只是用于自动生成网站其中的一部分,这些人都懂得如何编写HTML,所以他们一看就知道如何编辑HTML模板。至于后来完全为仅需要写blog的用户提供宿主服务的(也称作BSP),例如国内老BSP之一的blogbus.com,到现在的国内最多人用的MSN Spaces,都已经很傻瓜化。

顺便说一下的是,现在兼作宿主的BSP,提供存档功能仅仅是为了照顾大家的习惯。因为你看的根本就不是静态页面而是动态页面,动态筛选任何指定日期区间的帖子根本没难度也无差异。因此,这些BSP的存档一般都不允许你设置周期,就默认按月存档,不能好像Blogger那样按周或者日存档。

至于分类/标签是后来的BSP加上去的功能,Blogger这样的服务要实现分类的话它每次更新增量上传的数据量就要加大不少,如果你还经常修改分类/标签那么数据量就更加大了,这应该就是Blogger一直不肯增加分类这一功能的原因。不过最近Blogger提供的新的Blogger Beta服务开始支持这些功能,但Blogger Beta服务不能将blog以静态文件方式发布到你的FTP/SFTP,只能直接在Blog*Spot上展示。

2006年8月15日星期二

.NET 的灵魂是什么?

这是个很有趣的问题,但我觉得Microsoft也不能正面回答这个问题,估计他们会想rephrase这个问题,然后用他们的官方口吻回答。

先说说为什么会提这样的问题。那天和Piggest在逛购书中心,她说那些列很多Photoshop特效制作方法的书看再多都没有用,因为你看一个制作方法就只学到了这一个,而看《选择的艺术》这样的书,你才能领会到Photoshop的核心思想和使用方式,之后你就懂得如何灵活使用了。我接着就想,为什么ASP.NET的书都是那么肤浅,国内的书多数追求如何RAD式使用ASP.NET,国外的书就算在开发层面说得比较深入但都绝不提ASP.NET内部的一些运作机制,导致大部分ASP.NET的开发人员都只能按照一些官方给定的方式拼凑代码无法灵活发挥。为什么没有一本书能够抓得住ASP.NET的灵魂?是不是因为.NET本身就没有灵魂,所以你想抓也抓不到?

首先想一下MS会如何回答这个问题,.NET最有价值的地方就是语言无关。虽然它也好像Java那样应该是平台无关的,但是MS对非Windows平台的.NET Framework基本上毫无贡献,Mono发展至今也无法成为.NET Framework的第二选择。至于语言无关,这个特性的最大得益者似乎是MS而不是程序员,MS通过这招让大量其他语言的程序员转过来.NET。对于程序员来说,语言无关只是让不同语言的使用者能够方便的交流Assembly,但是对于设计与编码来说没有提供任何的核心思想。

接着MS应该会介绍.NET Framework自带的一大堆好好用的namespace,下面却是提供了很多很好用的类,例如ASP.NET 2.0就将Membership、Profile、WebPart等很实用的功能都引入了,大大方便了程序员。但是这又进一步突出了ASP.NET的RAD特性,除了RAD让人看不出它有任何深层次的思想。

那么除了提高开发效率,.NET有什么核心思想引导着所有的.NET开发人员,或许暂时没有。只有等MS慢慢成熟,整个企业塑造出一种除了赚钱和扩张以外的精神之后,.NET才可能有灵魂。

2006年8月14日星期一

第一次尝试使用 Windows Live Writer 发帖

今天在GFans看到有人提及Windows Live Writer,马上搜索一下是什么,结果发现是一个Blog工具,而且支持的API也不少,于是就下载了一个来玩玩。

安装的时候提示我是否需要装Windows Live Toolbar,犹豫了很久,最终决定装上,反正都是Beta,就试试看吧,虽然对Windows Live系列的Beta一向没有好感,结果发现原来是一个Google Toolbar的仿制品,T4拥有的功能它大部分都有,例如可以添加自定义按钮啊,可以自动填写表达啊,附加Virtual Earth, Live Mail, Live Spaces等我早就在Live Messenger看到厌烦的按钮。之后就很后悔选择了安装,马上通过IE的加载项管理把这些都禁用掉了。

然后说说装好的Windows Live Writer,填写Blog地址是发现出错,连接www.blogger.com/api总是被断开,不知道是哪方问题。之后发现它一个挺智能的地方,就是能够在你的Blog发一张临时贴,然后探测你的Blog的Style,那么你在编辑时就能够按照这种Style进行预览了。至于其功能,感觉一般半,就是界面漂亮,可以插入照片可惜我通常不直接上传照片而传到flickr,可以插入地图但遇到Virtual Earth的脚本错误。但总的来说MS现在的新产品都支持XHTML 1.0 Transition已经算是好事情,我开头也担心过它自动生成的是MS DOM那种糟糕的HTML。

暂时能说的就这么多了,试一下发表~

2006年8月11日星期五

Meebo 和 GMail + Talk 等 WebIM 的实现方式

我用Fiddler监听过Meebo和GMail + Talk,终于知道他们是如何实现event机制的了(也有人称之为Comet模式)。event包括好友发信息给你、好友上线/下线,甚至连是否显示某某人"is typing"的状态变换也算,反正就是指模仿桌面应用event机制需要服务器端主动触发客户端的东西。

例如有一个event.ashx是专门负责发送event消息的,那么工作的时候就保证一个客户端至少有一个活动的http连接在请求event.ashx。首先客户端要connect上去并发一个空的request请求event.ashx,然后服务器端则等待事件,如果在等待期间有事件发生则服务器端马上将事件信息写入response返回并disconnect掉,如果一段时间后没有事件发生则给出空的response并disconnect掉。客户端一旦收完response并被disconnect掉,马上进行下一次的connect和发送下一个空request以等待新的事件,如此重复下去。另外事件传输时可能也可以应用batch机制,也就是第一个事件触发后等几毫秒看看是否有紧随的事件,有的话也把其数据增加到response中一起发给客户端。

这样做的好处是,不会好像每秒一次请求检查事件那样非常消耗服务器端资源,但是占用着服务器的端口其实也是一种资源浪费。一台机65535个端口,幸运的话我想最多能够同时使用几百个,超过了网卡的缓冲区应该就负载不了了。

2006年8月8日星期二

中国IT从业人员学习障碍:心态大于语言

在查阅Atlas资料的时候我看到,竟然有人说用UpdatePanel不就已经很好,做出来的效果也无刷新,为什么还要花那么多时间去研究Atlas的客户端使用方式。这时候我想起Piggest关于中国CG界的评论,我才明白为什么中国的IT界总是和前沿技术扯不上关系,原来这不是语言造成的门槛,而是心态造成的障碍。为什么大家都不看英文的前沿资料呢?往往不一定是看不懂,而是不愿意使用前沿技术。使用前沿技术是有风险的,遇到致命问题将可能导致整个项目的失败,就算不至于项目失败项目也是在探索中前进的,项目的参与者必须一边研究一边之行执行项目,这是大多数单纯想赚钱的人不太愿意做的事情。 这就导致了大家都不愿意使用前沿技术,宁愿让别人把该技术用烂了自己才拿来用,因为用烂了的技术肯定是遍地资料的,别说"用",就算是"抄"也可以——随便搜索一下就可以把别人的成果拿过来。

同时这也造成一个恶性循环,就是在中国真正会去接触前沿技术的人也不会愿意和大家分享。如果你是常常看前沿技术文章的人,开头你会考虑将部分翻译为中文,或者用中文讲自己掌握到的资料表达出来,希望分享给中文社区的用户。但接着遇到的只会是其他人的忽略甚至是鄙视,你觉得你自己完全没必要对中文社区作贡献,这时候你就会下狠心不再管中文社区,反正你在英文社区也能和其他人很好的交流然后获得提高。

当然前沿技术接触者也有另外一种做法,很商业的做法,就是译书或自己出书。由于大家都不屑于看那些要摸索去做的东西,而热衷于那些能够抄来就用的东西,所以如果你在前沿技术中摸索出一条路来然后马上写一本实用性很强的书,即使这些实用之处完全没有发挥出技术的优势而仅仅是看起来很Cool,那也会有很多人买去抄,这时候你就能狠狠赚一笔了。这就好比市场上充斥着很多设计类软件的特效书,这些书根本不涉及该设计软件的核心或者说是灵魂,但它就是把最近热门特效的制作方法罗列出来了,然后就很多人去买。

无论如何,这种心态让大多数中国人最终成为了熟练操作一些用烂了的技术的工人,今天制造业上中国是世界工厂,明天在IT行业上也将只能是世界工厂,甚至有些人说仅仅是世界车间,因为先进的工厂也有自己的研发部门和市场部门但中国只有生产线。

为了兼容IE把页面的DOCTYPE去掉了

IE浏览我的Blog,总是以左侧高度作为页面总高度,右侧高度多出部分就会隐藏,不知道为什么这样,明明就是符合XHTML 1.0 Transition的。弄了一段时间没搞好,干脆就去掉DOCTYPE,让IE自己用Quirk Mode来解释吧。

2006年8月7日星期一

试用Microsoft Office Live的服务

很早之前就填写了Office Live Beta的申请,期待着二月能够收到邀请,结果一直没收到,直到最近收到Microsoft的邮件通知才知道已经不需要邀请也能注册了,于是马上跑去注册了一个。

Office Live Beta由低至高分为3个版本:Basics、Collaboration、Essentials,在Beta阶段全部免费,在Beta结束后只有Basisc免费。虽然Office Live暂时是免费服务,但注册还是需要信用卡的,这是为了避免滥用行为,以确保一个在现实中有一定credit的人才能注册一个Office Live站点。开头我注册了一个Basics站点,后来想想既然Essentials暂时也免费,而且MS说明Beta结束时会让你选择是否继续使用收费服务(MS应该不会好像中国的手机服务提供商那么无耻用前期免费服务来钓鱼吧),所以也就选择升级到Essentials了。

成功注册后就可以登录Office Live的Member Center了,快速的翻阅一下所有功能后你就能够快速上手了。在Member Center中你可以选择你的网站的Theme,编辑页面的内容(包括上传照片和文档)。可能由于Office Live针对的是小型企业或大型企业中的小团队,所以它的网站编辑环境都是很傻瓜的,有点像Google Pages那样,不过Google Pages做出来的网页很有个人网站风格而Office Live做出来的网页很有小企业门户风格。

在Member Center中,你还可以配置你的商业应用,这其实是一个类似Sharepoint的系统,可以建立Sharepoint列表,管理你的客户、项目、资源等等的信息。当然你还可以把你的员工的Live ID加入进来,让他们也有权限操作这些列表。如果你的员工还没有Live ID,或者你想为他们分配你的域名下的Email地址作为他们的Live ID,你也可以在Member Center中分配Email地址给他们。

整个Office Live,包括很早就推出了的Office Live Meeting服务,其实都不是MS新开发的东西,这些所谓服务以前都是直接销售的服务平台软件。MS应该知道只有大企业才有足够的资本去购买自己的服务器,然后在自己的服务器上运行这些大型服务并且管理企业内上千台的客户端,所以就提供了租用式的服务,也就是现在大家看到的Office Live。对于使用过Windows Server Family的人来说,Office Live应该是相当熟悉的,里面提供的服务就如一台小型的Windows Server,上面运行着受限Sharepoint Server和Exchange Server。以后随着Office Live的扩展,可能会有根多原本独立销售的服务平台软件变成以租用服务的方式销售。

2006年8月3日星期四

使用 Google Analytics 统计 Community Server 新注册用户来源

很多人用Google Analytics都不会用到“目标”这一功能,可能都以为这项功能是给电子商务网站用的吧。但这其实是一个很好用的功能,能够让你方便的衡量某一特定价值(不一定是金钱),监测该价值的来源。

现在用一个社区(Community Server)来举例,新注册的用户就是一个可衡量的价值。首先进入Google Analytics,在网站配置开启一个新的目标,接着按以下步骤设置:

1.目标地址写上"~/Msgs/default.aspx?MessageID=24",这是显示“注册成功”信息那页的地址,其中"~"指网站根目录的地址,在使用时你必须替代为真实的地址,目标名称写上“用户注册成功”。

2.然后填入步骤1写上"~/user/CreateUser.aspx"和“用户注册表单”,并把必要步骤给钩上。

3.最下面的匹配类型选标题匹配,目标价值输入100。

这样就设置完一个目标了,我解释一下为什么要这样设置:

1.需要步骤1,是为了避免有些用户不是在注册却无缘无关进入了“注册成功”页,然后就被统计为一个成功注册的用户。同时有步骤1的话也就能统计有多少人开始填写注册表单但最终没有完成注册的。

2.设置价值为100,是为了统计数据上好看。在数据报表上,加之为多少也就意味着每100个来源中有多少个人成功注册了。

设置好后等待几天,你就能够在Analytics看看收集到什么了。在已定义的导航渠道你能够看到所有访客中多少进入了注册表单,其中又有多少完成了注册,没有完成注册的转跳到什么页面了。你还能看一下不同的Referrer以及搜索关键字带来的价值,有些来源的用户数量很多但是价值很低,而有些则相反。

最后,如果你愿意改进Community Server的源代码的话,还可以考虑统计其他价值,例如将用户发帖/回贴的字数作为一种价值。

2006年7月26日星期三

初次使用Atlas JavaScript (Part 3 - 实现自己的应用)

在这里,我假设你肥肥的客户端是基于MVC设计,同时Web Service的内容是以数据为中心的。

在说客户端之前,必须先说说服务器端如何设计。如果服务器端你习惯了3层设计,那就保留你以前的数据访问层和业务逻辑层设计方法,用一模一样的方法完成这两层,只不过我们不再有通过Page表现的UI,而是通过Web Service直接暴露一些业务逻辑层的调用,这就是服务器端需要完成的一切。看起来没有了Page轻松多了,是不是?因为那些工作都转嫁给客户端了。接着要开始用Atlas做我们的客户端了,在这此之前你需要读一下Atlas的各种官方/非官方文档,了解一下它的使用方法,同时读一下它的JavaScript源代码了解一下它的内部结构。

我们首先来看看客户端的Model如何实现,客户端Model应该是用来存储数据并和Web Service打交道,所以它应该设计为一个处于客户端的业务逻辑层。然而这不是一个单纯的业务逻辑层,因为真正的业务逻辑在服务器端,客户端的Model仅仅是在客户端暴露和服务器端业务逻辑层一致的调用。同时它也内涵一个小小的数据访问层,这个数据访问层服务则Web Service打交道,需要包括批量调用和数据缓存与索引等能力。现在我们的分层看起来应该是这样的:
Server Data Access Layer <-> Server Business Layer <-> Client Data Access Layer <-> Client Business Layer
我想肯定有人要问,为什么不把整个业务逻辑层搬到客户端,然后就可省去服务器端的业务逻辑层咯,让客户端的数据访问层和服务器端的数据访问层直接沟通就好了。这样做的最大问题就是,你不知道客户端提交的数据是否一定经过了客户端那个业务逻辑层的处理,用户直接提交一些不符合业务逻辑的数据到服务器端就麻烦了。

继续说我们的客户端Model需要具有的能力。例如服务器端业务层有GetProductById这个方法用于返回一个Product对象,同时客户端的Controller也需要调用此方法,那就应该在客户端的Model暴露GetProductById这个方法。最简单的办法,当然是每次客户端执行GetProductById时,都通过Web Service调用服务器端的GetProductById,然而这却是最低效率的做法。想要提高效率的话,肯定要在客户端缓存数据,这时候客户端就会缓存一些Product对象,而GetProductById方法则需要根据一定的策略决定应该优先搜索缓存还是直接访问Web Service。缓存策略是需要根据应用制定的,什么情况下应该判定缓存过期是没有通则的,这需要你自己去设计。而缓存的方式则有比较固定的做法,就是利用JavaScript特有的关联数组作索引。例如ProductId是Guid,而且使用之前说到的GuidConverter直接转换为String,那就可以创建一个Product.IdIndex对象,然后以ProductId作为关联数组的键来保存Product对象,这样就可以通过Product.IdIndex[ProductId]方法检索一个该ProductId对应的Product对象了。

Model相关的对象,例如Product类和Product.IdIndex对象,可以完全不使用任何Atlas特性,仅仅是普通JavaScript类,因为它们完全不使用Atlas的任何特性都能够好好工作。但你也可以考虑让他们继承自Sys.Component,同时按照Atlas的方式来声明其属性、方法、事件,这样他们能够获得更好的封装性,对debug.dump更好的支持,以及可能更低的执行效率。

然后轮到说View了。View可以说是最简单的部分。说它简单,是因为它和服务器端的View没什么不同,只要你懂得制作服务器端控件,你就知道如何继承Sys.UI.Control制作客户端控件,然后和在Page中一样直接往DOM扔控件就可以了,而且还不需要管SessionState和ViewState这样的东西。有问题吗?例如需要数据,或者有事情需要报告吗?请举手,通过调用或者事件告诉Controller,Controller会通过Model为你处理好这一切,View需要管的仅仅就是用户看得到摸得着的东西。

最后需要搞定的就是Controller了,这时候你有两个选择:使用JavaScript或者使用XmlScript。如果Model和View已经让你写JavaScript写到手痛,那么这时候你可以考虑使用XmlScript来写Controller。你首先把View相关的元素用HTML声明,然后在XmlScript中声明相关的控件以及它们的属性如何绑定到对应的Model对象,那就行了。但如果在Controller里面设计动态创建控件,又或者涉及复杂的数据绑定方式,XmlScript就无法满足你的需求了,这时候还是老老实实地用JavaScript写吧。

2006年7月23日星期日

初次使用Atlas JavaScript (Part 2 - Web Service扩展)

Atlas对Web Service两方面的扩展包括:
1.自动生成javascript代理类的代码
2.在javascript代理类调用时使用JSON表达式进行数据交换

通过看Atlas项目的web.config,你可以发现*.asmx现在关联到了一个新的handler,也就是ScriptHandlerFactory,这个handler的行为很简单,如果请求是REST方式则用新的RestHandlerFactory来处理,否则交回给原来的WebServiceHandlerFactory处理。RestHandlerFactory也有一个选择分支,如果请求的是*.asmx/js,那就是请求客户端代理类的代码,使用RestClientProxyHandler处理,否则使用RestHandler处理,下面详细说说它们的原理:

先看看Web Service的引用,当你在ScriptManager添加一个Service引用的时候,实际添加的是到该*.asmx/js的script引用,然后就得到了代理类的代码,之后你就可以直接在javascript中使用该Web Service的类名称(包括完整的namespace)调用,参数列表则和该Web Service的参数列表一致,最后加上一个参数声明该调用的回调函数。RestClientProxyHandler需要生成如此一个代理类,就需要通过反射扫描所有的Web Services函数签名,然后将函数相关的所有类型注册到客户端。这个注册并不简单,因为Web Service有可能涉及任何类型,包括用户自定义的类型。如果是.NET Framework的一些基础类型,Atlas知道如何将它们转换为对应的javascript对应类;如果是Enum,Atlas也能够自动转换为对应的javascript代码;但遇到了不是基础的struct或者object,Atlas自身就不懂得如何将它们转换为对应的,这时候就要寻找是否有对应的JavaScriptConverter了。

JavaScriptConverter是处理服务器端和客户端之间对象串行话/并行话的基类。现在用创建一个GuidConverter的目标来举例,首先通过SupportedTypes属性声明自己支持的服务器端类型为Guid吧,再通过GetClientTypeName返回客户端类型的名称为String,这样RestClientProxyHandler就知道生成代理类时Guid类型的参数在客户端对应为String类型。

接着说说RestHandler,它处理Web Service参数的时候使用的是JSON表达式。和RestClientProxyHandler一样,对于能够处理的类型它自己能够进行串行话/并行话转换,对于不能够处理的参数它寻找对应的JavaScriptConverter帮助。

继续上面GuidConverter的例子,我们需要实现Serialize和Deserialize这两个函数,将Guid串行话为String仅需要这样:
return "\"" + ((Guid)o).ToString("D") + "\"" //o是待串行话object
而将String并行话为Guid则仅需要这样:
return (new Guid(s.Replace("\"", string.Empty))); //s是待并行话的String的表达式
最后在web.config的converter节注册一下GuidConverter,Web Service就能够正确的和客户端交换类型为Guid的数据了。

系统自带的JavaScriptConverter包括DataSetConverter、DataTableConverter、DataRowConverter,注意这3各类都没有实现Deserialize所以仅能用于将数据传送到客户端。

初次使用Atlas JavaScript (Part 1 - JavaScript扩展)

Atlas的服务器端控件已经说了,现在说说客户端脚本。Atlas的脚本分为xml-script和javascript两部分:xml-script作为一种描述性语言主要用来做Controller,直接放在页面里面;javascript经过Atlas库扩展后可以用来写Atlas特有的客户端控件,而其内容最好放在独立的js文件中通过引用来使用。xml-script我还没有专门研究过,不是太懂其机制,所以现在先说说其javascript扩展。

首先,Atlas对javascript语言本身作了一定的扩展,例如让javascript支持继承,还有抽象函数、寒暑重写等概念,可以说是极力模仿C#风格。这对于习惯用C#的人来说是好事情,而且内联而非prototype方式声明外露函数和属性也确保了内部变量的不会被外部访问到,从而确保了类的封装性,但这样做却让Altas的执行效率显得比其它framework要差一些。另外Atlas还对javascript本身的String、Array、Date、Number作了简单的扩展,目的是为了模仿.NET类库对应的类的常用方法,方便javascript编写者。关于Atlas对javascript的扩展,详细可以参考这篇ASP.NET Atlas对JavaScript的扩展。(由于Atlas还在不断改进中,无论是官访文档还是自己研究Atlas源代码者写的文档都仅仅是关注到作者认为应该关注的方面,所以资料不一定全面,而且还不一定适用于日后的版本,所以最好还是使用时参考Atlas的源代码。)

当然,Atlas不可能全面扩展javascript的对象让他们拥有所有服务器端对应对象的常用方法,这时候你就需要自己扩展了。例如Date对象没有AddYears等方法,而你的程序需要这方面的Date对象操作便利,你就可以模仿着服务器端的方法去写对应的方法。模仿的最简单办法就是用Reflector打开.NET Framework看,看看它是怎样实现的,然后你改用javascript实现。由于.NET Framework系统类的实现方法效率不可能太低,所以按照它的方法去做效率应该也不会差,不过你自己有更高效的方法就更好了。

2006年7月19日星期三

Atlas 的服务器端控件 - 易用但不灵活

首先,我会把Atlas服务器端控件区分为两类,纯服务器端控件和客户端逻辑封装控件。前者类似WebControl派生出来的控件那样,它自身并非直接呈现(render)一个客户端元素的实例就算,而是拥有复杂的服务器端逻辑,它呈现出来的HTML由交错的规则决定着;至于后者,则类似HtmlControl派生出来的控件,它所做的基本上就是将自身呈现为一个单一的客户端元素,其服务器端属性比较直接的生成客户端元素的属性或者子元素,不过客户端逻辑封装控件输出的不是HTML,而是Atlas特有的xml-script。

纯服务器端控件是Atlas服务器端的重点,这些控件在Microsoft.Web.UI而不在Microsoft.Web.UI.Controls,由此也可以看出他们的核心地位,它们是ScriptMananger(ScriptManagerProxy)和UpdatePanel。

首先说说ScriptManager,它都是一个非常有用的控件,扔一个ScriptManager到Page上面,它就会自动帮你生成引用Atlas核心js的引用,这样Atlas函数库就能够被使用,xml-script也能被解释。对于一个ScriptManager,你可以向它添加Script和Service引用。

可添加Script引用的包括Atlas内置的Script模块(AtlasUIDragDrop、AtlasUIGlitz、AtlasUIMap)和是你自己的js文件,如果添加的是js文件你可以使用"~"路径运算符以确保无论在什么路径的页面都能正确引用这个js文件。

添加Service引用的Service是ASP.NET Web Service,添加引用后你可以直接在JavaScript或xml-script中调用该Web Service而无须管后台是怎么实现的。另外,ScriptManager还有一个重要的属性,就是EnablePartialRendering,启用后ScriptManager就会去分辨一个PostBack是正常提交的PostBack还是XMLHttpRequest发回来的异步PostBack,如果是后者它就会优化处理,详细下面再解释。

然后说说UpdatePanel,这就是Atlas让你最容易开发一个“看起来很Ajax”的应用的地方。所谓的UpdatePanel,其实就是圈定一个区域,里面的控件或者外部注册为异步调用的控件触发PostBack时并不真正提交页面而是用XMLHttpRequest发送异步PostBack,之后分析从服务器端返回的HTML并更新这个区域内的HTML代码。你可以设置一个UpdatePanel不是每次都需要更新内容,当一个异步PostBack发生时就根据一堆的Trigger设置来判断一个UpdatePanel是否在本次异步PostBack时需要更新,同时ScriptManager开启了EnablePartialRendering的话则只有需要更新的区域内的HTML才会被发送到客户端,这样就可以节省沟通成本。

虽然这样看起来UpdatePanel非常灵活,区域有多大以及什么条件下才更新都能够设置,一眼看起来设置适当的话非常好用。但其实并不存在“设置适当”,UpdatePanel并不是万能,它不能像普通控件那里放在任何区域之内、包容任何控件、在页面生命周期的任何阶段声明,所以不是你喜欢怎样放就怎样放。而且当UpdatePanel多起来Trigger也不容易设置完美,总会导致应该更新而没更新的情况,而那个时候你就会想还是把所有UpdatePanel设置为总是更新算了。

UpdatePanel最坏的地方我认为是破坏对象结构。服务器端控件和客户端的DOM都是树状结构,目标就是保持逻辑的局部性,所以Response.Write和Document.Write这种破坏局部性的操作方式我觉得都不应该被使用,而UpdatePanel区域的刷新就如Response.Write一样,一刷就是整个区域。虽然在服务器端区域内的还是控件,还是可以按控件进行逻辑处理,但是在客户去这样一刷,DOM元素都被破坏掉了,区域内增加删除修改了哪些DOM元素完全没办法知道,这时候该区域内外的View和Model都独立,要建立一个跨越区域边界的Controller也不容易,当内部的View和Modal被整个替换掉的时候,Controller完全不知道变更的细节,而需要自己去适应新送上来的View和Model。对此暂时我觉得可行的解决方案就是建立一个内部的Controller,让该Contoller负责和外部Contoller的沟通,而该Controller由服务器端生成,所以它可以知道新旧的View和Model之间的变更细节。

简单来说,用UpdatePanel就如用不闪烁的IFrame一样,传统的做法能够马上用上,但效率不高,灵活性也低,最后你会发现不如整个页面都放在一个大的UpdatePanel里面,让它“看起来不闪烁”就算了。

2006年7月8日星期六

ViewState - ASP.NET 的一个特有存储容器

首先,我不确定是不是只有ASP.NET由ViewState,也不确认它有多特有,只是觉得这个东西对于Web开发MVC分离的进步很有帮助。

所谓的ViewState,就是用来存放关于View的State的地方。以前的存储容器包括Cookies, Session, Application, Cache, Hidden,有时候连传递变量用的QueryString也用作存储容器,但都不是专门用来存储View相关信息的地方,然而由于没有专门存放View相关信息的地方,所以人们只好乱放。不怕过期失效的变量,多数人会选择放在Session里,而且跨页面不会丢失,用户访问几个别的页面回来还能通过Session恢复本页的View。如果需要延长一些时间,而数据又不是很多的话,可以放Cookies,和Session类似。而数据真的很短,而且页面总是提交给自己的情况下,用QueryString作为一些跨页面生命周期的变量的保存方式也可以。而如果页面可以只用Post不用Get传递的话,那么Hidden也是一个很好的选择,因为Hidden容量大,不在地址留下信息。在ASP.NET当中,就是设计到大多数情况都是Post(除了直接链接),所以用Hidden存放和View相关的信息是非常适合的。

既然用Hidden存放就可以了,为什么需要ViewState这样的统一管理呢?最显然的理由就是加密。ViewState不是给用户看或者修改的信息,仅仅是因为View的状态会在页面生命周期之间丢失,所以我们要将这些信息输出到HTML再等Post的时候取回来,对ViewState加密(至少校验)能够确保View正确无误的恢复。ASP.NET内置的ViewState可以方便的设置校验和加密,只要你把可以序列化的对象存进去,它就能够自动序列化并输出到HTML,同时该对象的保存与恢复是跟控件名以及在控件树中的位置相关的,就如ASP.NET控件的其他特性一样,确保了树中不同位置的同ID控件的数据不会被混淆。简而言之,ViewState是保存跨页面生命周期有关变量的最好容器,但它又不能够跨出页面范围称为会话变量的容器(那应该是Session负责的哦),所以是真正符合其名称用来保存View的State的。

有很多ASP.NET的新手不知道ViewState的用途,认为它是ASP.NET的内部对象,平时还是仅用ASP那套公开的对象好了,那就会带来很多麻烦。例如有GridA和GridB两个页面,点击一个条目查看明细都回转到Details页面,同时Details页面要提供返回原来页面的途径(包括原本GridView所浏览到的分页)。如何存储原本页面的状态呢,包括它来自GridA还是GridB以及原来的GridView所在的分页?有人选择用QueryString传递给Details页面,让Details页面构造返回链接时再通过QueryString把状态传回去。显然,原来页面的状态不是对Details的Query(查询),那么用QueryString传递给Details页面是不合适的。也有人选择用Session传递,但是这个状态仅仅做一次传递,难道我对你说一句话也算是Session(会话)?当然不算。用Hidden是一个方法,但是基于我上面所说的ViewState对Hidden的改良,在这种情况下就应该用ViewState作为原本GridA或GirdB的View状态的保存方式,并且在经过Details页面返回之后再还原。需要说明的是,只有ASP.NET 2.0才在内部支持跨页面PostBack,ASP.NET 1.x不支持跨页面PostBack,也无法跨页面接受和保持ViewState。

最后就是使用ViewState需要注意的地方。过量使用ViewState当然有损效率,但这对于网站的数据正确性来说还不至于造成漏洞,真正会造成漏洞的就是ViewState重名。上面不是说了ViewState是根据控件树位置和控件ID保存所以不会重名的吗?在树是静态的情况下确实是这样,但是如果树是动态创建的,而ID则是可能在不同的语义下重复使用的,那就可能破坏数据的正确性了。例如一个DataControl的子控件自动命名为Control_1, Control_2, Control_3等,如果在PostBack后该DataControl被再次DataBind,那么DataSource可能已经改变了,所以需要清除所有的子控件并重新根据DataSource创建它们,此时别忘记了调用ClearChildState方法把子控件的ViewState和ControlState都清除掉,否则当你重新创建子控件而它们的名称还是Control_1, Control_2, Control_3等的时候,就可能会把原来的ViewState加载回去。

2006年7月1日星期六

OpenID - 继claimID之后

首页在这里:openid.net

OpenID的目的是一个真正的分布式Identity系统,可以用于验证你是某URL的Owner。在范例中,它假设你有一个Blog,然后你在另外一个支持OpenID的Blog发表评论,在评论后你就可以输入你的OpenID登录。所谓你的OpenID,就是一个URL,例如你的Blog的URL。在收到评论提交和OpenID后,该Blog系统就会去你的URL检索OpenID信息,并到指定的OpenID服务器验证你的登录,验证成功就确认你是来自该URL的用户。

如果用过Google Sitemaps,你就知道要查看该站点的统计信息必须先确认你是该站点的Owner,而方法就是Google会要求你对该站点的首页插入一个指定meta标记,然后Google检测到该标记后就确认你是该站点的Owner了。Google Sitemaps是由Google统一确认你的Owner身份,而OpenID则允许你在你的页面中通过<link rel="openid.server">指向任何一个OpenID验证服务器,这就是它所谓分布式的地方。至于指向的Server通过何种方法确认你是该URL的Owner则是不确定的,例如如果该Server就是你的BSP(Blog Service Provider),那么你提供你的Blog的URL和密码它验证,这是很简单的事情。

2006年6月28日星期三

ASP.NET 设计优秀之处

我觉得ASP.NET设计得最好的地方,就是控件树的概念。虽然WebForm的概念很烂,误导了不少没有Web开发经验的程序员,但它带来的控件这一概念及控件树结构就真的很好。

控件树优秀的地方,就在于它把整个页面的处理逻辑分割到控件,控件与控件之间是松耦合的,不存在任何依存关系。以往的Web开发,所有的代码都是平板式的接受Request平板式的输出Response,Request被看作由不同的Request属性组成,Response被看作不同的Response片断,而这些由平板式的代码混合在一起处理,导致Request属性和Response片断之间的耦合度很大。而在ASP.NET中,一个控件就只取它需要的Request属性,然后对它输出的Response片断负责,那样就相当于解耦了。

如果两个控件之间存在一定的逻辑,这些逻辑就应该交给一个可信赖的第三方统一管理。一般情况下,这个第三方是父控件,因为父控件有权统一管理子控件的行为,所以子控件之间的交互逻辑也可以交由父控件处理。另一种方法是通过专门的控件负责,例如WebPart之间的逻辑(WebPartConnection)统一由ConnectionsZone管理。控件间逻辑集中在一个地方,统一管理,需要时统一根据涉及控件的存在性判断丢弃该部分逻辑或者抛出异常,这还算是一定程度上的松耦合度。

有很多ASP.NET的新手,特别是有ASP经验的,一上来就喜欢用Response.Write输出一些无法用控件输出的信息,其实这是不对的,任何一类信息都应该由专门负责的对象负责输出,如果不存在该类对象你就要自己制作一个负责此类逻辑的类。用Response.Write输出debug信息是常见错误,实际上应该开启Trace然后用Trace.Write或者Trace.Warn来输出debug信息。用Response.Write输出script也是很多人在用的方法,实际上应该用Page.ClientScript下面的RegisterClientScriptBlock等专用script注册函数。至于输出的是HTML的话,则绝对不应该用Response.Write了,如果没有任何控件能实现你要的输出逻辑,就自己写一个,按照自己的逻辑写Render,Render就是专门给你书写自己的局部HTML输出逻辑的地方,而且在Render中你最好用HtmlTextWriter的特有方法来输出HTML,因为那样做能够比较容易将Render的代码迁移到别的Mark-up Language输出环境。

2006年6月21日星期三

学习应该追求“游刃有余”的效果

“游刃有余”这个词是从哪里听来的呢?是某年GDKOI听郭嵩山(中大搞ACM兼负责广东省OI的那位)说的。他当时要表达的意思是,中大不会保送仅仅是竞赛好的学生,你必须平时学习也好才行,因此他说必须平时学习游刃有余才好搞竞赛,否则还是专心学习好。当时我就觉得这句话应该将因果倒置一下才更真实反映现实,那就是你平时学习保持在游刃有余的水平就够了,这样你才能够在竞赛方面有所发挥。

后来见到Coo的del.icio.us收藏了一篇文章,标题大概是“让一个人失去创意的最好方法就是让他超负载工作”。然后最近ziyan又说,Benny对着教学处主任说“想发展兴趣的学生是不能参加高考的”,于是我决定写一下这个题材。

“参加高考”指什么?当然不是指高考这几天的考试。我也去高考了,但对我来说无非是玩下,成绩也不入档案,更加不存在复习备考,所以不算“参加高考”。“参加高考”指的是由于高考而由高考那天此向前延伸1年的全年备考,甚至再向前延伸0至2年的部分自由的剥夺期,而这1至3年的时间里,你就处于那种“超负载工作”的状态下,所以“参加高考”实质上指的应该是至少1年的“超负载工作”。

然后有人会说,“不是啊,我高三很轻松很堕落啊”。这时候我觉得就有必要将“让一个人失去创意的最好方法就是让他超负载工作”这句话向广义推进一下,让主语变为“一群人”。由于你所处的群体处于“超负载工作”中,而创意往往是需要通过讨论、阅读等途径激发灵感,正如一个处于低温物体中的粒子一样,即使它有一个时刻处于高能状态,它也只能将能量散发出去,而不能长期处于高能状态。

如果一个学生能够让其学习保持在“游刃有余”的状态,无论他学习怎样,他总能够将他剩余的能量发挥到创意上面去,这才叫做“发展兴趣”。至于“发展兴趣”好不好,这就要看个人了。有些人天生喜欢跟大流,那是没得说的。有些人“发展兴趣”结果更好了,自然有些人结果更差了,这属于正常现象,否则所有人都去“发展兴趣”了。Benny说过,如果高考是独木桥的话,竞赛就是峭壁——很宽很宽问题只是你是否爬得上。

最后,就是必须区分“游刃有余”和“好吃懒做”。“游刃有余”并不能成为“好吃懒做”的借口,否则你将剩余力量都浪费掉了结果就当然比“超负载工作”要差。

2006年6月7日星期三

为什么 BBS 在中国那么火 (Part 2 - 社区生态学)

好了,抛掉那些有暴力没美学的沙子,继续挖掘论坛的金矿。社区研究之BBS的死亡鉴定里面的理论是"火了-水了-完了",感觉对于大多数公开论坛都正确吧,但其实这里讨论的都是仅做内容服务的论坛,人们来论坛的唯一目的就是论坛里的内容,所以必须以内容养内容。但是对于不用以内容养内容的论坛来说,例如提供下载服务的TLF,因为人们是为了下载而来的,所以即使内容不怎么样也会有相当的流量,流量多了自然有人发言,发言多了既然有精华,然而即使水了也不会完了,因为流量仍在,水多水少好像一个生态环境那样自然平衡,而下载服务就好像绿色植物那样维持着食物链的存在性。

还有一些看起来不完全是以内容养内容的论坛,但又没有绿色植物的,例如一些下载交换论坛。所谓交换,就是下载不是由官方提供,而是靠大家上传,如果人都走了同样论坛会死掉。不过这样的论坛有一个好处,就是文字内容不是价值的主要来源,价值的主要来源是上传,这时候哪些内容有价值哪些内容没有价值是非常容易区分的,也就一定程度上避免的水化这个过程。

讲到内容价值,就不得不讲精华贴这个设计。这个设计是用于奖励发贴者的吗?初衷可能是,但结果绝对不是,精华贴的作用是为了浏览者尽快找到他们要找的东西,即使他们在无聊闲逛也尽快为他们提供有新鲜的有价值的事物让他们结束无聊阶段。看看TLF,已经不仅仅是有精华贴了,而是贴子分等级了,普通贴子普通颜色,不同类型的"公务贴"用不同颜色的粗体显示,让你一找就找到你想要的东西。例如在字幕区吧,发布字幕的贴子为绿色,而求字幕或者讨论字幕的贴子则为普通的黑色,这样找字幕的人一眼就能够找到字幕。即使你通过电影名称搜索字幕吧,搜索结果里也会用绿色把发布字幕那一帖显眼的显示出来,当然还可能看到蓝色的显眼贴,那是测评。有些人说论坛社区是按主题聚合,Blog社区是按人聚合,什么聚合有什么特点,但现实是——只有你想不到的聚合形式,没有实现不了的聚合形式。TLF那样把不同版面同一类型的公务贴同一颜色标记,也算是一种对内容的聚合形式,不是吗?

回到生态学的问题上,既然说到了论坛社区和Blog社区,那么就开始用社区这个词而放弃论坛这个词。越复杂的生态系统就越难建立,但建立后就越能够抵抗外力,如果一个社区构成部分相对复杂一些,例如包括论坛、电台、下载、测评,那么即使一方面受到外力的打击但仍能维持流量,要恢复并不难。社区上各式各样的用户,就如各式各样的生物,有产出有消耗,但只要一个社区能够维持食物链的稳定性,那就能够幸存下去。然而很不幸,数量最多的是那些食物链顶端的终极消费者,也就是那些仅消费不贡献的(例如长年潜水的),反而最少的是绿色植物,也就是那些需求低了与无偿贡献的。由此看来,如果单单由用户构成一个封闭的生态系统,基本上是死定的,必须依赖于与外部的交换才可能让这个生态系统运作下去。如何算是和外部交换?例如用户可以选择付费,然后这些费用最终转变成一些收费信息的来源。

为什么 BBS 在中国那么火 (Part 1 - 整理及评论)

王冉的原贴在这里:为什么BBS在中国那么火,这只是一个起头,但它提到了一个"先进性"例子:"在国外(至少是在美国),虽然也有一些所谓的论坛,但是往往都是围绕一些窄众关心的具体话题,譬如某个学术问题、某部电影或者某款新电脑",这用于下文评论douban。

keso的东拉西扯:为什么BBS在中国那么火,我觉得既然要提早期Internet,就必须把概念分开来——BBS和Forum是两样不同的东西。虽然他们的很多特性看起来都一样,甚至使用起来也完全一样,但基于历史的原因我还是习惯把它们分开。BBS是Internet早期的产物,那时候还没有Web,消息都来自BBS,在当时的情况来说,BBS就是Internet的绝大部分,而后来的Forum则仅仅是Web的一种使用方式。BBS有一种特性,就是不可替代性,在那个时候你要获取信息就要通过BBS,你不能选择不用BBS或者用其他形式。从名字来看,BBS是电子公告版,而不是论坛,这就让BBS看起来有点官方色彩("官方"在这里指理想中的人民自愿放弃其部分政治权利而授权形成的政府)。至于为什么BBS后来在中国习惯被翻译为论坛,这可能就是问题的根源了,也就是论坛特性加上民族特性所造成的结果。

麦田在社区(1):聊天室启示录中,提到了论坛的技术零成本和言论零成本特性。技术零成本这没什么好说的,任何一个易用的产品都必须具有这样的特性。言论零成本,才是真正和民族特性结合的地方。因为中国人习惯了隐藏自己的欲望,特别是那些显得有点“坏”的欲望,然后又不同于菩提树下的顿悟——“我选择没有欲望,因此没有欲望满足不了的痛苦”,中国人会选择在适当的场合释放这些欲望,而且有可能是加倍的或者以变态的形式释放这些欲望。于是论坛就成了这样一个场所,和五谷轮回之所一样。“你有压力,我有压力”,“未解决”?那就去释放一下咯,只不过前者用于释放言论的压力,后者用于释放膀胱的压力。

中文BBS:红旗下的蛋中,麦田还说到了“网络暴力”的问题,他总结网络暴力的4大特点,看完后我真的想说——网络暴力现在主要发生在网上,那实在是一大进步啊!(他这里所说的“网络暴力”应该仅指论坛言论上的攻击。)回想一下,我们花了某十年时间去进行“现实中的网络暴力”,其特点也一定程度上符合那4点,那个伤害大啊,死了多少人经济损失多少,而现在这种单纯在网上的网络暴力,幸运的话仅伤你的ID(甚至仅伤马甲),不幸的话才伤到你个人,但至少不会搞出人命。

为什么论坛能够在这种事情上那么热?因为它提供了一个“无代价战场”。有那么多自称“爱好和平”的人士,正如我所说,他们需要释放他们对暴力的欲望,这个特定场所最好就是一个无代价战场。其实无代价战场有很多选择,不过Battle Field对于很多人来说都太具有战略性,GTA的英文剧情又不那么容易懂,这时候论坛的技术零成本就绝对会让它中标了,就那么简单。

2006年5月22日星期一

还是喜欢工作两个月玩两个月的生活

以前习惯了,连续两个月很有热情的投入一个项目,改设计写代码,两个月之后就累了就不想做了,就算没做完也没激情了。然后开始打机,很专心的玩一个游戏,大概玩两个月又发现这个游戏不过如此,就回去做项目了。(Coo好像说过他也是这样。)

从寒假开始,接外面的东西做,其实平均下来一个月就¥400,也就是¥400分量的工作(就是隔几天做一点也行的那种),但是连续做几个月就很累,想放松,完全不想写代码。当然这也和最近写代码的方式有点相关。

以前用ASP写代码,虽然不算得上面向存储过程(因为Access根本没有Stored Procedure这个概念),但也差不多吧,反正就是我需要什么样的SQL语句,就写怎么样的,用得多的SQL语句就封装起来成为一个VBScript Function。现在用ASP.NET 2.0,尝试按照TimeTracker Starter Kit那种简单的3层架构写,但就发现写类代码就真的很累,或者应该叫做“累代码”。首先,看起来把逻辑都封装成累,对于UI代码来说是很爽的,因为UI代码再也不用管持久层需要怎样格式的数据,反正就是组织对象,用对象方法来存取数据,那就行了。但是写类代码时就会比较头痛,因为重复写那些Property并不简单,而调用方法基本上就是直接调用当前DataProvider的同名方法然后把数据传递过去。到了持久层,也就是DataProvider,需要进行类与平板的关系型数据进行交换,也是很头痛的,其实UI表现本来也是平板的嘛,对一层立体转换并不好过。还有就是,直接用存储过程可以使用一些复杂的,例如SELECT ... IN (SELECT ...)这样的嵌套语句,但是如果要先获取内SELECT的对象集合,然后for/foreach循环来获取外SELECT结果,那就惨咯。

据说有一些中型项目的项目经理还是偏好于面向存储过程设计,而不是面向对象,不知道大家看法如何。

2006年5月15日星期一

VeryCD做开放式中文IMDB以及吃素网站

http://base.verycd.com,和IMDB非常类似的功能,主要按照作品、人来收录,然后链接作品和人的关系(例如导演、演员、配音)。不过IMDB的资料(特别是照片)都来自官方,或者专业记者,能够拿着Invitation甚至VIP证出入各种尊贵场合的那些,而VeryCD的照片则靠网友上传。

不过VeryCD的资料应该是自动从IMDB同步获取的,所以看上去更加像一个“IMDB中文外挂”。也就是说,VeryCD从IMDB的网站获取所有IMDB的文字资料,然后同步到自己的数据库,同时在自己的数据库增加IMDB没有的字段(主要是电影/演员中文名和中文简介之类的字段),然后评论是VeryCD的而不是IMDB的。还有就是,用户能够好像修改wiki那样修改VeryCD上的资料,但如果用户修改了资料然后IMDB也更新了资料,那么是不是又用IMDB的覆盖,那就不知道了(反正就是wiki类的东西,覆盖来覆盖去都没所谓)。

正所谓弱肉强食,那些靠养着一群编辑的网站已经变成绿色植物成为了食物链的源头,过去dot-com的投资已经足够多也就造成了绿色植物过多,现在是时候为了平衡一下生态环境削弱一下它们的优势了。所谓的食肉动物,就是那些打着share或者aggregate旗号直接利用别人站点内容的站点,把植物拿过来消化一下,然后变成自己的组织和能量,真是个不错的主意。既然现在绿色植物过剩,那么在接下来一段时间做做吃素网站也不错,起码不用养着一身的叶绿素,吃就行了。至于再以后,就想想办法如何做肉食动物吧,那是以后的事了……其实我是很赞成研究企业生态学的,如果一个企业能够维持好和其他有利害关系的企业的关系,不把自己的食物吃紧,同时又保证有天敌存在帮忙吃掉一些自己的同行竞争者确保这一样不过过度繁殖,那就够了。

2006年5月13日星期六

ClaimID.com - 声明哪个是你

很有趣的服务,我是听Inside the Net的podcast听到的,可以来看看Inside the Net主持人Leo Laporte的页面:
http://claimid.com/leo
又或者ClaimID负责人之一的Fred Stutzman的:
http://claimid.com/fred

因为在线有很多同名称或者同ID的人,甚至有些是恶意冒充你的名字和ID的情况,于是有了claimID这样的服务——注册一个页面,声明哪些页面是我的或者提到的是我(例如我发布在各处的blog、我参加的项目、别人对我的评论),当然还可以声明哪些不是我。甚至,你把你的公钥(例如PGP Key)发上去也可以,这样别人就可以随时到该页面下载你的公钥,用于解密你发给他的邮件,或者其他加密信息。反正只要是URL,你就可以发上去,类似del.icio.us,不过你不是声明该URL的内容性质如何,而是该URL与你的真实身份有什么关联。

顺便说一句,我的claimID页面在这里:
http://claimid.com/cat

2006年5月7日星期日

英语国家开发人员总是不注意Encoding的问题

英语国家开发人员通常都不考虑需要Encoding的情况,因为他们开发的软件不需要Encoding,也不一定有机会测试Encoding,这真是麻烦。

任何一段开源代码到手后你首先要考虑它是否涉及文件读写或者HTTP收发等操作,如果涉及,就必须考虑Encoding,特别是对于有多种Encoding方式的语言(例如中文有)。如果很不幸那段开源代码对Encoding没有支持,那么你就必须自己动手去增加Encoding的支持。

如果Encoding涉及的地方只有几处,那改就是了。如果Encoding遍布整个代码,那就几乎不是改的问题了,而是重写,哎……做开发不能不懂Encoding,但Encoding实际上又真的是一个大问题。有很多与计算机架构有关的问题仅限于某些计算机,因为只有那类计算机使用该架构而需要考虑该架构的问题,这是很灵活的。但是字符是任何计算平台都要交换的,不可能强制要求所有平台都转用Unicode或者一种指定的Encoding,于是设备与设备之间沟通就必须支持Encoding之间的转换。优秀的设备(例如PC)必须支持多种Encoding共存,所以运行在这些设备的软件业需要有处理多种Encoding的能力,这看起来成了一个永远无法解决的问题……

2006年4月30日星期日

设计一个面向Photoshop用户的CSS设计器,应该很畅销!

很多美工,还是习惯用Photoshop设计页面然后Slice,这样的设计方式对他们来说比较直观,不需要在脑袋里想着效果然后写HTML和CSS然后再看实现得如何。能够脑袋里想着结果然后闭上眼睛写代码的,估计主要还是程序员,美工需要能够看得到摸得着一步一步去fit的东西。虽然现在也有些美工人员在尝试摆脱HTML table排版改用CSS,但是用Photoshop输出的table改div和CSS既不方便结果也不一定是最优的(相对能够闭上眼睛写HTML+CSS的人来说)。于是,我就想如果能够开发一个有点类似Photoshop那样的绘图见面,但又能够直接用于设计CSS的设计器,应该会很多人喜欢。

其实Frontpage和Dreamweaver已经很“所见即所得”,专门的CSS编辑器如TopStyle也是能够编辑CSS的同时显示效果,为什么还要像Photoshop那样?因为我觉得美工是靠feel工作的,例如导航栏的高度和文字定位,你要求美工修改一下height然后又调整一下padding和margin,尝试一下px做单位,又换用一下%做单位,这应该是比较痛苦的事情。人家在Photoshop里面,导航栏高度喜欢拉多高就拉多高,文字定文也是用鼠标放,这完全是靠看着整个页面的构图感觉来做的,对着一堆CSS改一下代码再看一下结果就很不爽。所以,这个类似Photoshop的CSS设计器一定要完全实现在可视化界面上实现所有操作,也就是说用户也是直接通过拖动就能够实现字体定位的,至于margin和padding就让设计器自己去管理,没必要让用户操心,用户最多选一下按px计算还是按%计算然后看看不同情况下变化的效果。

要说明的是,因为美工面对的往往不是一个fixed的页面,而是随浏览器可视区域变化而变化的页面,所以设计器本身应该支持不同可视区域大小的preview。例如我当前在1024*768可视区域的模式下设计,但是旁边有缩略图显示这个CSS在800*600中的显示效果,通过鼠标放置字体位置后,改变按px计算和按%计算不会影响1024*768模式下的效果(因为这是当前设计模式),但是800*600模式下的preview就会跟随改变,因为当前的设计效果按照不同的单位来定位的话在其它模式下的显示结果是不同的。

这个设计器的重点,应该就是div和ul的放置了,因为这两个东西现在用得最多。div的放置应该支持dock(停靠)的效果,或者是现在WinForm所支持的anchor效果(比dock更好)。在说这个之前,大家可以来看看一个在线的CSS Creator的效果:
http://www.csscreator.com/version2/pagelayout.php
这个东西允许你设置常见的排版模式,例如有没有Header和Footer,它们的高度多少,按px还是按%算,body部分分为几个column(最多3个),宽度又如何算,页面的最大最小高度如何。我所谓的div支持dock,就是类似现在主流所做的那样啦,3栏结构的话,左栏dock左边、右栏dock右边、中栏占满剩余空间(这不废话),如果超过3栏也能dock/anchor的话就更好了。anchor是一个比dock更好用的东西,dock只能依赖于4页面边缘,anchor则是任何一个控件的4边缘都可以依赖于仁和另外4个附近控件的边缘,如果CSS能够实现anchor效果就好了。另外就是要有一个preview显示无CSS时的效果,能够看到完全无CSS时出来的样子如何。

2006年4月22日星期六

ASP.NET 2.0 的编译模型并非完全像 MS 说的那样

上次说到了ASP.NET 2.0解决了Code-Behind需要同步声明控件的问题,说MS的图例解释2.0中aspx和cs的内容不再是继承的关系,而是partial的关系,是合并编译。然后我说了,如果是partial关系,那么处理过程就很复杂。因为partial不能增量编译,也就不能跨语言编译,必须把aspx部分内容完全翻译为cs或者其他对应Code-Behind语言,然后才能够多partial一起编译。但实际上当然不是这样做,现实中aspx既是partial,又像1.x那样要做为继承类再编译一次。

发现这个问题,是由于我在cs声明了一个private的函数,想在aspx中的数据绑定语句中使用,结果调用时竟然告诉我找不到该函数,然后我把private改成protected就可以了。如果aspx和cs真的完全是partial关系,那么private函数是能够找到的,private找不到说明aspx中数据绑定语句和cs肯定不是在同一个类中,也就是不是partial关系,而是好像1.x那样的继承关系。实际上,我说的aspx完全翻译为Code-Behind所用语言应该是做不到的,最多把aspx中的所有服务器控件声明翻译。因为都是声明,可能如果一个partial里面有的只是声明,那编译器内部就支持增量编译,因为声明在编译结果里就是一个比较独立的部分。然后aspx中的其他逻辑都是好像1.x那样,再继承编译好的Code-Behind代码,把UI代码加上去。

2006年4月6日星期四

监视你的用户

正如之前的StealthMeter或者GamePlaysMan一样,我时不时都利用流行的技术和设计想出一些坏东西来,这次轮到Keep Your Visitors Under Surveillance了,咔咔……为什么那么多个词不用,偏要用Surveillance,因为我就想做到好像闭路电视监视网一样的效果,能够在中央控制室同时监视多个用户,而且要是实时的。

Analytics和MeasureMap出来了都还没有机会用上,就又听说有一个新的更强的站点分析平台能够分析用户鼠标在页面不同部位停留的时间长度,然后用不同的颜色把鼠标停留时间的梯度显示出来,虽然实际效果如何还不知道(该平台还在Alpha阶段),不过通过用户鼠标停留时间应该能够看出用户的阅读方式(还是有不少人习惯用鼠标跟随目光的),以及用户对页面的哪些链接感兴趣(即使没有点击)。另外最近也看了Sliver(1993),觉得在监控室中对着满墙的监视屏幕看着不同房间的人在做不同的事会挺有趣,于是就产生了监视Web用户这个想法。

首先,要实现一种ClientViewState,也就是客户端的ViewState。和ASP.NET的服务器端ViewState用于保持页面生命周期间View的状态不同,ClientViewState就是显示反映客户端浏览器当前的状态,例如页面滚动到何处,用户鼠标停留在哪里。这个ClientViewState要是实时更新的,也就是客户端的所有操作相应都实时更新ClientViewState,对于好像鼠标移动这样的事情ClientViewState会产生Frame,而对于点击这样的更新产生KeyFrame,Frame和KeyFrame会使用Ajax模式直接传输到服务器以更新服务器端存储的ClientViewState,再传输时KeyFrame拥有更高的优先级,一旦产生KeyFrame则KeyFrame之前产生但未传输完的Frame就丢弃,仅当没有连续KeyFrame时才传送Frame。

然后,监视页面从服务器下载ClientViewState下来(使用Comet模式),把每一个ClientViewState释放到一个对应的IFrame,在该IFrame还原对应的被监视用户的当前ClientViewState,通过IE的DOM的按百分比缩小显示功能把IFrame内的内容缩小,这时候监视者就可以在监视页面看到用户实际操作的情况了。

实现ClientViewState不是难事,现在的DOM足以应付,然后把ClientViewState串行话传导服务器端然后再传到另一个客户端并还原也不难,关键就是传输途中用的Comet模式是否有效率。所谓Comet模式就是现在Talk in GMail和Meebo等WebIM采用的Ajax模式,和普通Ajax唯一不同的就是Comet使用keep-alive的HTTP连接从而实现服务器端主动Push数据到客户端,但到底这样的长连接能够应付多大的负载我实在还不知道,不过应该应付串行话的ClientViewState传输还是绝对没问题的。

至于实时监视有没有什么意义,这个我暂时不知道,对于成熟的站点来说可能没什么意义,但是对于Beta阶段需要知道用户使用方式的站点来说或许有用,正如软件易用性测试可以用走廊测试一样(在走廊上随机拦几个人来试用你的软件然后看他们的操作方式),你可以通过监视用户如何使用你的站点从而知道易用性方面是否有问题,同时也无需让用户填一堆关于使用方式的调查问卷。