Get the Flash Player to see this player.

你送的木偶淘气的脸,不知疲倦
泛黄的记忆摇曳不定,若隐若现
那些支离破碎的梦和懵懂的少年
呈在今天,你我面前

再唱起为你写的情歌,又(回)到从前
你歪着头带笑的眼,仍让我眷恋
那些牵手流连的街和厮守的长夜
刻在昨天,轮回之间

这段情该走多久
才不会让一切消散如云烟
这心痛还有多久
失去的一切才会重返流年

你来到我身边
拥抱还那么亲切
相爱却不能再见面
我捧着木偶的脸
连谢谢都哽咽
你终究听不见

总有那么一个城市,比家乡还要熟知
流浪的伤痕,是岁月无畏的旅程
雨总要落在,徘徊和希望之外
有那么一点点痛,只有握着伞的手能懂

臆想着渺茫的希望,期待着未来的伤
在挫败中成长,在混乱中失望
何时才能变坚强

收起你的伪装,收起你的放纵
江河上下不是家
哪怕寸土寸金,只要不见风雨
也就算是盛世年华

这是变变变变变不了的誓言
留给父母除了骄傲还有思念
如果人生还能再选择一次多好
绝对不让苍老再爬上他们的脸庞

上高速之前:生死时速 (2007 年 6 月 15 日 19:00 至 19:30)

      总有人问我为什么现在不学车,答案很简单:没车。即便学了,没有太多机会开,也就都忘了。我实话实说,估计小牛就是这样的。

      小牛在建外 SOHO 的旋转木马前上了车,还给我们带来了晚饭。小米想在上高速之前先填饱肚子,就让小牛替下开车,谁让这里面就只有他和他老婆还算会开车呢?我依旧坐在副驾驶,拿到了传说中相对好吃的那一盒饭,可是都没机会开口吃。因为小牛一坐到驾驶席上,就先说了一句:我根本就不会开车,汗呀~~~~~~

      小牛开车叫一个惊险,虽然不超速,但也算得上生死时速。何况这段路途还颇有拉力赛的样式:小牛用手,转向、加速、刹车;小米用眼,看地图、看路况、外带撞大运。因为本来从国贸上京沈高速,最正常的行程应该是从劲松往东,直奔四环,不过我们好像不是那么走的,我也是路痴,说不清楚。大概是在五环左右,一个劲地往前开:第一个路口不是,是否继续(Y/n)?是,再看看还有机会,不行再掉头;第二个路口不是,是否继续(Y/n)?是,我也不知道前面还有路口没,赌一把了;终于第三个路口,我们看到了京沈高速的标志牌。顺利转向,我开始吃饭,不久进了一个服务区,加好油,换了司机,换了我,全部系好安全带,打开 CD,漫长的高速之旅开始了。

      年轻人一向雷厉风行,从周二 (2007 年 6 月 12 日) 午饭之前开始有的想法,到随后做出决定,到周三午饭后搞定车子,到周四做好行前的必要准备,一切都是那么的想当然,那么的“听风就是雨”,难怪连同事都评论到:你们也真太快了!

      还是先来认识一下我们这一干人等:

  • 小米,嗯,是男生,事件的始作俑者,车主的儿子及我们的车夫;
  • 小牛,唯一的女生,小米的老婆,一路都坐在副驾驶,可见对小米同学的驾车技术还是颇有信心;
  • 文波,另一男生,想不到旅途中的他竟与工作中有那么大的差别,简直搞笑至极,用小米的话说就是:你丫太逗了。接下来的很多段子都是出自他的尊口……
  • 最后一个就是我了,他们仨的独家摄影师,带了一个高像素的连自己都不会用的全自动带全手动支持的数码照相机。

      自从计划定了以后,每个人就开始有自己的任务了。小米负责预定宾馆;文波负责搜集景点和旅游线路;我本来是负责买车票的,不过后来改成自驾游了,我就在家歇着好了,不过其间理了个头发,买了些衣服和游泳裤衩。接下来就是心浮气躁地等待周末的来临……

