GHawk

2005年12月10日 #

建议方正要是胜诉的话,为开源事业做点贡献

中国人民可以说是被盗版和Windows惯坏了。在Linux默认环境下的中文显示至今惨不忍睹。
看看现在一些主要的发行版本,默认设置下的日语的显示已经相当不错了。
作为汉字发祥地且拥有众多人口的中国,实在是有些悲哀。
文泉驿计划正在为实现这个目标努力。作为一个非盈利性组织,他们的贡献的确值得赞赏。
最近方正正在为字体的事情打官司,并提出了高额赔偿的要求。建议方正要是能够胜诉的话,贡献一些字体给开源组织,为弘扬汉语言文化多做点贡献。

posted @ 2007-08-23 15:01 GHawk 阅读(614) | 评论 (1)编辑 收藏

Maven2 用于度量和品质保证的插件


以上是一些常用的用于品质管理的插件。默认情况下都不用配置,相当方便。如果需要手动配置的话,根据网上的文档也相当容易配置。
apache的maven plugin页面: http://maven.apache.org/plugins/
codehaus mojo 页面: http://mojo.codehaus.org

posted @ 2007-05-28 14:32 GHawk 阅读(1516) | 评论 (1)编辑 收藏

Scripting in Mustang 的一点启发

2006 Sun Techdays Shanghai 的第2天下午有一个名为《Java Scripting: One VM, Many Languages》的Session。

Rags为大家展示了Mustang的一个新特性,Scripting in Java——脚本语言支持。

通过加入脚本引擎的支持,就能够在Java中解释Javascript,python,ruby等诸多脚本语言。

对于这个特性,想到的一个可能的应用就是在annotation中写脚本语言,然后在代码中用相应的脚本语言引擎解释执行。
保留到运行时的annotation可以用实现aop的功能,使用非inline的脚本就可以更灵活地控制aspect的行为。

比如:
//inline scripting
@ScriptBefore(script
="",language="javascript" )
public void foo() {
   
}

//non-inline scripting
@ScriptBefore(file
="scripts/logging.js",language="javascript")
public void bar() {

}

posted @ 2006-09-26 10:04 GHawk 阅读(1308) | 评论 (3)编辑 收藏

一个XPer提供的一些经验

前些天,和一位XPer进行了一次愉快的谈话。他向我讲述了一些感觉很有效的实践。

关于过程和迭代
他曾经参与过的项目的迭代是以月为迭代单位的,但事实上每周都会重复一个简单的过程。
在迭代过程中,他非常推崇Burn-Down Charts。这是一个Scrum的工具。通过Burn-Down Charts,能够把过程中间的变化记录下来,使过程高度可视化。等到一次迭代完成,回顾一下所有的Burn-Down Charts就能作为改进的判断依据。
KPT Meeting。所谓KPT Meeting就是 Keep-Prevent-Try metting。小组定期举行KPT会议(基本上是每周一次)。在KTP会议上,通过头脑风暴的方式每个人(不是某几个人)把各自认为前一阶段里做得好的方面写在Keep一栏里;做得不好的方面写在Prevent一栏里;希望尝试的写在Try一栏里。然后大家对这些项目进行评估和筛选。下一阶段中,Keep的项目继续保持,Prevent的项目应该杜绝,Try的项目进行尝试。

工具
在开展这些实践的时候,交流比较频繁。首推的工具是Mini white boardDC
选择Mini white board的原因并不是因为带有"mini"听上去会像 Mini Cooper 或者 iPod mini 那么cool。因为一块A3左右大小的白板非常适合个人或者结对使用,而且环保(省去了草稿纸)。虽然整个团队也有用于大规模交流的更大的白板,但那属于“竞争资源”,各自使用自己的白板更为方便。
交流结果产生后,为了不花不必要的时间去做精美的文档,一台轻便的DC往往是最合适的选择。当然,如果足够,手机上的照相功能也可以完成同样的任务。相比偷拍街上的MM,这些电子产品能够实现更大的价值。

关于结对
每天进行6小时的结对编程,分3次,每次2小时。每次和不同的成员组队。在结队的时候充分利用了上面提到的工具进行交流。如果出现两个人不能解决的问题的时候,会立即向整个团队提出,这样可能导致一次stand-up meeting。即使问题不能马上解决,至少也能确保每个人都知道这个问题。

posted @ 2006-08-24 15:45 GHawk 阅读(1422) | 评论 (0)编辑 收藏

关于locale的设定 (转)

 
locale是国际化与本土化过程中的一个非常重要的概念,个人认为,对于中文用户来说,通常会涉及到的国际化或者本土化,大致包含三个方面:看中文,写中文,与window中文系统的兼容和通信。从实际经验上看来,locale的设定与看中文关系不大,但是与写中文,及window分区的挂载方式有很密切的关系。本人认为就像一个纯英文的Windows能够浏览中文,日文或者意大利文网页一样,你不需要设定locale就可以看中文。那么,为什么要设定locale呢?什么时候会用到locale呢?

一、为什么要设定locale
正如前面我所讲的,设定locale与你能否浏览中文的网页没有直接的关系,即便你把locale设置成en_US.ISO-8859-1这样一个标准的英文locale你照样可以浏览中文的网页,只要你的系统里面有相应的字符集(这个都不一定需要)和合适的字体(如simsun),浏览器就可以把网页翻译成中文给你看。具体的过程是网络把网页传送到你的机器上之后,浏览器会判断相应的编码的字符集,根据网页采用的字符集,去字体库里面找合适的字体,然后由文字渲染工具把相应的文字在屏幕上显示出来。

 
在下文本人会偶尔把字符集比喻成密码本,个人觉得对于一些东西比较容易理解,假如你不习惯的话,把全文copy到任何文本编辑器,用字符集替换密码本即可。
 
那有时候网页显示乱码或者都是方框是怎么回事呢?个人认为,显示乱码是因为设定的字符集不对(或者没有相应的字符集),例如网页是用UTF-8编码的,你非要用GB2312去看,而系统根据GB2312去找字体,然后在屏幕上显示,当然是一堆的乱码,也就是说你用一个错误的密码本去翻译发给你的电报,当然内容那叫一个乱;至于有些时候浏览的网页能显示一部分汉字,但有很多的地方是方框,能够显示汉字说明浏览器已经正确的判断出了网页的编码,并在字体库里面找到了相应的文字,但是并不是每个字体库都包含某个字符集全部的字体的缘故,有些时候会显示不完全,找一个比较全的支持较多字符集的字体就可以了。
 

既然我能够浏览中文网页,那为什么我还要设定locale呢?
 
其实你有没有想过这么一个问题,为什么gentoo官方论坛上中文论坛的网页是用UTF-8编码的(虽然大家一直强烈建议用GB2312编码),但是新浪网就是用GB2312编码的呢?而Xorg的官方网页竟然是ISO-8859-15编码的,我没有设定这个locale怎么一样的能浏览呢?这个问题就像是你有所有的密码本,不论某个网站是用什么字符集编码的,你都可以用你手里的密码本把他们翻译过来,但问题是虽然你能浏览中文网页,但是在整个操作系统里面流动的还是英文字符。所以,就像你能听懂英语,也能听懂中文。
最根本的问题是:你不可以写中文。
 
当你决定要写什么东西的时候,首先要决定的一件事情是用那种语言,对于计算机来说就是你要是用哪一种字符集,你就必须告诉你的linux系统,你想用那一本密码本去写你想要写的东西。知道为什么需要用GB2312字符集去浏览新浪了吧,因为新浪的网页是用GB2312写的。
 
为了让你的Linux能够输入中文,就需要把系统的locale设定成中文的(严格说来是locale中的语言类别LC_CTYPE ),例如zh_CN.GB2312、zh_CN.GB18030或者zh_CN.UTF-8。很多人都不明白这些古里古怪的表达方式。这个外星表达式规定了什么东西呢?这个问题稍后详述,现在只需要知道,这是locale的表达方式就可以了。
 
二、到底什么是locale?
locale这个单词中文翻译成地区或者地域,其实这个单词包含的意义要宽泛很多。Locale是根据计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。
 
这个用户环境可以按照所涉及到的文化传统的各个方面分成几个大类,通常包括用户所使用的语言符号及其分类(LC_CTYPE),数字(LC_NUMERIC),比较和排序习惯(LC_COLLATE),时间显示格式(LC_TIME),货币单位(LC_MONETARY),信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES),姓名书写方式(LC_NAME),地址书写方式(LC_ADDRESS),电话号码书写方式(LC_TELEPHONE),度量衡表达方式(LC_MEASUREMENT),默认纸张尺寸大小(LC_PAPER)和locale对自身包含信息的概述(LC_IDENTIFICATION)。
 
所以说,locale就是某一个地域内的人们的语言习惯和文化传统和生活习惯。一个地区的locale就是根据这几大类的习惯定义的,这些locale定义文件放在/usr/share/i18n/locales目录下面,例如en_US, zh_CN and de_DE@euro都是locale的定义文件,这些文件都是用文本格式书写的,你可以用写字板打开,看看里边的内容,当然出了有限的注释以外,大部分东西可能你都看不懂,因为是用的Unicode的字符索引方式。
 
对于de_DE@euro的一点说明,@后边是修正项,也就是说你可以看到两个德国的locale:
/usr/share/i18n/locales/de_DE@euro
/usr/share/i18n/locales/de_DE
打开这两个locale定义,你就会知道它们的差别在于de_DE@euro使用的是欧洲的排序、比较和缩进习惯,而de_DE用的是德国的标准习惯。
 
上面我们说到了zh_CN.GB18030的前半部分,后半部分是什么呢?大部分Linux用户都知道是系统采用的字符集。
 
三、什么是字符集?
字符集就是字符,尤其是非英语字符在系统内的编码方式,也就是通常所说的内码,所有的字符集都放在/usr/share/i18n/charmaps,所有的字符集也都是用Unicode编号索引的。Unicode用统一的编号来索引目前已知的全部的符号。而字符集则是这些符号的编码方式,或者说是在网络传输,计算机内部通信的时候,对于不同字符的表达方式,Unicode是一个静态的概念,字符集是一个动态的概念,是每一个字符传递或传输的具体形式。就像Unicode编号U59D0是代表姐姐的“姐”字,但是具体的这个字是用两个字节表示,三个字节,还是四个字节表示,是字符集的问题。例如:UTF-8字符集就是目前流行的对字符的编码方式,UTF-8用一个字节表示常用的拉丁字母,用两个字节表示常用的符号,包括常用的中文字符,用三个表示不常用的字符,用四个字节表示其他的古灵精怪的字符。而GB2312字符集就是用两个字节表示所有的字符。需要提到一点的是Unicode除了用编号索引全部字符以外,本身是用四个字节存储全部字符,这一点在谈到挂载windows分区的时候是非常重要的一个概念。所以说你也可以把Unicode看作是一种字符集(我不知道它和UTF-32的关系,反正UTF-32就是用四个字节表示所有的字符的),但是这样表述符号是非常浪费资源的,因为在计算机世界绝大部分时候用到的是一个字节就可以搞定的26个字母而已。所以才会有UTF-8,UTF-16等等,要不然大同世界多好,省了这许多麻烦。
 

四、zh_CN.GB2312到底是在说什么?
Locale 是软件在运行时的语言环境, 它包括语言(Language), 地域 (Territory) 和字符集(Codeset)。一个locale的书写格式为: 语言[_地域[.字符集]]. 所以说呢,locale总是和一定的字符集相联系的。下面举几个例子:
 
1、我说中文,身处中华人民共和国,使用国标2312字符集来表达字符。
zh_CN.GB2312=中文_中华人民共和国+国标2312字符集。
 
2、我说中文,身处中华人民共和国,使用国标18030字符集来表达字符。
zh_CN.GB18030=中文_中华人民共和国+国标18030字符集。
 
3、我说中文,身处中华人民共和国台湾省,使用国标Big5字符集来表达字符。
zh_TW.BIG5=中文_台湾.大五码字符集
 
4、我说英文,身处大不列颠,使用ISO-8859-1字符集来表达字符。
en_GB.ISO-8859-1=英文_大不列颠.ISO-8859-1字符集
 
5、我说德语,身处德国,使用UTF-8字符集,习惯了欧洲风格。
de_DE.UTF-8@euro=德语_德国.UTF-8字符集@按照欧洲习惯加以修正
 
注意不是de_DE@euro.UTF-8,所以完全的locale表达方式是
[语言[_地域][.字符集] [@修正值]
 
生成的locale放在/usr/lib/locale/目录中,并且每个locale都对应一个文件夹,也就是说创建了de_DE@euro.UTF-8 locale之后,就生成/usr/lib/locale/de_DE@euro.UTF-8/目录,里面是具体的每个locale的内容。
 
五、怎样去自定义locale
在gentoo生成locale还是很容易的,首先要在USE里面加入userlocales支持,然后编辑locales.build文件,这个文件用来指示glibc生成locale文件。
很多人不明白每一个条目是什么意思。 其实根据上面的说明现在应该很明确了。
 
File: /etc/locales.build
en_US/ISO-8859-1
en_US.UTF-8/UTF-8
 
zh_CN/GB18030
zh_CN.GBK/GBK
zh_CN.GB2312/GB2312
zh_CN.UTF-8/UTF-8
 
上面是我的locales.build文件,依次的说明是这样的:
 
en_US/ISO-8859-1:生成名为en_US的locale,采用ISO-8859-1字符集,并且把这个locale作为英文_美国locale类的默认值,其实它和en_US.ISO-8859-1/ISO-8859-1没有任何区别。
 
en_US.UTF-8/UTF-8:生成名为en_US.UTF-8的locale,采用UTF-8字符集。
 
zh_CN/GB18030:生成名为zh_CN的locale,采用GB18030字符集,并且把这个locale作为中文_中国locale类的默认值,其实它和zh_CN.GB18030/GB18030没有任何区别。
 
zh_CN.GBK/GBK:生成名为zh_CN.GBK的locale,采用GBK字符集。
zh_CN.GB2312/GB2312:生成名为zh_CN.GB2312的locale,采用GB2312字符集。
zh_CN.UTF-8/UTF-8:生成名为zh_CN.UTF-8的locale,采用UTF-8字符集。
 
关于默认locale,默认locale可以简写成en_US或者zh_CN的形式,只是为了表达简单而已没有特别的意义。
 
Gentoo在locale定义的时候掩盖了一些东西,也就是locale的生成工具:localedef。
在编译完glibc之后你可以用这个localedef 再补充一些locale,就会更加理解locale了。具体的可以看 localedef 的manpage。
 
$localedef -f 字符集 -i locale定义文件 生成的locale的名称
例如
$localedef -f UTF-8 -i zh_CN zh_CN.UTF-8
 
上面的定义方法和在locales.build中设定zh_CN.UTF-8/UTF-8的结果是一样一样的。
 

六、locale的五脏六腑
 
刚刚生成了几个locale,但是为了让它们生效,必须告诉Linux系统使用那(几)个locale。这就需要对locale的内部机制有一点点的了解。在前面我已经提到过,locale把按照所涉及到的文化传统的各个方面分成12个大类,这12个大类分别是:
1、语言符号及其分类(LC_CTYPE)
2、数字(LC_NUMERIC)
3、比较和排序习惯(LC_COLLATE)
4、时间显示格式(LC_TIME)
5、货币单位(LC_MONETARY)
6、信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES)
7、姓名书写方式(LC_NAME)
8、地址书写方式(LC_ADDRESS)
9、电话号码书写方式(LC_TELEPHONE)
10、度量衡表达方式(LC_MEASUREMENT)
11、默认纸张尺寸大小(LC_PAPER)
12、对locale自身包含信息的概述(LC_IDENTIFICATION)。
 
