Author: qingbo

  • Web 开发之基本功

    我感觉有些人学了 PHP 再学 ASP.NET 又学 J2EE,学了 struts 1.x 学 2.x,每学一种新的 web 开发技术都像是学一种完全陌生的技术一样。

    实际上这么多种语言、框架并存,各有各的优缺点。然而所有这些语言、框架的基石,都是 HTTP 协议。最起码的,一个 web 开发者要知道,它是无状态的 (stateless)。客户端向服务器发起请求,通常是 GET/POST 方式,服务器通常返回一个 HTML 文档,有时候就是随意一种格式(比如图片、二进制或者纯文本)。HTTP 协议需要特别注意的地方还包括缓存 (cache control) 以及跳转 (301/302) 等。

    我觉得,框架应该是归纳整理重复性的劳动,吸纳优秀的设计模式,而不是努力掩盖 HTTP 协议的本质。对 Web 开发者掩饰 web 的本质,是非常邪恶的一件事情。ASP.NET 就一直不遗余力地朝着这个目标发展,硬要把桌面程序的编程模式搬到 web 开发上来,Windows form、web form 的概念混为一谈,毒害了无数无辜的程序员。程序员接触到新的开发环境时,很可能到处碰壁,满地找牙(这个牙,可能就是 ASP.NET 给装的假牙——Event)。前段时间看了一下 Tapestry,发现该项目的老大有和 ASP.NET 类似的理念,按照他的说法,页面上的链接宁可在服务器端被 OnClick 处理一下然后 302 redirect,也不舍得直接指向实际的链接——声称这样更符合思维习惯。看到这个,我就明白了为什么 JavaEye 上有人惊呼,Tapestry 有点像 ASP.NET 啊,真好!说实话,看到这样的赞美,我就慌了。要 Tapestry 是我写的(假设而已,我现在还没这个能力),我就会反驳:“你才像 ASP.NET 呢!你们全家都是 ASP.NET!”.

    同志们,看清本质吧。web 开发真正的核心是 HTTP 协议,至于 Java 还是 C# 还是 PHP,它们只是服务器用来生成发送给客户端的那个 HTML 或其它格式的文档用的。尽管它们会影响到程序性能、开发速度等诸多方面,但是如果你连 HTTP 协议都不了解,即使使用的语言、框架再好、再高性能,也如同把一座钢铁大厦建在流沙之上。

    等了解了 HTTP 协议,再往上看哪种语言更适合 web 开发,哪个框架更好。否则就像是农民还没搞清楚土壤成分就随便种庄稼一样,收成是好不了的。

  • Google Reader 也有 twitter 功能

    google-reader-share

    点击 Google Reader 左侧栏最上面一块中的 “Your stuff” 链接,即可看到类似上图的界面。我这个截图是在点击了 “Show Options” 链接之后的情况,默认只有一个输入框,没有标题、tag. 这不就是 twitter 吗?呵呵。

    我是昨天才发现 Google Reader 还有这么一个功能,平常我也就点点 share 按钮,分享些自认为有用或有趣的东西。看到这个界面上那个 bookmarklet 了吧?使用它可以分享任意一个链接,即使它不在任何一个 RSS 中。

    另外 Google Reader 最近刚刚发布了 comments 功能,许多人欢呼雀跃,我却觉得没啥用处。虽然将某 item 的 comments 汇集到一起容易造成信息泛滥,但是分散在各个 share 中形不成规模,不利于思想的碰撞与融合,这样更不好。

    如果 Google Reader 能够像某些 RSS 阅读器那样让 blog 作者们 claim 自己的 RSS,然后看到所有在 Reader 中收到的评论,那是多么好的事情。我想许多读者也想在 Reader 中直接与作者交流,而不需跑到该 blog post 的页面。这牵扯的东西又太多了,如果这样,blog 的流量可能大减,等等。

    Reader team 的 blog post 说 “We have much more planned for this feature”,让我们拭目以待吧。

  • 用 Firefox 快速搜索

    本文分享一下我在 Firefox 中使用搜索引擎的经验,希望对朋友们有所帮助。

    se-bar

    Firefox 界面右上角的搜索栏可是我严重依赖的东西。我不会先打开 google.com 然后再输入关键字搜索。不管你在做什么,有东西要搜索的时候,只需按下组合键 Ctrl+E 或者 Ctrl+K,焦点就会定位到搜索栏,输入你的关键字,回车,搜索结果就列出了。你可能不希望搜索结果在当前页面打开,很简单,在 Ctrl+E/Ctrl+K 之前按下 Ctrl+T,新建一个 Tab 就可以了。还有一个更便捷的方式是不用事先新建 tab,输入关键字之后按 Alt+Enter,搜索结果会自动在新 tab 页打开 (Alt+Enter 同样适用于地址栏)。

    如何快速使用非默认搜索引擎搜索?

    点击搜索栏左侧的小按钮打开搜索引擎列表,然后点击最下面的 “Manage Search Engines…”,选中你需要使用的搜索引擎,然后点击右侧的 “Enter keyword…”,输入一个关键字,越短越好,一个字母就足够了,如下图所示:

    se-manage

    依我之见,默认的 Google 就不需要设置 keyword 了。keyword 有啥用呢?举个例子,我给 Wikipedia 的搜索引擎指派了关键字 “w”,那么在地址栏 (location bar) 输入 “w Java” 然后按下 Alt+Enter,就会在新 tab 打开 wikipedia 的搜索结果(实际上是直接 redirect 到 Java 词条的页面)。

    还不知道怎么快速定位到地址栏?至少有三个快捷键:Alt+D/F6/Ctrl+L

    搜索指定版本的 Java 文档

    比如 Java 6,可以到这个页面去安装对应的搜索引擎。安装之后给它分配一个关键字 “j”,就可以像上面说的一样快速搜索 Java 文档了。

    实际上安装的搜索引擎只是在你的 Firefox profile 文件夹的 searchplugins 子目录中保存了一个文件。一般的路径是:
    C:\Documents and Settings\[USERNAME]\Application Data\Mozilla\Firefox\Profiles\[PROFILE NAME]\searchplugins

    打开刚刚安装的 Java 6 文档的 search plugin 描述文件可以看到,它实际上是借助 Google 的 site search 与 “I’m feeling luck” 来实现的,搜索 url 的模板是:
    http://www.google.com/search?sitesearch=java.sun.com/javase/6/docs/api/?sourceid=Mozilla-search&btnI=I’m Feeling Lucky&q={searchTerms}

    看到这个,你肯定知道怎么自己创建其它版本 Java 文档的 search plugin 了。如果有多个版本,你可以分别指定j4/j5/j6作为关键字。

    假如没有这样的 search plugin,我想找 String 类的文档,得在 Google 中输入 “Java 6 String” 回车,然后再点击结果中的第一条。有了它,只需要在地址栏输入 “j6 String” 回车,String 类的文档页就直接打开了。

    各位还有什么可以补充的?呵呵。

    附:Firefox keyboard shortcuts

  • 收到了咔嚓鱼的免费杯子

    杯子是免费的,不过要5元运费。本来我也没太大兴趣做个印着照片的杯子,不过去年年底的时候买了个麦当劳的优惠卡,送一张咔嚓鱼的优惠券,可以免费得到一个拼盘照片马克杯,只需支付5元运费,还不错。这个杯子好像原价要三十多块呢。

    上周末我才打起精神翻看我的照片,从里面找了几张看起来还可以的——那么多照片,想找几张能拿出来看的,还真是不容易啊,由此说明,数码代替了胶片造成了照片平均质量的大幅下滑。

    大致的步骤:

    1. 首先上传要印在杯子上面的照片
    2. 然后按照优惠卡上的地址去输入优惠码
    3. 成功后就得到了优惠产品的链接
    4. 顺着链接点过去,选择照片,定制杯子样式,最后 checkout
    5. 在订单信息那一页下面有个填写优惠码的地方,比较容易迷惑人(如果优惠码填写在这里,是会报错的),实际上不要理会这个字段,空着它点击继续,在下一步结算的时候就可以发现咔嚓鱼已经把优惠计算进去了,最后账单总额是5元。

    然后就等着收杯子吧。我是15号下午订制,17号就收到了,速度很不错。而且包装也相当安全,里面是两块为马克杯量身定做的模具一样的细泡沫塑料,外面还有一层泡泡纸——这天下班回来的路上我就是靠挤爆泡泡消磨时间的,哈哈。

    杯子实际上也就放在那儿当个摆设,像我这么能喝水的人,那一杯还不够我一口喝……

    话说惠普为这个咔嚓鱼的推广真是费尽心机,免费冲洗,免费杯子,但是用户享受完免费的服务,还会接着去买东西吗?至少我没太大的需求。

  • Web framework 过度集成 JavaScript/Ajax

    前文提到 Matt Raible 在比较 Java web framework 的时候有一个重要的指标(他将之排在第一个):

    Ajax Support: Is it built-in and easy to use?

    与 JavaScript 有关的指标还有一个:

    Validation: How easy is it to use and does it support client-side (JavaScript) validation?

    我个人却觉得服务器端的框架不应该对 JavaScript/Ajax 如此高度地集成,它们毕竟是客户端的东西。程序员必须有清晰的概念,什么是服务器,什么是客户端,它们之间是怎么交互的。微软的 ASP.NET 做了一个很不好的表率,将 web 开发用 Windows 桌面程序开发的理念来进行,迷惑了不少程序员——就连官方翻译 “Form” 成中文也是“窗体”,真是不伦不类。而程序员需要深究其运行机制的时候,就不得不折腾 POSTBACK 那一坨屎了。Ajax 开始流行之后,微软又不失时机地在 ASP.NET 加入了 Ajax 的控件,广受那些喜欢拖拖拽拽像搭积木一样“编程”的程序员的欢迎——这就是 “The Microsoft way”.

    Ajax 从概念上讲是革命性的,但在技术角度上说,了解 JavaScript 再去掌握 Ajax,就好象学会了 Java 再去学集合类怎么用一样简单。Ajax 技术被炒得神乎其神,以至于大家的简历上不得不再加一条“精通 Ajax”,一部分原因可能就是框架的集成使其神秘化,并且使大批程序员不愿或不敢去看它的真实面目。真要问问 Ajax 异步更新的下面是怎么回事,估计 80% 的人不知道。

    在 Tapestry 5 解释其 JavaScript 集成的 wiki 页面中,开头就写:

    Modern frameworks, and perhaps none more aggressively than RoR, go a long way in removing the pains of JS from developing web applications. Tapestry 5 is no exception and even though it is currently in the beta stages, it has come along way in making JS transparent to developers (not to mention 3rd party libraries like tapestry5-components).

    从这段文字可以看出作者对 JS 的集成多么自豪。然而 making JS transparent, 可能吗?当然你以 demo 一下透明是多么的酷,多么炫。不过做一个框架出来不是 demo 用的,而是要为实际环境中的程序做基础。实际应用的时候,用户不可能像 demo 的那样顺利地完成任务,遇到问题的时候还是得去看 JavaScript 怎么执行的,Ajax 是怎么个机制,所以真正的透明是 100% 不可能的。我认为,本来很小很简单的一个技术被集成到 web 框架中,一定程度上增加了调试、调优的难度,得不偿失。

    另外,Tapestry 的表单验证做到了一处配置,客户端、服务器双重验证。这一点本来不错,但它用了一种比较花哨的方式来显示错误信息——在出错的字段上冒泡。而且这种方式在某些情况下会出问题。这一定是作者的个人喜好,demo 起来大家一定觉得很酷。但实际使用的时候,很大的概率是这种 UI 与应用环境的 UI 格格不入,你还得想办法 tweak.

    框架的目的应该是总结设计模式,减少重复劳动,而不是集大成于一身就好了。从这一点上说我倒是挺喜欢 struts 1.x 的,你完全可以控制作为响应发送到客户端的 HTML 代码。Tapestry 则不然,默认插入的 CSS,表单页面不可避免的 JavaScript……

    Matt Raible 的 presentation 中谈到如何选择框架的时候,有一句话 “Eliminate, Don’t Include“,我觉得不仅仅适用于如何选择框架,同样适用于框架的设计者如何选择 feature set.

  • Java web framework 之选择

    homer

    相比 PHP, Ruby, Python 等语言,用 Java 来做 web 开发面临着太多的选择。不过也许正因为 Java 的选择更多,所以才有更多的人选择 Java?这一点不能确定。来这里看看,光是持久层的 ORM 工具就有这么一大坨。当然这里面还是 Hibernate 为王,这个选择比较容易做。

    然而到了表现层,虽然框架不是那么多,但是选择起来可不容易。每个框架需要评估的方面至少包括性能、学习/开发速度、可维护性、用户群体 (community) 等。

    Matt Raible 曾经对 Java web framework 们做了比较 (PDF 下载,建议右键另存为),我觉得这个 presentation 可以从39页开始看。第43、43页的建议和 tips 非常有价值:

    • Don’t believe blogs and articles – 也就是说,该 presentation 39页之前的内容要谨慎对待 🙂
    • Try it yourself
    • Believe developers, not evangelists
    • Believe developers that are experienced with the framework and have used it in production
    • Beware of corporate interests – they can twist marketing
    • Books are a good sign

    如何选择呢?”Pick 2-3 frameworks for your type of application… and prototype! If prototyping is painful, switch.” 嗯,不要仅仅看各种派别的人整天在那争论孰优孰劣,虽然他们的争论很有价值,但是每个框架都有自己的优点和缺点,甚至有些优缺点是主观的。你选择的框架应该在你的项目环境(项目本身以及团队情况)中扬长避短,将优势发挥到最大。”You should try a framework before dissing it!”

    其实我是在考虑 struts 1.x 与 Tapestry 5 的取舍,所以才在网上到处逛。TheServerSide 上就有不少争论,那阵势跟 vim 和 Emacs,Windows 和 Linux 阵营之间的争吵差不多,只是多数人还没有上升到信仰的高度。直到看了这个 presentation 之后我才开始考虑,对,做个简单的 Tapestry 程序,不就一目了然了吗?一直犹豫不决是因为我对 Tapestry 太不了解了。

    在这个过程中,我很快知道了 Tapestry 如何加入到一个 web 项目中,它是怎么接管服务器收到的请求,请求处理的流程,表单验证,以及最后返回给客户端的 HTML 代码。REST like 的 URL,非常简单的配置文件(相比 struts),灵活的 component 都是我喜欢的特性;然而在我看来它对 JavaScript 的集成有点过分,绑定了 Prototype/script.aculo.us。

    回到 Matt Raible 的 presentation,他提出的比较标准中有一条是 Ajax Support (18页),Is Ajax support built-in and easy to use? 说实话,我非常不喜欢 JavaScript/Ajax 被集成到一个服务器端的框架中。(+) 本来我是想在这扯一大段的,不过想了想可以另外扯出一篇文章来凑数,哈哈。

    很可能我将选择 Tapestry 来开始新的项目,但我 90% 的可能不会使用其 Ajax component,另外 10% 的可能是它的实现真的很棒。

    struts 1 太老了,它之所以仍然流行是因为其庞大的用户群体。有些比较让人烦的问题,比如除了配置文件之外,其请求处理流程使得使用 oscache 这样的页面缓存变得不那么顺手。但是我非常喜欢它丝毫不入侵客户端这一点,它做了一个框架应该做的事情,不多也不少。webwork 过继给 struts 的 struts 2 我个人非常不喜欢,配置文件仍然是一大坨,非常之杂乱,action class 也很乱,也想将魔爪伸向客户端的 JavaScript(ajax taglib) 与 CSS(theme)——我可能存在误解,因为我仅有的 struts 2 经验是在别人的一个项目中帮忙,可能是团队成员的问题。

    不出意外的话 Tapestry 就是我的选择了,我将在这里分享自己的使用经验与心得。

    P.S. 我非常希望交几个 Java web 方面(甚至 PHP/Python/Ruby)的朋友,没事的时候请教一些问题,有意请联系我 Gmail/Gtalk: zhouqb (89.9% uptime guarantee during work hours)

  • Trac 与 Redmine

    Trac is an enhanced wiki and issue tracking system for software
    development projects.

    Redmine is a flexible project management web application.

    实际上 Redmine 应该是 Trac 的 clone,基于 RoR. 它们都是对项目开发、管理非常有帮助的系统,可不仅仅是 bugzilla +
    wiki,Timeline 这个 feature 将 bug tracking system, wiki,
    以及版本控制系统的最新动态整合起来,让你一眼看到项目的最新进展。

    我用 Trac 多一些,现在也在用,也更倾向于 Trac,而 Redmine 只是短暂地用过一段时间,下面简单地对二者的重要特性做一下比较。实际上
    python/ruby 我都不熟悉,所以下面的比较如有错误请多多指教 🙂

    svn 支持

    • Trac 只支持本机,通过 python 直接读取目录中的数据

    • Redmine 是通过正常方式(http/svnserve之类)访问,支持远程代码库,不过要实现在 timeline 中实时看到最新的代码
      commit 比较麻烦一点

    多项目支持

    • Trac 可以在 Apache 中配置某目录的子目录全部为 Trac project,但是实现它们之间的综合管理比较麻烦

    • Redmine 原生支持多项目,管理比较方便

    用户管理

    • Trac 和 svn 一样自己基本不实现用户认证机制,主要依赖 web server 或其它认证机制

    • Redmine 有完整的用户注册、登录及角色权限管理

    多语言支持

    • Trac 的 i18n 做得不好,据说有许多字符串直接写在代码里

    • Redmine 支持多语言较好,已经有许多翻译。不过根据我使用 Redmine 的经验,中文翻译质量一般

    甘特图、日历、ticket 进度、time tracking 这些特性都是redmine独有的。

    P.S. 建议大家看看 Fenng 的网站运维之道 之知识管理与积累

    http://trac.edgewall.org/wiki/TracMultipleProjects/ComprehensiveSolution

  • 安装 TortoiseSVN 并不需要重启

    在 Windows 下面,TortoiseSVN 应该是最常用的 Subversion 客户端了。但是安装完成的时候它都会要求重启计算机,连 FAQ 上都是这么说的:

    You rebooted your PC of course after the installation? If you haven’t please do so now.

    不过我觉得实际上并不需要重启。它要求重启只是为了跟 explorer 的绑定生效,而为了重启 explorer 去重启计算机显然有点得不偿失。

    只需打开任务管理器,找到 explorer 进程并结束它,然后点击菜单“文件-新建任务(运行…)”,输入 explorer 回车,等一下任务栏出来的时候,一起就都 OK 了。

  • JavaScript/CSS 部署 – 从石器时代到工业时代

    随着前端开发在 Web 开发中的地位日渐重要,前端相关的资源部署也成了各大网站开始重视的一步。

    对于一个访问量相当大的网站来说,从前端方面考虑,除了使用 CDN 向客户端分发 JavaScript/CSS 资源,充分利用客户端浏览器缓存也是减轻服务器压力的一个重要途径。

    首先按照我们的“常识”,认为 JavaScript/CSS 默认就是在浏览器中缓存“很长”一段时间的,作为最简单的策略,我们就依赖这种缓存机制来降低服务器的压力。然而,对于一个不断改进、更新的网站来说,JavaScript/CSS 的变动是相当频繁的。

    如何让浏览器知道这些静态资源的改变呢?让我们从最简单的解决方案开始摸索和学习,慢慢改进。

    刀耕火种的方式

    把这件事扔给开发者吧。以 JSP 页面为例,首先在一个统一的 jsp 文件(例如 version.jsp)中定义所有静态资源的版本号:

    [code lang=”html”]
    <%
    String dialogVersion = “20090101”;

    %>
    [/code]

    然后在每个用到 JavaScript/CSS 的页面中包含该文件并且给静态资源加上版本号:

    [code lang=”html”]
    <%@ include file="/WEB-INF/include/common.jspf" %>



    [/code]

    开发者负责在每次 commit 代码前检查自己修改过的静态资源,去修改相应的版本号。这给本来就不堪重负的开发者又增加了一项艰巨任务,他们难免会有疏忽的时候,等到有客户反馈问题,才想起来——哦,忘记改版本号了!

    自动化

    一个成熟的开发团队应该使用版本控制工具来管理代码,一般最常用的是 Subversion. 除此之外,还应该有一套完善的部署脚本,执行一两个命令即可完成整个部署过程,而不是手动地去复制、修改代码。这个过程可能包含从 svn 检出代码、编译(解释型语言则省去了这一步)、选择性的复制(避免覆盖生产环境中的特定配置)等等。

    有了这些基础,把上面手工修改版本号的工作交给自动脚本去做就可以了。”svn info” 命令可以列出文件最后被修改的版本号,我们只要得到这个版本号,把它写在 version.jsp 中即可。写这个脚本可能稍微麻烦一点,不过 python, perl, bash, ruby, 随便什么语言都可以,或者是一个 ant task. 这是一劳永逸的事情。开发者们可以把精力集中在真正的前端开发商了。

    不要用 query string!

    我本来以为到此为止就很完美了,但查阅了一些资料之后发现我的许多观点都是完全错误的。

    首先,在上述的方法中,版本号是作为 query string 附在静态资源的 URL 上的,然而:

    According the letter of the HTTP caching specification, user agents should never cache URLs with query strings. While Internet Explorer and Firefox ignore this, Opera and Safari don’t – to make sure all user agents can cache your resources, we need to keep query strings out of their URLs.

    以上摘自文章 “Serving JavaScript Fast

    正确的办法是将版本号插在文件名之中,后缀之前,然后利用 Web Server 的 URL Rewrite 功能指向不带版本号的真实文件,实际上就是欺骗一下客户端。具体做法请参考上文,或者 “Automatically Version Your CSS and JavaScript Files“.

    仅仅 304 还不行!

    假设浏览器的缓存中什么都没有,这时用户去访问一个网站,引用到的 JavaScript/CSS 都要到服务器上去下载。服务器将这些静态资源返回给客户端的同时,会附加一个 Last-Modified 时间值。下次客户端再请求该资源,会把这个时间值附加在 If-Modified-Since 这个 header 字段中传给服务器,意思就是如果在这个时间之后修改过才有必要重新传输。如果没有修改,响应的状态码就是 304.

    下面用 fiddler 抓取 IE 浏览器请求我的 blog 首页的过程,做个测试。第一张图,蓝色虚线上面是缓存为空时访问页面的请求,虚线下面是重启浏览器之后再次访问的请求情况。为了说明问题,我只保留了与问题密切相关的一些请求,可以点击查看大图。

    ie-fiddler

    请求 #0, #28 都是请求页面本身,两次都是全部传输,状态码是200. 请求 #1, #2 分别和 #29, #30 对应,一个是 JavaScript,一个是 CSS. 从 body 那一列可以看出消息体长度都是 0,而状态码是 304. 再看请求 #3,在第二次访问时,浏览器根本没有再次请求该资源,关键就在于 Caching 那一列的属性。

    尽管减少了数据传输,请求数却是影响服务器承载能力的最重要因素。理想的情况自然是像 #3 请求的 widget02.css 那样,告诉浏览器在一年以后该资源才会过期。

    如果使用的 Web server 是 Apache httpd,那么可以利用它的 mod_expires 来实现 JavaScript/CSS 自动添加 Expires 头信息。具体方法在前面提到的文章里都有。

    最后再次列一下参考的宝贵资源:

    1. Automatically Version Your CSS and JavaScript Files
    2. Serving JavaScript Fast
    3. Best Practices for Speeding Up Your Web Site
    4. What Is Caching and How Does It Apply to the Web?

    事实上本人的实际经验还停留在刀耕火种的程度,只是把最近学到和想到的东西分享一下而已……在未来的项目中我肯定会实践这些想法,并在可能的情况下把实际的代码分享出来。文中如有错误,欢迎指正。

  • 表单自动focus并不总是好的

    许多网站的登录页面为了提升用户体验,都在页面加载完成后让用户名那个input自动获取焦点。就连struts的html:form标签也考虑到了这一点,提供了一个focus的属性。

    Gmail, Delicious都是这么做的。它们一般是hook页面的onload事件,调用input元素的focus方法。但是在网速较慢的情况下,onload可能会在表单显示之后好久才触发,而该事件触发前,用户可能已经填写了好几个表单项。假如此时用户刚好开始填写密码,就出现了下面的情景:

    auto-focus

    不仅破坏了两个已经填好的字段,对密码的安全也是一个威胁。

    某些方法例如jQuery提供的domReady可以比onload更早执行,然而在网速极慢的时候仍然无法完全避免这种情况。

    解决办法?我想大概有两种思路吧,一种是在用户名字段后面紧接着就插入一段javascript,让该字段获取焦点。

    另一种,当表单中有字段获得焦点时,hook到onload的函数不再设置焦点。实现方式可以有很多种。