出发:旋转木马 (2007 年 6 月 15 日 18:00 至 19:00)

      开始总得有点变故,还好是小变故。小米的项目被催得很紧,前几天不抓紧的他,险些在关键时刻被迫加班。不过尽管如此,还是为了给他调一段粗心的代码,浪费了近半个多小时的宝贵时间,就因为一个地方他多写了一个“s“。不过总算是搞定了,他老婆买了晚饭,说定在建外 SOHO 的旋转木马处等他。把老婆放了半个小时的鸽子,哥哥于是有些心急了。

      蓝色的东风标志 307 很快地开到了门下,我和文波边鄙视他边上了车,不幸我坐在了副驾驶。之所以用不幸来形容,就是谁知道小米的技术的如何。虽然事后被证明还是不错的,不过这段路途还是把我吓了够呛。

      小米说他喜欢开快车,着急的时候更喜欢开快车。可偏偏周五的下班时间,二环路上堵得一塌糊涂,从六点半出来到七点,也就挪动了一个公交车站的距离。于是小米开始加三、画龙、在长安街上超速、在国贸违章停车,总之用尽一切办法,还好在七点钟左右的时候,接到了小牛。

      我和文波第一次见到了小牛和小牛开车。

一首奥运题材的歌曲

At this moment
We’re waiting for the flame to be burning
Looking at the sparkle stars
Count down from the infinite
Until now, we’re here together

In the ancient times
We warred each other spending our countless souls
Since when father became the god
They turned the killing into the games
And today, we win it with the glory

Give me your hand, and clap for me
Without your shouting I cannot sing

Give me your hand, and hold me up
No matter what can’t break me down

Give me your hand, and pray for me
Even reality far from the dreams

Give me your hand, and trust me do
Give me your hand, to make me strong enough to break

      今天午后散步的时候,在街旁,看到一个乞丐模样的年轻人枕着吉他在花池边上午睡。这可和我平时所见的衣着锦绣、个性张扬的路边弹唱者大相径庭。吉他不会因为主人的卑微而失声,音乐也更不会因为演唱者的身份而变得暗淡,也许唯一会的就是曾经心中的那份梦想,会被现实蚕食,而终被忘记……

我们的世界
不像你们想像得那样美好
华丽的生活
不管雨点在风中飘

有时也要忍饥挨饿
唱准每一个艰难的音符
有时也要风餐露宿
枕着吉他进入梦想

梦中都是音乐的酒吧
壁炉里迸出耀眼的火花
精灵的乐队
魔鬼的歌手
是摇滚与交响的合成