其中,与中文输入关系最密切的就是 LC_CTYPE, LC_CTYPE 规定了系统内有效的字符以及这些字符的分类,诸如什么是大写字母,小写字母,大小写转换,标点符号、可打印字符和其他的字符属性等方面。而locale定义zh_CN中最最重要的一项就是定义了汉字(Class “hanzi”)这一个大类,当然也是用Unicode描述的,这就让中文字符在Linux系统中成为合法的有效字符,而且不论它们是用什么字符集编码的。
 
LC_CTYPE
% This is a copy of the "i18n" LC_CTYPE with the following modifications: - Additional classes: hanzi
 
copy "i18n"
 
class "hanzi"; /
% <U3400>..<U4DBF>;/
<U4E00>..<U9FA5>;/
<UF92C>;<UF979>;<UF995>;<UF9E7>;<UF9F1>;<UFA0C>;<UFA0D>;<UFA0E>;/
<UFA0F>;<UFA11>;<UFA13>;<UFA14>;<UFA18>;<UFA1F>;<UFA20>;<UFA21>;/
<UFA23>;<UFA24>;<UFA27>;<UFA28>;<UFA29>
END LC_CTYPE
 
在en_US的locale定义中,并没有定义汉字,所以汉字不是有效字符。所以如果要输入中文必须使用支持中文的locale,也就是zh_XX,如zh_CN,zh_TW,zh_HK等等。
 
另外非常重要的一点就是这些分类是彼此独立的,也就是说LC_CTYPE,LC_COLLATE和 LC_MESSAGES等等分类彼此之间是独立的,可以根据用户的需要设定成不同的值。这一点对很多用户是有利的,甚至是必须的。例如,我就需要一个能够输入中文的英文环境,所以我可以把LC_CTYPE设定成zh_CN.GB18030,而其他所有的项都是en_US.UTF-8。
 

七、怎样设定locale呢?
 
设定locale就是设定12大类的locale分类属性,即 12个LC_*。除了这12个变量可以设定以外,为了简便起见,还有两个变量:LC_ALL和LANG。它们之间有一个优先级的关系:
LC_ALL>LC_*>LANG
可以这么说,LC_ALL是最上级设定或者强制设定,而LANG是默认设定值。
1、如果你设定了LC_ALL=zh_CN.UTF-8,那么不管LC_*和LANG设定成什么值,它们都会被强制服从LC_ALL的设定,成为 zh_CN.UTF-8。
2、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_*=en_US.UTF-8,并且没有设定LC_ALL的话,那么系统的locale设定以LC_*=en_US.UTF-8。
3、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_*,和LC_ALL均未设定的话,系统会将LC_*设定成默认值,也就是LANG的值 zh_CN.UTF-8 。
4、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_CTYPE=en_US.UTF-8,其他的LC_*,和LC_ALL均未设定的话,那么系统的locale设定将是:LC_CTYPE=en_US.UTF-8,其余的 LC_COLLATE,LC_MESSAGES等等均会采用默认值,也就是LANG的值,也就是LC_COLLATE=LC_MESSAGES=……= LC_PAPER=LANG=zh_CN.UTF-8。
 
所以,locale是这样设定的:
1、如果你需要一个纯中文的系统的话,设定LC_ALL= zh_CN.XXXX,或者LANG= zh_CN.XXXX都可以,当然你可以两个都设定,但正如上面所讲,LC_ALL的值将覆盖所有其他的locale设定,不要作无用功。
2、如果你只想要一个可以输入中文的环境,而保持菜单、标题,系统信息等等为英文界面,那么只需要设定LC_CTYPE=zh_CN.XXXX,LANG=en_US.XXXX就可以了。这样LC_CTYPE=zh_CN.XXXX,而LC_COLLATE=LC_MESSAGES=……= LC_PAPER=LANG=en_US.XXXX。
3、假如你高兴的话,可以把12个LC_*一一设定成你需要的值,打造一个古灵精怪的系统:
LC_CTYPE=zh_CN.GBK/GBK(使用中文编码内码GBK字符集);
LC_NUMERIC=en_GB.ISO-8859-1(使用大不列颠的数字系统)
LC_MEASUREMEN=de_DE@euro.ISO-8859-15(德国的度量衡使用ISO-8859-15字符集)
罗马的地址书写方式,美国的纸张设定……。估计没人这么干吧。
4、假如你什么也不做的话,也就是LC_ALL,LANG和LC_*均不指定特定值的话,系统将采用POSIX作为lcoale,也就是C locale。

posted @ 2006-08-16 10:45 GHawk 阅读(735) | 评论 (0)编辑 收藏

PostgreSQL的主机认证配置

转自 http://www.linuxsir.org/bbs/showthread.php?t=32116

pg_hba.conf 文件
客户端认证是由 $PGDATA 目录里的文件pg_hba.conf 控制的,也就是说, /usr/local/pgsql/data/pg_hba.conf. (HBA 的意思是 host-based authentication:基于主机的认证.) 在initdb初始化数据区的时候,它会 安装一个缺省的文件.

