鲲鹏's profile鲲鹏(BirdGu)的BlogPhotosBlogListsMore Tools Help

Blog


    January 01

    巨星沉浮——看看超级计算机排行榜(应用篇)

    应用篇

    前面有人问超级计算机用在什么地方,现在就来回答这个问题。不过Top500每期榜单上都有一半左右的系统的用途是“Not Specified”。因此只能再结合这些系统的用户来推测它们的用途。

    我们先来看一下每期榜单的第一名的用户都是谁:
    1993年6月,美国Los Alamos National Laboratory
    1993年11月,日本National Aerospace Laboratory,系统的名字叫“数字风洞”(Numerical Wind Tunnel)
    1994年6月,美国Sandia National Laboratories
    1995年6月到1995年11月,日本National Aerospace Laboratory,就是93年11月那台,好像是升过级了。
    1996年6月,日本东京大学
    1996年11月,日本筑波大学计算科学中心(Center for Computational Science)
    1997年6月到2000年6月,美国Sandia National Laboratories
    2000年11月到2001年11月,美国Lawrence Livermore National Laboratory
    2002年6月到2004年6月,日本地球模拟中心
    2004年11月,IBM/DOE (美国能源部)
    2005年6月到2006年6月,DOE/NNSA/LLNL (美国能源部/核武器安全管理局/Lawrence Livermore国家实验室)

    在Los Alamos, Sandia, Lawrence Livermore,核武器研究是这些国家实验室的重要研究课题。而在核武器研究领域,超级计算机的一项重要应用就是核爆炸的计算机模拟。当然,这些实验室也同时担负着新能源的研究,其它军工技术和系统,以及一些基础理论研究项目。

    在2005年11月,Los Alamos有14套系统上了Top 500,Sandia有12套,Lawrence Livermore有10套,NNSA有2套。总计有38套,占总数的7.6%。

    日本著名的Earth Emulator是由日本National Space Development Agency(国家空间发展局), Atomic Entergy Research Institute(原子能研究所), Marine Scinece and Technology Center(海洋科学与技术中心)三家合作开发的。主要研究任务是大气,海洋和地球环境变化的预测和评估,对自然灾害的预测,人类活动和自然关系的研究等等。

    在1993年6月的傍单上,占有比例比较高的应用领域有:
    Geophysics, 6%, 在这一领域的很多用户是石油公司。用于对地震勘探资料的分析。
    Automotive,5.4%,比如福特(第30名,321名),丰田(35,299,445),日产(170),宝马(221),克莱斯勒(227),三菱(Mitsubishi Motos)(236),法国标致(241),马自达(272,387,388),本田(282),雷诺(285),大众(287),通用(GM)(289),米其林(313),奥迪(361),日本大发(Daihatsu)(372),日本电装(393)(电装是丰田的供应商)。而且大部分都是Cray的系统。
    在汽车工业,超级计算机可以用于车身结构的有限元分析,汽车碰撞的计算机模拟。

    Aerospace, 5%,用户有美国NASA,洛克西德,波音,麦道,洛克维尔(Rockwell),格鲁曼,以及法国,英国,荷兰等的宇航研究机构和公司。

    其它还有Eletronics(3.2%), 能源(2.8%)

    到2000年6月,电信方面的系统达到了12%,用户有德国电信,法国电信,阿尔卡特,Sprint,荷兰电信,韩国Freetel等等。
    Finance领域的系统达到了9.8%,用户以银行为主,比如荷兰银行,美洲银行等,还有很多机构只写了银行,没有具体名称。
    Weather and Climate Research,气象与气候研究则有5.2%。
    Geophysics (3%), Aerospace(3.8%), Automative(4.8%)这些仍然是超级计算机的传统应用领域。

    2006年6月,初了Not Specified的以外,Semiconductor达到了9.4%,Geophysics和Finance都有6.4%,Weather and Climate Research有3%。Gaming方面有7台,居然全是中国人的,中国大陆5台,台湾2台,而且好像是同一家公司的,所以都是HP的Blade Cluster BL-20P系统。 这是盛大的吗?真有钱。

    2006年6月,中国大陆有28套系统上榜,我们来看看这些系统都在什么地方(括号中为排名):

    国家气象局 (35),
    上海超级计算中心,曙光4000A(53),
    中国科学院 (124)
    某保密机构,(138)
    某基因科学研究机构(152)
    某石油公司 (167,481)
    另一家基因科学研究机构 (188)
    又是一家基因科学研究机构 (201)
    深圳银河计算机公司 (224)
    南开大学科学计算研究所 (233)
    某游戏公司(266到270
    中石油 (293)
    中石化 (393,394,441)
    北京方正世纪信息系统公司(Beijing Founder Century Information System Co.) (422,446)
    Digital China (423, 447, 448, 449) (这家是什么公司?)
    某能源公司 (429,430)

    台湾地区有三台,除了那家游戏公司的两台以外,另一台在“国家”高性能计算中心,排名第491。

    巨星沉浮——看看超级计算机排行榜(体系架构篇)

    系统架构篇

    93年的时候,SMP(对称多处理)的架构站了将近一半,49.8%。但是我们前面说道,80年代后期MPP架构已经开始兴起,因此在TOP500中也占到了23.8%,处于第二位。但是由于前面提到的那为明星(或者说流星)Thinking Machinese采用的是MPP架构,因此MPP也就包揽了前4名,其它的MPP系统排名也都很靠前。

    NET,Cray和HP都是SMP架构的。其中NEC SX-3/44R和SX-3/44占据了第5,6位。

    我对并行计算的体系结构也不熟,这里简单解释一下SMP和MMP,有不对的地方还请行家不吝赐教。

    SMP的系统个处理器是共享系统内存的。处理器和内存都连在系统总线上。现在的多CPU的PC服务器都属于这种架构。受总线带宽的限制,能够支持的处理器不可能太多。比如NEC的SX-3/44就只有4个CPU,排名9-20的Cray Y-MP C916/16256都是16个CPU。提高整体性能相当程度上要依靠提高处理器的性能。

    MPP的系统有很多计算节点组成,每个节点有处理器和内存,节点间不共享内存。节点间的联接方式很多。比如NEC的Earth Emulator有640个节点,节点间是通过640*640的专用连接设备连接的。

    MPP的架构处理器可以作到非常多。比如93年第一名的Thinking Machinese TMC CM5系统就已经用了1024颗CPU (型号不明),最高速度59.7GFLOPS。

    除了这两中架构以外,93年的时候还有19.4%的单处理器系统。主要是NEC, Fujitsu, Hitachi等日本厂商的系统。 这里可以看出美国当时已经把重点转向了并行系统,而日本人还固守在单处理器系统上,这可能也是后来两国在超级计算机领域拉开差距的一个原因吧。

    另外还有7%的SIMD系统,就是单指令,多操作数系统。所谓SIMD就是一条指令,同时对多对操作数。比如同时计算16对数字之间的加法。Intel MMX中就有很多这样的指令。Thingking Machinese,MasPar是这种架构的主要供应商。另外还有就是那台被错划给HP的DECmpp SX 100。

    到了94年6月的时候,MPP(40.2%)超过了SMP(39.2%)。但到95年6月,SMP反扑,以48.2%对43.8%反超。但另外两种架构所战比例已经微不足道了。这以后一直到99年6月,SMP和MPP互相拉锯,各有胜负。但是99年11月的时候,MPP以51.6%对33.8%较大比例超出。并在以后保持了对SMP的优势。

    95年11月第一次出现了叫做Constellations(星座)的体系架构。不过对这种架构我也不了解。16套系统全部是SGI的。

    97年6月单处理器系统从榜单上消失,然后是11月,SIMD退出。当年6月,日后的王者Cluster露了一小脸,加洲伯克莱大学的Berkley NOW以10.14GFLOPS据344位。该系统是该校自己搭建的,使用了100个SUN的CPU,具体型号不名。97年11月478位,然后就出榜了。

    98年6月,洛斯阿拉莫斯国家试验室自己搭的Avalon Cluster,用了68颗Alpha芯片,以19.33GFLOPS居314位。不过只在榜上出现了这一次。

    99年6月份第一次出现了有专业厂商制造的群集系统。AlphaServer SC, 256颗Alpha芯片,最高速度154.4GFLOPS,居48位。Top500数据库把它归到HP名下,不过当时拥有Alpha的Compaq还没有被HP收购。
    同年Sun和Fujitsu也有群集系统上榜。这以后,Cluster的份额开始缓慢上升。

    2002年11月,SMP退出。同期MPP和Constellations以40.80%持平,Cluster以18.6%处第三。再没有别的体系结构了。但是MPP的总FLOPS数达到了167TFLOPS,而Constellations只有47TFLOPS,因此性能上还是MPP胜出。就是Cluster也有77TFLOPS。说明Constellations不是太理想的架构。

    果然,到了2003年6月,Cluster就以29.8%超过了Constellations的28.00%。

    2003年11月,Cluster(42%)超过了MPP的32.6%。这以后,Cluster一直稳居榜首,并不断扩大优势。到了2006年6月,有超过72.8%的系统采用了Cluster架构,而MPP还剩下19.6%,Constellations更是只有7.6%了。

    但是,这只是从系统数量上来说的, 如果从性能来说的话,MPP仍然占有绝对优势。Top10中,1-4名和8-10名7套系统都是MPP架构的,Cluster只占了7,8两位。法国Bull SA的NovaScale以Constellations架构占了第5位。

    中国的曙光和联想的系统都是属于Cluster架构的。

    Cluster架构的优点是使用成熟,通用,廉价的部件搭建,门槛比较低,但要作到顶级还有一定困难。这可能也是使Top500的上榜厂商数量大增的原因。93年的时候只有12个厂商的系统上榜,而2006年,这个数字增加到了25家,还有7套系统是由最终用户自己搭的。可以说Cluster架构打破了少数专业厂商对超级计算机领域的垄断。

    ----------------分割线-------------------
    写这个系列文章的缘起是前几天在别的论坛给人科普超级计算机方面的知识,为此查了一些资料,主要是Top500,发现这个榜单中还是有不少有趣的东西。

    在那个论坛也有人问银河机用的是什么CPU。如果是说当年的银河1号亿次计算机的话,那么回答是,银河1号中没有CPU。

    现在在网上银河1号的具体技术资料还真不好找。好在在一篇银河主要试制人员的介绍中明确说明银河1号的体系结构是参照Cray-1的。这就好办了。

    下面抄一短对Cray-1的描述,原文是英文的,我给翻译成中文了

    引用

    Cray-1是Cray第一款使用集成电路(IC)的设计。虽然集成电路在60年代就出现了,但是只到70年代早期才达到高速计算所需要的性能要求。在Cray-1中使用的IC是非常简单的,一般只包含4个逻辑门,每个大概包含4个三极管。Cray-1一共拥有约20万个逻辑门。
    IC被插在大型的5层印刷线路板上,最多的一快板有144个IC。为了散热,线路板背靠背地装在一起。24个28英寸高的机架中装配了72对这样的线路板。一个典型的模块,比如一个单独的处理单元,包含有1到2快线路板。整个机器中一共有113种,1662个模块。

    所以,也可以说真个Cray-1就是一快CPU。

    巨星沉浮——看看超级计算机排行榜(中国篇)

    中国篇

    2001年11月,Top500上第一次出现了中国大陆地区的超级计算机系统,不过很可惜,三台全是HP的。

    2002年11月,联想的一台DeepComp 1800排名43,这是中国厂商的系统第一次上榜。该系统安装在Academy of Mathematics and System Science (数学与系统科学研究所?),拥有512颗P4 Xeon 2GHz CPU,使用Myrinet网络,最高运算速度(实测)1297GFLOPS.

    2003年6月,联想又有两台新上榜,分列98和297位。两个系统都是256颗P4 Xeon 2.4G。但前一台用的是Myrinet,最高速度711.7GFLOPS,后一台用的是Gigabit Ethernet,最高速度385.4GFLOPS。节点间的通讯速度对整体性能的影响可见一斑。

    2003年11月,联想的DeepComp 6800排到了第14位。该系统用了1024颗Itanium2 1.3GHz的CPU, QsNet的网络,最高速度4193GFLOPS。

    同年,清华的DeepSuper-21C排到163,用了256颗P4 Xeon 3.06/2.8GHz, Myrinet。最高速度877.3GFLOPS。

    2004年6月,安装在上海超级计算中心的曙光4000A排到了第10位。这是中国厂商的系统得到过的最好成绩了。该系统使用了2560颗AMD Opteron 2.2G CPU, Myrinet网络,最高速度11264GFLOPS。

    同年新上榜的还有浪潮的TS10000,安装在山东高性能计算中心,使用了192颗P4 Xeon 2.8G, Infiniband 4X的网络,最高速度749GFLOPS。排名372。

    这一年前面那两台稍差一点的联想的系统都已经被挤出Top500了。

    2005年6月,深圳银河有一套系统排到第100,就安装在他们自己公司,562颗Xeon CPU,最高速度3413GFLOPS。

    同一期榜上,第150,151,152三个系统全部属于中国的一家游戏公司,Top500上称之为Gaming Compnay(B),三个系统全部是HP的Blade Cluster BL-20P, 800颗 P4 Xeon 2.8GHz CPU, GigEthernet网络,最高速度2199.7GFLOPS。真有钱啊!

    到了2006年6月,更是有5套这样的系统列在这家Gameing Compnay(B)名下,分别安装在上海(2套),成都,西安和北京。

    现在(2006年6月)的榜上,安装在中国的系统一共有28套,占5.6%, 仅次于美国(59.8%),英国(7%)和日本(5.8%)。但是其中只有三套是中国厂商生产的,曙光,联想和深圳的银河各一套。而使用的CPU更是全部进口的。

    期盼着看到使用龙芯3的曙光5000能在Top500上出现。

    巨星沉浮——看看超级计算机排行榜(厂商篇)

    注:本系列文原发于www.javaeye.com,转到这里做个备份。

    ----------------------------------前言---------------------------------

    Top500 (www.top500.org) 项目开始于1993年。目的是跟踪高性能计算领域的发展趋势。每年6月和11月会推出全球运算速度最快的500个computer systems (因为集群技术的使用,有些系统可能已经不适合继续称为计算机了)。Top 500项目使用Linpack基准测试程序,测试结果的单位是FLOPS,即每秒浮点运算数。不过常用的单位是GFLOPS,1G等于10的9次方。近年来也常用TFLOPS,就是10的12次方了。

    从1993年到现在13年时间了,回顾这个榜单,也能从一个侧面反映这10多年来IT业界的一些历史。

    -----------------------------------正文--------------------------------

    说起超级计算机就不能不说Cray。1993年6月的Top500中Cray占到了205台,41.00%。当年Cray在超级计算机领域的领导地位可见一斑。

    不过93年的时候Cray已经在走下坡路了。到1996年11月(当年Cray的创始人Seymour Cray在一次交通事故中去世,同年Cray被SGI收购)Cray在Top500中的份额跌到了26.2%,然后是2000年6月的10.8%, 2003年6月的5.2%,2006年6月的3.2%(16台)。我们清晰地看到了巨星陨落的轨迹。

    80年代后期MPP(大规模并行计算)开始崛起。这方面的先驱者包括了Thinking Machines,93年6月榜单上的另一个明星。虽然数量不多,只有54台,占10.8%,但是它包揽了前四名,再加上第7名,Top10中占了一半。而Cray只能包揽第9到第20。当时Cray对MPP是不太感冒的,认为为这样的系统开发软件是非常困难的。

    但是Thinking Machines的衰落却比Cray要快得多。因为他的业务过份以来DARPA的合同,靠着DARPA的合同在1989年获得了盈利(82年成立的),但是1991年DARPA受到批评,认为它在采购中过分偏向Thinking Machinese,对IBM和Cray不公平。因此,1992年,公司再次陷入亏损。1994年申请破产保护,然后硬件部分出售给SUN,公司本体转型为一家做数据挖掘的软件公司,然后在1999年被Oracle收购。

    在Top500榜上,Thinkning Machines的份额在94年6月上升到15.8%,Top10中还有4台。95年6月跌到7.40%,99年6月,从榜上完全消失。

    DARPA从善如流,给了IBM和Cray以公平,却毁掉了Thinking Machines。

    今天IBM无疑是Top500上的天王巨星。2006年6月,Top500中IBM占了47.8%(239台),超过93年时的Cray。而且IBM包揽了前3名,再加上第8名,Top10中有4个,11名到20名中还有7台IBM。今天在超级计算机领域,IBM已经远远超过了当年Cray曾取得过的地位。

    谁能想到在93年6月的榜单上,IBM的成绩是0呢?不过到当年11月的时候,IBM就已经有16台上榜,最好的成绩是一台IBM 9076-005 SP1,排名76。这应该是属于IBM RS6000系列的。

    另外,HP的发展也很快。93年6月的时候只有3%的份额,到2000年6月上升到10.6%,2006年6月的时候达到了31.2%,仅次于IBM。而且这两家加起来就有79%份额。所以第三名Dell就只有可怜的4.4%的份额了。

    不过93年HP的成绩是有水分的。当年列在HP名下的有一个DECmpp SX 200,排名175,写的Vendor是Hewlett-Packard(Compaq)。但是当时DEC还没有被Compaq收购,Compaq也没有被HP收购。可怜的DEC!

    Top500还有一个大输家,就是日本。93年6月富士通,日立,NEC三家合计站了20%,但是到2006年6月,这个比例已经下降到了不足3%。日本在超级计算机领域已经被美国远远甩在了后面。不过超级计算机毕竟是要用钱堆出来的,所以这也从侧面反映了日本在90年代以后经济上的衰退。

    不过日本也并非没有亮点。NEC的Earth Emulator在2002年6月到2004年6月间连续两年占据榜首位置,即使到了2006年6月仍然据第十。Earth Emulator拥有640个计算节点,每个节点有8个向量型的运算处理器。这个目前普遍采用的使用通用CPU的MPP系统,在思路上还是有很大不同的。

    October 23

    ASP模式应用中多客户数据管理方案探讨

        ASP(Application Service Provider)模式应用与普通应用之间的一个重要差别是ASP系统需要管理多个客户的数据。不同客户之间的数据完全独立,没有关联。不允许一个客户查询另一个客户的数据。即使多个客户共享一个数据库,对单个客户来说,其它客户的数据可以看作是不存在的。 这里主要讨论在J2EE应用中如何解决这个问题。当然,这里讨论的方法对于其它架构的系统可能也适用。

    方案1:单数据源,单套表。

    所有客户的数据都存放在一个数据库的同一套表中, 在部分表中增加标示字段,表明该记录是属于哪个客户的。具体哪些表中要增加标示字段当然要看具体应用,不过我觉得可能大部分表示实体对象的表中都需要加。在很多查询条件中都需要包括这个标示字段。即使是用户自定义的查询,系统也需要在查询条件中加入该字段。

    优点:数据源和数据库的管理都比较简单。数据源管理方面和普通的J2EE应用没有差别。

    缺点:增加程序的复杂性。如果应用比较复杂,很多数据表都需要加入客户表示字段,很多查询都需要包括该字段,会比较麻烦。如果有遗漏,特别是查询条件中遗漏该字段,就会造成一个客户看到另一个客户的数据。另外,需要在系统的安全性方面做比较细致的设计。比如,某个功能通过在URL中包含某个实体的关键字以查询该实体的信息,或对该实体进行操作。在普通应用中后台只需要根据关键字查出该实体即可。但是在ASP应用中,就必须额外判定该实体是否属于当前登录用户,或者要在查询中条件中加入客户标示。当然如果有办法自动完成这样的检查或者自动修改发到数据库的查询条件,这个缺点就可以避免。只是我现在还没有想到这样做的好方法。

    -------------------------------------

    方案2:多套表,多数据源

    数据库中每个客户一套表。可以是MySql, PostgresSQL, SQLServer中的不同数据库,或者Oracle中的不同schema。在应用服务器中配置不同的数据源,或者使用不同的连接池。 在访问数据库,需要得到数据库连接时,根据当前用户所属的客户选择合适的数据源或者连接池。

    优点:不同客户的数据物理分离,安全性比较好。除了获取数据库连接部分的程序以外,其它程序和普通应用没有两样。不同客户的数据可以放置在不同的数据库服务器中,分担数据库服务器的负荷。

    缺点:数据库连接的利用效率不高。ASP模式的主要客户是中小企业。这样带来的结果是客户数可能会很多,但是单个客户的用户数和并发登录数都不会太多。在系统这边来说,则是数据源或连接池很多,但每一个的利用效率都不高。在数据库服务器这边仍然会有很多连接,因为每个数据源或连接池都需要保持一定数量的可用连接。这样通过连接池共享数据库连接而减少总连接数的好处被大大削弱了。 另一个缺点是如果需要增加客户时,需要在应用服务器中配制新的数据源,或者修改应用自己的数据库连接池配制。某些情况下可能无法作到在应用不中断的情况下使这些配制生效。

    ------------------------------------

    方案3:多套表,多Schema,单数据源。

    这个方案基本是方案2的变种。很多数据库提供Schema,比如PostgresSQL中,同一个数据库下可以有多个Schema,Oracle中,每个用户就是一个Schema。即使用同一个用户登录数据库,只要在表名前加上schema名字,就能访问不同schema中的表。不同客户的数据就可以存放在不同的schema中。这样就能用同一个数据源或连接池,只是在所有的表名前要根据当前用户加上合适的shcema名字。如果要程序员自己这么做当然是很麻烦的。但如果用Hibernate就方便了。因为Hibernate中可以配制default schema,Hibernate在生成SQL时会自动在表名前加上schema名字。 因此如果使用Hibernate实现该方案就需要多个SessionFactory,每个客户对应一个SessionFactory。除了default schema以外,这些SessionFactory的配置完全一样。当程序需要SessionFactory的时候,需要有一个分配程序根据当前用户选择合适的SessionFactory。这些SessionFactory可以在系统启动时根据配制文件全部建立好,也可以采取lazy initialize的方法,这样也能支持动态增加客户。

    优点:除了方案2的优点以外,共享数据源或连接池,效率更高。

    缺点:对实现手段有一定依赖性。使用Hibernate会比较容易实现,其它方式我不清楚。每个SessionFactory都会有一定开销。多个SessionFactory会增加这部分开销,增加到多少程度,对性能有多少影响还有待测试。多个SessionFactory的情况下,二级缓存会否互相干扰,还是每个SessionFactory有各自自己的二级缓存也有待测试。

    ------------------------------------

    所有以上方案都是所有客户共享同一个应用(WAR或EAR)。这种方式有一个缺点,不过这个缺点和数据源无关。如果用户可以以点菜式的方式选购不同功能,也就是说虽然不同客户共享一个应用,但是提供给他们的功能是不同的。这种情况下,程序中会有很多地方要判断用户是否可以使用某功能。这会带来另一种麻烦。这个和权限控制有类似,也许可以用Acegi之类的框架解决。但和权限控制又不完全一样。因为有时简单地提示用户他无权使用某功能可能不够友好,而需要以一种更优雅的方式提供用户降级过的系统功能。当然这个和具体应用有关,这里就不展开了。

    ------------------------------------

    方案4:多套表,多应用 这个方案和以上方案不同,除了数据物理分离以外,应用也物理分离。每个客户有各自自己的WAR或EAR。如果使用方案3中的多Schema的方法,那么数据源可以共享,每个应用的SessionFactory有不同的default schema。

    优点:应用简单。这样的应用和普通的J2EE应用没有任何区别。支持高度定制化的系统功能。每个应用基本相同,又可以有很大差别。比如用户没有选择的功能更本就不部署。

    缺点:应用服务器中每个应用都会有一定的开销,占用一定的固定的内存。这个开销来自于应用服务器管理应用的数据结构;每个应用的class loader,和读入的类的字节码(应用服务器会判断出不同应用的同样名字的类具有同样的字节码,从而只保存一份吗?对次我表示高度怀疑);每个应用还可能会有自己的线程,比如任务调度线程。这些开销可能会使一个应用服务器能部署的应用不会太多(和ASP模式的潜在客户数比较),从而在客户数比较大的情况下需要增加应用服务器的数目。

    September 17

    东京船舶科学博物馆的照片

    已上传。大量船模的照片。
    September 14

    终于装好MacOS了

    终于在我的Dell笔记本上装好MacOSX了。虽然MacOS不认识我的Intel Wireless 2200的无线网卡,Radeon 9000的显卡也无法支持CI和QE,不过总的来说还算不错。MacOS的启动速度比Windows XP快了很多。界面美观,字体平滑,使用方便,等等等等,优点还是不少的。就应用软件方面来说, Java的开发工具自然是没有问题的。NeoOffice对Office文档的支持也不错。 通过fink可以装很多Unix下的opensource软件, 毕竟MacOS的系统服务层是FreeBSD的嘛。

    这个Windows Live在Safari下的表现还算凑合,虽然标题区的高度显然不对,居然占了三分之二的屏幕。不过既然你能看到这篇文章,那说明至少写文章是没有问题的。

    目前最不爽的是当时买笔记本的时候为了降低成本,买了XGA的显示器,分辨率最高只有1024*768,结果界面上的字都显得有些大,能显示的内容显得有些少⋯⋯到底还是一分价钱一分货啊。

    August 04

    太难看了。

    今天才看到这里改版了,而且改名叫Windows Live Spaces了。可是,这是什么呀?!上面的Banner广告和硕大的Search框居然要占掉三分之一的页面,太过分了!而且这部分还属于不可定制的部分。这个界面大概也可以入选最差界面了。
     
    今天在杂志上看到了Windows Vista的界面,好像和XP比也没有什么突破性的改进。就这样,据说对机器硬件的要求还大幅增加了。心中的天平又向Mac倾斜了一分。
    July 30

    新宿エイサー祭

    周六去新宿,原打算在新宿吃了午饭,然后转道去新宿御苑。开始出了新宿站东口,发现马路被人群围了起来,车也不让走了。开始还不知道除了什么事,我的第一念头是“是不是有人集会游行啊。”不过当时肚子饿,急于找地方吃饭,就没挤进人群去看。等吃完饭,沿着那条路往新宿御苑走的时候,发现有趣的事情了。

    有身着日本传统服装的人排着队站在马路中间,或身背着,或手拿着大小不一的鼓。这是要表演啊。再看他们的衣服背上印着“硫球”的字样。“哦,这是来自硫球的民间艺术表演吧?。”这时又发现路边的电线杆上挂着旗帜,写着“新宿エイサー祭”。“祭”基本就相当于某某节日的意思,就像我们的什么“桃花节”,“桂花节”什么的。不过“エイサー”是什么意思,我就不知道了。管他呢,看着队伍都排好了,应该快开始表演了吧。果然,等了一会儿,音乐响起,演员们也开始随着音乐一边舞蹈,一边敲鼓,不时地还会随着音乐吆喝几句。看了一会,拍了些照片,继续往前走,看到前面隔了一段距离还有一队人也在表演。穿的衣服不一样,动作却差不多。(照片见我的相册。)

    表演大概进行了有10分钟左右,音乐停了,演员们也都散了。“就那么简单啊————”不过,旗帜上写了是从1:30开始的,当时已经快三点了,估计我是看了个尾巴。好吧,继续向御苑前进。

    等到御苑里游逛了一个半小时再回到新宿————“表演还在继续啊。” 这时我才明白,原来是有不同地方出的队伍轮流来表演,而且一条大街上分了四块,也就是同时有来自四个地方的队伍在表演。不过表演的内容都差不多,都是一边敲鼓一边跳舞,然后再叫几声。每支队伍头上有个人舞动一面大旗,还有个人脸上涂着油彩,戴着奇怪的高帽子,从装束看像是拌鬼的,从动作看又有点像是指挥。也许这个舞蹈本来是一种驱鬼的仪式?呵呵,瞎猜,瞎猜。

    演员肯定都是业余的,年纪大的我看能有四,五十岁,不过跳起来还是很有劲的样子。我看到的最小的一个只有3岁的样子,当然对她来说,与其说是表演,不如说是在队伍边上走来走去而已。不过有几个小学生模样的,跳起来倒也是煞有介事。很可惜,此时我的照相机没电了,所以没法拍更多的照片了。

    回家以后查了一下,原来“エイサー”是源自硫球的一种民间舞蹈,是专门在“盂兰盆节”的时候跳的。在日本“盂兰盆节”是非常重要的一个节日,有点像中国的清明节,也是一个祭祀祖先的日子。“盂兰盆节”是在每年阴历的7月15日(日本的阴历和中国的一样)。据说这一天以前要把祖先的灵魂请回家,和活人一起生活4天,然后再以送魂火的形式送回阴间。日本公司一般会在8月中旬放一周左右的假,也是因为这个节日的缘故。虽然我们中国人不过这个节日,不过我还是很盼着它快点来,因为放假了我就可以回家了。

     

    July 15

    行在东京

    在东京,“电车”是最重要的交通工具了。日语中所说的电车,其实就是我们所说的地铁、轻轨和电气火车。事实上在日本,这三者也确实是连成一个网络,无法明显区分的。
    日本电车是由多家公司运营的,其中最大和最重要的就是JR了。JR原来是国营公司,后来私有化的。JR的线路遍布全国,是全国性的公司。著名的新干线就是属于JR的。在东京,属于JR的线路并不多。不过其中有一条非常重要的线路,就是山手线。山手线是环状线,串起了东京市内最重要的商业区和繁华地带。象东京站,秋叶原,上野,池袋,新宿,涩谷,这些地方就算是没有去过日本的人也都听说过吧。山手线以内称之为“都心”,这个就有点像上海“内环线以内”的概念了。事实上。论长度的话,山手线大约介于上海的内环线和中环线之间。
    在“山手线”以内的主要是“东京地铁”的九条地铁,和“都营地下铁”的四条线路,这些基本都是在地下的。另外JR除了山手线以外,还有一条中央线东西方向穿过都心,这两条线路都是高架的。这样,在“都心”内,电车线路真可用密如蛛网来形容了。
    在山手线以外的东京地区以及周围的神奈川,千叶,茨城三个县,则是由JR和其它私营电车公司的线路构成的网络覆盖。从我手头一本东京及周边地区的地图册上的路线图来看,这些公司就包括了京浜急行,东京急行,小田急,京王,西武,东武,京成等及家。这些公司的线路之间,以及和JR,东京地铁之间全部是连通的,而且互相之间很少有路线重复的情况。说明虽然是不同的公司建设,运营,但还是服从一定的统一规划的。
    由于不同公司的线路是连通的,所以电车就在不同公司的线路之间直通运行。比如我原来上班做的车就是从长津田发成,先走东急(东京急行)的线路,然后到涩谷转为地铁的半藏门线,穿过都心到了北千住以后又直通到东武铁道的线路上(当然我没有坐那么远),这个过程中是完全不需要下车换乘的。不过不同的班次会有不同的终点站,不是每一班都会有那么长的线路的。
    就象中国的火车分快车慢车一样,日本的电车也分“各停”————就是每站都停,“急行”————只停大站,“快急”————停的站更少,“通勤准急”————只在上下班高峰期开行(有点象上海公交车里的高峰车)等几种。复杂的线路网络,加上复杂的时刻表,就是日本人常常会搞不清楚路线情况。比如我这次搬家以后,老板问我搬到哪里了,我说了一个地名,他茫然地看着我,显然是不知道那个地方。我说在XX线路的XX站附近,他的眼神仍然很茫然。直到我说离莆田(知道电影“莆田进行曲”吗?)只差一站路,总算是“哦————”了。因此,在日本,有很多提供电车线路查询服务的网站。你只要输入起点和终点,以及计划出行的时间,就能查出应该坐什么线路的车,在什么地方需要换乘,需要多少时间,票价是多少。而且可以给出几条不同的线路组合供选择。
    在每个车站都有经过列车的时刻表。据我的观察,还是非常准确的。正常运行的情况下,误差一般不会超过半分钟。应该说,他们的调度控制系统还是非常出色的。
    为什么我要强调“正常运行”的情况下呢?因为给我的感觉,电车还是三天两头会出事的。
    基本上在每个电车站都会有显示屏用来显示行车信息。如果某条线路出现问题,列车不能按时刻表运行,那么其它线路(我不清楚是所有线路,还是只是相关线路)的显示屏上就会显示出来:几点几分,什么线路出现了什么问题,列车延误,现在处于什么状况。这个主要是提醒原打算坐那条线路的人根据情况改变路线。一搬来说也都不是什么大事故,比如有人进入线路啦,人身事故啦(不清楚具体是什么样的人身事故),设备点检(怀疑就是设备故障,否则哪有上班高峰时候点检的。)啦等等。当然也有比较少见的,比如有一次是说风太大(冬天),导致“东西线”(高架的)车辆必须减速运行。另一次说是某条线因为工人罢工,导致全线停运。
    日本人坐车还是比较讲秩序的,不会下车的人还没下完,上车的就一拥而上。一般也不太会抢位子,甚至会有位子空着,没有人去坐的情况。不过日本人似乎也不大会给需要照顾的人让座。有一次,上来一个抱小孩的,周围就没有人让座。当时我坐着,因为马上要下车了,所以就起来向门边走,但是那个抱小孩的也不去坐我空出来的位子。到了车站,下了车以后我回头看,那个位子已经被别人坐了,而那个抱小孩的依然还站着。这样的情况碰到过不止一次,不知道日本人都是怎样的想法。
    大部分日本人上下班都是坐电车。有很多人家里有车,但那只是周末或假日开出去玩的,上下班仍然坐电车。
    我原来住的地方,上下班单程要一个半小时,中间要换三次车。虽然日本同事听了都会说“好远,好远”,不过我知道这样的情况在日本人中也是挺普遍的。因为我发现有很多人是和我走一样的路线,但是坐得比我更远的。
    不过我既然是租房子,自然就有比较大的自由度。所以我后来换了一套比较近一点的房子,现在每天坐公共汽车上下班了。公共汽车的情况就下次再说吧。
     
     

     
    July 12

    Hibernate: Association or Not Association

    在使用Hibernate的时候,要不要使用Hibernate提供的对Association关系映射的功能,也就是要不要使用one-to-one, one-to-many,以及Collectionde映射呢?我就见过这样的项目,所有的持久化类都是孤立的,只在类中存放外键,one-to-one,one-to-many, set之类的一概不用,并且这一条作为该项目的设计准则。
    我以为,对Association关系的支持是ORMapping框架提供的重要功能。把该功能放着不用,犹如买了彩电,却只看黑白老电影。
    Hibernate对Association关系的支持提供了两个最大的好处:
    1. 易于表现和维护复杂的对象关系。
    2. 对复杂业务逻辑的表达更简洁,更容易。因为复杂业务逻辑一般都是需要多个互相关联的对象协作完成的。
    比如有这样两个类:
    class A {
        private B b;
       
        public B getB () {
            b;
        }
    }
    class B{
    ...
    }
    根据业务逻辑的需要,A当中完全可以有这样的方法:
    public void foo () {
        b.bla ();
    }
    这样,对A外部的对象来说,无论是这样的代码:
        A a = ... // get a
        a.getB().method();
    或者是:
        a.foo ();
    由于Hibernate对Association关系的透明支持,我们都不需要关心A中的B是怎么拿到的。
    而如果不用Association关系,A就得是这个样子的:
    class A {
        private Long bId;
       
        public Long getBId() {  return bId; }
    }
    此时,原来的A.foo已经无法实现了。因为不依赖Dao的话,在A当中已经拿不到B的实例了。因此只能在业务逻辑层中写:
        A a = ... // get a
        B b = bDao.findByPK (a.getBId());
        b.bla ();
    这还不是最糟的。
    如果B是这样的(使用Association):
    class B {
        private Set<A> as;
       
        public Set<A> getAs () { return as; }
        public void method () {
            // do something on as
        }
    }
       
    如果不用Association,那么方法method就只能实现在业务逻辑层中了:
        Set<A> as = aDao.findAsByBId (bId);
        // do something on as.
       
        显然,因为不用Association, aDao.findAsByBId这样的方法被额外增加出来了。而且本来从逻辑上来说应该属于实体类的方法,现在也必须放在业务逻辑层中了。
       
        Hibernate对Association关系的透明支持提供的其它好处还包括:
    1. 在多对多关系的情况下,程序员无需维护关系表。
    2. 聚合关系的级联删除。
    3. 使HQL更加简单,清晰。否则,还要用HQL做出多表连接来。
    当然,事无绝对。什么时候不应该用Association呢,我以为有以下几种情况:
    1.  只是记录,以后不会再用到Association关系的。比如存放在数据库中的日志。
    2. 比如上面的例子中,B可能对应的A的数量特别大,此时在B中就可以不建Set<A> as这样的成员变量。
    3. 有性能测试数据表明,只有在实体类中存放外键才能达到性能要求。注意,是需要有性能测试数据支持这个理由,而不是靠猜的。
    除以上情况以外,我以为都应该根据对象模型使用Association关系。
    July 11

    Prefactoring 5.4 使用文本进行通信(Communicate with Text)

        文本是在系统间进行通信的一种极好的手段。你不需要考虑系统间的同质性。基本数据类型,比如double,的物理表示方式在两个系统间可能是不同的。文本是一种通用语,任何系统都可以把基本数据类型转换为文本。文本可以是有无格式的,也可以是有格式的,比如“用逗号分割的文件”或者XML。另外,文本形式可以被测试人员读懂和创建,以适应依赖数据的测试[*]。
       
        [*]参见《UNIX编程艺术》,作者Eric S. Raymond (Addison-Wesley Professional, 2003) for discussion on text
       
        但在系统内部的文本就是另一会事了。当字符串被读入程序后,它们应该尽快被转换成相应的数据类型。通过这样的转换,可以把针对用户和其它程序的信息的外部表现形式与同一信息的内部使用方法区别开来。如果在外部表现中有错误,失败就会发生在翻译过程中,而不是被推迟到你试图去处理数据的时候。
       
        在前一节中,我们使用枚举类型(enumeration)来定义CDCategories。如果CDCategory的所有可能取值保存在配置文件中,那么它们应该以字符串的形式保存在文件中。从文件中读入数据的时候,字符串值应该被转换为相应的枚举值。如果输入数据拼写错误,或者与任何枚举值都不匹配,(输入程序)应该立刻报告错误,而不是等到以后再来试图处理。
       
        创建枚举数据类型必须依赖于编程语言。如果一种语言不支持枚举类型,那么通过创建包含对应不同枚举值的静态成员的类可以达到同样的效果。这些值与字符串之间的相互转换可以是这个类的一部分。即使一种语言支持枚举类型,那么创建包含此类转换函数的类也是值得的。例如:
       
        class CDCategory
        {
            static String string_values [] = {
                "NewRelease", "GoldenOldie", "Regular"};
            static int corresponding_values [] = {
                 NEW_RELEASE_CD, REGULAR_CD, GOLDIE_OLDIE_CD}
            static int NEW_RELEASE_CD = 0;
            static int GOLDIE_OLDIE_CD = 1;
            static int REGULAR_CD = 2;
            String to_string(int category)
            {
                return string_values[category];
            }
            int from_string(String a_string) throws BadValueException
            {
                for (int i = 0; i < string_values.length; i++)
                {
                    if (a_string.equals(string_values[i]))
                        return corresponding_values[i];
                }
                    throw new BadValueException(  );
            }
        }
    译者:Java程序员有福了,终于在JDK 1.5中有了枚举类型。
     
    指导原则:使用文本还是不使用文本 (To Text or Not to Text)
    只在程序之间使用文本,而不要在程序内部使用。
     
     
    译者注:之所以翻译此篇文章,是因为看到公司里一些程序员喜欢在函数之间用字符串来传递一些标志,选项之类的参数。有些明显属于“是/否”开关的参数也使用字符串,而不是使用boolean。所以有针对性地翻译了这一节。
       
     
    July 10

    Prefactoring节译 2.6 抽象

    (所有关键字,类名和其它标识符保持原样不翻译)
     
        在创建一个UseCase的描述或者一个可能的类的模型时,应该避免使用基本数据类型,装作int和double之类的不存在。几乎所有数字的类型都可以使用“抽象数据类型”(ADT)来描述。某样东西的价格是Dollar型的(如果你希望面向全球的话,也可以是CurrencyUnit)。仓库中某类物品的数量是Count型的。优质客户能享受的折扣可以表示为Percentage类型。CDDisc的尺寸可以被表示成Length型(也可以是LengthInMeters或者LengthInInches,如果你准备把卫星送入太空的话)。CDRelease上的一首歌的长度可以被存储为TimePeriod类型。
        使用ADT可以使注意力集中在“可以对这个类型做什么”上,而不是“如何表示这个类”。一个ADT说明你想要对变量做什么。你可以把一个变量声明为基本数据类型,而通过变量命名来反映这种意图。但是被声明为抽象数据类型的变量可以拥有内置的验证逻辑,这是声明为基本数据类型的变量所做不到的。
        每一个ADT都需要相关的描述。例如,一个Count表示物品的数量。Count可以是0或者正数。如果一个Count是负数,则代表一个不合法的数量。将一个变量声明为Count能够传达这个信息。你可以创建Count的变种。比如CountWithLimit类型可以有一个上限,如果超过这个上限则会触发一个错误。
        你可以将限制施加于不同的数据类型上。比如(人类的)Ages的范围是0到150岁;(机动车的)SpeedLimits在5到80英里/小时之间;(正常飞行)的Elevations(高度)[*]在0到60,000英尺之间。所有这些类型都可以表示为一个int或者double,但这是实现层面的事情,不是抽象的问题(issue)。
       
        [*] 以前,蒙大拿州除了“合理的速度”以外没有其它速度限制,速度的上限仍然可以是一个合理的数值(比如200)。
       
        抽象类型能包含的信息不局限于验证规则。价格可以表示为Dollar。Dollar的字符串表示形式和double的字符串表示形式是不一样的。Dollar的字符串表示至少包含一个货币符号,可能还包含千位分隔符(比如逗号)。一个Dollar和一个数字相乘会得到一个带分币的Dollar,但分币数不会是分数。这里是一个可能的Dollar类:
        
        class Dollar
            {
            Dollar multiply_with_rounding(double multiplier);
            Dollar add(Dollar another_dollar);
            Dollar subtract(Dollar another_dollar);
            String to_string(  );
            };
     
        如果你使用的语言能够为类定义操作符(比如+和-),你可以为操作使用合适的数学运算符号。你也可以使用合适的方法名使一个Dollar在合适的上下文中自动转换成一个String。你如何表示用于某个数据的抽象数据类型属于实现细节。你可以为每一种类型创建一个类。如果你使用C++,你可以用typedef来定义没有额外功能的简单的(抽象数据)类型。对其它语言来说,你可以把这些简单的(抽象数据)类型转换为基本数据类型。这种情况下,你也可以使变量名中包含类型名(比如double price_in_dollars)。
     
    指导方针:如果你要抽象,就要抽象到底。
    不要使用基本数据类型描述数据项。
     
        这个指导方针建议使用明确(explicit)的类型来描述问题以及解决方案。通过从一开始就使用抽象数据类型,你可以有效地获得更高的抽象度。如果你显示地表示出所有参数和属性的类型,那么当这种显示类型带来妨碍的时候,你总是可以将他们转换为隐式的类型。但要反过来做就要困难得多了[**]。
       
        [**]这里是一篇关于使用在隐式数据类型(动态数据类型?)的语言中有力地进行测试(strong testing)的文章:http://www.artima.com/weblogs/viewpost.jsp?thread=4639
       
    2.6.1 不仅仅是String
        很多原来使用String表示的数据类型都可以表示为更富有表现力的数据类型。例如,虽然姓名可以被声明为String,但你也可以将其声明为组合数据对象:
     
        class Name
            {
            String first_name;
            String last_name;
            String title;          // e.g. Mr. Mrs.
            String suffix;      // e.g. Jr. III
            };
     
        为了避免“任何东西都是字符串”综合症,为那些由字符组成,但不包含验证、格式和其他元信息的变量起一个不同的类型名。例如CommonString。用这个名字代替String来描述属性的数据类型,而把String只用于实现时候的类型。然后问自己“这个属性真的是一个CommonString吗?”
    让我们重新来看一下Address类。使用CommonString,我们可以把这个类描述为:
     
       class Address
            {
            CommonString line1;
            CommonString line2;
            CommonString city;
            CommonString state;
            CommonString zipcode;
            }
     
        CommonString可以包含任何字符,就好像int可以表示任何整数值(只有硬件限制)。在Address中,一些字段绝对不是CommonString。一个state (州)不是CommonString。只有一些特定的值可以表示合法的州。对美国邮递地址来说,如果我们使用缩写来表示州,则必须使用“美国邮政服务正式缩写表中”的缩写。所以state应该被声明为类型State。这个类型可以提供合适的验证机制,它可以检查一个字符串是否包含在正式缩写表中,或者提供可以用于下拉式列表框的所有合法缩写的列表。
    美国邮政编码也不仅仅是一个CommonString。你可以将它描述为NumericString类型(全部由数字组成的字符串),或者是FormattedString类型(由5个数字加破折号再加4个数字组成),或者ZipCode类型。如果任何数字的组合都是合法的,那么使用NumbericString或者FormattedString也许是合适的。但是把一个属性声明为ZipCode类型允许我们抽象出它的实际表现形式。
        不要简单地因为实现的目的把类合并起来。你可以把SocialSecurityNumber和PhoneNumber都定义为由数字和两个破折号组成的字符串。但这并不表明他们是等价的东西,这只是偶然内聚。它们是具有不同验证规则的完全不相同的两个类。在美国电话号码不能由1或0开始。某些范围的社会保险号码是不能使用的。这些数字可以用同样类型的格式化字符串来输入和显示,但是每个类的内在语义是完全不同的。你永远也不能用一个社会保险号来拨电话,也不能用一个电话号码来记录收入税。(好吧,也许有一天有人能举出一个反例,所以我永远不能说“永不”。)
        很多也许是CommonString的数据也可以被赋予自己的数据类型。例如文件名一般是字符串,但是不能包含一些特定的字符。在Windows中,文件名中不能包含 \, /, :, *, ", ?, <, >, or |。FileName类型可以表示一个文件名,同时强制执行这种限制。使用抽象数据类型的某种好处在GUI的开发中就更加明显了。例如,界面代码可以识别FileName类型,并自动在一个文本框的边上插入一个“浏览”按钮。
        在Web中,HTTP GET和PUT命令的参数值是编码过的字符串。除了数字和字母的字符都用它们的16进制值编码。编码后的字符串发送给Web服务器。虽然未编码的字符串和未编码的字符串都可以实现为字符串,但它们是不同的。你可能会得到不合法的编码字符串——包含未编码的标点符号,就象一个黑客可能会发送给服务器的那样。你可以在服务器中使用EncodedWebString类表示字符串。如果一个输入不能通过EncodedWebString的验证,它将被抛弃。
     
    指导方针:很多字符串都不仅仅是字符串。
    把String当成基本数据类型。用抽象数据类型描述属性,而不用String。
     
     
     

    石币

        照片中的那块圆形带孔的石头摆放在日比谷公园的一处花坛边。另一张照片是对这块石头的说明。翻译如下:
        这块圆形的石头是太平洋Yap岛(现属于密克罗尼西亚联邦)上当钱用的石头货币,被称之为“石币”。石币小的直径6cm左右,大的可以达到直径3m。
        一般来说,直径大小,表面光滑程度,形状是否好看以及搬运的难易程度决定了石币的价值。
        这块石币呈长径1.35m,短径1.00m的椭圆形。据说在大正13年(1924年)的时候,这块石币可以相当于1000日元使用。
    July 09

    一道面试题的分析(续)

    前面的讨论中留下了一个问题,还没有分析完。就是表示“币种”的数据类型。因此继续讨论的话就要超出了面试题目的范围,所以另开一文。

    如果对币种信息的要求只是为了区别不同Money对象所代表的货币种类,以保证只有相同币种的Money对象才能进行计算,那么无论是String, int还是enum都够了。但是,在一个稍大一点的项目中,要求可能都不会仅限于此。比如需要在显示金额的时候能显示正确的货币符号,或者一些系统中还需要显示3位的币种代码,比如用USD表示美元,RMB表示人民币。在有用户输入或者和其它系统交互的情况下,还会需要判断输入的数据是否是合法的,系统支持的币种。有这些需求的情况下,自然的,我们需要一个单独的类Currency。至于在Currency内部使用具体什么类型,参见前一篇中的讨论。

    因此,Money类应该是这个样子的:

    public class Money {
        private Currency currency;
        private BigDecimal amount;
       
        public Money (Currency aCurrency, BigDecimal aAmount) {
            ...
        }
       
        public Money add (Money aMoney)
        throws IncompatibleCurrencyException {
            ...
        }
       
        public Money subtract (Money aMoney)
        throws IncompatibleCurrencyException {
            ...
        }
    }

    如果系统中需要由用户来维护系统所能支持的币种,那么一般需要在数据库中维护一张表,此时Currency就是一个可持久化类了。

    关于这个设计问题,还请参看我前面翻译的那篇“Prefactoring”的2.6节。虽然那篇文章主要讨论的是分析和领域建模阶段,但是很多讨论对于设计阶段仍然是有效的。

     

    一道面试题的分析

    这是我为公司设计的一道Java语言的面试题……嗯……也可以说是两道啦:
    写一个Money类,包含金额和币种两个属性,以及加、减两个方法。
    附加题:将上题中的Money类实现为一个ValueObject。
     
    先来看本题。
    第一个要考察的方面是为两个属性选择正确的数据类型。对金额而言,最自然的选择是float或者double。但这不是最好的选择。在计算机中,float和double都是有误差的。比如1有可能是1.00000001。所以对金钱这么重要而敏感的东西来说,还是BigDecimal比较好。当然,对于后面的加减运算来说比较麻烦一些。这时还是让人比较怀念C++的运算符重载的。
    用int怎么样呢?如果用int来表示以分为单位的金额的话,到也不是不可以。这样精度有保证,而且运算也比较方便。不过如果要表示5厘……就没办法了。
    对币种来说,很多人选择了String。不过我以为String不如int,或者short。虽然在Java中,String几乎可以当成基本数据类型使用,但是String毕竟是对象,还是经常要考虑null不null的问题,而且字符串匹配总是不如int(或short)方便和快捷。
    到了JDK 1.5的时代,我们又多了一个选择:enum。不过如果是在一个信息系统中,货币种类是可以由用户维护的话,那么enum就不能用了。
    除此以外,从OO的角度来说,还有更好的设计吗?有。不过这个问题我留到下一篇讨论。
     
    然后是方法的设计。因为有币种,因此加、减时自然都要考虑同币种的金额才能相加(暂时不考虑汇率换算的问题)。如果币种不相同怎么办?这就是考察的第二个重点: 错误处理。
    有不少人使用返回值表示计算的成功与否。比较奇怪的是他们大部分都没有C,C++程序员的经历,不知道是不是学校教育的结果。也有人直接使用System.out.println输出错误信息,这个就更离谱了。
    正解应该是使用Exception。偷懒的话,可以使用IllegalArgumentException。如果知道自己定义一个Exception,比如IncompatibleCurrencyException,那如果是我面试的话,这样的人技术上基本就算过关了。
    所以两个方法的原型应该是:
        public void add (Money operand)
        throws IncompatibleCurrencyException
       
        public void subtract (Money operand)
        throws IncopatibleCurrencyExcepiton
     
    如果使用BigDecimal表示金额,那么这里还有一个BigDecimal的加减方法的名称的问题。不过这倒不重要。写程序的时候总有API可以查的。
    对于参数是否可以为null,有两种选择。一种是参数为null时,什么也不做。另一种是抛出异常,比如NullPointerException。我认为第二种选择为好。大部分情况下,用null做参数调用add和subtract方法恐怕不是程序员的本意,而是其它程序出错的结果。直接抛出异常有助于及早发现其它部分的错误。
    除了属性和加,减方法以外,构造函数以及getter/setter函数虽然没有提到,但作为一个完整的类,还是应该要有的。从健壮性的角度考虑,在构造函数中应该对输入参数有一定判断。比如金额不能为null等。
     
    接下来是附加题。什么是ValueObject。简单说,ValueObject的值(状态)一旦创建以后就不会改变了。所以可以当基本数据类型用。比如Java中的String, BigDecimal, BigInteger都是ValueObject。StringBuffer就不是。
    要把Money变成ValueObject,首先不能有setter。构造函数是从类外部设定属性值的唯一途径。
    其次,add和subtract方法不能修改this的属性,运算结果要以新的Money对象返回,这样其原型就要变成:
        public Money add (Money operand)
        throws IncompatibleCurrencyException
       
        public Money subtract (Money operand)
        throws IncopatibleCurrencyExcepiton

    第三,最好是能重新定义equals和hashCode两个方法。如何重新定义,请看Pearson Education的《Effective Java Programming Language Guide》7,8两章。
     
    遗憾的是,到目前为止能全部达到以上要求的应聘者还没有出现过。更不用说我心中一个非常不现实的奢望了,就是看到一个因聘者首先写下这一行:
    public class MoneyTest extends TestCase {
    ......

    Hibnerate Annotation使用注意事项


    在以前,我们在Java源代码中使用特殊的JavaDOC标签定义ORMapping规则,然后使用xDoclet生成映射规则文件(.hbm.xml)文件。现在有了Hibernate Annotation,连映射规则文件也不需要了,使用更加方便了。这里说说使用Hibernate Annotation时需要注意的一些地方。这些内容分散在Hiernate Annotation Reference文档和example中,与spring相关的部分则出现在Spring的文档中,这里把它们整理在一起。对我自己来说是起到备网的作用,对于其它Hibernate Annotation的用户,系统也能起到帮助查询的作用。
     
    1. AnnotationConfiguration及配置映射规则。
    在没有使用Hibnerate Annotation的使用,我们一般是使用org.hibernate.cfg.Configuration来配置和生成SessionFactory,使用Hibernate Annotation以后,要使用org.hibernate.cfg.AnnotationConfiguration类。该类在hibernate-annotation.jar,而不是hibernate3.jar中。
    如果使用Spring + Hibernate的架构,要在Spring的配置文件里配置LocalSessionFactoryBean的时候,要记得定义property configurationClass。对于使用Annotation定义映射规则的类,在hibernate.cfg.xml文件中不能使用<mapping resource="..." />的形式,而要使用<mapping class="..." />的形式。LocalSessionFactoryBean的mappingResources不能调用AnnotationConfiguration的addClass和addPackage方法,因此使用Annotation定义映射规则的类,仍然要通过hibernate.cfg.xml文件来使AnnotationCongiguration载入它们的映射规则。使用Spring + Hibernate + Hibernate Annotation的情况下,LocalSessionFactoryBean的定义一般应该是:
        <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
             autowire="no"  >
            <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
            <property name="dataSource" ref="myDataSource"/>
            <property name="configLocation" value="classpath:hibernate.cfg.xml" />
        </bean>
     
    2. <mapping package="..." /> 与<mapping class="..." />
    前一种形式只是载入制定包中package-info.java文件中定义的Annotation,而该包下的所有持久化类仍然需要通过后一种形式逐一载入它们的映射规则。
     
    3. 使用sequence生成id
    使用sequence生成id时,id属性的Annotation应该是:
        @Id
        @GeneratedValue (strategy=GenerationType.SEQUENCE, generator="SEQ_BOOK_ID")
    需要注意的是,GeneratedValue中的generator不是sequence的名字,而是一个另外定义的SequenceGenerator的名字。这个SequenceGenerator应该定义在类的级别,因此比较完整的代码是想这个样子的:
    @Entity
    @AccessType("property")
    @Table(name="BOOK")
    @SequenceGenerator (name="SEQ_BOOK_ID", sequenceName="SEQ_BOOK_ID")
    public class Book {
        private Long id;
       
        @Id
        @GeneratedValue (strategy=GenerationType.SEQUENCE, generator="SEQ_BOOK_ID")
        public Long getId() {
            return id;
        }
        ......
    }   
     
    4. AccessType
    如果要Hibnerate使用get/set方法存取属性的话,一定要在类级别加上:
    @AccessType("property")

    5. NamedNativeQuery
    使用NamedNativeQuery定义native SQL query的时候,即使只返回一个字段,也需要定义SqlResultSetMapping,见下面的例子:
    @SqlResultSetMapping(name="keyWords", columns=@ColumnResult (name="key_word"))
    @NamedNativeQuery (name="listKeyWords",
                       query="select distinct key_word from key_words order by key_word",
                       resultSetMapping="keyWords")
     
     
    先写这些,以后再补充。

    在中国推广敏捷方法的难点

    我从2000年开始接触xp,之后看了不少敏捷方法的书,曾试图在两家公司推广敏捷方法,但效果一直不好。我认为在中国推广敏捷方法存在两个仅靠软件公司和技术人员难以克服的困难。
    第一个困难主要存在项目范围、进度、成本三个变量的弹性程度上。
    所有的敏捷方法,无论是否明确提出,我认为都是要求项目范围、进度、成本是可协商的。拥抱变化意味着:
        1. 能随时接受需求变更,但同时也能对项目进度进行相应的调整。
        2. 在发现事先制定的项目进度不合理的情况下,能对项目进度进行调整,以完成规划的项目功能。 或者对项目功能范围进行调整,以满足事先制定的项目进度。
        也就是说,范围,进度,成本,三个变量,只少要有一个是可变的。而且,当范围或进度发生变化的情况下,成本要仍然保持不变,也是比较困难的。
    但目前国内签订的软件委托开发(或称项目外包合同)几乎都是闭口合同(在我所知的范围内)。在合同中都是明确规定项目的交付期限和开发费用的。虽然很多合同不会包含详细的需求规格说明书,或在合同签订的时候不会包含,但往往都会有一份技术附件,规定了系统必须提供的功能。在这个技术附件规定的范围内,对项目功能项的调整和协商的余地都不会是很大的。在范围,进度,成本限定的情况下,无疑地,拥抱变化就意味着拥抱死亡。而不能拥抱变化,那我认为敏捷也就无从谈起了。
    所以在签订闭口合同的情况下,软件公司的最佳选择就是尽早完成项目的详细需求分析,然后冻结需求,尽一切方法把用户的需求变更挡回去。这样就不能实现真正的迭代开发。最多只能在分析、设计、开发、测试阶段实现“走了样”的迭代开发。
    那么在企业的内部软件开发部门,情况是否会好一些呢。当然,因为是一个企业内,往往不会有硬性的合同规定,在范围、进度、成本这些方面,协商的余地必然会大一些。但情况也未必会好到哪里去。
    我曾经在一家国有大型企业下属的软件开发公司工作,该软件公司曾是母公司的内部软件开发部门,后来独立出来的。不过至今其主要业务仍然来自母公司。这种情况在国内是非常多见的。该软件公司无论在独立前,还是独立后,都有一个引以为自豪的口号:“后墙不倒”。“后墙”就是指项目的交付日期,这句口号的意思就是不惜一切代价保证项目按期交付,而不论这个最后期限是多么的不合理,或者根本就是领导拍脑袋的结果。
    “后墙不倒”,意味着项目最后期限是没有商量余地的。“后墙不倒”同时也隐含着项目范围也是不可商量的。因为不存在“为了满足最后期限,可以调整项目范围”的规定。 既然“后墙不倒”,那“后墙”以后的开发计划是不存在的,因此必须在最后期限交付事先规划的所有功能。把某些功能移到最后期限以后,也意味着对“后墙不倒”这一政策的违反。
    能不能对项目范围进行调整,以满足最后期限,则往往取决于项目经理的沟通能力,与项目主管或用户部门的关系,以及用户部门的“通情达理”度。
    对内部软件开发部门来说,项目成本方面可以考虑的比较少一些。因此如果项目进度紧,可以多扑一些人上去。但我们都知道,增加开发人员对缩短开发进度的帮助是有限的。
    就我所知,上述情况在很多企业的内部软件开发部门都是存在的。对于以承接母公司的软件开发项目为主的独立核算的软件公司来说,情况也差不多。毕竟,项目是否按期完成往往是作为领导和部门业绩考核,发放奖金的依据的。
    因此,对于很多企业的内部软件开发部门来说,要完全按照敏捷方法的要求去做,仍然是存在很多制约因素的。
    第二个困难在于程序员的素质。
    目前无论是正规大学还是职业培训结构培训出来的程序员,普遍缺少面向对象编成的基本概念和基本感觉。这里我说的,不是仅仅知道什么叫类,什么叫继承,而是要能写出基本象样的面向对象程序。而现在很多程序员,即使会用Java,C++,写出来的仍然是面向过程的程序。而现在,无论是从学校,还是从社会,要招到具有一定OOP基础的程序员实在太困难了。
    我认为OOP是敏捷的基础。没有OO的基本概念和基本感觉,是没有办法进行诸如TDD, Refactoring这些工作的。没有TDD, Refactoring,也就几乎不可能作到代码共享,短迭代。没有了短迭代,也就没有敏捷了。
    目前中国学校的计算机人才教育已经成为了不仅是敏捷方法的推广,也是中国软件行业发展的制约因素。

    HttpUnit使用心得

    1. 使用JavaScript
    日前,在一个项目中试用了一下HttpUnit。发现一些常用的程序写法,为了适应HttpUnit,必须做一些改变。特别是在JavaScript方面。

    例如,如果有如下的HTML Form:
    <form name="form1" action="xxx.do" method="POST">
    <input type="text" name="t" />
    ......
    </form>

    那么在JavaScript中通过以下的语句改变输入框“t”的值是非常方便的:
    form1.t="xxx";

    但是,这句语句在HttpUnit中是会出错的。HttpUnit不认识这样的语法。如果要使用HttpUnit,必须使用以下写法:
    document.forms[0].t="xxx"
    或是:
    document.forms["form1"].t="xxx";

    事实上,"form1.t='xxx'"是利用了IE对Javascript的扩展,这一扩展HttpUnit是不支持的。这就是问题的根源。因此如果要使用HttpUnit,就必须遵循“ECMA-262”标准。

    2. Submit form
    如果form中存在多个submit按钮(<input type="submit".....>),调用WebForm.Submit ()时具体触发的是哪个submit按钮是不确定的。这时最好是使用WebForm.submit (SubmitButton button)这个方法。SubmitButton可以通过WebForm.getSubmitButton方法得到。

    搬家

    最近访问CSDN上的Blog的速度总是很慢,而且发的文章居然在首页上显示不出来,管理功能也经常进不去。受不了了,决定把CSDN上的Blog也合并到这里来。