Category Archives: 编程

Java 栈上的 JavaScript

注:以上视频在 youtube 上,你懂得该怎么做。

这是我在 ShenJS 2015 上面所做的一个 lighting talk,当时的题目叫做 Enterprise JavaScript。

其实那天准备了很多演示和代码片段,无奈时间太短,只把 slides 简单过了一下就没有时间了,于是在这里做一个完整的介绍。

Enterprise JavaScript 是一个非常大的话题,会涉及到跟各种 Java 中间件的互操作问题,这里仅就如何在一个完整的 Java 栈上面以 JavaScript 作为首要编程语言来提高 Java 系统的开发效率展开讨论。

作为一门常年把持 TIOBE 排行榜冠军的静态编译类语言,Java 固然有数不胜数的优点,以至于其在企业级应用开发领域拥有无可替代的地位。但是,相信每一个 Java 开发人员都会在日常工作中忍受其种种弊端带来的烦恼,其中最令人发指的也正是其静态编译类的特性。静态类型在减少开发人员出错可能性的同时降低了语言的动态扩展能力;而编译执行除了能保证代码的编译期校验以外,更多的就只剩下无尽的等待。尽管现在大多数的工具链和集成开发环境都可以做到增量编译,但是增量编译后可执行代码的热部署能力却是差强人意。尽管有一些商业的开源的解决方案,但总归不是天生特性,使用很不方便。

其实针对 Java 缺陷的改良,社区始终也没有停止过尝试。著名的成果包括 ScalaGroovyJythonJRuby 以及我们今天要讨论的 JavaScript。社区通常都有这么一个共识:Java 虚拟机是个好东西,但 Java 语言本身不一定。因此改良的重点全部集中在语言层面,通过引入具有不同特性的新语言,经过编译器或解析器翻译成 Java 字节码,再交给虚拟机来运行。

另一方面,JavaScript 近两年人气爆棚,有逐渐发展成为全栈开发语言的趋势。尽管在 Node 平台上使用 JavaScript 开发后端应用早已是习以为常的事情,但是在 Java 平台上,这件事情就显得不那么容易了。说到在 Java 开发栈上运行 JavaScript 就不能不提大名鼎鼎的 Rhino。这是早在 1997 年就已经出现的 JavaScript 引擎,并完全由 Java 代码编写而成。后来被 JDK 1.6 收纳为官方默认的 JavaScript 引擎。这也就意味着在整个 Java 发展历史中,都是一直可以支持运行 JavaScript 的。不过由于大环境(在 Java 平台上面跑 JavaScript 毕竟接受度不高)以及 Java 虚拟机对动态语言支持能力欠缺等问题,使得这件事情并没有流行起来。

另外一个导致 Rhino 不够流行的原因,就是对其性能的诟病。在早期的时候 Rhino 是将 JavaScript 代码直接编译为 Java Class,运行期性能非常出色,往往可以击败很多用 C 实现的基于 JIT 技术的引擎。不过生成 Java 二进制代码和装载这些生成的类是个非常重量级的操作,需要耗费大量时间,而且编译过程产生的许多无用中间类和字符串无法被虚拟机垃圾回收掉。不过后来引入了解析模式以后这些问题就得到了解决。一些 性能 测试 报告 显示出 Rhino 的性能依旧非常良好。

后来 Java 官方也意识到虚拟机对动态语言欠缺支持以后,就从 JDK 1.7 开始引入了 invokedynamic 的技术,可以有效提升动态语言的运行性能,而且 Oracle 也在 JDK 1.8 中自带了一个基于此技术实现的新一代的 JavaScript 引擎 Nashorn,终于让性能不再是什么大问题了。

javascript-in-jvm-2

针对在不同版本的 JDK 上运行 JavaScript,可以有不同的解决方案(如上图)。如果是在老版本的 1.6 上面,只能使用 Rhino,以及在 Rhino 之上封装的开发框架 Ringo,这个组合里面没有对 invokedynamic 的支持。当然由于向后兼容性的存在,其实这个解决方案是可以适用于后续 1.7 和 1.8 版本的。如果是在 1.7 版本上面尝试使用 JavaScript,那么可以考虑 DynJS 引擎,这是另外一个独立的 JavaScript 引擎实现,支持了 invokedynamic 技术,与此配套的还有一个 Nodyn 项目,是对 Node 的一个移植。不过很可惜,这套开发栈没等流行起来,就被后来的技术给超越了,因此除非你必须要坚守在 1.7 上面,否则并不建议采用。最后,也是这个话题的终极解决方案,就自然是升级到 JDK 1.8 并使用官方的 Nashorn 引擎,与其对应的框架可以选用 Vert.x。Vert.x 是由 Eclipse 开发的,实现了良好的事件驱动和异步非阻塞机制,这些都是 Ringo 所没有的。再有 Vert.x 还是 reactive 的。不过 Oracle 官方有一个基于 Nashorn 引擎的 Node 实现,叫做 avatar.js,不过已经有一段时间没有更新过了。