文件 pg_hba.conf 的常用格式是一套记录, 每行一条。空白行或者井号(“#”)开头的行被忽略。一条记录 是由若干用空格和/或 tab 分隔的字段组成。

每条记录可以下面三种格式之一

local database authentication-method [ authentication-option ]
host database IP-address IP-mask authentication-method [ authentication-option ]
hostssl database IP-address IP-mask authentication-method [ authentication-option ]

各个字段的含义如下:

local
这条记录适用于通过 Unix 域套接字的联接.

host
这条记录适用于通过 TCP/IP 网络的联接.请注意,除非服务器是 带着 -i 选项或者等效的配置参数集启动的,否则 TCP/IP 联接将完全被禁止掉.

hostssl
这条记录适用于试图建立在 TCP/IP 上的 SSL 之上的联接. 要使用这个选项,服务器必须带着 SSL 支持编译.而且在服务器启动的时候, 必须用 -l 选项 或等效的配置设置打开 SSL.

database
声明记录所适用的数据库。值 all 表明该记录应用于所有数据库, 值 sameuser 表示于正在联接的用户同名的数据库。 否则它就是某个具体的 Postgres 数据库名字.

IP address, IP mask
这两个字段以各主机的 IP 地址为基础, 控制一条 host 记录应用于哪个主机. (当然,IP 地址可能会被欺骗(spoofed),但是这个考虑 超过了 Postgres 的考虑范围.) 准确的逻辑是,对于匹配的记录

(actual-IP-address xor IP-address-field) and IP-mask-field
必需为零.

authentication method(认证方法)
声明一个用户在与该数据库联接的时候必须使用的认证方法. 可能的选择如下,详细情况在 Section 4.2.


trust
无条件地允许联接.这个方法允许任何有登录客户机权限的用户以任意 Postgres 数据库用户身份进行联接.

reject
联接无条件拒绝.常用于从组中“过滤”某些主机.

password
要求客户端在尝试联接的时候提供一个口令, 这个口令与为该用户设置的口令必须匹配.

在 password 关键字后面可以声明一个可选的文件名. 这个文件包含一个用户列表,列表记录的是那些适用口令认证记录的用户, 以及可选的候选口令.

口令是以明文的方式在线路上传输的.如果要更好的保护,请使用 crypt 方法.

crypt
类似 password 方法,但是口令是用一种简单的 口令对应协议加密后在线路上传送的.这么做在密码学理论上是不安全的, 但可以防止偶然的线路侦听.在 crypt 关键字后面 可以有一个文件,文件里包含适用口令认证记录的用户列表.

krb4
用 Kerberos V4 认证用户.只有在进行 TCP/IP 联接的时候才能用. (译注:Kerberos,"克尔波洛斯",故希腊神话冥王哈得斯的多头看门狗. Kerberos 是 MIT 开发出来的基与对称加密算法的认证协议和/或密钥 交换方法.其特点是需要两个不同用途的服务器,一个用于认证身份, 一个用于通道两端用户的密钥交换.同时 Kerberos 对网络时间同步 要求比较高,以防止回放攻击,因此通常伴随 NTP 服务.)

krb5
用 Kerberos V5 认证用户.只有在进行 TCP/IP 联接的时候才能用. (译注:Kerberos V5 是上面 V4 的改良,主要是不再依赖 DES 算法, 同时增加了一些新特性.)

ident
服务器将询问客户机上的 ident 服务器以核实正在联接的用户身份. 然后 Postgres 核实该操作系统用户是否被允许以其请求的数据库用户身份与数据库联接. 只有在使用 TCP/IP 联接的时候才能用这个选项. 跟在 ident 关键字后面的 authentication option 声明一个 ident map(身份映射), 该文件声明那些操作系统用户等效于数据库用户.见下文获取详细信息.


authentication option(认证选项)
这个字段根据不同的认证方法(authentication method)有不同的 解释.

认证时使用与联接请求的客户端 IP 地址和所要求的 数据库名字匹配的第一条记录. 请注意这里没有 “fall-through(越过)” 或者 “backup(备份)”:如果选定了一条记录但认证失败, 那么将不会继续考虑下面的记录.如果没有匹配的记录,则拒绝访问.

在每次联接的请求时,文件 pg_hba.conf 都会被重新读取.因此很容易就能在服务器运行的时候修改访问权限.

在 Example 4-1 里是 pg_hba.conf 的一个例子. 阅读下文理解不同认证方法的细节.

Example 4-1. 一个 pg_hba.conf 文件的例子

# TYPE DATABASE IP_ADDRESS MASK AUTHTYPE MAP

# 允许在本机上的任何用户以任何身份联接任何数据库
# 但必须是通过 IP 进行联接

host all 127.0.0.1 255.255.255.255 trust

# 同样,但用的是 Unix-套接字联接

local all trust

# 允许 IP 地址为 192.168.93.x 的任何主机与数据库
# "template1" 相连,用与他们在自己的主机上相同 ident 的用户名标识他自己
# (通常是他的 Unix 用户名)

host template1 192.168.93.0 255.255.255.0 ident sameuser

# 允许来自主机 192.168.12.10 的用户与 "template1" 数据库联接,
# 只要该用户提供了在 pg_shadow 里正确的口令.

host template1 192.168.12.10 255.255.255.255 crypt

# 如果前面没有其它 "host" 行,那么下面两行将拒绝所有来自
# 192.168.54.1 的联接请求 (因为前面的记录先匹配
# 但是允许来自互联网上其它任何地方有效的 Kerberos V5 认证的联接
# 零掩码表示不考虑主机 IP 的任何位.因此它匹配任何主机:

host all 192.168.54.1 255.255.255.255 reject
host all 0.0.0.0 0.0.0.0 krb5

# 允许来自 192.168.x.x 的任何用户与任意数据库联接,只要他们通过 ident 检查
# 但如果 ident 说该用户是 "bryanh" 而他要求以 PostgreSQL 用户 "guest1" 联接,
# 那么只有在 `pg_ident.conf' 里有 "omicron" 的映射,说 "bryanh" 允许以
# "guest1" 进行联接时才真正可以进行联接.

host all 192.168.0.0 255.255.0.0 ident omicron

posted @ 2006-06-07 10:42 GHawk 阅读(1177) | 评论 (0)编辑 收藏

UP & XP之争,意义何在?(续)

虽然我没能去参加BEA的活动,但是相关的资料已经下载并且浏览过了,确实收获不少。所以,对于庄兄的这些想法我很理解。

相信不只你我,大部分的人都比较认同敏捷化的过程,希望使过程变得敏捷。的确,这是个好东西,之前我也说过“敏捷过程是三赢的”这样的话。

我所关心的问题是“如何能够用好XP?”。

庄兄认为“汤的味道,不需要什么过程控制”,我也会认同。为什么?因为你我都是中国人。大部分中国人不会认为汤的味道需要什么过程控制。但是想想看,如果你在不同地方买到的肯德基炸鸡味道各异;同一批次生产的同型号的汽车形状各异;银行里取出来的一叠百元大钞大小不一,你不会觉得奇怪么或是有那么一点点愤怒么?

西方人(甚至学习西方的日本人)对品质的重视程度却完全不同。他们不允许肯德基炸鸡的味道有很大偏差(即便你觉得无所谓);“2毫米工程”不允许整车的总装长度发生2毫米以上的偏差(即便你觉得无所谓);百元大钞……(我想谁都不会无所谓)。

所以,一切质量都有标准,一切标准都应该被度量!这就是工程学的目标之一,为了实现更严格的质量标准,就需要过程控制和度量。

庄兄所说,用测试用例保证代码的质量其实还是采用了“测试用例”作为度量的标准。唯一的问题是:“如何确保测试用例的质量”。显然,我们不能把一把不直的尺子度量出来的结果作为可靠的参考依据。怎么解决呢?“结对编程”么?嗯,这是一个不错的方式,那么最终该信赖谁呢?是Pair中的A还是B呢?或者,是Leader么?那么又是谁提出的要求呢?是老板么?还是客户?政府?法规?市场?……问题没有终结了。

不要学习哲学家的方法,提出一层又一层无法解决的问题。我们是工程师,应该试图解决问题才对!解决问题的关键在于,XP同样需要标准!为了制定标准,必要的文档是不可以少的。而且,标准本身的质量是严苛的。因为,作为标准,他不可以含糊其辞、模棱两可。在标准的基础之上,我们才可以谈什么TDD、Pair Programming之类的实践。

回到争论的开端。我引用了林先生的话“UP是正楷;XP是草书。要先学好UP才能学好XP,先学XP会乱套。”我对这句话的理解如下:这句话并没有批判UP或是XP,只是指出了一个学习的顺序。我认为这句话是有实践依据的,因为UP强调的是一种经典的工程方法。软件工程本来就源于其他行业的工程实践经验。UP利用大量的文档对开发活动进行约束和记录。正是这种重量级的过程规范了规范了从PM到Coder的所有活动,有问题可以参照文档,看看自己应该怎么做。文档也可以作为日后评估这个过程的依据。随着整个团队和每个个人的经验不断积累,开发活动中的日常行为渐渐形成了一种职业习惯。然后可以通过对UP的配置,逐渐减少文档的使用量,一些没有必要的文档就可以省去,更具团队的实际能力调整过程。UP是可配置的,不必要的文档没有存在的理由,这一点UP和XP没有什么两样。当然,随着大家的职业习惯越来越好,经验越来越丰富,个人和团队就可以采用更敏捷更轻便的过程,逐渐过渡到XP上去。

反过来,如果一开始就没有详尽的文档,很多活动(比如设计、版本控制)往往会脱离控制,进入一种无序的、混乱的状态。没有文档可参考,就意味着很多问题只能问人,而不同人的回答可能各异,同一个人对同一个问题的两次回答也可能不同!当然,如果整个团队的工程素养和个体的职业习惯都比较好的情况下可能不会发生类似的情况。但是这种工程素养和职业习惯从哪里来,可能单靠的XP是不足以培养出来的。

“UP是正楷;XP是草书。要先学好UP才能学好XP,先学XP会乱套。”这句话表明了UP和XP在一定程度上是存在冲突的,并且提出了一条路线去降低和避免这个冲突。

再次需要强调的是庄兄所提到的“XP是一种思想”,这点我认同。但是我认为这个除了思想之外,还是一种“文化”。这种思想和文化也是出于软件工程多年来的实践,其中也不免有UP等其他过程。不能简单地认为“我们只要吸取历史的教训,提出新的思想和文化就不会再犯同样的错误了。”很多时候历史总是一次又一次地重演着。新的思想和文化如果不能被准确地理解和运用,它所带来的可能仍然是它原本想解决的问题。只有我们具备了引入这种文化的基础,才能把它变成自己的文化,否则这仍然是挂在嘴边行于表面的一种不求精髓只求模仿的伪文化、伪思想。这一点对于UP和XP的实践者来说没有什么两样。

posted @ 2006-04-25 15:03 GHawk 阅读(2021) | 评论 (4)编辑 收藏

UP & XP之争,意义何在?

不光是做软件,凡是做产品,最后关注的总是产品的质量

举个例子,比如你做一锅汤:
今天你状态很好,做完后尝了尝,感觉很美味,你的家人尝了以后也有同感,喝完后感觉心情舒畅、意犹未尽。
隔了一个礼拜,你做同样的汤给家里人喝。做完后你尝了尝,感觉依然美味,盼望着得到家人的赏识,然而他们却说味道咸了点。你很奇怪,为什么同样自己尝过了,家里人却感觉不一样呢?是不是最近加班多了,休息不好,味觉不准了?
一个月过后,你要去国外出差,给家里请了个临时保姆。一天,他也做了这么个汤,做完后,他也尝了尝,感觉口味很不错,可是端上桌,家里人说这汤太辣了。原来这保姆才从湖南老家出来不久……

因此,只把焦点放在最后的产品上往往是不够的。需要对“做汤的过程”加以控制。所以工程界会比较关注过程的管理,在软件领域也称作“软件生命周期管理”。

再来看看UP和XP。它们都属于软件过程,只不过各有特色。

再拿刚才那个做汤的例子:
大家都听说过德国人的厨房像化学实验室,天平、计时器、量杯……装备齐全,再配上精确的菜谱,严谨的德国人能够确保不用尝那最后一口都做出口味基本一致的汤。
换了中国人,大部分人都不会模仿德国人做菜的方式。解决方案很简单,让你的太太和孩子都尝那最后一口,再根据反馈调整几次,同样能做出全家人满意的汤。

这个例子也许不太贴切,但是可以联想一下:德国人做汤倾向于UP;中国人做汤倾向于XP

UP和XP最终目的都是为了保证产品的质量,不同的是,两个过程所强调的方法不同。我想,没有人会说“UP的目的在于变态地追求文档的完美”、“UP是为了要程序员学会写各种各样文档”……之类的话。同时,也没人会说“XP就是不要文档只要代码”、“XP就是要变态地追求完美的代码”……这样的话。

这些不正确的看法,只是人们对于这两种过程的误解。或许是来自于开发人员和项目经理的那些“不堪回首的经历”。

“UP害惨了整个软件行业,让开发人员没完没了地写文档而忽略了代码,XP才是王道”这样的话,我不敢苟同,仍然有很多企业使用着UP这样的重型软件工程,就好比德国人依然喜欢把厨房弄得像个实验室。

XP固然是个好东西。但是,不知道大多数人对于XP的热衷是出于对XP文化的理解,还是国人惯有的“一窝蜂”似的行为。不晓得一个“能够熟练阅读代码的Leader”是不是能够真正运用好XP,确保他的团队能够尽可能少地出现"Over engineering"这种违背Agile精神的东西,或是能够让他的团队保证“每周只工作40小时”这样的基本实践?

对于不同的技术和过程,应该给予冷静的分析和慎重的选择。每个过程和技术都不能以“正确”或“不正确”来定性,只能以“合适”和“不合适”来定性。因为正确或不正确是要严格证明的,而合适不合适是来源于工程实践的结果。所以,COBOL依然在金融领域起着举足轻重的作用,科学家们仍不忘Fortran,汇编和C仍然健在……

另外不得不提的是文化上的差异。为什么很多时候,我们学习国外的先进技术,购买了整套生产线,引进了全套图纸,请国外专家做了详细的全程化培训,国人生产出的产品品质依然不如国外原产的?这是每个中国人都应该思考的问题……

 

posted @ 2006-04-23 18:28 GHawk 阅读(1869) | 评论 (4)编辑 收藏

对"UP是正楷,XP是草书"的反思

“UP是正楷,XP是草书。先学好了UP,才能学好XP;先学XP再学UP就会乱套。 ”

老师曾这么说。最近,对这句话有了深刻的体会。

软件过程是一个以人为中心的活动。人是项目中最难确定和控制的因素。休息的质量、情绪的起伏都会影响整个活动。为了尽可能地约束这种个体的不确定行为和减少开发过程中不必要的误会。"UP"采用了大量的文档来对整个开发过程进行控制。这些文档主要分为以下几类:

  • 计划文档——项目的开发计划、迭代计划、测试计划等。
  • 技术文档——项目的设计文档、某个操作的说明文档等。
  • 记录文档——日常的会议纪要、每日进度反馈、评估报告等。

文档成了UP活动的主要部分。在UP中,往往大量的资源用于文档的制作。这些文档的目的是为了尽可能减少不必要的沟通成本和误会,也为了在发生问题的时候能够尽快确定原因找到解决方法。

而正是因为如此繁重的资源消耗,导致真正的设计和代码只占到了总开销的很少部分。这对很多人来说不可理解,甚至觉得本末倒置。于是很多敏捷方法诞生了,最具代表性也是对UP思想最具颠覆性的就属XP了。

对外,XP以快速的反应速度来响应客户的需求;对内,XP以高质量的代码和设计来确保尽可能不产生不必要的文档和资源开销。

从表面上看,在当今,XP确实是一种非常理想的开发过程。

但是,从没有过程到XP往往会非常失败。这是为什么?问题的关键还在于人。

  • 无过程-->UP -->XP

UP利用文档来约束和规范人们的开发活动。当一个没有经验的团队经历UP后,就等于把性格各异、习惯差别不同的人统一成了“相对较一致的开发人员”。

他们有一致的编码习惯,有共同的用语,有严格的规则。随着经验的积累,这个团队间的默契越来越高。此时,如果过程由UP向XP切换,付出的代价就会相对较低。

  • 无过程-->XP-->UP

XP主张快速反应。如果一个没有经验的团队在一开始就尝试XP,那么后果可能是惨痛的。因为一个没有经验的团队其成员间的相互了解颇少,对于一件事,往往十个人有十种想法。当缺少文档约束时,在以代码和设计为中心的活动中,成员之间往往因为水平的参差不齐导致无休止的讨论甚至争论,代码被不必要地频繁改动。这是因为,在团队建设早期,成员之间往往连最基本的尊重和信任都不存在。 这种无意义的活动往往会严重影响项目的正常进行。

所以,学习和应用过程不仅仅是个体的事,而是整个团队的事。只有当团队采用严格文档化的过程并且经过磨合后,才能渐渐向轻量级的过程迁移,逐渐将不必要的文档删减掉,采用更灵活的过程。但是,此时并不是“没有文档”而是“心中有文档”。

posted @ 2006-03-01 16:25 GHawk 阅读(1633) | 评论 (4)编辑 收藏

加载Classpath中的文件(转)

   URL url = this.getClass().getResource("EJBConfig.xml");
        
try {
            File xmlFile 
= new File(URLDecoder.decode(url.getFile(),"UTF-8"));
            
if(xmlFile.exists())
                System.out.println(
"OK");
        } 
catch (UnsupportedEncodingException e) {
            e.printStackTrace();  
//To change body of catch statement use File | Settings | File Templates.
        }

posted @ 2006-01-19 22:07 GHawk 阅读(748) | 评论 (0)编辑 收藏

敏捷软件开发 读书笔记 (4)——OO五大原则(3.LSP——里氏替换原则)

OCP作为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构,维持设计的封闭性。

“抽象”是语言提供的功能。“多态”由继承语义实现。

如此,问题产生了:“我们如何去度量继承关系的质量?”

Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。

该原则称为Liskov Substitution Principle——里氏替换原则。林先生在上课时风趣地称之为“老鼠的儿子会打洞”。^_^

我们来研究一下LSP的实质。学习OO的时候,我们知道,一个对象是一组状态和一系列行为的组合体。状态是对象的内在特性,行为是对象的外在特性。LSP所表述的就是在同一个继承体系中的对象应该有共同的行为特征。

这一点上,表明了OO的继承与日常生活中的继承的本质区别。举一个例子:生物学的分类体系中把企鹅归属为鸟类。我们模仿这个体系,设计出这样的类和关系。

 lsp-fig1.jpg

类“鸟”中有个方法fly,企鹅自然也继承了这个方法,可是企鹅不能飞阿,于是,我们在企鹅的类中覆盖了fly方法,告诉方法的调用者:企鹅是不会飞的。这完全符合常理。但是,这违反了LSP,企鹅是鸟的子类,可是企鹅却不能飞!需要注意的是,此处的“鸟”已经不再是生物学中的鸟了,它是软件中的一个类、一个抽象。

有人会说,企鹅不能飞很正常啊,而且这样编写代码也能正常编译,只要在使用这个类的客户代码中加一句判断就行了。但是,这就是问题所在!首先,客户代码和“企鹅”的代码很有可能不是同时设计的,在当今软件外包一层又一层的开发模式下,你甚至根本不知道两个模块的原产地是哪里,也就谈不上去修改客户代码了。客户程序很可能是遗留系统的一部分,很可能已经不再维护,如果因为设计出这么一个“企鹅”而导致必须修改客户代码,谁应该承担这部分责任呢?(大概是上帝吧,谁叫他让“企鹅”不能飞的。^_^)“修改客户代码”直接违反了OCP,这就是OCP的重要性。违反LSP将使既有的设计不能封闭!

修正后的设计如下:

 lsp-fig2.jpg

但是,这就是LSP的全部了么?书中给了一个经典的例子,这又是一个不符合常理的例子:正方形不是一个长方形。这个悖论的详细内容能在网上找到,我就不多废话了。

LSP并没有提供解决这个问题的方案,而只是提出了这么一个问题。

于是,工程师们开始关注如何确保对象的行为。1988年,B. Meyer提出了Design by Contract(契约式设计)理论。DbC从形式化方法中借鉴了一套确保对象行为和自身状态的方法,其基本概念很简单:

  1. 每个方法调用之前,该方法应该校验传入参数的正确性,只有正确才能执行该方法,否则认为调用方违反契约,不予执行。这称为前置条件(Pre-condition)。
  2. 一旦通过前置条件的校验,方法必须执行,并且必须确保执行结果符合契约,这称之为后置条件(Post-condition)。
  3. 对象本身有一套对自身状态进行校验的检查条件,以确保该对象的本质不发生改变,这称之为不变式(Invariant)。

以上是单个对象的约束条件。为了满足LSP,当存在继承关系时,子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松;而子类中方法的后置条件必须与超类中被覆盖的方法的后置条件相同或者更为严格。

一些OO语言中的特性能够说明这一问题:

  • 继承并且覆盖超类方法的时候,子类中的方法的可见性必须等于或者大于超类中的方法的可见性,子类中的方法所抛出的受检异常只能是超类中对应方法所抛出的受检异常的子类。
    public class SuperClass{
        
    public void methodA() throws IOException{}
    }


    public class SubClassA extends SuperClass{
        
    //this overriding is illegal.
        private void methodA() throws Exception{}
    }


    public class SubClassB extends SuperClass{
        
    //this overriding is OK.
        public void methodA() throws FileNotFoundException{}
    }

  • 从Java5开始,子类中的方法的返回值也可以是对应的超类方法的返回值的子类。这叫做“协变”(Covariant)
    public class SuperClass {
        
    public Number caculate(){
            
    return null;
        }

    }


    public class SubClass extends SuperClass{
        
    //only compiles in Java 5 or later.
        public Integer caculate(){
            
    return null;
        }

    }

可以看出,以上这些特性都非常好地遵循了LSP。但是DbC呢?很遗憾,主流的面向对象语言(不论是动态语言还是静态语言)还没有加入对DbC的支持。但是随着AOP概念的产生,相信不久DbC也将成为OO语言的一个重要特性之一。

一些题外话:

前一阵子《敲响OO时代的丧钟》和《丧钟为谁而鸣》两篇文章引来了无数议论。其中提到了不少OO语言的不足。事实上,遵从LSP和OCP,不管是静态类型还是动态类型系统,只要是OO的设计,就应该对对象的行为有严格的约束。这个约束并不仅仅体现在方法签名上,而是这个具体行为的本身。这才是LSP和DbC的真谛。从这一点来说并不能说明“万事万物皆对象”的动态语言和“C++,Java”这种“按接口编程”语言的优劣,两类语言都有待于改进。庄兄对DJ的设想倒是开始引入DbC的概念了。这一点还是非常值得期待的。^_^
另外,接口的语义正被OCP、LSP、DbC这样的概念不断地强化,接口表达了对象行为之间的“契约”关系。而不是简单地作为一种实现多继承的语法糖。

posted @ 2006-01-18 18:12 GHawk 阅读(3941) | 评论 (2)编辑 收藏

敏捷软件开发 读书笔记 (3)——OO五大原则(2.OCP——开闭原则)

开闭原则很简单,一句话:“Closed for Modification; Open for Extension”——“对变更关闭;对扩展开放”。开闭原则其实没什么好讲的,我将其归结为一个高层次的设计总则。就这一点来讲,OCP的地位应该比SRP优先。

OCP的动机很简单:软件是变化的。不论是优质的设计还是低劣的设计都无法回避这一问题。OCP说明了软件设计应该尽可能地使架构稳定而又容易满足不同的需求。

为什么要OCP?答案也很简单——重用。

“重用”,并不是什么软件工程的专业词汇,它是工程界所共用的词汇。早在软件出现前,工程师们就在实践“重用”了。比如机械产品,通过零部件的组装得到最终的能够使用的工具。由于机械部件的设计和制造过程是极其复杂的,所以互换性是一个重要的特性。一辆车可以用不同的发动机、不同的变速箱、不同的轮胎……很多东西我们直接买来装上就可以了。这也是一个OCP的例子。(可能是由于我是搞机械出身的吧,所以就举些机械方面的例子^_^)。

如何在OO中引入OCP原则?把对实体的依赖改为对抽象的依赖就行了。下面的例子说明了这个过程:

05赛季的时候,一辆F1赛车有一台V10引擎。但是到了06赛季,国际汽联修改了规则,一辆F1赛车只能安装一台V8引擎。车队很快投入了新赛车的研发,不幸的是,从工程师那里得到消息,旧车身的设计不能够装进新研发的引擎。我们不得不为新的引擎重新打造车身,于是一辆新的赛车诞生了。但是,麻烦的事接踵而来,国际汽联频频修改规则,搞得设计师在“赛车”上改了又改,最终变得不成样子,只能把它废弃。

OCP-fig1.JPG

为了能够重用这辆昂贵的赛车,工程师们提出了解决方案:首先,在车身的设计上预留出安装引擎的位置和管线。然后,根据这些设计好的规范设计引擎(或是引擎的适配器)。于是,新的赛车设计方案就这样诞生了。

 OCP-fig2.JPG

显然,通过重构,这里应用的是一个典型的Bridge模式。这个实现的关键之处在于我们预先给引擎留出了位置!我们不必因为对引擎的规则的频频变更而制造相当多的车身,而是尽可能地沿用和改良现有的车身。
说到这里,想说一说OO设计的一个误区。
学习OO语言的时候,为了能够说明“继承”(或者说“is-a”)这个概念,教科书上经常用实际生活中的例子来解释。比如汽车是车,电车是车,F1赛车是汽车,所以车是汽车、电车、F1赛车的上层抽象。这个例子并没有错。问题是,这样的例子过于“形象”了!如果OO设计直接就可以将现实生活中的概念引用过来,那也就不需要什么软件工程师了!OO设计的关键概念是抽象。如果没有抽象,那所有的软件工程师的努力都是徒劳的。因为如果没有抽象,我们只能去构造世界中每一个对象。上面这个例子中,我们应该看到“引擎”这个抽象的存在,因为车队的工程师们为它预留了位置,为它制定了设计规范。
上面这个设计也实现了后面要说的DIP(依赖倒置原则)。但是请记住,OCP是OO设计原则中高层次的原则,其余的原则对OCP提供了不同程度的支持。为了实现OCP,我们会自觉或者不自觉地用到其它原则或是诸如Bridge、Decorator等设计模式。然而,对于一个应用系统而言,实现OCP并不是设计目的,我们所希望的只是一个稳定的架构。所以对OCP的追求也应该适可而止,不要陷入过渡设计。正如Martin本人所说:“No significant program can be 100% closed.”“Closure not complete but strategic”

(下一篇就要讲LSP了,我觉得这是意义最为重要的OO设计原则,它直指当今主流OO语言的软肋,点出了OO设计的精髓。)

posted @ 2006-01-18 00:26 GHawk 阅读(7550) | 评论 (7)编辑 收藏

开源 Java 测试框架(转)

from  http://blog.csdn.net/wangyihust/archive/2006/01/02/568616.aspx

 JUnit   

JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。

http://www.junit.org/

 Cactus   

Cactus是一个基于JUnit框架的简单测试框架,用来单元测试服务端Java代码。Cactus框架的主要目标是能够单元测试服务端的使用Servlet对象的Java方法如HttpServletRequest,HttpServletResponse,HttpSession等。

http://jakarta.apache.org/cactus/

 Abbot   

Abbot是一个用来测试Java GUIs的框架。用简单的基于XML的脚本或者Java代码,你就可以开始一个GUI。

http://abbot.sourceforge.net/

 JUnitPerf   

Junitperf实际是junit的一个decorator,通过编写用于junitperf的单元测试,我们也可使测试过程自动化。

http://www.clarkware.com/software/JUnitPerf.html

 DbUnit   

DbUnit是为数据库驱动的项目提供的一个对JUnit 的扩展,除了提供一些常用功能,它可以将你的数据库置于一个测试轮回之间的状态。

http://dbunit.sourceforge.net/

 Mockrunner   

Mockrunner用在J2EE环境中进行应用程序的单元测试。它不仅支持Struts actions, servlets,过滤器和标签类还包括一个JDBC和一个JMS测试框架,可以用于测试基于EJB的应用程序。

http://mockrunner.sourceforge.net/index.html

 DBMonster   

DBMonster是一个用生成随机数据来测试SQL数据库的压力测试工具。

http://dbmonster.kernelpanic.pl/

 MockEJB   

MockEJB是一个不需要EJB容器就能运行EJB并进行测试的轻量级框架。

http://mockejb.sourceforge.net/

 StrutsTestCase   

StrutsTestCase 是Junit TestCase类的扩展,提供基于Struts框架的代码测试。StrutsTestCase同时提供Mock 对象方法和Cactus方法用来实际运行Struts ActionServlet,你可以通过运行servlet引擎来测试。因为StrutsTestCase使用ActionServlet控制器来测试你的代码,因此你不仅可以测试Action对象的实现,而且可以测试mappings,from beans以及forwards声明。StrutsTestCase不启动servlet容器来测试struts应用程序(容器外测试)也属于Mock对象测试,但是与EasyMock不同的是,EasyMock是提供了创建Mock对象的API,而StrutsTest则是专门负责测试Struts应用程序的Mock对象测试框架。

http://strutstestcase.sourceforge.net/

 JFCUnit   

JFCUnit使得你能够为Java偏移应用程序编写测试例子。它为从用代码打开的窗口上获得句柄提供了支持;为在一个部件层次定位部件提供支持;为在部件中发起事件(例如按一个按钮)以及以线程安全方式处理部件测试提供支持。

http://jfcunit.sourceforge.net/

 JTestCase   

JTestCase 使用XML文件来组织多测试案例数据,声明条件(操作和期望的结果),提供了一套易于使用的方法来检索XML中的测试案例,按照数据文件的定义来声明结果。

http://jtestcase.sourceforge.net/

 SQLUnit   

SQLUnit是一个单元测试框架,用于对数据库存储过程进行回归测试。用 Java/JUnit/XML开发。

http://sqlunit.sourceforge.net

 JTR   

JTR (Java Test Runner)是一个开源的基于反转控制(IOC)的J2EE测试框架。它允许你构建复杂的J2EE测试套件(Test Suites)并连到应用服务器执行测试,可以包括多个测试实例。JTR的licensed是GPL协议。

http://jtrunner.sourceforge.net/

 Marathon   

Marathon是一个针对使用Java/Swing开发GUI应用程序的测试框架,它由recorder, runner 和 editor组成,测试脚本是python代码。Marathon的焦点是放在最终用户的测试上。

http://marathonman.sourceforge.net

 TestNG   

TestNG是根据JUnit 和 NUnit思想而构建的一个测试框架,但是TestNG增加了许多新的功能使得它变得更加强大与容易使用比如:
*支持JSR 175注释(JDK 1.4利用JavaDoc注释同样也支持)
*灵活的Test配置
*支持默认的runtime和logging JDK功能
*强大的执行模型(不再TestSuite)
*支持独立的测试方法。

http://testng.org/

 Surrogate Test framework   

Surrogate Test framework是一个值得称赞单元测试框架,特别适合于大型,复杂Java系统的单元测试。这个框架能与JUnit,MockEJB和各种支持模拟对象(mock object )的测试工具无缝给合。这个框架基于AspectJ技术。

http://surrogate.sourceforge.net

 MockCreator   

MockCreator可以为给定的interface或class生成模拟对象(Mock object)的源码。

http://mockcreator.sourceforge.net/

 jMock   

jMock利用mock objects思想来对Java code进行测试。jMock具有以下特点:容易扩展,让你快速简单地定义mock objects,因此不必打破程序间的关联,让你定义灵活的超越对象之间交互作用而带来测试局限,减少你测试地脆弱性。

http://www.jmock.org/

 EasyMock   

EasyMock为Mock Objects提供接口并在JUnit测试中利用Java的proxy设计模式生成它们的实例。EasyMock最适合于测试驱动开发。

http://www.easymock.org/

 The Grinder   

The Grinder是一个负载测试框架。在BSD开源协议下免费使用。

http://grinder.sourceforge.net/

 XMLUnit   

XMLUnit不仅有Java版本的还有.Net版本的。Java开发的XMLUnit提供了两个JUnit 扩展类XMLAssert和XMLTestCase,和一组支持的类。这些类可以用来比较两张XML之间的不同之处,展示XML利用XSLT来,校验XML,求得XPath表达式在XML中的值,遍历XML中的某一节点利DOM展开。

http://xmlunit.sourceforge.net/

 Jameleon   

Jameleon一个自动化测试工具。它被用来测试各种各样的应用程序,所以它被设计成插件模式。为了使整个测试过程变得简单Jameleon提供了一个GUI,因此Jameleon实现了一个Swing 插件。

http://jameleon.sourceforge.net/index.html

 J2MEUnit   

J2MEUnit是应用在J2ME应用程序的一个单元测试框架。它基于JUnit。

http://j2meunit.sourceforge.net/

 Jetif   

Jetif是一个用纯Java实现的回归测试框架。它为Java程序单元测试以及功能测试提供了一个简单而且可 伸缩的架构,可以用于个人开发或企业级开发的测试。它容易使用,功能强大,而且拥有一些企业级测试的重要功能。Jetif来源于JUnit, JTestCase以及TestNG的启发,有几个基本的概念直接来自于JUnit, 比如说断言机制,Test Listener的概念,因此从JUnit转到Jetif是非常容易的。

http://jetif.sourceforge.net/

 GroboUtils   

GroboUtils使得扩展Java测试变得可能。它包括用在Java不同方面测试的多个子项目。在GroboUtils中最常被到的工具是:多线程测试(multi-threaded tests),整体单元测试(hierarchial unit tests),代码覆盖工具(code coverage tool)。

http://groboutils.sourceforge.net/

 Testare   

TESTARE是用来简化分布式应用程序(比如:在SERVLETS,JMS listeners, CORBA ORBs或RMI环境下)测试开发过程的一个测试框架。

https://testare.dev.java.net/

posted @ 2006-01-10 10:41 GHawk 阅读(1235) | 评论 (0)编辑 收藏

敏捷软件开发 读书笔记 (2)——OO五大原则(1.SRP 单一职责原则)

      一点说明:OO的五大原则是指SRP、OCP、LSP、DIP、ISP。这五个原则是书中所提到的。除此之外,书中还提到一些高层次的原则用于组织高层的设计元素,这些放到下次再写。当然,OO设计的原则可能不止这五个,希望大家多提宝贵意见,多多交流。

      在学习和使用OO设计的时候,我们应该明白:OO的出现使得软件工程师们能够用更接近真实世界的方法描述软件系统。然而,软件毕竟是建立在抽象层次上的东西,再怎么接近真实,也不能替代真实或被真实替代。

      OO设计的五大原则之间并不是相互孤立的。彼此间存在着一定关联,一个可以是另一个原则的加强或是基础。违反其中的某一个,可能同时违反了其余的原则。因此应该把这些原则融会贯通,牢记在心!

1. SRP(Single Responsibility Principle 单一职责原则)
      单一职责很容易理解,也很容易实现。所谓单一职责,就是一个设计元素只做一件事。什么是“只做一件事”?简单说就是少管闲事。现实中就是如此,如果要你专心做一件事情,任何人都有信心可以做得很出色。但如果,你整天被乱七八糟的事所累,还有心思和精力把每件事都作好么?
fig-1.JPG
     “单一职责”就是要在设计中为每种职责设计一个类,彼此保持正交,互不干涉。这个雕塑(二重奏)就是正交的一个例子,钢琴家和小提琴家各自演奏自己的乐谱,而结果就是一个和谐的交响乐。当然,真实世界中,演奏小提琴和弹钢琴的必须是两个人,但是在软件中,我们往往会把两者甚至更多搅和到一起,很多时候只是为了方便或是最初设计的时候没有想到。 

      这样的例子在设计中很常见,书中就给了一个很好的例子:调制解调器。这是一个调制解调器最基本的功能。但是这个类事实上完成了两个职责:连接的建立和中断、数据的发送和接收。显然,这违反了SRP。这样做会有潜在的问题:当仅需要改变数据连接方式时,必须修改Modem类,而修改Modem类的结果就是使得任何依赖Modem类的元素都需要重新编译,不管它是不是用到了数据连接功能。解决的办法,书中也已经给出:重构Modem类,从中抽出两个接口,一个专门负责连接、另一个专门负责数据发送。依赖Modem类的元素也要做相应的细化,根据职责的不同分别依赖不同的接口。最后由ModemImplementation类实现这两个接口。
fig-2.JPG

      从这个例子中,我们不难发现,违反SRP通常是由于过于“真实”地设计了一个类所造成的。因此,解决办法是往更高一层进行抽象化提取,将对某个具体类的依赖改变为对一组接口或抽象类的依赖。当然,这个抽象化的提取应该根据需要设计,而不是盲目提取。比如刚才这个Modem的例子中,如果有必要,还可以把DataChannel抽象为DataSender和DataReceiver两个接口。
 

posted @ 2006-01-09 21:17 GHawk 阅读(5531) | 评论 (5)编辑 收藏

敏捷软件开发 读书笔记 (1)——设计的目标

软件设计是一种抽象活动,设计所要实现的是产出代码。就这一点来说,任何人都会设计。但是,正如我们日常生活中所耳闻目睹或亲身经历,设计有优劣之分。

从项目管理的角度去理解,设计是为了满足涉众(Stakeholders)的需求。显然,一个设计应该满足客户对系统的功能及非功能需求。但单是满足了这一点,并不能称为一个好的设计。因为开发者同样属于涉众!而开发者的需求又是怎样的呢?至少,应该有以下几条吧:

  • 老板希望软件交付后,不应该有很高的维护成本。如果开发人员为了维护而经常出差或者加班且久久不能投入新项目,显然,换了谁是老板都不愿意这种事情发生。
  • 开发人员呢?谁愿意放弃和家人朋友而拼死拼活在单位加班,总是有这么多麻烦事缠着你,烦不烦哪!
  • ……等等

所以,设计应该尽可能多地照顾到维护和变更。

为了兼顾各户满意和维护成本,设计应该不断挑战其终极目标——松耦合。不管是XP或UP,这个目标都不会改变。OO设计中的五大原则,其根本目的就是降低组件间的耦合度,避免牵一发则动全身的现象发生。降低耦合度不仅能够提高软件内在的质量,还能大大减少不必要的编译时间、减少向版本控制系统提交源码的网络开销……

如何鉴别设计的这一指标?软件工程中有专用的度量:CBO(Coupling Between Objects),那是由公式计算出来的,也有很多工具支持,值得一试。(听过几次李维先生的讲座,他经常拿Together的度量功能炫耀^_^)

但是,作为一个开发人员,对手中的代码应该有适当的敏感性。毕竟,这些代码是你亲手创造的,谁不希望自己的作品得到众人的赞许?或许能换得一次加薪升职的机会^_^ 退一步,这可关系到宝贵的休息时间啊。所以,开发者应该对自己的产品有这样一种意识:及时修正设计中不合理的地方。

敏捷过程告诉我们:在代码“有味道”的时候进行重构。“有味道”是代码正在变质的标志,很遗憾,能够使代码保持原味的防腐剂还没发明。为了保证代码质量,及时重构是必要的。这就像在烧烤的时候为了防止烤焦,你得坐在炉子前经常翻动肉块一样。

如何闻出代码的味道?认真学习一下OO吧,别以为OO很简单,就是继承+封装+多态,谁都会。即使是书中记述的五大原则,想要运用自如,也得多感觉感觉才行。很多时候,我们不知不觉就把蛆虫放进了代码中……

好了,下一篇:OO五大原则

posted @ 2006-01-06 18:17 GHawk 阅读(1571) | 评论 (3)编辑 收藏

敏捷软件开发 读书笔记 (序——废话)

7-5083-1503-0l.gif

最近正在读这本书,喜欢影印版,是因为书中漂亮的插图。:)惭愧,如此的好书到现在才去读。
准备边读边记录些心得,今天先说些废话。:P