醒来还是这个肮脏的世界
没人愿意施舍多一点
拖着空虚的身躯
走向下一个地铁站口
拿起吉他
忘记所有的梦想

      Zhangdi 同学说,评论我的《Java 和 .Net 在异常处理机制上的区别》帖子说:我并不认为 C#(.NET)这样处理 Exception 是好的。我觉得这与 strong-type 的概念相冲突。几个问题:

  1. 在设计一个接口的时候,是不是需要同时告诉用户,这个接口有可能抛出什么类型的 Exception?

    答:这种说法自然是没错的,异常应该是接口设计的一部分,但是告知用户的方式可以有很多种。Java 的这种方式只是其中较为有效的方式之一,他通过编译器做出严格的限制,这其中兼有优劣。

  2. 如果需要,那么这个 Exception 类型是否应该成为一个接口声明的一部分?

    答:其实这还要从程序整体的设计角度来讨论。在 Java 中非 RuntimeException 其实已经不是一种普通的异常那么简单。因为最终在程序运行的时候,Java 不允许有非 RuntimeException 被抛出到虚拟机,这也就是说这种异常看上去只存在于设计时。他更像是程序运行期间的一种消息传递机制,只是首先这种消息“有别于(Except)”其他的返回类型,另外就是它会沿着调用栈强行向上追溯。显然这种异常更多的是在程序设计阶段起到操控程序非正常运行时行为的作用。因此他更像是一种接口,自然声明这样的接口也是理所应当的。

          但是 Java 还有 RuntimeException 的概念,而 .Net 或许就是没有区分他们而已,因为一旦程序遇到异常,无论是被程序本身恢复,还是被虚拟机捕捉到导致强行非法结束,都是可以接受的,毕竟对用户而言除了造成不方便,并不会有安全上的隐患。

  3. 一个已知的有可能发生的 Exception 被操作系统捕获是否可以说是用户在处理这个接口的声明的时候,出现了疏忽呢?

    答:这个自然,如果不是 Java 概念中的 RuntimeException,那么如果真有这样的异常被虚拟机捕捉到肯定就是 Bug,但至少是一个安全的 Bug。而同时,就像我在帖子里说到的,需要在开发难度和设计完整性上达到一定的平衡。.Net 这样自然忽视了后者,但同时显然使用就方便多了。

  4. strong-type 的目标是在编译器发现尽可能多的关于类型的错误,如果我们把接口的定义看作类型的一部分,那么是否可以说,这个有可能将 checked exception 暴露给操作系统的编译器在 strong-type 上有缺陷呢?

    答:没看懂!:-)

      还是那句话,我觉得我写这篇帖子已经足够公正了。.Net 在异常处理的设计上显然不如 Java 更趋近完美,但毕竟还是可以用的,还是好用的,剩下的瑕疵是可以通过程序员的经验,以及良好的开发习惯和管理来弥补的。

      关于 Java 和 .Net 优劣的争论一直在继续,而在异常处理方面体现得最为激烈,因为他们之间的差异是如此明显。.Net 晚于 Java 出现,那么 Java 对 .Net 就理应起到很重要的借鉴作用,但是伟大的 Anders Hejlsberg 为什么没有继续 Java 的实现方式,而是另辟蹊径,这是一个非常值得研究的问题。因为我们要承认一个真理:正确的东西大家都是一样的正确,错误的却各有个的错误。可以肯定这不可能是 Anders 的疏忽,那么他的道理究竟何在,或者说他们之间究竟有什么区别?

      在你能耐下心来看完这篇帖子之前,我想要明确告诉你一个结论:Java 和 .Net 在异常处理的本质上是没有区别的。

一、Java 是如何处理异常的

      如果一个 Java 方法要抛出异常,那么需要在这个方法后面用 throws 关键字定义可以抛出的异常类型。倘若没有定义,就认为该方法不抛出任何异常。如果从方法的入口和出口的角度去考虑一下这个规范,我们知道参数可以认为是方法的入口(当然某些情况下也可以是出口),而返回值则是方法的出口,这是在程序正常执行的情况下,数据从入口入,出口出。要是程序非正常执行,产生异常又当如何? 被抛出的异常应该如何从方法中释放出来呢? Java 的这种语法规范就如同给异常开了一个后门,让异常可以堂而皇之“正确”地从方法里被抛出。

      这样的规范决定了 Java 语法必须强行对异常进行 try-catch。设想一下,对于以下的方法签名:

public void foo() throws BarException { ... }