下面展示一些使用 JavaScript 开发 Java 应用的示例。

注:在尝试运行这些样例代码之前,请确认已经正确安装了 JDK 1.8u51 或以上版本、Vert.x 3.x 和 Maven 3.x。

一、操作 ArrayList

arraylist

二、Filter and Reduce

filter-and-reduce

三、Web 服务

webserver

四、Spring、Stick 和 Hibernate(SSH)

这是一个比较复杂的例子,首先项目使用 Maven 来管理,并通过 jetty-maven-plugin 来运行。项目内部使用 Spring 作为整个集成的核心,用 Hibernate 来实现数据访问。Stick 是基于 Ringo 的一个组件,实现了类似 express 一样的功能。下面的这段代码,主要演示了如何通过 JavaScript 实现一个 router,并在 router 内部调用 Spring 和 Hibernate 的 API 实现数据查询,并返回 JSON 结果。此项目的完整源代码请在 Github 上获取(这里面还包含了会上的演示文稿,所有的样例都在 demo 目录下)。

运行该项目前,需要首先安装一个 MySQL 数据库,并运行在默认的 3306 端口,root 用户的口令为 mysecretpassword。创建一个名为 ringo-ssh 的数据库,字符集为 UTF-8。然后使用如下的 SQL 语句创建一个数据库表 T_TASK,并插入一些数据:

另外,该项目依赖了我们公司的两个开源项目,分别是 origincdeio-runtime。请从 Github 上 clone 下来以后,进入项目目录,运行 mvn clean install 来编译和安装。注意执行的顺序为先 origin 再 cdeio-runtime。

最后,可以通过运行 mvn jetty:run 命令来启动这个示例。

注:如果是第一次运行该示例,Maven 会下载很多依赖包,这个过程根据网速快慢会长短不一,请耐心等待。

以下是系统运行后的控制台截图和 curl 的返回结果。

ringo-ssh-maven

ringo-ssh-curl

总之,在 Java 平台上使用 JavaScript 作为首要开发语言是完全可行的,在上面这个示例中,除了 Hibernate 必须的领域实体等是使用 Java 来编写的之外,其他的大部分逻辑都是可以用 JavaScript 完成的,而且可以跟各种 Java 中间件完美集成。有关这方面的深入介绍远不止一篇博客所能言尽,有兴趣了解更多的可以参考我公司的 CDEIO 平台。

ShenJS 2015 那些年我们一起追过的盛典

这还是我第一次听说过有追会的,当我第一天来到 ShenJS 的现场。

深圳的天气一如既往的好,也一如既往的热。

对 JSConf 其实早有耳闻,放眼全世界都算是最顶尖 JS 开发者的盛会。JSConf CN 三年前悄然兴起,我就在望眼欲穿的盼。沪——京——杭——终于,就跟 Linkin Park 要光临深圳开演唱会一样,我听说了传说中的 ShenJS。

虽然在经济上总跟“北上广”齐名,但是在技术峰会方面,深圳永远无法望“京沪”项背。或许是我所接触的行业不同,国内的互联网圈子或许会更活跃,更喜欢组织活动,进行分享。可惜我们是搞企业信息系统集成的——对,就是那些成天面对需求变更,被外行指导软件开发,永远延期,没有最烂只有更烂的项目外包企业——在这里,Oracle 是我们的天空,Java 是我们的空气,Apache 是我们的信仰。

这似乎跟 JS 没有太大关系,除了我个人的 JS 情节。