先粗略地概览了一遍全书。本书主要分以下几个部分:

  1. 敏捷软件过程。主要以XP为例。这部分的最后一章,用一个对话式的小故事讲述了一个非常小的过程。给了读者关于敏捷过程的形象化的认识。
  2. 敏捷设计。这部分是个很大的看点。它讲述了设计中一些常见的问题,及其应对(用几个经典的设计原则)。
  3. 案例实践。讲述了如何利用设计模式去实践第二部分中提到的设计原则和避免设计中的“味道”。

之所以觉得这本书好,还与一个人有关。就是交大软件学院的林德彰老师。林先生的课,风趣幽默,能够用直观形象的语言让学生对讲课内容产生深刻的印象。(我可不是托儿,网上能搜到些林先生讲课的片断,要是怀疑,可以验证一番)。记得在软件工程这门课里,林先生给我们讲了很多有关设计原则的内容,其中就有“开闭原则(OCP)”、“里氏替换原则(LSP)”等……就把这本书当作是一本补充读物吧。

言归正传。个人感觉这本书的总体风格,就和所要讲的“敏捷”一样,并不带着厚重的学院派风味,而是更注重实践。并不是没有理论,只是把理论融入到了实践中,简化了理论的复杂性。读起来感觉很带劲儿。

废话说到这里,下一步的计划就是跟着自己的进度写读书心得了。我想把对书中内容的理解和以前在林先生的课上所学的结合在一起,导出阅读此书时的大脑活动镜像。