暗含了两方面的意思:第一,该方法要抛出 BarException 类型的异常;第二,除了 BarException 外不能抛出其他的异常。而正是这第二点的缘故,我们要如何保证没有除 BarException 之外的任何异常被抛出呢? 很显然,就需要 try-catch 其他的异常。也就是说,一般情况下,方法不抛出哪些异常就要在方法内部 try-catch 这些异常。

      Java 这样的机制既有优点,也有缺点。先来说说优点:

  • 很显然,这种规范是由 Java 编译器决定的。倘若 Java 程序的入口点 main() 方法没有任何异常抛出,就是说要在 main() 方法内部,即整个程序内部捕捉所有的异常,否则将无法通过编译。这样编译器保证了程序对每个异常都有相应的计划和处理,不会有未处理的异常被泄露到虚拟机中,导致程序意外中断或退出,也就是增强了程序的健壮性。当然,Java 有 RuntimeException 的概念,这样的异常仍然可以随时被抛出到虚拟机中。
  • 强行 try-catch 要求把异常作为程序设计的一部分看待。就如同方法的参数和返回值一样,在编写一个方法时,要结合上下文做出通盘的考虑和打算。虽然异常是所谓的“意外情况”,但是这些“例外”理应是被我们全部了解并处理的。
  • 方便调试。异常理应在正确的位置被捕捉。当异常发生时,我们能更清楚的了解到其来源和相应处理程序的位置,而免去了在整个调用栈中摸索的麻烦。
  • 在不借助任何文档的情况下,从方法签名就可以知晓应该对哪些异常进行处理。

      Java 异常处理机制的这些优点也直接导致了他的致命弱点:将程序变得异常繁复。往往一个简单的程序,功能代码寥寥几行,而异常处理部分却占用了程序的绝大部分篇幅;同时导致缩进深度加深,既不利于书写,也不利于阅读。另外他的强行 try-catch 需要程序员有更高深的造诣,能够通盘考虑异常处理设计问题,这个在程序开始之初或者对于初学者是一个不小的门槛。这往往会阻碍其推广与发展,因为低水平初学者的信心往往因此而受到打击。然而对于高手来说,编译器是否能帮助他们找到未被处理的异常只是一个方便与否的问题,只要在编写方法时注意了异常处理,即便没有编译器的支持,情况也不会糟糕太多。反而倒是由于要遵循这样复杂的异常处理规范,以至于大多数人都可能为了图一时方便,对异常的基类型 Exception 或 Throwable 进行笼统地捕捉,这样做的危害就是那些你无法处理的异常被溺死在处理程序中,(按照异常处理原则,我们应该只捕捉那些可以被处理或恢复的异常,而把其他的异常继续抛出。至于这样做的优势,以及不这样做所带来的问题,不是一两句能够说清楚,这里就不展开讨论了。)导致程序的不稳定和不确定。既没有发挥 Java 语法在这方面的优势,反而增加了忧患。

二、.Net 是如何处理异常的

      一句话概括 .Net 的异常处理方式就是随心所欲。没有人要求你一定要抛出异常,也更没有人要求你一定要捕捉异常。未被捕捉的异常会被以 Unhandled Exception 的形式抛出到虚拟机中。在此我就要先解决一下文章开头提到的问题,为什么说 Java 和 .Net 这两种异常处理机制在本质上是相同的。可以从两个方面来考虑:

  1. 默认情况下。Java 在默认情况下 main() 方法是不抛出异常的,正如前面所说的,这要求所有的异常都必须在 main() 方法内部被捕捉;而 .Net 则没有这种约束,他的 Main() 以至于整个应用程序中的任何一个方法对异常都是完全开放的。这样来看,这两者刚好是对立互补的。
  2. 非默认情况下。Java 可以通过在 main() 方法后面加 throws 关键字使得整个应用程序对异常开放;而 .Net 则可以通过给应用程序域(Application Domain)的 UnhandledException 事件添加委托达到捕捉所有异常的目的,很显然这又是对立互补的。