从我接触 JS 的那天起就被她吸引了,虽然当时根本不知道那是一种什么样的语言。那个时候 Web 开发很纯净,纯净到只有 IE 6,Firefox 还是 1.0,Chrome 还没有出生,什么 jQuery 还不流行,prototype.js 在争议与无奈中被一遍又一遍的使用,Crockford 刚刚发明 JSON,即时函数调用还在他脑子里,至于 Node.JS 嘛——孩子,你说的是用来处理 DOM 的函数库吗?就这样在懵懂中我完成了职业生涯的第一个像模像样的项目,整个前端重度依赖 JavaScript。也是那个时候才知道,原来一种语言可以有那么多种风格截然不同的写法,浏览器原来有那么大的差异,IE 6 原来是那么的慢。或许正是这次项目的成功让我对 JS 产生了好感和依赖。“任何能够碰巧用 JS 编写的程序最后就一定会被用 JS 编写”,这个碰巧在我这里成为了必然。

转眼间十年已逝,Java 和 JavaScript 都迎来了 20 周年的庆典,我走入了 ShenJS 的会场。

一进门就被热烈气愤所惊骇:这是技术人员的聚会吗?程序猿会有那么嗨吗?竟然还有人是从外地专门赶来的?有人遭遇台风到不了的?竟然有神人参加过全部四届的 JSConf 大会?Oh my god,这是我想要的感觉,外加舞台上还有个这么逗逼的主持人,Oh yeah,程序猿的高潮瞬间就一泻千里了。

JSConf 无愧为国内最高水平的 JS 会议,总共邀请了来自 11 个国家和地区的 21 位讲师,为大家奉上两天不间断的饕餮盛宴。先来看看菜单吧:

JS中国开发者大会   ShenJS

主持人 Goddy Zhao 必须要提一下,他的开场和串场也真是醉了,还让单身狗明白,“找不到对象”的原因是因为写了太多的空指针。

言归正传,这毕竟还是一次技术的交流会议,总得有点干货吧。

一、React + Flux

这个一点都不稀奇,这半年除了 ES6,估计就属 React 最风光无限了。Web 开发就是这样,为了解决 DOM 性能瓶颈,八仙过海,各显神通。不过 Facebook 给出的解决方案还是让社区为之一振。Virtual DOM(就是在内存中构建一棵虚拟的 DOM 树,在界面需要更新的时候,先在内存中做一次 diff,只将发生变更了的 DOM 节点应用到界面上)的引入让社区瞬间沸腾(Herman 说 React 在阿里很火),看上去多此一举的计算,却能显著提高渲染的性能。在符合要求的前提下,两棵 DOM 树的 diff 复杂度能控制在 O(n) 水平,充分体现了 Facebook “牛逼闪闪”的技术实力。就连不怎么好看的 JSX 也逐渐被爱屋及乌了,况且 React 的组件特性还能顺带解决 CSS 的一些固有问题

Flux 更是简单得一塌糊涂,简单到竟然不一定需要第三方类库来实现(当然还是会有的)。比起 MVC 和 MVVM 这样的重量级框架,React + Flux 就可以解决从数据流动到界面渲染的全套 UI 过程,而且强制要求的单项数据流,也使得架构设计变得简单许多。

因为 Virtual DOM 需要先期做一次 diff,那么很明显的性能瓶颈就有可能会出现在这个地方。Facebook 何许人也,immutable.js 就好像是 React 的孪生姐妹一样,早就在那里等待大家的临幸了。这次会议上听说的唯一一个新概念就是 Persistence Data Structure,其实也可以完美的和 React 集成来实现更多炫酷的能力。我之所以对这个概念非常感冒是因为在曾经的一个项目中,客户就要求实现这样一个需求:可以将组织机构变更的历史记录下来,并随时可以回滚。这正是 PDS 的应用场景呀,当时我懂得少,把这需求给搪塞回去了。

Web 开发还经常前前后后的变化。曾经的服务器比终端性能好,大部分计算在服务器上完成,以实现所谓的瘦客户端;后来终端性能提升了,为了释放服务器的计算能力,降低网络传输负载,将一部分的运算(尤其是界面渲染)转交给终端,以实现所谓的富客户端(或者那个时候的流行词 Web 2.0);现在 React 的来临再一次提出服务器端渲染的能力,而且 isomorphic/universal 的类库还使得前后端的代码一致而通用。

不过 React + Flux 也不是完全没有问题。Flux 官方对异常处理没有一个明确的方案指引,Yahoo Fluxible 的实现也通篇没提到过关于异常数据该如何流动的问题,这些还需要时间和更多的社区实践来总结跟积累了。测试方面,Jest 的性能是个严重的问题,coding.net 的同学们在单元测试的选型中就遭遇了这个问题,最终投向 Karma 的怀抱。另外我觉得 Jest 的侵入性太强,不如 Karma 有那么好的框架整合能力,对于混合使用各种技术的单元测试应该不是个明智之选。