posted @ 2005-12-27 12:00 GHawk 阅读(1593) | 评论 (4)编辑 收藏

一个介绍Java开源项目及工具的网站

按功能进行了归类
http://java-source.net


再追加一个中文的
http://www.open-open.com

posted @ 2005-12-20 13:45 GHawk 阅读(499) | 评论 (1)编辑 收藏

用 Cobertura 测量测试覆盖率

http://www-128.ibm.com/developerworks/cn/java/j-cobertura/

用 Cobertura 测量测试覆盖率

找出隐藏 bug 的未测试到的代码

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Elliotte Rusty Harold, 副教授, Polytechnic University

2005 年 5 月 26 日

Cobertura 是一种开源工具,它通过检测基本的代码,并观察在测试包运行时执行了哪些代码和没有执行哪些代码,来测量测试覆盖率。除了找出未测试到的代码并发现 bug 外,Cobertura 还可以通过标记无用的、执行不到的代码来优化代码,还可以提供 API 实际操作的内部信息。Elliotte Rusty Harold 将与您分享如何利用代码覆盖率的最佳实践来使用 Cobertura。

尽管测试先行编程(test-first programming)和单元测试已不能算是新概念,但测试驱动的开发仍然是过去 10 年中最重要的编程创新。最好的一些编程人员在过去半个世纪中一直在使用这些技术,不过,只是在最近几年,这些技术才被广泛地视为在时间及成本预算内开发健壮的无缺陷软件的关键所在。但是,测试驱动的开发不能超过测试所能达到的程度。测试改进了代码质量,但这也只是针对实际测试到的那部分代码而言的。您需要有一个工具告诉您程序的哪些部分没有测试到,这样就可以针对这些部分编写测试代码并找出更多 bug。

Mark Doliner 的 Cobertura (cobertura 在西班牙语是覆盖的意思)是完成这项任务的一个免费 GPL 工具。Cobertura 通过用额外的语句记录在执行测试包时,哪些行被测试到、哪些行没有被测试到,通过这种方式来度量字节码,以便对测试进行监视。然后它生成一个 HTML 或者 XML 格式的报告,指出代码中的哪些包、哪些类、哪些方法和哪些行没有测试到。可以针对这些特定的区域编写更多的测试代码,以发现所有隐藏的 bug。

阅读 Cobertura 输出

我们首先查看生成的 Cobertura 输出。图 1 显示了对 Jaxen 测试包运行 Cobertura 生成的报告(请参阅 参考资料)。从该报告中,可以看到从很好(在 org.jaxen.expr.iter 包中几乎是 100%)到极差(在 org.jaxen.dom.html 中完全没有覆盖)的覆盖率结果。


图 1. Jaxen 的包级别覆盖率统计数据

Cobertura 通过被测试的行数和被测试的分支数来计算覆盖率。第一次测试时,两种测试方法之间的差别并不是很重要。Cobertura 还为类计算平均 McCabe 复杂度(请参阅 参考资料)。

可以深入挖掘 HTML 报告,了解特定包或者类的覆盖率。图 2 显示了 org.jaxen.function 包的覆盖率统计。在这个包中,覆盖率的范围从 SumFunction 类的 100% 到 IdFunction 类的仅为 5%。


图 2. org.jaxen.function 包中的代码覆盖率

进一步深入到单独的类中,具体查看哪一行代码没有测试到。图 3 显示了 NameFunction 类中的部分覆盖率。最左边一栏显示行号。后一栏显示了执行测试时这一行被执行的次数。可以看出,第 112 行被执行了 100 次,第 114 行被执行了 28 次。用红色突出显示的那些行则根本没有测试到。这个报告表明,虽然从总体上说该方法被测试到了,但实际上还有许多分支没有测试到。


图 3. NameFunction 类中的代码覆盖率

Cobertura 是 jcoverage 的分支(请参阅 参考资料)。GPL 版本的 jcoverage 已经有一年没有更新过了,并且有一些长期存在的 bug,Cobertura 修复了这些 bug。原来的那些 jcoverage 开发人员不再继续开发开放源码,他们转向开发 jcoverage 的商业版和 jcoverage+,jcoverage+ 是一个从同一代码基础中发展出来的封闭源代码产品。开放源码的奇妙之处在于:一个产品不会因为原开发人员决定让他们的工作获得相应的报酬而消亡。

确认遗漏的测试

利用 Cobertura 报告,可以找出代码中未测试的部分并针对它们编写测试。例如,图 3 显示 Jaxen 需要进行一些测试,运用 name() 函数对文字节点、注释节点、处理指令节点、属性节点和名称空间节点进行测试。

如果有许多未覆盖的代码,像 Cobertura 在这里报告的那样,那么添加所有缺少的测试将会非常耗时,但也是值得的。不一定要一次完成它。您可以从被测试的最少的代码开始,比如那些所有没有覆盖的包。在测试所有的包之后,就可以对每一个显示为没有覆盖的类编写一些测试代码。对所有类进行专门测试后,还要为所有未覆盖的方法编写测试代码。在测试所有方法之后,就可以开始分析对未测试的语句进行测试的必要性。

(几乎)不留下任何未测试的代码

是否有一些可以测试但不应测试的内容?这取决于您问的是谁。在 JUnit FAQ 中,J. B. Rainsberger 写到“一般的看法是:如果 自身 不会出问题,那么它会因为太简单而不会出问题。第一个例子是 getX() 方法。假定 getX() 方法只提供某一实例变量的值。在这种情况下,除非编译器或者解释器出了问题,否则 getX() 是不会出问题的。因此,不用测试 getX(),测试它不会带来任何好处。对于 setX() 方法来说也是如此,不过,如果 setX() 方法确实要进行任何参数验证,或者说确实有副作用,那么还是有必要对其进行测试。”

理论上,对未覆盖的代码编写测试代码不一定就会发现 bug。但在实践中,我从来没有碰到没有发现 bug 的情况。未测试的代码充满了 bug。所做的测试越少,在代码中隐藏的、未发现的 bug 就会越多。

我不同意。我已经记不清在“简单得不会出问题”的代码中发现的 bug 的数量了。确实,一些 getter 和 setter 很简单,不可能出问题。但是我从来就没有办法区分哪些方法是真的简单得不会出错,哪些方法只是看上去如此。编写覆盖像 setter 和 getter 这样简单方法的测试代码并不难。为此所花的少量时间会因为在这些方法中发现未曾预料到的 bug 而得到补偿。

一般来说,开始测量后,达到 90% 的测试覆盖率是很容易的。将覆盖率提高到 95% 或者更高就需要动一下脑筋。例如,可能需要装载不同版本的支持库,以测试没有在所有版本的库中出现的 bug。或者需要重新构建代码,以便测试通常执行不到的部分代码。可以对类进行扩展,让它们的受保护方法变为公共方法,这样就可以对这些方法进行测试。这些技巧看起来像是多此一举,但是它们曾帮助我在一半的时间内发现更多的未发现的 bug。

并不总是可以得到完美的、100% 的代码覆盖率。有时您会发现,不管对代码如何改造,仍然有一些行、方法、甚至是整个类是测试不到的。下面是您可能会遇到的挑战的一些例子:

  • 只在特定平台上执行的代码。例如,在一个设计良好的 GUI 应用程序中,添加一个 Exit 菜单项的代码可以在 Windows PC 上运行,但它不能在 Mac 机上运行。

  • 捕获不会发生的异常的 catch 语句,比如在从 ByteArrayInputStream 进行读取操作时抛出的 IOException

  • 非公共类中的一些方法,它们永远也不会被实际调用,只是为了满足某个接口契约而必须实现。

  • 处理虚拟机 bug 的代码块,比如说,不能识别 UTF-8 编码。

考虑到上面这些以及类似的情况,我认为一些极限程序员自动删除所有未测试代码的做法是不切实际的,并且可能具有一定的讽刺性。不能总是获得绝对完美的测试覆盖率并不意味着就不会有更好的覆盖率。

然而,比执行不到的语句和方法更常见的是残留代码,它不再有任何作用,并且从代码基中去掉这些代码也不会产生任何影响。有时可以通过使用反射来访问私有成员这样的怪招来测试未测试的代码。还可以为未测试的、包保护(package-protected)的代码来编写测试代码,将测试类放到将要测试的类所在那个包中。但最好不要这样做。所有不能通过发布的(公共的和受保护的)接口访问的代码都应删除。执行不到的代码不应当成为代码基的一部分。代码基越小,它就越容易被理解和维护。