因此,就好像一个是“正反”,一个是“反正”,加在一起“正反反正”都是一样的,对于达到控制异常的目录来说,是没有区别的。

      很多 Java 爱好者都鄙视 .Net 的这种行为,一方面他令程序变得不够健壮,因为默认情况下没有强制的办法要求所有的异常都被处理,或被正确处理;另外,他为调试增加了困难,不借助文档或代码你将无法了解到一个方法可能抛出什么异常,而当一个异常被抛出的时候,同时异常处理代码又写得不够完善,你将不得不仔细查看整个调用栈来确定异常出现的位置,而对于这一点 Java 默认是强制的。

      但是 Anders 的想法总是有道理的。

  1. .Net 代码写起来非常容易。这是对于初学者,或者那些只是想实现一些测试性小功能的人而言,你完全没有必要考虑太多异常处理的细节,你要的就是写代码,然后让他跑起来。这样的简单性无疑是你希望看到的,这样的简单性无疑更有利于 .Net 在市场上的推广。由于他在这方面并没有什么理论上的漏洞,也就仍然适合构建庞大的项目,只是感觉没有那么舒服罢了。
  2. 一定程度上增加了程序的安全性。难道不捕捉异常可以被成为是安全的吗?这个话也许要从另外一方面来想,前面说过,有些 Java 程序员(绝对不占少数)为了图省事,在强行捕捉异常的压迫下,选择捕捉异常的基类型,也就是捕捉所有的异常。这样当有你无法处理的异常出现时,他们就溺死在了你的代码中,而外面的程序全然不知,还在以一种不确定的状态运行着,这就可能是危险的开始。而如果是 .Net,那么 Unhandled Exception 会被虚拟机捕获,导致程序异常退出,虽然这从面子上对于用户不是一个好的交代,但是深层次地他避免了程序在危险的状态下继续运行。

      总之,萝卜白菜各有所爱。我的这篇帖子力求公正地讨论了这个问题,希望能对你有所帮助。

晚霞照亮了天
彩虹挂在我身边
下过雨的心情只想黑夜快快来临

血色的天际
没有月亮独自寻觅
混沌从天地开合之初流浪到这里

我的心里翻了天
远远近近都是白色闪电
惊天霹雳像远山那边男孩的呐喊

明天总该有雨吧
不然只有我一个人在哭
请不要再安排彩虹
生命中没有那样的演出

明天你不再来了吧
这样好不只我一个人孤单
鹊桥相合在七月初七
我的生命中没有那一天

      我终于决定不再继续看极限编程大师 Kent Beck 所写的那本 Test Driven Development: By Example 了。因为我实在无法忍受我与作者在软件开发流程模型上的分歧。但这并不妨碍我认同并实践作者所谓“测试先行”的理念。我是一个趋近完美主义者,因此我更喜欢像设计模式那样偏重设计的方法论,我不能认可一个靠不断修改而成长起来的软件。

      按照 Kent Beck 极端的测试驱动开发模型,一个软件应该是这样写出来的:

  1. 写测试代码。
  2. 编译测试代码,编译无法通过。(因为没有实现代码。)
  3. 写一份最简单的实现代码,让编译通过。
  4. 迭代地修改代码以减少实现代码的重复度,降低实现代码和测试代码之间的耦合度。

就是这样,一个软件被一点一点修改而成。他强调了在这个过程中有两个主意事项:第一,在添加任何功能之前都要先写测试,除非代码是用于调试的,这也就是所谓测试先行的概念;第二,当编译无法通过时尽量不要再写新的测试。

      我之所以不认同这个模型,就是因为其中间产生了大量的迭代过程,而这些过程很可能只在头脑中出现,或者根本就不曾出现。这些过程一方面增加了开发的消耗,也在某种程度上引入了更多的不确定性。但是其中测试先行的做法,能最大程度保证单元测试的完整性与正确性。

      因此,我理想中的开发流程模型应该是这样的:

  1. 用户需求调研,导出成软件需求
  2. 根据软件需求进行设计
  3. 根据设计编写测试用例
  4. 根据测试用例编写测试代码
  5. 构建软件框架使测试代码能够编译通过
  6. 写 Mock 来保证测试用例代码准确无误
  7. 按部就班地写程序,用测试用例来保证所写的代码准确无误

这其中可以按照极限编程的思想以一个可独立发布可运行的模块作为单位,这样可以有效地控制模块的大小,还可以避免出现过度设计的问题。

Next Page »