二、异步流程控制

这个话题应该是 JS 社区永远无法停止的。JS 异步单线程的特性,刺激了各种最强大脑对于此问题的思考。就目前状况来看,解决方案无疑有如下几种:

1、Promise:ES6 先天支持的特性,ES5 也有各种第三方库可以使用,算是最容易获得的解决方案。
2、co + generator:这是最接近 ES7 async/await 写法的解决方案,需要 JS 运行时支持 ES6 的 generator 特性,或通过 Babel 等转译。
3、thunks:这是在严老师的《Koa 和 Toa 的框架原理及开发实践》课程中提到过的一种解决方案,能够友好的支持 promise,generator 和回调等各种异步返回方式。
4、async/await:这个应该算是异步流程控制的终极解决方案了,可惜是 ES7 中才会出现的特性,但是依靠 Babel 尝尝鲜是不成问题的,但谁能保证最终的规范不会发生变化呢?

当然,重要的事情最后再说,庄恒飞同学给出的 fibjs 是以消灭这个问题的思路去解决问题的,有兴趣的大大们可以多多关注。

三、架构实战

说实在,因为不从事互联网这种高并发、重架构的日常工作,所以对其中的一些设计没有太多感觉。而且因为没有真正遇到过问题,对其解决方案的精妙之处也就无从感知了。只是在演示中看到各种性能的提升。外行就不在这里评论了,免得被笑掉大牙。

四、炫酷演示

我把 90 后(尼玛,那么年轻)沈毅同学的 ECharts 课程放到炫酷演示里面应该毫不为过,顺便也刺激一下身为东道主的企鹅公司,就算不开 session,也好歹赞助几个公仔吧。百度在前端领域为社区做出的贡献是有目共睹的,我们公司在日常开发中使用的唯一一个国产开源产品就是百度 FEX 团队出品的 F.I.S。当然这个课程并不是纯粹的演示,还是深入介绍了一些难点问题的解决思路和实现原理,本人对 canvas 及相关内容不甚熟悉,也不妄加评论了。

真正的炫酷演示还是来自于三个老外。感觉国内大牛们分享的大多是跟工作有关的内容,或者或多或少会在工作中碰到或用到的;而国外的牛牛们似乎更偏重娱乐与兴趣,不知是环境使然还是文化使然。

三个炫酷演示都是跟硬件有关,也再一次证明了 JS 作为全栈语言的强大实力。曾几何时,硬件的开发都永远是阳春白雪,束之高阁的。什么汇编还有 C,都不是普通码农所能靠近的。然而 JS 正在悄悄的改变着这一切。Arduino 作为标准的开源硬件,JS 作为使用最广泛使用的编程语言,他们的结合剩下的就是想象力的无限放飞。使用 Johnny Five 就可以通过电脑连接 Arduino 而实现用 JS 控制硬件的能力,Andrew 的 Nodebot 就是这么一个原理。Tim 的 NERD DISCO 更是使用了浏览器的 Web Audio API,在浏览器上实现了声音可视化的效果。其实技术都不是问题,音乐 + 灯光 + DJ 调音器,呈献给现场的就是一段炫爆了的电子 DISCO 秀,真是”不会写 JS 的 DJ 不是好讲师呀”。

有了声音自然还要有图像,平面的图像电影院都不怎么放了,3D 的东西也见太多了,VR 是近些年红的发紫的新鲜事物,ShenJS 也真是给力,Martin 关于 WebGL 和 WebVR 的介绍也算是让我们这些无法接触到像 Google Cardboard 这类玩意儿的天朝子民能开开眼界。而且他现场演示使用的 IDE 真是与众不同,实时写代码,还能及时看效果,关键是现场写程序调 3D 图形建模,可见功底之扎实呀,曾几何时,我们学过的这些知识都还给大学老师了。

Martias 的 p2p pipes 现场写代码,佩服佩服,讲什么都不重要了,但是老外们的 JS 都不写分号的吗?

五、其他

剩下的这些就不知道怎么分类了。

PM2 和 Keymatrix.io 的介绍,说实在的下午第一节,我基本上半睡半醒,只知道功能确实强大,还给出了 ShenJS2015 一个七折的优惠码。当然最令人兴奋的还是作为讲师的 Alexandre 竟然是 PM2 的作者,“敬仰犹如滔滔江水连绵不绝,又如黄河泛滥一发不可收拾”。