不要漏掉测量单元测试包和类本身。我不止一次注意到,某些个测试方法或者类没有被测试包真正运行。通常这表明名称规范中存在问题(比如将一个方法命名为 tesSomeReallyComplexCondition,而不是将其命名为 testSomeReallyComplexCondition),或者忘记将一个类添加到主 suite() 方法中。在其他情况下,未预期的条件导致跳过了测试方法中的代码。不管是什么情况,都是虽然已经编写了测试代码,但没有真正运行它。JUnit 不会告诉您它没有像您所想的那样运行所有测试,但是 Cobertura 会告诉您。找出了未运行的测试后,改正它一般很容易。



回页首


运行 Cobertura

在了解了测量代码覆盖率的好处后,让我们再来讨论一下如何用 Cobertura 测量代码覆盖率的具体细节。Cobertura 被设计成为在 Ant 中运行。现在还没有这方面的 IDE 插件可用,不过一两年内也许就会有了。

首先需要在 build.xml 文件中添加一个任务定义。以下这个顶级 taskdef 元素将 cobertura.jar 文件限定在当前工作目录中:

<taskdef classpath="cobertura.jar" resource="tasks.properties" />

然后,需要一个 cobertura-instrument 任务,该任务将在已经编译好的类文件中添加日志代码。todir 属性指定将测量类放到什么地方。fileset 子元素指定测量哪些 .class 文件:

<target name="instrument">
  <cobertura-instrument todir="target/instrumented-classes">
    <fileset dir="target/classes">
      <include name="**/*.class"/>
    </fileset>
  </cobertura-instrument>
</target>

用通常运行测试包的同一种类型的 Ant 任务运行测试。惟一的区别在于:被测量的类必须在原始类出现在类路径中之前出现在类路径中,而且需要将 Cobertura JAR 文件添加到类路径中:

<target name="cover-test" depends="instrument">
  <mkdir dir="${testreportdir}" />
  <junit dir="./" failureproperty="test.failure" printSummary="yes" 
         fork="true" haltonerror="true">
    <!-- Normally you can create this task by copying your existing JUnit
         target, changing its name, and adding these next two lines.
         You may need to change the locations to point to wherever 
         you've put the cobertura.jar file and the instrumented classes. -->
    <classpath location="cobertura.jar"/>
    <classpath location="target/instrumented-classes"/>
    <classpath>
      <fileset dir="${libdir}">
        <include name="*.jar" />
      </fileset>
      <pathelement path="${testclassesdir}" />
      <pathelement path="${classesdir}" />
    </classpath>
    <batchtest todir="${testreportdir}">
      <fileset dir="src/java/test">
        <include name="**/*Test.java" />
        <include name="org/jaxen/javabean/*Test.java" />
      </fileset>
    </batchtest>
  </junit>
</target>>

Jaxen 项目使用 JUnit 作为其测试框架,但是 Cobertura 是不受框架影响的。它在 TestNG、Artima SuiteRunner、HTTPUni 或者在您自己在地下室开发的系统中一样工作得很好。

最后,cobertura-report 任务生成本文开始部分看到的那个 HTML 文件:

<target name="coverage-report" depends="cover-test">
 <cobertura-report srcdir="src/java/main" destdir="cobertura"/>
</target>

srcdir 属性指定原始的 .java 源代码在什么地方。destdir 属性指定 Cobertura 放置输出 HTML 的那个目录的名称。

在自己的 Ant 编译文件中加入了类似的任务后,就可以通过键入以下命令来生成一个覆盖报告:

% ant instrument
% ant cover-test
% ant coverage-report

当然,如果您愿意的话,还可以改变目标任务的名称,或者将这三项任务合并为一个目标任务。



回页首


结束语

Cobertura 是敏捷程序员工具箱中新增的一个重要工具。通过生成代码覆盖率的具体数值,Cobertura 将单元测试从一种艺术转变为一门科学。它可以寻找测试覆盖中的空隙,直接找到 bug。测量代码覆盖率使您可以获得寻找并修复 bug 所需的信息,从而开发出对每个人来说都更健壮的软件。



回页首


参考资料



回页首


关于作者

Elliotte Rusty Harold 出生在新奥尔良,现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他和妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,同住的还有他的猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学计算机科学的副教授,讲授 Java 技术和面向对象编程。他的 Cafe au Lait 网站是 Internet 上最受欢迎的独立 Java 站点之一,姊妹站点 Cafe con Leche 已经成为最受欢迎的 XML 站点之一。他的著作包括 Effective XML Processing XML with Java Java Network Programming The XML 1.1 Bible 。目前他正在从事 XML 的 XOM API、Jaxen XPath 引擎和 Jester 测试覆盖率工具的开发工作。


posted @ 2005-12-17 11:23 GHawk 阅读(411) | 评论 (0)编辑 收藏

EasyMock 2.0_ReleaseCandidate 文档翻译

     摘要: EasyMock 2.0_ReleaseCandidate Readme Documentation for release 2.0_ReleaseCandidate (October 15 2005)© 2001-2005 OFFIS, Tammo Freese. 翻译:GHawk, 2005-12-15 EasyMock 2 is a library that provides an ...  阅读全文

posted @ 2005-12-15 16:06 GHawk 阅读(4746) | 评论 (2)编辑 收藏

过程的代价

这个月刚进入公司,加入了一个10人左右的团队,用Java做一个网站后台。

客户是日本公司,他们做了项目的大部分分析(Requirements, Use cases, Domain model...)。我们负责的是详细设计和开发。我是项目开始几星期后才进的公司。Schedule也已经为我分配好了。大家都按照schedule上的安排工作着。

上星期开会的时候得知,日本这次采用的是agile过程。而我们的schedule更类似于RUP这样的过程。RUP这个学院派和Agile这个造反派狭路相逢,问题也就出现了。

大家工作都很卖力,为了能按进度提交制品,有时还通宵达旦解决问题。我们这支团队的战斗力和信心是不容怀疑的。可是大家努力的结果换来的却是用户的抱怨。大家都困惑不解。问题究竟出在哪儿?

日方在项目中强调的是Agile过程,我们采用的则是传统的过程。一开始,两个过程方法之间的差异并不大;对我们提交的制品,客户也没有什么异议。但是,直到客户提出问题之前,我们所提交的制品都是一些设计文档。而我们的制品也仅限于此——没有一个可用的EAR包、没有写过 test case。很明显,我们犯了agile的大忌。

Agile所强调的是快速的构建、轻量级的迭代、TDD等。由于之前没有写test case,详细设计也没有test case可以参照。设计本身是不是合理,是不是testable也不得而知。致使在设计test case的时候无从下手,很多类甚至都没有办法测试。整个架构的可行性很难估算。

往后考虑。一次大规模的重构可能是少不了的。虽然agile过程本身提倡以TDD为基础的重构。但是现在的重构可能造成的代价已经不是一次轻量级的增量迭代了。

说到这里,总结几点,希望能在以后的工作中引起注意:
1. Agile很难管理,项目早期应该对各种风险有尽可能全面的评估,schedule的设置中应该定义好 test case 和 build 的时间点。
2. 设计不必太详细,用频繁的测试和重构完善设计。
3. Test case 优先设计,这样在架构中就会对testability有足够多的考虑。
4. 团队内部对共同的难题应该及早进行讨论和解决,问题的解决方案应该传递到每个组员,尽可能保证团队的能力同步。

posted @ 2005-12-14 09:57 GHawk 阅读(1184) | 评论 (5)编辑 收藏

EJB 异常处理的最佳做法

http://www-128.ibm.com/developerworks/cn/java/j-ejbexcept/

EJB 异常处理的最佳做法

学习在基于 EJB 的系统上编写可以更快解决问题的代码

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Srikanth Shenoy, J2EE 顾问

2002 年 5 月 05 日

随着 J2EE 成为企业开发平台之选,越来越多基于 J2EE 的应用程序将投入生产。J2EE 平台的重要组件之一是 Enterprise JavaBean(EJB)API。J2EE 和 EJB 技术一起提供了许多优点,但随之而来的还有一些新的挑战。特别是企业系统,其中的任何问题都必须快速得到解决。在本文中,企业 Java 编程老手 Srikanth Shenoy 展现了他在 EJB 异常处理方面的最佳做法,这些做法可以更快解决问题。

在 hello-world 情形中,异常处理非常简单。每当碰到某个方法的异常时,就捕获该异常并打印堆栈跟踪或者声明这个方法抛出异常。不幸的是,这种办法不足以处理现实中出现的各种类型的异常。在生产系统中,当有异常抛出时,很可能是最终用户无法处理他或她的请求。当发生这样的异常时,最终用户通常希望能这样:

  • 有一条清楚的消息表明已经发生了一个错误
  • 有一个唯一的错误号,他可以据此访问可方便获得的客户支持系统
  • 问题快速得到解决,并且可以确信他的请求已经得到处理,或者将在设定的时间段内得到处理

理想情况下,企业级系统将不仅为客户提供这些基本的服务,还将准备好一些必要的后端机制。举例来说,客户服务小组应该收到即时的错误通知,以便在客户打电话求助之前服务代表就能意识到问题。此外,服务代表应该能够交叉引用用户的唯一错误号和产品日志,从而快速识别问题 ― 最好是能把问题定位到确切的行号或确切的方法。为了给最终用户和支持小组提供他们需要的工具和服务,在构建一个系统时,您就必须对系统被部署后可能出问题的所有地方心中有数。

在本文中,我们将谈谈基于 EJB 的系统中的异常处理。我们将从回顾异常处理的基础知识开始,包括日志实用程序的使用,然后,很快就转入对 EJB 技术如何定义和管理不同类型的异常进行更详细的讨论。此后,我们将通过一些代码示例来研究一些常见的异常处理解决方案的优缺点,我还将展示我自己在充分利用 EJB 异常处理方面的最佳做法。

请注意,本文假设您熟悉 J2EE 和 EJB 技术。您应理解实体 bean 和会话 bean 的差异。如果您对 bean 管理的持久性(bean-managed persistence(BMP))和容器管理的持久性(container-managed persistence(CMP))在实体 bean 上下文中是什么意思稍有了解,也是有帮助的。请参阅 参考资料部分了解关于 J2EE 和 EJB 技术的更多信息。

异常处理基础知识

解决系统错误的第一步是建立一个与生产系统具有相同构造的测试系统,然后跟踪导致抛出异常的所有代码,以及代码中的所有不同分支。在分布式应用程序中,很可能是调试器不工作了,所以,您可能将用 System.out.println() 方法跟踪异常。 System.out.println 尽管很方便,但开销巨大。在磁盘 I/O 期间, System.out.println 对 I/O 处理进行同步,这极大降低了吞吐量。在缺省情况下,堆栈跟踪被记录到控制台。但是,在生产系统中,浏览控制台以查看异常跟踪是行不通的。而且,不能保证堆栈跟踪会显示在生产系统中,因为,在 NT 上,系统管理员可以把 System.outSystem.err 映射到 ' ' ,在 UNIX 上,可以映射到 dev/null 。此外,如果您把 J2EE 应用程序服务器作为 NT 服务运行,甚至不会有控制台。即使您把控制台日志重定向到一个输出文件,当产品 J2EE 应用程序服务器重新启动时,这个文件很可能也将被重写。

异常处理的原则

以下是一些普遍接受的异常处理原则:

  1. 如果无法处理某个异常,那就不要捕获它。
  2. 如果捕获了一个异常,请不要胡乱处理它。
  3. 尽量在靠近异常被抛出的地方捕获异常。
  4. 在捕获异常的地方将它记录到日志中,除非您打算将它重新抛出。
  5. 按照您的异常处理必须多精细来构造您的方法。
  6. 需要用几种类型的异常就用几种,尤其是对于应用程序异常。

第 1 点显然与第 3 点相抵触。实际的解决方案是以下两者的折衷:您在距异常被抛出多近的地方将它捕获;在完全丢失原始异常的意图或内容之前,您可以让异常落在多远的地方。

:尽管这些原则的应用遍及所有 EJB 异常处理机制,但它们并不是特别针对 EJB 异常处理的。

由于以上这些原因,把代码组装成产品并同时包含 System.out.println 并不是一种选择。在测试期间使用 System.out.println ,然后在形成产品之前除去 System.out.println 也不是上策,因为这样做意味着您的产品代码与测试代码运行得不尽相同。您需要的是一种声明控制日志机制,以使您的测试代码和产品代码相同,并且当记录日志以声明方式关闭时,给产品带来的性能开销最小。

这里的解决方案显然是使用一个日志实用程序。采用恰当的编码约定,日志实用程序将负责精确地记录下任何类型的消息,不论是系统错误还是一些警告。所以,我们将在进一步讲述之前谈谈日志实用程序。

日志领域:鸟瞰

每个大型应用程序在开发、测试及产品周期中都使用日志实用程序。在今天的日志领域中,有几个角逐者,其中有两个广为人知。一个是 Log4J,它是来自 Apache 的 Jakarta 的一个开放源代码的项目。另一个是 J2SE 1.4 捆绑提供的,它是最近刚加入到这个行列的。我们将使用 Log4J 说明本文所讨论的最佳做法;但是,这些最佳做法并不特别依赖于 Log4J。

Log4J 有三个主要组件:layout、appender 和 category。 Layou代表消息被记录到日志中的格式。 appender是消息将被记录到的物理位置的别名。而 category则是有名称的实体:您可以把它当作是日志的句柄。layout 和 appender 在 XML 配置文件中声明。每个 category 带有它自己的 layout 和 appender 定义。当您获取了一个 category 并把消息记录到它那里时,消息在与该 category 相关联的各个 appender 处结束,并且所有这些消息都将以 XML 配置文件中指定的 layout 格式表示。

Log4J 给消息指定四种优先级:它们是 ERROR、WARN、INFO 和 DEBUG。为便于本文的讨论,所有异常都以具有 ERROR 优先级记录。当记录本文中的一个异常时,我们将能够找到获取 category(使用 Category.getInstance(String name) 方法)的代码,然后调用方法 category.error() (它与具有 ERROR 优先级的消息相对应)。

尽管日志实用程序能帮助我们把消息记录到适当的持久位置,但它们并不能根除问题。它们不能从产品日志中精确找出某个客户的问题报告;这一便利技术留给您把它构建到您正在开发的系统中。