509.pic

Hax 的 JavaScript: The World’s Best Programming Language,我不得不先上图了,因为群里已经有人将此作为表情了。我在这里想评论的一点就是,这明显是一群 JSer 在一起意淫的结果嘛,但是那又怎样,JavaScript 专治各种不服。

Lighting Talk 是个有意思的环节,每个程序猿都有分享的意愿,这里就能满足你“好为人师”的心理。两天的小讲座内容着实不少:有介绍安全漏洞的,有介绍自己小产品的,有诉说开发经验的等等,太多了没都记下来。让我印象深刻的是华大基因的介绍,因为以前在 Amazon Awsome Day 的活动中,华大基因对 AWS 的应用也是作为典型案例被介绍的(这算广告吗,能给点赞助费吗)。当然还有我自己分享的关于 Enterprise JavaScript 的内容,可惜时间太短了,又是个大话题,准备的一些 Demo 都没来得及演示。后面抽空写个博客详细展开一下。

最后的最后,以 Herman 老师的课程作为结束吧,虽然全篇没太注意他在讲什么,但就是觉得印象深刻。Herman 老师的前端开发摩尔定律更是令人俯首称臣呀:每 18 到 24 个月,前端开发的难度就会增加一倍!干!

因为住的地方离南山比较远,所以两天的 Afterparty 都没有参加,尤其是 Nodebot 的分组对抗,尤其遗憾。后面又听群里说周日的派对上面很多人聊得非常投机,也没有机会参与。这是算是我第一次参加这种规模的技术峰会,收益颇深。难怪会有这么多忠实粉丝不远万里、不怕险阻、不惧台风、不辞艰辛的来执着追随着这个会议。或许所有 JSer 的内心都跟 JS 本身一样无比复杂,又无比单纯。回归生活我们是被人揶揄的程序猿,在这里我们是崇尚自由的乐天派。不论下一届的 JSConf 开在哪里,相信永远会是“那些年我们一起追过的盛典”。

版权所有,欢迎转载!

答 zhangdi 同学的问题

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

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

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

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

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

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

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

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

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

答:没看懂!:-)

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

Java 和 .Net 在异常处理机制上的区别

关于 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 这两种异常处理机制在本质上是相同的。可以从两个方面来考虑:

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

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

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

.Net 代码写起来非常容易。这是对于初学者,或者那些只是想实现一些测试性小功能的人而言,你完全没有必要考虑太多异常处理的细节,你要的就是写代码,然后让他跑起来。这样的简单性无疑是你希望看到的,这样的简单性无疑更有利于 .Net 在市场上的推广。由于他在这方面并没有什么理论上的漏洞,也就仍然适合构建庞大的项目,只是感觉没有那么舒服罢了。

一定程度上增加了程序的安全性。难道不捕捉异常可以被成为是安全的吗?这个话也许要从另外一方面来想,前面说过,有些 Java 程序员(绝对不占少数)为了图省事,在强行捕捉异常的压迫下,选择捕捉异常的基类型,也就是捕捉所有的异常。这样当有你无法处理的异常出现时,他们就溺死在了你的代码中,而外面的程序全然不知,还在以一种不确定的状态运行着,这就可能是危险的开始。而如果是 .Net,那么 Unhandled Exception 会被虚拟机捕获,导致程序异常退出,虽然这从面子上对于用户不是一个好的交代,但是深层次地他避免了程序在危险的状态下继续运行。

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

测试驱动开发和软件开发流程模型

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

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

  • 写测试代码
  • 编译测试代码,编译无法通过(因为没有实现代码。)
  • 写一份最简单的实现代码,让编译通过

迭代地修改代码以减少实现代码的重复度,降低实现代码和测试代码之间的耦合度。

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

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

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

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

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

不要跟 prototype 一起学坏

新版本的 JSON 修改了 API,将 JSON.stringify() 和 JSON.parse() 两个方法都注入到了 Javascript 的内建对象里面,前者变成了 Object.toJSONString(),而后者变成了 String.parseJSON()。

不可否认,经过这样的修改,JSON 的接口确实比以前漂亮且好用多了,对于这样的对象:

以前的代码得这样写:

而现在的代码是:

前者 JSON 作为一个工具类,提供一组方法实现转换功能,而后者让你觉得对象天生就具有这样的方法。如果对于编译语言来说,这可能没什么问题,但是对于 Javascript 问题就来了。考虑下面的代码片段:

如果是一个干净(没有包含任何其他 Javascript 类库)的 Javascript 执行环境,这段代码应该不会输出任何内容,因为 obj 是空的;但是当代码中包含了新版本的 JSON 后,你会发现结果是 toJSONString。很多情况下我们会用 Javascript 的 Object 来模拟 Map,那么上面的这段代码的功能就是遍历此 Map,一个空的 Map 怎么会凭空出来了一个成员?这正是由于新版本的 JSON 的实现所决定的:

JSON 并不是第一个采用这种写法的 Javascript 类库,始作俑者就是我文章标题中提到的 prototype。prototype 为 Object 注入了 extend 和 instanceof 等方法来支持 Javascript 的面向对象扩展,这样写的后果就是造成了非常严重的兼容性问题。你有没有注意到为什么有些 Javascript 的类库特别声明”使用了 prototype”或者说是”与 prototype 兼容”?因为如果他使用了 prototype 那么你需要在用此类库的时候格外小心,像上面那样的代码不能正常工作;而如果说其与 prototype 兼容,那么可能该类库并没有使用 prototype 但是你使用 prototype 并不会影响他的正常运行。

prototype 固然是个成功且应用广泛的 Javascript 框架,但是这并不能说明他解决问题的思路就是正确的。开发编译语言程序的人们都知道要以组织或公司的名称作为命名空间前缀,来避免冲突,奈何如此著名的两大 Javascript 框架提供方反而积极去打破这个规则呢?难道实现了功能就可以不考虑兼容性的问题吗?

如何重写 Equals 方法

不要以为这是件容易的事情,先来看一个正确的 Equals 方法应该具备的条件:

  1. obj.Equals(obj) 应该永远返回真
  2. obj1.Equals(obj2) 和 obj2.Equals(obj1) 应该返回相同的结果
  3. 如果 obj1.Equals(obj2) 而 obj2.Equals(obj3) 那么 obj1.Equals(obj3) 应该也为真

别以为我的智商就小学水平,回去看看你的代码,这些都能做到吗?写一个具备如此条件的 Equals 方法不是件容易的事情!从实现角度说,一般来说分为两种情况:

一、给直接继承自 Object 基类的类型重写 Equals 方法

二、给间接继承自 Object 基类的类型重写 Equals 方法

参考了 Jeffry Richter 的 Applied Microsoft.NET Framework Programming 一书。

window.eval() 和 eval() 的却别?

前两天作了一个试验,把 Javascript 文件当作一个字符串读到浏览器的内存中,然后使用 eval() 方法来解析,对某些内容的 Javascript 文件(我用的是 prototype.js)会有异常抛出。好像浏览器的 Javascript 引擎总是试图去执行一些本来是声名性的语句,例如定义 function。在我的试验中,当解析到 prototype 给 Object 添加的 extend 方法时,说第一个参数 destination 没有定义。

当研究了另一个非常有名的 Ajax 框架 Dojo 之后发现,如果使用 window.eval() 方法,上面的问题就没有了,解析可以非常顺利地进行,就像把 Javascript 文件通过

新发现的一个好东西 Script#

Script# 就是一个用 C# 代码来写脚本语言,然后通过特定的编译器将其转换成 Javascript。

不想在我这里说太多,作者的 blog 上面已经写的很清楚了。

有兴趣的可以到这里看一下。

另外还觉得这个东西生成的 Javascript 所引进的面向对象模型有点意思,等我研究一下再来说说。

偶然间发现一个 π 的算法

long a = 10000, b = 0, c = 2800, d, e = 0, f[2801], g;

main() {
for (; b – c; ) f[b++] = a / 5;
for (; d = 0, g = c * 2; c -= 14, printf(“%.4d”, e + d / a), e = d % a);
for (b = c; d += f[b] * a, f[b] = d % –g, d /= g–, –b; d *= b);
}

结果是
5926 5358 9793 2384 6264 3383 2795 0288 4197 1693 9937 5105 8209 7494 4592 3078 1640 6286 2089 9862 8034 8253 4211 7067 9821 4808 6513 2823 0664 7093 8446 0955 0582 2317 2535 9408 1284 8111 7450 2841 0270 1938 5211 0555 9644 6229 4895 4930 3819 6442 8810 9756 6593 3446 1284 7564 8233 7867 8316 5271 2019 0914 5648 5669 2346 0348 6104 5432 6648 2133 9360 7260 2491 4127 3724 5870 0660 6315 5881