要了解关于 Log4J 日志实用程序或 J2SE 所带的日志实用程序的更多信息,请参阅 参考资料部分。

异常的类别

异常的分类有不同方式。这里,我们将讨论从 EJB 的角度如何对异常进行分类。EJB 规范将异常大致分成三类:

  • JVM 异常:这种类型的异常由 JVM 抛出。 OutOfMemoryError 就是 JVM 异常的一个常见示例。对 JVM 异常您无能为力。它们表明一种致命的情况。唯一得体的退出办法是停止应用程序服务器(可能要增加硬件资源),然后重新启动系统。
  • 应用程序异常:应用程序异常是一种定制异常,由应用程序或第三方的库抛出。这些本质上是受查异常(checked exception);它们预示了业务逻辑中的某个条件尚未满足。在这样的情况下,EJB 方法的调用者可以得体地处理这种局面并采用另一条备用途径。
  • 系统异常:在大多数情况下,系统异常由 JVM 作为 RuntimeException 的子类抛出。例如, NullPointerExceptionArrayOutOfBoundsException 将因代码中的错误而被抛出。另一种类型的系统异常在系统碰到配置不当的资源(例如,拼写错误的 JNDI 查找(JNDI lookup))时发生。在这种情况下,系统就将抛出一个受查异常。捕获这些受查系统异常并将它们作为非受查异常(unchecked exception)抛出颇有意义。最重要的规则是,如果您对某个异常无能为力,那么它就是一个系统异常并且应当作为非受查异常抛出。

受查异常是一个作为 java.lang.Exception 的子类的 Java 类。通过从 java.lang.Exception 派生子类,就强制您在编译时捕获这个异常。相反地, 非受查异常则是一个作为 java.lang.RuntimeException 的子类的 Java 类。从 java.lang.RuntimeException 派生子类确保了编译器不会强制您捕获这个异常。



回页首


EJB 容器怎样处理异常

EJB 容器拦截 EJB 组件上的每一个方法调用。结果,方法调用中发生的每一个异常也被 EJB 容器拦截到。EJB 规范只处理两种类型的异常:应用程序异常和系统异常。

EJB 规范把 应用程序异常定义为在远程接口中的方法说明上声明的任何异常(而不是 RemoteException )。应用程序异常是业务工作流中的一种特殊情形。当这种类型的异常被抛出时,客户机会得到一个恢复选项,这个选项通常是要求以一种不同的方式处理请求。不过,这并不意味着任何在远程接口方法的 throws 子句中声明的非受查异常都会被当作应用程序异常对待。EJB 规范明确指出,应用程序异常不应继承 RuntimeException 或它的子类。

当发生应用程序异常时,除非被显式要求(通过调用关联的 EJBContext 对象的 setRollbackOnly() 方法)回滚事务,否则 EJB 容器就不会这样做。事实上,应用程序异常被保证以它原本的状态传送给客户机:EJB 容器绝不会以任何方式包装或修改异常。

系统异常被定义为受查异常或非受查异常,EJB 方法不能从这种异常恢复。当 EJB 容器拦截到非受查异常时,它会回滚事务并执行任何必要的清理工作。接着,它把该非受查异常包装到 RemoteException 中,然后抛给客户机。这样,EJB 容器就把所有非受查异常作为 RemoteException (或者作为其子类,例如 TransactionRolledbackException )提供给客户机。

对于受查异常的情况,容器并不会自动执行上面所描述的内务处理。要使用 EJB 容器的内部内务处理,您将必须把受查异常作为非受查异常抛出。每当发生受查系统异常(如 NamingException )时,您都应该通过包装原始的异常抛出 javax.ejb.EJBException 或其子类。因为 EJBException 本身是非受查异常,所以不需要在方法的 throws 子句中声明它。EJB 容器捕获 EJBException 或其子类,把它包装到 RemoteException 中,然后把 RemoteException 抛给客户机。

虽然系统异常由应用程序服务器记录(这是 EJB 规范规定的),但记录格式将因应用程序服务器的不同而异。为了访问所需的统计信息,企业常常需要对所生成的日志运行 shell/Perl 脚本。为了确保记录格式的统一,在您的代码中记录异常会更好些。

:EJB 1.0 规范要求把受查系统异常作为 RemoteException 抛出。从 EJB 1.1 规范起规定 EJB 实现类绝不应抛出 RemoteException



回页首


常见的异常处理策略

如果没有异常处理策略,项目小组的不同开发者很可能会编写以不同方式处理异常的代码。由于同一个异常在系统的不同地方可能以不同的方式被描述和处理,所以,这至少会使产品支持小组感到迷惑。缺乏策略还会导致在整个系统的多个地方都有记录。日志应该集中起来或者分成几个可管理的单元。理想的情况是,应在尽可能少的地方记录异常日志,同时不损失内容。在这一部分及其后的几个部分,我将展示可以在整个企业系统中以统一的方式实现的编码策略。您可以从 参考资料部分下载本文开发的实用程序类。

清单 1 显示了来自会话 EJB 组件的一个方法。这个方法删除某个客户在特定日期前所下的全部订单。首先,它获取 OrderEJB 的 Home 接口。接着,它取回某个特定客户的所有订单。当它碰到在某个特定日期之前所下的订单时,就删除所订购的商品,然后删除订单本身。请注意,抛出了三个异常,显示了三种常见的异常处理做法。(为简单起见,假设编译器优化未被使用。)


清单 1. 三种常见的异常处理做法

100  try {
101    OrderHome homeObj = EJBHomeFactory.getInstance().getOrderHome();
102    Collection orderCollection = homeObj.findByCustomerId(id);
103    iterator orderItter = orderCollection.iterator();
104    while (orderIter.hasNext()) {
105      Order orderRemote = (OrderRemote) orderIter.getNext();
106      OrderValue orderVal = orderRemote.getValue();
107      if (orderVal.getDate() < "mm/dd/yyyy") {
108        OrderItemHome itemHome = 
              EJBHomeFactory.getInstance().getItemHome();
109        Collection itemCol = itemHome.findByOrderId(orderId)
110        Iterator itemIter = itemCol.iterator();
111        while (itemIter.hasNext()) {
112          OrderItem item = (OrderItem) itemIter.getNext();
113          item.remove();
114        }
115        orderRemote.remove();
116      }
117    }
118  } catch (NamingException ne) {
119    throw new EJBException("Naming Exception occurred");
120  } catch (FinderException fe) {
121    fe.printStackTrace();
122    throw new EJBException("Finder Exception occurred");
123  } catch (RemoteException re) {
124    re.printStackTrace();
125    //Some code to log the message
126    throw new EJBException(re);
127  }

现在,让我们用上面所示的代码来研究一下所展示的三种异常处理做法的缺点。

抛出/重抛出带有出错消息的异常
NamingException 可能发生在行 101 或行 108。当发生 NamingException 时,这个方法的调用者就得到 RemoteException 并向后跟踪该异常到行 119。调用者并不能告知 NamingException 实际是发生在行 101 还是行 108。由于异常内容要直到被记录了才能得到保护,所以,这个问题的根源很难查出。在这种情形下,我们就说异常的内容被“吞掉”了。正如这个示例所示,抛出或重抛出一个带有消息的异常并不是一种好的异常处理解决办法。

记录到控制台并抛出一个异常
FinderException 可能发生在行 102 或 109。不过,由于异常被记录到控制台,所以仅当控制台可用时调用者才能向后跟踪到行 102 或 109。这显然不可行,所以异常只能被向后跟踪到行 122。这里的推理同上。

包装原始的异常以保护其内容
RemoteException 可能发生在行 102、106、109、113 或 115。它在行 123 的 catch 块被捕获。接着,这个异常被包装到 EJBException 中,所以,不论调用者在哪里记录它,它都能保持完整。这种办法比前面两种办法更好,同时演示了没有日志策略的情况。如果 deleteOldOrders() 方法的调用者记录该异常,那么将导致重复记录。而且,尽管有了日志记录,但当客户报告某个问题时,产品日志或控制台并不能被交叉引用。



回页首


EJB 异常处理探试法

EJB 组件应抛出哪些异常?您应将它们记录到系统中的什么地方?这两个问题盘根错结、相互联系,应该一起解决。解决办法取决于以下因素:

  • 您的 EJB 系统设计:在良好的 EJB 设计中,客户机绝不调用实体 EJB 组件上的方法。多数实体 EJB 方法调用发生在会话 EJB 组件中。如果您的设计遵循这些准则,则您应该用会话 EJB 组件来记录异常。如果客户机直接调用了实体 EJB 方法,则您还应该把消息记录到实体 EJB 组件中。然而,存在一个难题:相同的实体 EJB 方法可能也会被会话 EJB 组件调用。在这种情形下,如何避免重复记录呢?类似地,当一个会话 EJB 组件调用其它实体 EJB 方法时,您如何避免重复记录呢?很快我们就将探讨一种处理这两种情况的通用解决方案。(请注意,EJB 1.1 并未从体系结构上阻止客户机调用实体 EJB 组件上的方法。在 EJB 2.0 中,您可以通过为实体 EJB 组件定义本地接口规定这种限制。)

  • 计划的代码重用范围:这里的问题是您是打算把日志代码添加到多个地方,还是打算重新设计、重新构造代码来减少日志代码。

  • 您要为之服务的客户机的类型:考虑您是将为 J2EE Web 层、单机 Java 应用程序、PDA 还是将为其它客户机服务是很重要的。Web 层设计有各种形状和大小。如果您在使用命令(Command)模式,在这个模式中,Web 层通过每次传入一个不同的命令调用 EJB 层中的相同方法,那么,把异常记录到命令在其中执行的 EJB 组件中是很有用的。在多数其它的 Web 层设计中,把异常记录到 Web 层本身要更容易,也更好,因为您需要把异常日志代码添加到更少的地方。如果您的 Web 层和 EJB 层在同一地方并且不需要支持任何其它类型的客户机,那么就应该考虑后一种选择。

  • 您将处理的异常的类型(应用程序或系统):处理应用程序异常与处理系统异常有很大不同。系统异常的发生不受 EJB 开发者意图的控制。因为系统异常的含义不清楚,所以内容应指明异常的上下文。您已经看到了,通过对原始异常进行包装使这个问题得到了最好的处理。另一方面,应用程序异常是由 EJB 开发者显式抛出的,通常包装有一条消息。因为应用程序异常的含义清楚,所以没有理由要保护它的上下文。这种类型的异常不必记录到 EJB 层或客户机层;它应该以一种有意义的方式提供给最终用户,带上指向所提供的解决方案的另一条备用途径。系统异常消息没必要对最终用户很有意义。


回页首


处理应用程序异常

在这一部分及其后的几个部分中,我们将更仔细地研究用 EJB 异常处理应用程序异常和系统异常,以及 Web 层设计。作为这个讨论的一部分,我们将探讨处理从会话和实体 EJB 组件抛出的异常的不同方式。

实体 EJB 组件中的应用程序异常
清单 2 显示了实体 EJB 的一个 ejbCreate() 方法。这个方法的调用者传入一个 OrderItemValue 并请求创建一个 OrderItem 实体。因为 OrderItemValue 没有名称,所以抛出了 CreateException


清单 2. 实体 EJB 组件中的样本 ejbCreate() 方法

public Integer ejbCreate(OrderItemValue value) throws CreateException {
    if (value.getItemName() == null) {
      throw new CreateException("Cannot create Order without a name");
    }
    ..
    ..
    return null;
}

清单 2 显示了 CreateException 的一个很典型的用法。类似地,如果方法的输入参数的值不正确,则查找程序方法将抛出 FinderException

然而,如果您在使用容器管理的持久性(CMP),则开发者无法控制查找程序方法,从而 FinderException 永远不会被 CMP 实现抛出。尽管如此,在 Home 接口的查找程序方法的 throws 子句中声明 FinderException 还是要更好一些。 RemoveException 是另一个应用程序异常,它在实体被删除时被抛出。

从实体 EJB 组件抛出的应用程序异常基本上限定为这三种类型( CreateExceptionFinderExceptionRemoveException )及它们的子类。多数应用程序异常都来源于会话 EJB 组件,因为那里是作出智能决策的地方。实体 EJB 组件一般是哑类,它们的唯一职责就是创建和取回数据。

会话 EJB 组件中的应用程序异常
清单 3 显示了来自会话 EJB 组件的一个方法。这个方法的调用者设法订购 n 件某特定类型的某商品。 SessionEJB() 方法计算出仓库中的数量不够,于是抛出 NotEnoughStockExceptionNotEnoughStockException 适用于特定于业务的场合;当抛出了这个异常时,调用者会得到采用另一个备用途径的建议,让他订购更少数量的商品。


清单 3. 会话 EJB 组件中的样本容器回调方法

public ItemValueObject[] placeOrder(int n, ItemType itemType) throws
NotEnoughStockException {

    //Check Inventory.
    Collection orders = ItemHome.findByItemType(itemType);
    if (orders.size() < n) {
      throw NotEnoughStockException("Insufficient stock for " + itemType);
    }
}



回页首


处理系统异常

系统异常处理是比应用程序异常处理更为复杂的论题。由于会话 EJB 组件和实体 EJB 组件处理系统异常的方式相似,所以,对于本部分的所有示例,我们都将着重于实体 EJB 组件,不过请记住,其中的大部分示例也适用于处理会话 EJB 组件。

当引用其它 EJB 远程接口时,实体 EJB 组件会碰到 RemoteException ,而查找其它 EJB 组件时,则会碰到 NamingException ,如果使用 bean 管理的持久性(BMP),则会碰到 SQLException 。与这些类似的受查系统异常应该被捕获并作为 EJBException 或它的一个子类抛出。原始的异常应被包装起来。清单 4 显示了一种处理系统异常的办法,这种办法与处理系统异常的 EJB 容器的行为一致。通过包装原始的异常并在实体 EJB 组件中将它重新抛出,您就确保了能够在想记录它的时候访问该异常。


清单 4. 处理系统异常的一种常见方式

try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (NamingException ne) {
    throw new EJBException(ne);
} catch (SQLException se) {
    throw new EJBException(se);
} catch (RemoteException re) {
    throw new EJBException(re);
}

避免重复记录

通常,异常记录发生在会话 EJB 组件中。但如果直接从 EJB 层外部访问实体 EJB 组件,又会怎么样呢?要是这样,您就不得不在实体 EJB 组件中记录异常并抛出它。这里的问题是,调用者没办法知道异常是否已经被记录,因而很可能再次记录它,从而导致重复记录。更重要的是,调用者没办法访问初始记录时所生成的唯一的标识。任何没有交叉引用机制的记录都是毫无用处的。

请考虑这种最糟糕的情形:单机 Java 应用程序访问了实体 EJB 组件中的一个方法 foo() 。在一个名为 bar() 的会话 EJB 方法中也访问了同一个方法。一个 Web 层客户机调用会话 EJB 组件的方法 bar() 并也记录了该异常。如果当从 Web 层调用会话 EJB 方法 bar() 时在实体 EJB 方法 foo() 中发生了一个异常,则该异常将被记录到三个地方:先是在实体 EJB 组件,然后是在会话 EJB 组件,最后是在 Web 层。而且,没有一个堆栈跟踪可以被交叉引用!

幸运的是,解决这些问题用常规办法就可以很容易地做到。您所需要的只是一种机制,使调用者能够:

  • 访问唯一的标识
  • 查明异常是否已经被记录了

您可以派生 EJBException 的子类来存储这样的信息。清单 5 显示了 LoggableEJBException 子类:


清单 5. LoggableEJBException ― EJBException 的一个子类

public class LoggableEJBException extends EJBException {
    protected boolean isLogged;
    protected String uniqueID;

    public LoggableEJBException(Exception exc) {
	super(exc);
	isLogged = false;
	uniqueID = ExceptionIDGenerator.getExceptionID();
    }

	..
	..
}

LoggableEJBException 有一个指示符标志( isLogged ),用于检查异常是否已经被记录了。每当捕获一个 LoggableEJBException 时,看一下该异常是否已经被记录了( isLogged == false )。如果 isLogged 为 false,则记录该异常并把标志设置为 true

ExceptionIDGenerator 类用当前时间和机器的主机名为异常生成唯一的标识。如果您喜欢,也可以用有想象力的算法来生成这个唯一的标识。如果您在实体 EJB 组件中记录了异常,则这个异常将不会在别的地方被记录。如果您没有记录就在实体 EJB 组件中抛出了 LoggableEJBException ,则这个异常将被记录到会话 EJB 组件中,但不记录到 Web 层中。

单 6 显示了使用这一技术重写后的清单 4。您还可以继承 LoggableException 以适合于您的需要(通过给异常指定错误代码等)。


清单 6. 使用 LoggableEJBException 的异常处理

try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (NamingException ne) {
    throw new LoggableEJBException(ne);
} catch (SQLException se) {
    throw new LoggableEJBException(se);
} catch (RemoteException re) {
    Throwable t = re.detail;
     if (t != null && t instanceof Exception) {
       throw new LoggableEJBException((Exception) re.detail);
     }  else {
       throw new LoggableEJBException(re);
     }
}

记录 RemoteException

从清单 6 中,您可以看到 naming 和 SQL 异常在被抛出前被包装到了 LoggableEJBException 中。但 RemoteException 是以一种稍有不同 ― 而且要稍微花点气力 ― 的方式处理的。
会话 EJB 组件中的系统异常

如果您决定记录会话 EJB 异常,请使用 清单 7所示的记录代码;否则,请抛出异常,如 清单 6所示。您应该注意到,会话 EJB 组件处理异常可有一种与实体 EJB 组件不同的方式:因为大多数 EJB 系统都只能从 Web 层访问,而且会话 EJB 可以作为 EJB 层的虚包,所以,把会话 EJB 异常的记录推迟到 Web 层实际上是有可能做到的。

它之所以不同,是因为在 RemoteException 中,实际的异常将被存储到一个称为 detail (它是 Throwable 类型的)的公共属性中。在大多数情况下,这个公共属性保存有一个异常。如果您调用 RemoteExceptionprintStackTrace ,则除打印 detail 的堆栈跟踪之外,它还会打印异常本身的堆栈跟踪。您不需要像这样的 RemoteException 的堆栈跟踪。

为了把您的应用程序代码从错综复杂的代码(例如 RemoteException 的代码)中分离出来,这些行被重新构造成一个称为 ExceptionLogUtil 的类。有了这个类,您所要做的只是每当需要创建 LoggableEJBException 时调用 ExceptionLogUtil.createLoggableEJBException(e) 。请注意,在清单 6 中,实体 EJB 组件并没有记录异常;不过,即便您决定在实体 EJB 组件中记录异常,这个解决方案仍然行得通。清单 7 显示了实体 EJB 组件中的异常记录:


清单 7. 实体 EJB 组件中的异常记录

try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (RemoteException re) {
    LoggableEJBException le = 
       ExceptionLogUtil.createLoggableEJBException(re);
    String traceStr = StackTraceUtil.getStackTrace(le);
    Category.getInstance(getClass().getName()).error(le.getUniqueID() +
":" + traceStr);
    le.setLogged(true);
    throw le;
}

您在清单 7 中看到的是一个非常简单明了的异常记录机制。一旦捕获受查系统异常就创建一个新的 LoggableEJBException 。接着,使用类 StackTraceUtil 获取 LoggableEJBException 的堆栈跟踪,把它作为一个字符串。然后,使用 Log4J category 把该字符串作为一个错误加以记录。

StackTraceUtil 类的工作原理

在清单 7 中,您看到了一个新的称为 StackTraceUtil 的类。因为 Log4J 只能记录 String 消息,所以这个类负责解决把堆栈跟踪转换成 String 的问题。清单 8 说明了 StackTraceUtil 类的工作原理:


清单 8. StackTraceUtil 类


public class StackTraceUtil {

public static String getStackTrace(Exception e)
      {
          StringWriter sw = new StringWriter();
          PrintWriter pw = new PrintWriter(sw);
          return sw.toString();
      }
      ..
      ..
}

java.lang.Throwable 中缺省的 printStackTrace() 方法把出错消息记录到 System.errThrowable 还有一个重载的 printStackTrace() 方法,它把出错消息记录到 PrintWriterPrintStream 。上面的 StackTraceUtil 中的方法把 StringWriter 包装到 PrintWriter 中。当 PrintWriter 包含有堆栈跟踪时,它只是调用 StringWritertoString() ,以获取该堆栈跟踪的 String 表示。



回页首


Web 层的 EJB 异常处理

在 Web 层设计中,把异常记录机制放到客户机端往往更容易也更高效。要能做到这一点,Web 层就必须是 EJB 层的唯一客户机。此外,Web 层必须建立在以下模式或框架之一的基础上:

  • 模式:业务委派(Business Delegate)、FrontController 或拦截过滤器(Intercepting Filter)
  • 框架:Struts 或任何包含层次结构的类似于 MVC 框架的框架

为什么异常记录应该在客户机端上发生呢?嗯,首先,控制尚未传到应用程序服务器之外。所谓的客户机层在 J2EE 应用程序服务器本身上运行,它由 JSP 页、servlet 或它们的助手类组成。其次,在设计良好的 Web 层中的类有一个层次结构(例如:在业务委派(Business Delegate)类、拦截过滤器(Intercepting Filter)类、http 请求处理程序(http request handler)类和 JSP 基类(JSP base class)中,或者在 Struts Action 类中),或者 FrontController servlet 形式的单点调用。这些层次结构的基类或者 Controller 类中的中央点可能包含有异常记录代码。对于基于会话 EJB 记录的情况,EJB 组件中的每一个方法都必须具有记录代码。随着业务逻辑的增加,会话 EJB 方法的数量也会增加,记录代码的数量也会增加。Web 层系统将需要更少的记录代码。如果您的 Web 层和 EJB 层在同一地方并且不需要支持任何其它类型的客户机,那么您应该考虑这一备用方案。不管怎样,记录机制不会改变;您可以使用与前面的部分所描述的相同技术。



回页首


真实世界的复杂性

到现在为止,您已经看到了简单情形的会话和实体 EJB 组件的异常处理技术。然而,应用程序异常的某些组合可能会更令人费解,并且有多种解释。清单 9 显示了一个示例。 OrderEJBejbCreate() 方法试图获取 CustomerEJB 的一个远程引用,这会导致 FinderExceptionOrderEJBCustomerEJB 都是实体 EJB 组件。您应该如何解释 ejbCreate() 中的这个 FinderException 呢?是把它当作应用程序异常对待呢(因为 EJB 规范把它定义为标准应用程序异常),还是当作系统异常对待?


清单 9. ejbCreate() 方法中的 FinderException

public Object ejbCreate(OrderValue val) throws CreateException {
     try {
        if (value.getItemName() == null) {
          throw new CreateException("Cannot create Order without a name");
        }
        String custId = val.getCustomerId();
        Customer cust = customerHome.fingByPrimaryKey(custId);
        this.customer = cust;
     } catch (FinderException ne) {
     	  //How do you handle this Exception ?
     } catch (RemoteException re) {
	  //This is clearly a System Exception
	  throw ExceptionLogUtil.createLoggableEJBException(re);
     }
     return null;
}

虽然没有什么东西阻止您把 FinderException 当应用程序异常对待,但把它当系统异常对待会更好。原因是:EJB 客户机倾向于把 EJB 组件当黑箱对待。如果 createOrder() 方法的调用者获得了一个 FinderException ,这对调用者并没有任何意义。 OrderEJB 正试图设置客户远程引用这件事对调用者来说是透明的。从客户机的角度看,失败仅仅意味着该订单无法创建。

这类情形的另一个示例是,会话 EJB 组件试图创建另一个会话 EJB,因而导致了一个 CreateException 。一种类似的情形是,实体 EJB 方法试图创建一个会话 EJB 组件,因而导致了一个 CreateException 。这两个异常都应该当作系统异常对待。

另一个可能碰到的挑战是会话 EJB 组件在它的某个容器回调方法中获得了一个 FinderException 。您必须逐例处理这类情况。您可能要决定是把 FinderException 当应用程序异常还是系统异常对待。请考虑清单 1 的情况,其中调用者调用了会话 EJB 组件的 deleteOldOrder 方法。如果我们不是捕获 FinderException ,而是将它抛出,会怎么样呢?在这一特定情况中,把 FinderException 当系统异常对待似乎是符合逻辑的。这里的理由是,会话 EJB 组件倾向于在它们的方法中做许多工作,因为它们处理工作流情形,并且它们对调用者而言是黑箱。

另一方面,请考虑会话 EJB 正在处理下订单的情形。要下一个订单,用户必须有一个简档 ― 但这个特定用户却还没有。业务逻辑可能希望会话 EJB 显式地通知用户她的简档丢失了。丢失的简档很可能表现为会话 EJB 组件中的 javax.ejb.ObjectNotFoundExceptionFinderException 的一个子类)。在这种情况下,最好的办法是在会话 EJB 组件中捕获 ObjectNotFoundException 并抛出一个应用程序异常,让用户知道她的简档丢失了。

即使是有了很好的异常处理策略,另一个问题还是经常会在测试中出现,而且在产品中也更加重要。编译器和运行时优化会改变一个类的整体结构,这会限制您使用堆栈跟踪实用程序来跟踪异常的能力。这就是您需要代码重构的帮助的地方。您应该把大的方法调用分割为更小的、更易于管理的块。而且,只要有可能,异常类型需要多少就划分为多少;每次您捕获一个异常,都应该捕获已规定好类型的异常,而不是捕获所有类型的异常。



回页首


结束语

我们已经在本文讨论了很多东西,您可能想知道我们已经讨论的主要设计是否都物有所值。我的经验是,即便是在中小型项目中,在开发周期中,您的付出就已经能看到回报,更不用说测试和产品周期了。此外,在宕机对业务具有毁灭性影响的生产系统中,良好的异常处理体系结构的重要性再怎么强调也不过分。

我希望本文所展示的最佳做法对您有益。要深入理解这里提供的某些信息,请参看 参考资料部分中的清单。



回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 单击本文顶部或底部的 讨论参加本文的 讨论论坛


  • 下载本文所讨论的 实用程序类


  • 您可以阅读 Sun Microsystems 的 EJB 规范了解关于 EJB 体系结构的更多信息。


  • Apache 的 Jakarta 项目有几个珍品。 Log4J 框架即是其中之一。


  • Struts 框架是 Jakarta 项目的另一个珍品。Struts 建立在 MVC 体系结构的基础上,提供了一个彻底的分离,它把系统的表示层从系统的业务逻辑层中分离出来。


  • 要详细了解 Struts,请阅读 Malcom Davis 所写的讲述这个主题的很受欢迎的文章“ Struts, an open-source MVC implementation”( developerWorks,2001 年 2 月)。请注意:有一篇由 Wellie Chao 撰写的最新文章定于 2002 年夏季发表。


  • 您可以通过阅读相关的 J2SE 文档了解关于新的 Java Logging API(java.util.logging)的更多信息。


  • 刚接触 J2EE?来自“WebSphere 开发者园地”的这篇文章告诉您如何 用 WebSphere Studio Application Developer 开发和测试 J2EE 应用程序(2001 年 10 月)。


  • 如果您想更多了解关于测试基于 EJB 的系统的知识,请从最近的 developerWorks文章“ Test flexibly with AspectJ and mock objects”(2002 年 5 月)开始。


  • 如果您不满足于单元测试,还想了解企业级系统测试的知识,请看看 IBM Performance Management, Testing, and Scalability Services企业级测试库提供了什么。


  • Sun 的 J2EE 模式Web 站点着重于使用 J2EE 技术的模式、最佳做法、设计策略以及经验证的解决方案。


  • 您可以在 developerWorks Java 技术专区找到数以百计关于 Java 编程的方方面面的文章。


回页首


关于作者

Srikanth Shenoy 的照片

Srikanth Shenoy 专门从事大型 J2EE 和 EAI 项目的体系结构、设计、开发和部署工作。他在 Java 平台一出现时就迷上了它,从此便全心投入。Srikanth 已经帮他的制造业、物流业和金融业客户实现了 Java 平台“一次编写,随处运行”的梦想。您可以通过 srikanth@srikanth.org与他联系。

posted @ 2005-12-10 11:25 GHawk 阅读(321) | 评论 (0)编辑 收藏