Technique
Last edited May 17, 2009
More by Yang »

RUP VS XP

                                                     2008-10-29   Yang Gu

序言

在软件开发过程中是应该采用RUP这样的重量级还是采用XP这样的敏捷开发呢?很显然,对于我来说,我更倾向于后者。可是,是不是擅长敏捷开发的开发人员就不适合RUP呢?No, 在我看来,恰恰相反,一般情况下,敏捷开发人员在RUP这样的重型开发过程中,表现的更好,更优异,更能够运用自如,驾轻就熟。由于主流的敏捷开发模型都尊崇敏捷软件开发的哲学理念,虽然他们之间还是有本质的区别,但接下去,我将以XP代替敏捷软件开发。

大家都知道RUP重文档和流程,其主要依靠文档和流程来控制进度,质量,以及降低后期的维护成本。而XP则容易让人觉得其不重视文档,甚至可能没有文档,只关注于源代码。

从我这些年的经验来看,我却认为,XP更注重文档,更注重流程。只不过其和RUP的文档角度不一样而已。XP基于这样的观点:创建有价值的,需要的文档,引 入有价值的,需要的流程,不拘泥于特定的规范约束,提供最大的灵活性以便以低成本高效率的创建软件系统,其更关注于人。而RUP是基于规范标准约束的角度,文档和流程,必须要有,尽管这些文档和流程也许当前不会带来多少好处,因为他们坚信这些文档和流程在开发阶段,后期维护或者写特定的场景会带来其绝对的价值。为什么这么说呢?

RUP

RUP 的开发模型中,需要什么样的文档,需要什么样的流程,都清清楚楚,如,需求分析,架构设计,详细设计,软件配置管理,单元测试,集成测试,功能测试,发 布,部署等等各个开发阶段,都必须具备相应的文档,这些文档的主要目的,就是增进各个开发人员之间的了解,方便后期的维护,先不管这些文档实际上到底有没 有用,但是,一定要有,因为,在创建这些文档之时,其实就已经假定它肯定有用的了,可是,遗憾的是,很多时候,偏偏这些文档并未带来预料之中的好处,恰恰 相反,耗费在这些文档的维护上的成本却远远的大于其所带来的好处,尤其是在设计文档和代码之间的同步。当然我并未忽略RUP中的迭代开发,但其每一个迭代 周期也是遵循上述的完整的规范。

XP

XP的开发模型中,我们认 为,各种文档和流程,当真正需要的时候我们才需要,换句话说,当我们发现某个文档或某一流程对当前工作来说的确需要,的确带来好处,或者很明显对以后的工 作有着显著的影响(根据以往的经验判断)的时候我们才认为真正的需要,此时我们才会创建这些文档或者引入这一流程。

架构设计

一个软件系统的架构设计很明显对整个系统的扩展性,伸缩性,性能等等非功能性需求有着重要的影响,因此,架构设计一开始就非常重要,因此,我们需要架构设计 文档,其可以帮助开发人员快速理解整个系统的高层结构,而且,通常情况下,由于架构设计文档描述的是系统的高层结构,子系统,以及关键部分的设计,因此, 其内容并不是很多,看完并理解这样的文档并不会花费多少时间。此外,一个系统的架构一旦确定,通常情况下也不容易轻易更改,因此,维护它也不会花多少成 本。所以,软件架构文档的确很值得,并且很需要。当然,如果你的系统很小,你也可以考虑不做架构设计(当然,架构设计其实存在于任何规模的系统,只不过, 在比较小的系统中,很可能只是存在于你的大脑中,仅仅是未形成文档而已)。XP并不要求你一定要做架构设计,也不要求你一定要有架构设计文档,不过,从很 多的实践经验中,XP建议对任何系统,有架构设计总会带来很多好处。

详细设计

在详细设计中,详细设计文档是很 重要的,这里面通常包括Class Diagram,Sequence Diagram,可能还包括State Diagram.因为详细设计的粒度比架构设计的粒度要细的多,一般会细到每个具体的类,以及具体的方法,因此,详细设计文档描述了很多细节,而且,这些 都可以很容易的对于到实现代码,这导致该文档通常情况下都很庞大,少则十几页,多则几十页,上百页。阅读这样的文档会比较耗时间,维护这样的文档更不容 易,尤其是在详细设计文档和代码之间保持同步更是需要不小的成本。如果很理想,你一次就设计的很成功,详细设计文档和代码之间不需要同步,那还可以接受, 可是,在软件开发中,偏偏你不能避免这样的事实:无论你经验再丰富,你也总是不能一次就成功,因为软件开发的细节本就千差万别。因此,你总是需要在文档和 代码之间经常进行同步。而且还有一个更重要的因素迫使你经常修改你的代码,那就是Refactoring,你需要经常持续不断的Refactor你的代 码,因为这又这样你才能确保你的代码不在腐化,而Refactoring又最容易改变代码之间的结构,这很容易导致和详细设计文档不一致。也就是说,不论 如何,你总是需要在详细设计,尤其是详细设计文档上花费比较可观的成本。RUP 采用这样的流程,尊崇这样的文档。

从敏捷开发的核心价值 可以工作的软件胜过面面俱到的文档可以看到,我们只创建有价值的东西。因此,在架构设计之后,不会耗费太多的精力在详细设计(应该说是详细设计文档 上),不是说没有详细设计,而仅仅是详细设计并未文档化,因为,在设计的同时,已经转化为代码,只是详细设计的过程就在于写代码之中,如结对变成(PP 之时,两个开发人员之间的讨论,这些讨论涉及到在详细设计文档中可能出现的Class DiagramSequence Diagram等,这些文档出现在记事本,白纸,白板之上,仅仅是未文档化而已。同样,如前所说,开发人员经常储蓄不断的Refactoring,由于没 有文档需要同步,因此,也就不会耗费那么多时间在文档的同步之上。当然,XP绝对不是说完全不需要文档,如果需要,你可以创建详细设计文档,比如,某个地 方的结构比较复杂,而且很重要,你也许应该把他纪录下来,不过,当你遇到这种情况时,你是否应该考虑其应该是架构设计的一部分。此外,事实上除了详细设计 文档之外,还有几份很重要的文档:

Test Case Suite

单元测试用例其本身就是一份很重要的文档,从一个详细的测试用例你可以看出一业务需求,可以看出一个接口的contract。在以前的经验中,当看某一个 方法很复杂,或者不知道某一个接口如何使用时,最终我总能通过运行和阅读test case 而弄明白。

JavaDoc文档

比如,你需要在javadoc中写清楚该接口或者方法的contract,包括前置条件,后置条件,业务逻辑等。

源代码

早在1992年,Jaxk W. Reeves博士就发表了非常著名的论文:What is Software Design? 并提出这样的观点,Code as Design. 十年后,经过多年的实践经验之后,XP也认为,源代码比那些设计文档重要的多。的确,不论如何,文档也只是辅助工具,其不可能取代源代码,而且,源代码是 唯一以份可以执行的文档。该文档最精确的表达当前软件系统的状态,绝无二义。从我的经验来看,我认为,开发人员更乐意看源代码,而不是文档。因为我就是一 名开发人员,我就是更乐意看源代码,而不是文档。当然,请注意,我说的是和源代码对应的那些需要耗掉你很大成本的那些不必要的详细设计文档。当然,如果代 码质量低下,可维护性差,注释不全,没有详尽的测试用例,那看详细设计文档显然要高效的多,这种情况想,详细设计及其文档明显比源代码自身重要的多。这也 就引出了一点,代码质量非常重要,尤其是可读性。

详细设计小结

上述三点最近的例子就是前段时间阅读的Spring Security Spring Batch两个开源项目,关于这两个项目的部分论述,请参阅后面。这两个开源项目整天情况是这样,首先会定义领域概念,领域需求,然后有一个比较高层的架 构设计文档,再加上一份领域模型,最后再加上一份User Manuel。然后其他的就是项目javadoc,source,unit test case ,functional test case,integration test case.我在熟悉领域概念和领域需求,架构设计以及领域模型之后,就开始阅读他的源代码,期间采用这样的方式:对于任何类和接口,先看javadoc 在看源代码,最后再运行test case 并看test case的源代码。javadoc和源代码是必读的,当通过javadoc文档已经很明了之时,看源代码之时,效率是相当高的,如果,通过javadoc 和源代码仍然未明白(可能有些算法或业务比较复杂,也可能是该部分代码质量较低,可读性叫差),则运行对应的测试用例,并阅读测试代码,最后总能弄明白。

从上面可以看出,XP不是不重视文档,其更重视文档,只是其重视更有效更重要的文档(java doc sourcetest case)而不是对于当前开发过程中优先级较低的详细设计文档。XP遵循只要需要,只要必要,无论什么文档,都可以去创建,并没有固定的规范说什么样的文 档必须有(当然,java doc sourcetest case肯定好似要有的了,而且必须高质量),因此,如果认为当前某一个文档(不局限于详细设计文档)很重要,那你随时可以考虑创建它。事实上,对于后期 维护人员和新的开发人员来说,详细设计文档的确可以高效的帮助理解系统,因此,我是建议在系统开发完成之后,比如某一个版本发布之后,可以考虑创建详细设 计文档,因为这个时候,需求已经不会再频繁的改变,代码同样不会再频繁的改变,你耗在文档维护上的成本会小很多,相比其带来的价值还是很值得的。

集成,发布,部署

RUP一样,XP 同样也很重视这些,但XP更重视简单,轻便,小巧,易用,自动化。

其他文档

XP中,对于开发人员来说,还有几份很重要的文档,包括测试报告,测试覆盖率报告,项目依赖报告等,当然这些报告文档也可能包括在RUP中。

RUPXP对开发人员的专业技术要求

从上面阐述可以看出RUPXP一个最大的区别:RUP依靠一致规范的流程来贯穿真个开发生命周期,而XP则以成本最小化,高效来实施。RUP重在标准,而XP则重在灵活,实际上在我看来,在某种角度来很所,XP对开发人员的专业技术要求可能更高:

1. XP中,你需要深厚的技术底蕴和丰富的经验,你才能在架构设计,详细设计,开发,集成,部署,发布等各个部分做到合理取舍,收放自如,你只有对软件开发 生命周期的各个阶段有很深的理解和掌握相关的技术,你才能很好的驾驭它。而对于RUP,也学你无需关注这些,也学你只要掌握了OOA/OOD/OOP等一 系列技术之后,你就可以遵循其已有的规范和流程而完成项目。对于XP,你除了必须掌握这些技术之外,你还必须要在各种各样的复杂场景中进行权衡以及折中。

2. 虽然RUPXP你都可以贯穿测试自动化,软件质量,持续集成(CI),重构(Refactoring),测试驱动开发(TDD)等等最佳实践,但你使用 RUP,并不意味你必须实施这些最佳实践,然而,如果你使用XP,你却必须贯穿实施这些最佳实践,否则,项目很容易失败。再换一个角度考虑,像 CIRefactoringTDD这些最佳实践很多时候都会催生高质量,易维护,易扩展的系统,这也是一个成功的项目所必须具备的。

因此,在我看来,一个习惯于敏捷开发的的开发人员,往往都具备优秀开发技术,因为,只有你深刻理解这些开发流程,掌握各个开发阶段涉及的技术,并把这些融会贯通,融为一体,这样你才能浑然天成,挥洒自如,才能合理正确出色的发挥敏捷开发所支持的灵活性。正如Kent Beck所说,要掌握TDD并不是一件容易的事情,但是,一旦你掌握了它,你就会喜欢上它。这也是为什么在这几年的工作中,我花了这么多精力在敏捷开发 上,而在solar的开发过程中,虽然有比较规范的开发流程,很像RUP,但却是很容易适应。当然,我不是说RUP很简单,我在RUPXP上都并没有丰 富的经验(因此,我上述表达的未必正确),我只是从这几年的经验中得出这样的结论:要习惯于敏捷开发的确比习惯RUP要困难的多。

再次强调,在我看来,RUPXP各个开发阶段设计的各种规范,文档,标准及技术,其实没多大区别,而且很多都是相通的(很多最佳实践都可以在RUP或者XP中实施),真正根本区别在于,这两者的开发模型侧重点不一样,出发点视角不一样。RUP依靠标准,文档,规范来确保项目成功,而XP主要依赖于人。

到底是否需要文档

很明显,对于RUP来说,文档是必须的,而且在特定的时间点必须开始,还要比较详细,比如相信设计文档,粒度到每个类,每个方法。而对于XP,特定类型的文 档未必需要,或者说,需要创建的时间点并不确定,对于当前来说,和RUP区别最大的就是详细设计文档,XP不建议在开发阶段花太多的成本在详细设计文档 上。以我的经验,如果需要,我建议在某个版本发布之后在补充详细设计文档。

短信存储中心(SOLAR)

Solar 产品最重由产品经理和我两个人开始,当然,最初是产品经理一个人发起,我在开始架构设计时加入。在看看solar的开发过程中,solar的开发遵循公司 统一的开发流程,这个开发流程比较类似RUP,也需要很多文档,很多标准。从该产品最初开始,我参与该产品的部分模块的架构设计,之后负责部分模块的详细 设计,然后就接着编码。同时引入CI,在编码的过程中,详细设计文档和代码越来越不同步,但是由于开发时间比较紧张,因此,并未同步,在开发的中后期由于 产品经理休产假,因此,陆陆续续有其他的开发人员加入,到了产品发布是,相信设计文档已经和代码严重不同步,因此,在产品发布之后,团队成员再重新开始完 善详细设计文档。架构设计文档始终保持着很小很小的变化。

先在看来,在solar的开发过程中,一开始花在详细设计文档上的时间与其所带来的好处的确很不理想,比较巧的是在后期的开发过程,由于时间紧迫,导致没时间同步详细设计文档,现在在我看来,这也是没意料到的好处。

OSSXP

OSSOpen Source Software)会采用什么样的开发模型呢?RUP还是XP?好像我还没看到那个OSS采用RUP,不知道eclipse community里的项目是否都采用RUP,我暂时还未深入了解,但从其提供的测试报告和测试覆盖率等外部报告来看,很像是XP。我这里就说所我最熟悉的 Spring 系列OSS

Spring社区的所有开源项目,均采用敏捷开发模式,也许没有明确的哪一种敏捷开发模型,但至少当前采用这 样的方式:领域概念,领域需求,架构设计,领域模型,TDDRefactoringCI等。并没有比较冗长的详细设计。Spring社区的系列项目, 在规模是应该是具备一定的程度的了,以我前段时间研究Spring SecuritySpring Batch的经验来看,熟悉并掌握这两个项目(包括项目的需求,设计,到所有源代码)还是比较轻松的,总共加起来差不多连个礼拜左右。当然,这也主要得益 于其高质量的代码。相反,这段时间要就的Hibernate却让人很头痛,其代码质量并不如想象中的那么高,但也没办法,这也是当前最流行的ORM框架, 也还是必须要深入了解其底层源码,总不能自己再另外实现一套这样的ORM框架,那也不显示。

结论

说了这么多,结论应该很明显了:如果采用RUP开发,必须要有详细设计文档,如果采用XP开发,不一定需要详细设计文档,但我建议开发阶段不考虑创建维护详细设计文档,如果可以,尽量把详细设计文档推迟到产品发布结束之后再完善。如果孤立的考虑这句话,你是不是很容易理解为XP没有详细设计?我相信是的。

但是,恰恰相反,XP更尊重设计,尊重持续不断的设计,XP需要你随时准备设计,只有这样,你的设计才会持续不断的越来越优雅,越来越美,越来愈好。


 
行家一出手,便知有没有

很多人都说招人困难,招高手更难,而且很多时候效率都很低,其实不然,正所谓行家一出手,便知有没有,高手过招,很多时候本就是一招基本就可以决胜负,尤其是古龙大师的高手更是如此。

如果我是面试官,偶会(针对JAVA):

1.    IDE:正如高手通常都有一柄绝世好剑一样,做开发的,必须能够运用至少一中IDE,否则,你再怎么牛叉也高不到哪儿去,这是真正的高手最基本的要求。不要跟我说,你只用什么记事本,瞎扯谈。

要求:对IDE要能运用自如,通常一般都不用鼠标,都使用快捷键.

结论如下:如果一个自称所谓的高手,在用自己最擅长的IDE时都还在动不动就鼠标,那这个人你大可以不用招了,立即cut 掉,这样的人天生就是吹牛吹的厉害。而如果哪位老兄能够对于哪一个IDE能够运指如飞,嘿嘿,那恭喜你,他很可能就是你的先锋或虎将,请继续其他的面试步骤。

PS:我说的是IDE啊,比搞个什么notepad来瞎搅和,不要告诉我那是你的IDE,你都是用它来做开发的,那偶更是想都不用想就CUT掉你。

2.    代码功底:千万记住软件工程师是做开发的,一个不写代码的所谓的工程师彻底就是一个废物,烂人一个。编码能力对我们是如此的重要,我们每天都和它打交道呢。这里说个一次面试中的一个小插曲.

曾经和一个公司的CTO交流过,谈到软件工程师和程序员的区别:
他问:你认为软件工程师和程序员有什么区别?
偶回答:没有区别,可以说是对等。
干净利落地,他说:错,软件工程师……
没等他说完(偶知道他想说什么),偶回答:哦,我明白你的意思。你的意思是软件工程师还要做设计,而程序员仅仅是coding。其实,程序员是之前(90年代)大家的叫法,在我看来程序员基本和初级软件工程师对应,而高级程序员基本和高级软件工程师对应。当然,现在的工程师划分的比较细了,还有什么初级软件工程师,中级软件工程师,资深高级软件工程师呢。
……

嘿嘿,实际上我更喜欢叫开发者(Developer),多好听的名字……

事实上,从一个人写代码的过程中,就可以看出很多非常非常有价值非常非常有用的信息,基本上你可以判断这样的一个开发者是不是一名合格优秀的(或者是否可以培养成优秀的)开发者,即便只是写一个很简单的小程序。

要求:优秀的开发者必定具备优秀的代码功底,尤其是代码质量。
结论如下:如果哪位老兄过了第一关,来到这里了,那让他写一个很简单的程序看看其代码功底,如果不行(如命名,注释etc)直接cut掉。

PS:不要小看注释,很多人甚至都不知道什么地方该注释,该是什么样的注释,如果你知道注释的目的,嘿嘿,这个不是问题。很多时候,很多人狂往上面放注释,嘿嘿,在他们看来,注释看着舒服,你看注释和代码相间,相互映衬,多漂亮,不过这种人总比一定注释都没有要好得多,请注意,我不是说他们写的注释比没有注释好(很多时候可能比没注释还糟糕),我是说至少他们有需要写注释这样的意识,这个比没注释的要好的多。

上面两步都过了,那恭喜你,这个哥们多半就是你要牛人呢,那请接着下面的有针对性的问题吧。


最后,以我的经验,如果过了第一关的人,很多时候第二个关必定会表现的很好。正所谓,近朱者赤近墨者黑,高手就是高手,该厉害的地方自然都很厉害。

不过,也许我发表这篇文章之后,很多人可能就为了应付我这样的面试就都刻意去熟悉那些快捷键,那这里的第一关可能作用就不大了,果真如此,那这篇文章可真是功德无量哦,也是可喜可贺。

小样,你的IDE是否经常借助鼠标呢?小心哦,你很难找到工作呢,如果遇到我这样的面试官,当然,那也要等我有资格做面试官咯,呵呵








保护用户隐私,提供系统注销功能

最近发现一个比较普片问题,好像基本所有的网站都不提供注销功能,只要你注册了,你就休想注销,中国太不重视个人隐私。像google之类的都提供注销功能,哪一天你不想在用那个用户了,又要保护自己的隐私,那你就选择注销用户,反观国内的网站,呵呵,只要你注册了就休想注销,只要你上了贼船就别想下船,呜呼。

所以,以后在注册一个用户之前,先看看该系统提不提供注销功能,如果没有提供注销功能,则尽量不要注册,即便注册,也不能用真实信息去注册。对于提供注销功能的网站,可以考虑用真实信息注册。不过最好除非不得已,否则,所有注册都不应该提供真实信息。
这样的TDD实践方式有问题?请教大家的TDD实施方式.
2007-09-10


我一直都这么做,可是在一次实践交流(其实就是面试,一家号称采用敏捷开发的公司)之中,我这样的方式被称之为钻了牛角尖。”你是不是从书上随便看了一下 TDD相关的资料之后,就认为你已经掌握了TDD了?”,我一直都这么做,目前已经有几个项目都是采用这样的开发方式,可是,在这次的交流之后我可是有点 动摇了,我是不是做错了?我想知道大家都怎么样运用TDD的?而我是这样做的,就看看那次交流现场开发的吧:
以下称那个朋友为TDDer(可是两个人哦,连面试都是PP,)吧。
TDDer提的需求:把字符串"Tdd is a software devolopment technology"  按照单词反转为
"technology devolopment software a is Tdd"
在开始之前,我问了两次TDDer,确认需求是不是这样,确认需求如此之后,我就开始了:
1. 编写测试代码:
Java代码 复制代码
  1. public class StringReverseTest {  
  2.   
  3.     @Test  
  4.     public void testReverse(){  
  5.         StringReverser sr=new StringReverser();  
  6.         String str = "Tdd is a software devolopment technology";  
  7.         Assert.assertEquals("technology devolopment software a is Tdd",sr.reverse(str));  
  8.     }  
  9.   
  10. }  
public class StringReverseTest {

    @Test
    public void testReverse(){
        StringReverser sr=new StringReverser();
        String str = "Tdd is a software devolopment technology";
        Assert.assertEquals("technology devolopment software a is Tdd",sr.reverse(str));
    }

}


2. 编写产品代码
Java代码 复制代码
  1. public class StringReverser {  
  2.     public String reverse(String str) {  
  3.         return "technology devolopment software a is Tdd";  
  4.     }  
  5. }  
public class StringReverser {
    public String reverse(String str) {
        return "technology devolopment software a is Tdd";
    }
}

然后完成.
之后给TDDer说完成了,然后,TDDer问我,这样就完成了?
我说功能是实现了,可以说完成了,不过还有一步,重构,去除重复代码.TDDer仅仅看了一眼就直摇头,他说“你写这样的代码有什么用,如果我换 另外一个字符串呢?你这个还能跑么?",我说"你的需求只是转那个字符串,而且我找你确认过,如果你要换另外一个字符串,那也就是说需求变更,那我调整代 码罗,我说TDD不是这样的么?我平时工作中就这么做的。",TDDer只有一个劲的摇头了,而我也知道,这次没戏了.:D
其实,我知道我还有一步没做,那就是重构,我当时在白纸上写的,嘿嘿,根本没法运行测试,所以这一步也比较麻烦,尽管重构可以不依赖于测试,但我已经习惯于依赖测试。
谈论了一些话题之后:我收到的临别赠言是:
“钻了牛角尖。”
”你是不是从书上随便看了一下TDD相关的资料之后,就认为你已经掌握了TDD了?”
“Code Smell”
“你这个是十足的TDD的反模式,如果你这个给我们公司的xxxx看的话,这个一定是他培训时举的最好的反模式了”
于是,我向他们咨询他们是怎么实施TDD的?可是我最后还是没有答案.
终于,讨论结束了,我带着满腔困惑和不解回到家,打开笔记本,开始了这个征程:
1. 建立eclipse工程,然后编写测试代码,并运行测试,失败了。
测试代码:
Java代码 复制代码
  1.  package org.opensource.test;  
  2. import org.junit.Assert;  
  3. import org.junit.Test;  
  4. public class StringReverseTest {  
  5.     @Test  
  6.     public void testReverse(){  
  7.         StringReverser sr=new StringReverser();  
  8.         String str = "Tdd is a software devolopment technology";  
  9.         Assert.assertEquals("technology devolopment software a is Tdd",sr.reverse(str));  
  10.     }  
  11. }  
 package org.opensource.test;
import org.junit.Assert;
import org.junit.Test;
public class StringReverseTest {
    @Test
    public void testReverse(){
        StringReverser sr=new StringReverser();
        String str = "Tdd is a software devolopment technology";
        Assert.assertEquals("technology devolopment software a is Tdd",sr.reverse(str));
    }
}


产品代码:
Java代码 复制代码
  1. package org.opensource.test;  
  2. public class StringReverser {  
  3.     public Object reverse(String str) {  
  4.         return null;  
  5.     }  
  6. }  
package org.opensource.test;
public class StringReverser {
    public Object reverse(String str) {
        return null;
    }
}


2. 编写产品代码,确保测试通过:
Java代码 复制代码
  1. package org.opensource.test;  
  2. public class StringReverser {  
  3.     public Object reverse(String str) {  
  4.         return "technology devolopment software a is Tdd";  
  5.     }  
  6. }  
package org.opensource.test;
public class StringReverser {
    public Object reverse(String str) {
        return "technology devolopment software a is Tdd";
    }
}


3. 重构,消除重复代码:
Java代码 复制代码
  1. package org.opensource.test;  
  2. public class StringReverser {  
  3.     private static final String SPACE = " ";  
  4.     public String reverse(String str) {  
  5.         String[] words=str.split(SPACE);  
  6.         StringBuilder result=new StringBuilder();  
  7.         for (int i = words.length-1; i >=0; i--) {  
  8.             result.append(words[i]).append(SPACE);  
  9.         }  
  10.         return result.toString().trim();  
  11.     }  
  12. }   
package org.opensource.test;
public class StringReverser {
    private static final String SPACE = " ";
    public String reverse(String str) {
        String[] words=str.split(SPACE);
        StringBuilder result=new StringBuilder();
        for (int i = words.length-1; i >=0; i--) {
            result.append(words[i]).append(SPACE);
        }
        return result.toString().trim();
    }
} 




完了,这样已经完了.

后来和一个朋友聊到这个事情,他也笑的不成样子,他认为我很懒,应该写完整点。而我始终认为我这样做是对的,而且现实中我也是这么做。我跟朋友 说,如果要说有问题,那就是我本应该引导客户,他的需求的确是这个么?可是,当时TDDer要要看看我写的代码的风格,而不是于客户的沟通.当然我这个朋 友并不是一个TDD的爱好者.而我是一个TDD迷恋者

我不知道我这样做是不是不对,因此到这里来请教大家,你们都是怎么做的?
我承认,对于TDD的理论,我的确是从书上看得,从网上学到,但是跟多的是从开源里面去体会,在项目产品中去实践。我已经在四个项目中采用这样的方式,一直都感觉很好.
因此,很是困惑,请大家指点指点,我想知道大家都是怎么实施TDD的.

在我的开发生活中,TDD,Refactor.CI是三个最重要也最常见的伙伴,我就是这样实践他们的.

虽然这个demo很简单,但是足以说明我的开发工程.
当然这里写起来这么麻烦,但是在开发这样的一个demo过程中其实是很快的。

这次的经历告诉我:在面试时,要看看一个人的TDD能力,一定要在电脑上做,真真实实的去做,这样才知道,事实上,当时我带了笔记本去了.

我永远不会忘记:Clean code that works.

PS:把数据库扔在一边!(请直接下载我最后回复中的详细文档和源代码吧) 曾 引起强劲的反映,我现在在做的一个项目中,目前功能基本实现,现在正在处理扣费部分,到目前为止,都未考虑数据库,数据当前都是序列化到文件系统中 的。:D 我只是想说的是,不说其他的,如果考虑数据库,单单我运行测试的时间都要浪费不少,效率都要低很多,只从这点上考虑,我已经非常不愿意在前期考虑数据库的 设计了.在我看来,数据的存储方式本就不应该影响业务模型及业务逻辑.


附件是这个demo的源码.
敏捷?敏捷?敏捷 ……
2007-04-04

关键字: 敏捷开发

如果你根本就没有尝过某一卷冰淇淋的甜头,你就跟别人说这个很甜,我相信这个是很难让人信服的.只有他去尝过太才会确定是不是甜的,说不定他觉的很苦很涩。

并不是书上说的都是对的。敏捷是一种思想。而实际上敏捷方法是一系列的实践,比如TDD,Refactoring,Continuous Integration等等,这些都是久经考验的技术实践,而且,这些技术很多时候都是一环扣一环的,你不会写测试,怎么TDD?你说你不TDD,我只写测试,可是你设计的系统更本就没法测试,写一个测试要花你很多很多时间,你还会继续么?重构,常常听到很多人在耳边想起,可是,他们却不知道重构的基础是有详尽的Test Suite,听他们这么说,我只能这样理解,他们根本就没看过Martin的名著重构,因为他们都从不写测试用例的。无法可想,实际我们现在已经有了很多很多的测试用例了,只可惜,这些用例都没有用,一但运行,红了一大片,而且很多测试都达不到目的,实际上我们当前项目有些member居然把测试用例当作炫耀的资本,“看看,我们写了多少测试”,但他却没问他自己,“你浪费了多少时间….”。

没有用的测试用例远比没有测试用例还要糟糕,因为那不但浪费时间还浪费金钱……如果你做某一件事情不会给你带来好处,我相信你绝对不愿去做,当然,如果可以给你带来炫耀的资本,我想你还是会去做的。

不是每个人都像Kent Benk那样迷恋绿色条的,当然习惯TDD的也很快就恋上那绿色条的。中国的开发人员太虚太浮了(当然并不是所有),至少我现在感觉周围的很多人都是这样.两个方安放在一起,都没测过,就说这个方案性能没有那个方案好,你以前有过这方面的经验么?你测过么?都没有,那你简直就是在放屁,想当然的。

不要想当然了,你不去做你永远都不知道的。

OO技术已经这么多年了,可是又有多少人能合理很好也运用?做一样东西不去弄清楚就瞎掰瞎侃。

其实最终还是人的问题,如果你跟一个一年级的小朋友说:1 x 1001001加起来要方便快捷,可是他认为1001加 起来要方便快捷,而且那个小朋友不信任你,因此他就是不认同你的观点,那你还能怎么办?我想只能无语了。敏捷开发里很重要的一点就是信任,你要信任你的搭 挡,信任他们,很郁闷的是,我在一次和同事的聊天中提到,每人都应该信任项目组的所有人,我却没想到的是后来在一次开会中,居然说我认为他不信任…….遇到这样的同事(应该算是TeamLeader吧)我无法可想,当时唯一的想法就是辞职。两个人之间都已经失去了信任还在一起做什么?即便在一起也做不出什么东西出来。

事实上敏捷开发设计很多技术,如果你要敏捷,你需要具备很多技术底蕴,像OO,模式,TDD,重构,持续集成,还有更重要的思维方式等等,其要要敏捷不是一件容易的事,并不是你什么时候想敏捷就敏捷的。

人,软件开发绝对是人。一个项目如果失败,多数情况下一定是人的问题。务实点,一个人务实点总是好的.

每个人都会犯错,只要犯错就要付出代价的.

 
C/C++利剑-Eclipse 的CDT-4.0.0-M5终于发布了
2007-03-16

          等了很久了,Eclipse 的CDT-4.0.0-M5终于发布了,虽然不是release,但是已经可以用了,可以慢慢的等他的release版本了.

          以前写写C或者C++总觉得没有很顺手的工具,用VC也不知道为什么,总是感觉不爽,估计是自己不太喜欢付费的东西吧,如果一个不用付费的和另一个付费的 只是相差那么一点点,我想我还是宁愿使用免费的,更何况Eclipse本来就不比VC。以前用VS.net 2003,总是感觉不爽,也许是我不会用它吧,总之开发C或者C++ eclipse还是我的首选。

          遗憾的是,之前的CDT在代码辅助的时候速度奇慢,根本没法用,当然我说的是使用sun的JDK的时候,如果使用IBM的JDK的话,是没有任何问题的,而CDT-4.0.0已经没有这个问题,使用sun的jdk运行还是很好,真的很高兴.

           实际上CDT-4.0.0在2007-2-20号就已经发布了,只可惜这段时间一直很忙,一直没到这个地方发泄.

终于发泄了一下心中的快感。

www.eclipse.org/cdt/

 
Struts1 资源配置重装载
2007-04-16

Struts1 的资源一直不能重装载,严重影响开发效率,经历了一个上午的折腾之后,决定解决这个问题,否则,效率太低了.
其实实现也很简单,当前基于filter,当然,如果需要移植到其他的实现方式也是易如反掌的。
目前仅支持struts1不支持struts2,我对struts2不熟悉,没什么兴趣,struts1也是公司采用才没有办法的,不过,如果要支持struts2,我想也不是什么难事。
当前支持的配置包括:struts-config,message resources资源文件以及Plugin的配置。对于Plugin的配置仅仅测试过验证器validate配置和tiles插件的配置,其他的未测试.

配置如下:在web.xml中添加以下配置即可,建议产品上线时注释或者删除该配置
Java代码 复制代码
  1. <!-- StrutsCofigReloadFilter  start -->  
  2.   <filter>  
  3.     <filter-name>strutsCofigReloadFilter</filter-name>  
  4.     <filter-class>  
  5.          com.numen.framework.struts.filter.StrutsCofigReloadFilter  
  6.     </filter-class>  
  7.   </filter>  
  8.   
  9.   <filter-mapping>  
  10.     <filter-name>strutsCofigReloadFilter</filter-name>  
  11.     <url-pattern>*.do</url-pattern>  
  12.   </filter-mapping>  
  13.   
  14.   <!-- StrutsCofigReloadFilter  end -->  
<!-- StrutsCofigReloadFilter  start -->
  <filter>
    <filter-name>strutsCofigReloadFilter</filter-name>
    <filter-class>
         com.numen.framework.struts.filter.StrutsCofigReloadFilter
    </filter-class>
  </filter>

  <filter-mapping>
    <filter-name>strutsCofigReloadFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>

  <!-- StrutsCofigReloadFilter  end -->


附件是整个项目文件.
Enjoy youself!
 
2006-12-11

持续集成实践之CruiseControl

持续集成实践之CruiseControl
hyysguyang 2006-1024
 
1. 疲于奔命得一天
       这么模块以前已经编写好得,现在怎么出问题了?
       上个礼拜编写得测试用例怎么现在通不过了?
       终于完成了一个功能了,提交代码,突然间想起昨天花了很大精力修复的那个bug,那个模块可是以前就已经完成了的,心惊胆颤,我现在完成的这个模块是否对其他模块产生影响?    哎,这些问题是什么时候出现的?我该从什么地方开始?要是在出问题的时候谁能告诉我该有多好啊?没办法,捉bug吧……OK,好了,一切都好了。
这么模块以前已经编写好得,现在怎么出问题了?
       ……
       重复,重复,再重复,疯掉了,这应该是机器做的事情,为什么……恩,编写脚本吧,哦,好像持续集成能够解决这个问题。
2. 持续集成概述
       持续集成来源于XP(极限编程)的一个实践。事实上,这个实践早就存在,并且很多并没有实际XP的也在实际着它。
2.1 持续集成的优点
       持续集成最基本的优点就是:持续集成可以减少集成阶段修复bug消耗的时间,从而最终提高生产力。  
       正如在solar的开发过程中我们所遇到的,因为某个人在工作的时候踩进了别人的领域、影响了别人的 代码,而被影响的人还不知道发生了什么,于是bug就出现了。这种bug是最难查的,因为很难有人去特意关注它,随着时间的推移,问题会逐渐恶化。直到有 一天,某一不小心触碰到了,然后就是花很多很多的经历去修复。嘿嘿,你现在修复了,说不定下个礼拜这个bug又冒出来了呢。
2.2 持续集成的优点
       如果使用持续集成,这样的bug绝大多数都可以在引入的同一天就被发现。而且,由于一天之中发生变动 的部分并不多,所以可以很快找到出错的位置。如果找不到bug究竟在哪里,你也可以不把这些讨厌的代码集成到产品中去。所以,即使在最坏的情况下,你也只 是不添加引起bug的特性而已。
       当然,持续集成的排错能力取决于测试技术,你所拥有的测试套件越是详尽,你的系统bug越少,质量越高,排错能力越强。因此,在实施持续集成 之前,你应该要有一套详尽的单元测试套件,当然,如果你采用TDD的方式进行开发那是最好不过了。
2.3 持续集成最佳实践
       持续集成的关键是自动化。绝大多数的集成都可以而且应该自动完成。读取源代码、编译、连接、测试,这 些都可以自动完成。最后,你应该得到一条简单的信息,告诉你这次创建是否成功:“yes”或“no”。如果成功,本次集成到此为止;如果失败,你应该可以 很简单地撤消最后一次的修改,回到前一次成功的创建。在整个创建过程中,完全不需要你动脑子。在solar中,我们采用邮件通知的方式。Martin Fowler 在其文章中提到,持续集成应该:
²        单一代码源
²        自动化创建脚本
²        自测试的代码
²        主创建
²        频繁的提交(Check in)代码
       更详细的内容请参考Martin Fowler的文章 Continuous Integration
2.4 持续集成与日构建
           
       持续集成不是日构建,与日构建建相比它还有以下特点:
       1. 持续集成强调了集成频率,和日创建相比,持续集成显得更加频繁。
       2. 持续集成强调及时反馈,日创建的目的是得到一个可以使用的稳定的发布版本,而持续集成强调的是集成失败之后向开发人员提供快速的反馈,当 然成功创建的结果也是得到稳定的版本。
       3. 日创建并没有强调开发人员提交(check in)源码的频率,而持续集成鼓励并支持开发人员尽快的提交对源码的修改并得到尽快的反馈。
             
从上面明显看出,“ 频率”和“反馈”这两个词出现的特别多,持 续集成有一个基本要点,那 就是“ 经常性的集成比偶尔集成要好”。Martin Fowler 认为对于持续集成来说,集成越频繁,效果越好 ,如果你的集成不是经常进行的(少于每天一次),那么集成就是一件痛苦的事情,如果集成偶尔才进行一次(一周甚至一个月), 等到集成阶段发现bug,然后找原因解决bug,会耗费你大量的时间与精力,而且这种方式有点象传统的集成模式,这违背了持续集成的初衷.
3. Solar 持续集成环境
       Solar的持续集成环境基于CruiseControl搭建。 CruiseControl 原属于thoughtworks公司,后来开放源码,它是一个流行的功能强大的持续集成框架,包括了邮件通知,ant 和各种源码控制工具的插件。并提供了 web 接口,用于查看当前和以前的创建的结果。在下文我们采用cc代替Cruise Control。
4. CruiseControl环境搭键
4.1 Build Loop
       前面在讨论持续集成的时候讲到其最重要的特征之一是自动化,而 CC 的 Build Loop 就是为支持自动化而设计的,Build Loop 也是 CC 的核心。CC 提供了一个 daemon 进程,该进程自动根据配置的时间间隔或指定的某个具体时间读取 CC 配置文件并进行循环创建,每次 CC 都会重新加载配置文件,在CC运行过程中,修改了配置文件不用重新启动 CC。
       Build Loop 过程中所做的工作如下:访问源码源码控制系统,查看是否有代码被修改,如果有,获取源码的新版本,并根据配置对源码进行一次 Build,创建一个日志文件,最后向开发人员通知 build 的结果,活动图如下:
图1. Build Loop 状态图
4.2 CC的配置文件
       cc最主要的配置文件是一个叫做config.xml的配置文件,当然你也可以取其他名字,只要运行cc的时候指定输入文件就行了,类似于ant命令一样。以下是一个示例文件的内容。
config.xml


  1. <xml version="1.0" encoding="GBK"?>  
  2.   
  3.   
  4. <cruisecontrol>  
  5.    
  6. <property environment="env"/>  
  7.     <property name="ant.home" value="${env.ANT_HOME}"/>  
  8.     <property name="product.name" value="numen"/>  
  9.   
  10.     
  11.   <project name="solar-CI-build" buildafterfailed="false">  
  12.   
  13.     <dateformat format="yyyy-MM-dd HH:mm:ss"/>  
  14.     <listeners>  
  15.       <currentbuildstatuslistener file="logs/${project.name}/currentbuildstatus.txt"/>  
  16.     listeners>  
  17.   
  18.     <bootstrappers>  
  19.       <currentbuildstatusbootstrapper  
  20.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  21.     bootstrappers>  
  22.   
  23.     <modificationset ignoreFiles="*.css,*.js,*.jsp">  
  24.       <svn localworkingcopy="checkout/${product.name}"/>  
  25.     modificationset>  
  26.   
  27.     <schedule interval="7200">  
  28.        
  29.       
  30.     <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  31.            target="run.command.tests" multiple="3"/>  
  32.               
  33.              
  34.       <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  35.            target="run.plain.tests"  multiple="1"/>  
  36.               
  37.     schedule>  
  38.   
  39.     <log dir="logs/${project.name}">  
  40.       <merge dir="checkout/${product.name}/test/report/xml"/>  
  41.     log>  
  42.   
  43.     <publishers>  
  44.       <currentbuildstatuspublisher  
  45.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  46.   
  47.       <artifactspublisher dir="checkout/${product.name}/build"  
  48.                           dest="artifacts/${project.name}"/>  
  49.   
  50.       <htmlemail mailhost="mail.cenosoft.cn"  
  51.                  returnaddress="tech@cenosoft.cn"  
  52.                  defaultsuffix="@cenosoft.cn"  
  53.                  buildresultsurl="http://192.168.7.236:10000/buildresults/${project.name}"  
  54.                  css="report/css/cruisecontrol.css"  
  55.                  xsldir="report/xsl"  
  56.                  logdir="logs/${project.name}"  
  57.                  charset="GBK">  
  58.         <map alias="guyang" address="hyysguyang@gmail.com"/>  
  59.         <failure address="guyang" reportWhenFixed="true"/>  
  60.       htmlemail>  
  61.             <XSLTLogPublisher directory="WebRoot/rss" outfilename="numenbuildstatus.rss" xsltfile="report/rss/xsl/buildstatus.xsl" />  
  62.     publishers>  
  63.   project>  
  64.   
  65.     
  66.   <project name="solar-nightly-build" buildafterfailed="false">  
  67.        
  68.     <dateformat format="yyyy-MM-dd HH:mm:ss"/>  
  69.     <listeners>  
  70.       <currentbuildstatuslistener file="logs/${project.name}/currentbuildstatus.txt"/>  
  71.     listeners>  
  72.   
  73.     <bootstrappers>  
  74.       <currentbuildstatusbootstrapper  
  75.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  76.     bootstrappers>  
  77.   
  78.     <modificationset requiremodification="false">  
  79.       <svn localworkingcopy="checkout/${product.name}"/>  
  80.     modificationset>  
  81.   
  82.     <schedule>  
  83.       <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  84.            target="run.all"  
  85.            time="0300"/>  
  86.     schedule>  
  87.   
  88.     <log dir="logs/${project.name}">  
  89.       <merge dir="checkout/${product.name}/test/report/xml"/>  
  90.     log>  
  91.   
  92.     <publishers>  
  93.       <currentbuildstatuspublisher  
  94.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  95.   
  96.       <artifactspublisher dir="checkout/${product.name}/build"  
  97.                           dest="artifacts/${project.name}"/>  
  98.   
  99.       <htmlemail mailhost="mail.cenosoft.cn"  
  100.                  returnaddress="tech@cenosoft.cn"  
  101.                  defaultsuffix="@cenosoft.cn"  
  102.                  buildresultsurl="http://localhost:10000/buildresults/${project.name}"  
  103.                  css="report/css/cruisecontrol.css"  
  104.                  xsldir="report/xsl"  
  105.                  logdir="logs/${project.name}"  
  106.                  charset="GBK">  
  107.         <map alias="guyang" address="hyysguyang@gmail.com"/>  
  108.   
  109.         <failure address="guyang" reportWhenFixed="true"/>  
  110.       htmlemail>  
  111.             <XSLTLogPublisher directory="WebRoot/rss" outfilename="numenbuildstatus.rss" xsltfile="report/rss/xsl/buildstatus.xsl" />  
  112.   
  113.     publishers>  
  114.   
  115.   project>  
  116.   
  117. </cruisecontrol>  

4.3 配置示例
Ø         下载CC
Ø         Build CC
Ø         建立build结构
Ø         编写config.xml
Ø         引入wrapper buildfile
Ø         Report Result
4.3.1下载CC
     你可以从下面的连接中下载cc的源码包或者binary包。
       CC Download
       Source Distribution
       Binary Distribution
       解压缩到某一个目录,如D:\cruisecontrol-2.5,以下我们用 CC_HOME 来引用这个目录,将%CC_HOME%\main\bin添加到系统的%PATH%环境变量中去。
4.3.2 Build CC
    %CC_HOME%目录下的结构如下:

4.3.3创建 cruisecontrol.jar
       运行 %CC_HOME%\main\build.bat , 该命令会编译 , 测试 , 并创建%CC_HOME%\main\dist\cruisecontrol.jar。现在即可通过以下方式运行 %CC_HOME%\main\bin\cruisecontrol.bat ,
%CC_HOME%\main\dist\java jar cruisecontrol.jar
或者在命令行输入cruisecontrol命令。
4.3.4创建 cruisecontrol.war
       在创建 cruisecontrol.war 之前,我们需要传递三个参数给创建程序,分别用来指定 CC 产生
       的日志的目录(user.log.dir),创建过程中产生的状态文件的名字 (user.build.status.file),还有创建过程中输出的人工制品所在的目录 (cruise.build.artifacts.dir)。假如我们的build home为/home,则其对应的值如下:
user.log.dir=/home/logs
user.build.status.file=currentbuildstatus.txt
cruise.build.artifacts.dir=/home/artifacts
转到%CC_HOME%\reporting\jsp\,然后运行以下命令:
              build -Duser.log.dir=/home/logs
                     -Duser.build.status.file=status.txt
              -Dcruise.build.artifacts.dir=/home/artifacts
       当然你可以不指定这三个参数,仅仅运行build,然后会提示你输入这三个参数的。
       另外一种方法,就是在该目录下创建一个新文件 override.properties,文件内容如下,在改文件中指定这三个属性,然后build,就可以了,如。user.log.dir=/home/logs
                     user.build.status.file=status.txt
                     cruise.build.artifacts.dir=/home/artifacts
4.3.5建立build结构
 
 
图3. CC Build结构
       artifacts 目录:存放CC 在 build loop 过程中产生的人工制品,如JAR,对于我们的solar就是每一次集成build的solar.jar,solar-test.jar
       checkout 目录:作为 CC 从源码控制系统检出
的项目存放的目录,对于我们的solar就是从svn上检出存放的目录,当然这里可以存放多个项目。
       Logs 目录:CC 的 build loop 过程中产生日志所在的目录
4.3.6编写config.xml
开发利器之单元测试
2006-12-11

这是以前一次新员工单元测试的培训文档,只可惜那次的收效并不大,不过对我来说却是很有帮助,促使我对单元测试做了一次很好的总结,以前比较零散,只是在记忆中而已。

开发利器之单元测试
hyysguyang 2006-08-30
0.导言
1.单元测试的分类
1.1 逻辑单元测试(plain junit test
1.2. 集成单元测试
1.3. 功能单元测试
2.单元测试的动机
3.单元测试的目标
4.确保可测试性
5.测试策略
5.1stub进行粗粒度测试
5.2mock objects进行孤立测试
5.3Cactus进行容内测试
6 测试覆盖率
7.最佳实践
8.Spring的经验
9.参考资料
 
0.导言
Never in the field of software development was so much owed by so many to so few lines of code.
       软件开发领域中此前从未有过这样的事情:很少几行代码对大量的代码起了重要的作用。
                                                                        ——Martin Fowler
       Any program feature without an automated test simply doesn’t exist.
       任何没有经过自动测试的程序特性就等于不存在的特性。
                                                                            —— Kent Beck
 
1. 单元测试的分类
1.1  逻辑单元测试(plain junit test
    逻辑单元测试主要检查代码逻辑性,这些测试通常只是针对单个方法。你可以通过mock
objects 或者stub来控制特定的方法的边界。这种类型的单元测试是最重要也是最基本的。其在单元测试中的重要性不亚于POJO.这种类型的测试最大的特点就是执行速度快,而这也是单元测试最重要的一个特性之一。
    可以参看附近中的文件过滤器ExtensionFileFilter.java及其测试用例。
1.2. 集成单元测试
    这种单元测试主要是在真是环境(或者真实的环境的一部分)下的两个组件相互交互的测试。例如:一段访问数据库的程序已经被测试证实能够有效地访问到数据库,那么就可以提供和数据库交互的接口,最典型的就是Cactus容器内测试,以及公司的测试commandLavender已经测试非command组件的vanillacommad的测试实例可以参看附近中的规则命令SolarRuleEntryCommand.java及其测试用例。
1.3. 功能单元测试
    这种单元测试越出了集成单元测试的边界,目的是为了确认激励-响应。如访问页面测试。
    更相信的分类可以参考相关的测试文献,junit in action 上面也提到,事实上上面提到的概念,都来自于它。
    没 有什么是最好的,最适合的就是最好的!写单元测试你不得不写额外的代码,如果写测试没有好处,我们又何必去浪费时间呢。因此你应该确信测试用例能给你带来 好处,这样你才会愿意去写,也只有这样你才能写的出对于你有帮助的测试用例。因此,在你未确信单元测试给你带来好处之前,请不要去写它。既然这样,我们就 简单看看单元测试到底给我们带来什么好处。
 
2.单元测试的动机
2.1. 确保产品质量,单元测试可以有效的挑出很多常见的代码错误。
2.2.  一边编写应用代码一边编写测试,有助于定义类的需求。有助于明确需求,
如对于一个方法,对于传入的null参数应该如何处理?是否应该返回null?你必须为这些情况编写测试,所以你一定得把这些行为都想得清清楚楚,之后就可以在JavaDoc中写明它得用法。
2.3. (Test Suite)是一种强大而重要得文档。可执行得文档,保持与代码同步。测试套件
2.4. Test Suite确保有效的回归测试。详尽的
    当我们增加了新功能,或者修正bug时,我们可以确保现存的功能仍然正确。同时增强自信。确保不断疯狂的重构。重构保证我们不管在什么时候对所需的功能有最优的实现。优秀的开发人员都习惯于重构。每次我commitSCM(如公司采用的svn)中的代码都是正确的,因为在commit之前我看到了绿色动态条,我的所有的代码都通过了测试,这很好。这样你难道不会更加自信么?
    对于重构的精妙叙述请参阅Martin Fowler 的经典重构:改善既有代码的设计.
    就上述几点,我相信测试用例就值得我去写。其实还有很多其他的好处,但目前来说我体会最深的就是这几点。既然单元测试有这么多好处,那就让我们看看,我们该测什么?什么时候我们需要写测试用例呢?
3.单元测试的目标
 
    其实编写单元测试很简单,不论是stub也好,还是mock object也好,
抑或是容器内测试也好,入门都很简单,更重要的是测试策略,针对什么类你采用对应的测试方法,是stub呢还是mock object,你应该知道你该测什么,而不该测什么,这才是最重要的,当然
这些需要不断的实践,经验越丰富你就越清楚。
    单元测试一般是白盒测试,通常需要对被测试的类的内部细节有足够的了解。在写测试用例时请永远记住:
    1. 每一个测试用例只应该测试一个类,而不是间接测试其合作者。
       因为没个需要测试的类都会有自己的测试用例,它不再需要其他的来间接测试它。
    2. 基于第一条,你应对你的类进行隔离测试。采用stub,或者mock object,替代你要测的类的协作对象。把你要测的类隔离开来进行测试。
    单元测试也意味着你要知道哪些是不应该被测试的。当你明白哪些是不该测试的,剩下哪些该测试就显而易见了。明确该测什么不该测什么,这点很重要。
    对于什么该测,什么不该测试,以前我及其迷惘,因为我不知道某个类我到底该不该测,思来想去之后,我后面决定:
    任何时候对你的任何代码不放心,请你立刻写一个对应的测试。确定测试通过之后,直到你放心为止。
    以我的经历,写测试用例不难,但写有用的测试用例就不是那么容易了。提高你的测试能力一个比较好的做法就是动手去写,分享别人的经验,实践一些最佳实践。
    既然我们已经确认我们应该要写单元测试了,那么就存在一个问题:这个类容易测试么?可测试么?这就涉及代码可测试性的问题,在下一节,我们就来看看代码的可测试性吧。
 
4.确保可测试性
       不同的代码(类或方法)其可测试性是有很大区别的,比如:给你一个计算ab的方法,相信你很快就写好它的测试代码,可是如果让你测试一个servlet或访问数据库的代码呢?也许你就要花上一点时间了。比如,对于EJB组件必须在应用服务器中运行,要测他也许你要启动一个应用服务器,且要部署整个应用,要编写并部署这样一个组件的一个测试用例就会花费很长的时间,运行这样的测试用例同样要花费很长很长的时间,这样你就不能很频繁的运行测试套件,你也就失去很多单元测试所带来的好处。
既然我们要编写单元测试,那可测试性就理所当然的成为程序代码质量的一个重要方面。事实上代码的可测试性与程序代码的质量有很大的关联。
       容易测试的代码通常会更好、更可读、并且更加易于维护。对于难测试的代码通常也难于维护和升级。改善代码的可测试性很大程度上会改善你的设计。大多数情况下,编写可测试的程序代码会促成高质量的程序代码。比如:
       1.如果一个方法长的无法确保其中所有的代码都被执行了,那么这个方法必定也是不可读的、难于维护的。
2.如果有些代码依赖于在Singleton中持有的全局静态状态,这些代码无法独立进行测试。
       3.假若代码不是可插入的——也就是说无法把一个特定的合作者替换成一个模仿测试对象会使的程序难以扩张。
       如果你不知道如何测试某段代码,请考虑重构,看看是否能对实现略加修改以便进行测试。
有些编程惯例容易导致不可测试的代码,如:
1. Singleton反模式
难以用stub来替换Singleton对象。无法被IoC容器创建和管理。
2. 静态Façade
       由静态方法组成的façade也不容易测试。由于所有的调用者都绑定到了静态Façade,你不可能插入一个测试实现。此外,静态方法无法被覆盖,这样实际上失去了一种重要的OOP能力。
一般只有在下列情况下才应该使用静态方法。
n        不属于任何类的、过程性的工具方法。
n        操作ThreadLocal状态。
n        返回Singleton的实例。
遵循一定的原则和利用一些技巧可以提高代码的可测试性,如:
针对接口编程,而非针对类编程
n        使用Strategy设计模式
n        牢记迪米特法则
n        尽量减少对环境相关API的依赖
n        合理划分类的职责
n        隐藏实现细节
n        重构以便在测试期间覆盖某些方法
从上面的几点可以看出,编写容易测试的代码与编写优秀的高质量的代码所遵循的编程惯例是一致的。事实上先编写测试用例再编写产品代码(或者采用TDD)的方式进行开发逼着你遵循这些原则,很自然的,你在不经意之中,就遵循了这些优秀的编程管理,当然实际上不仅仅这些,还有很多其他的OOD原则,都与上述的这些类似。
5.测试策略
       并不是每个类都可以用同样的方式来测试的,比如测试访问数据库的代码,测试依赖与容器的组件(Servlet,EJB),因此需要针对具体要测试的类采用相应的测试方式。下面是一般测试策略。
n          stub进行粗粒度测试
n          mock objects进行孤立测试
n          Cactus进行容器内测试
前两种其实都是为了解决同一个问题:如何保持测试的独立性,而最后一种测试实际上属于集成单元测试。
在单元测试的目标中,我们提到,每一个测试用例只应该测试一个类,而不是间接测试其合作者。既然这样,那这个类的合作者我们怎么去创建呢?很多时候他们类代码我们也许还没编写,而我们现在用用,该怎么办?采用stub或者mock object进行隔离测试。
5.1用stub进行粗粒度测试
   一般来说,你都可以编写你所测试的类所依赖的其他类的stub来替代协作的对象。不过这样你可能就需要编写大量的stub代码。而且有些时候编写这样的stub并不是那么容易,如当你测试一个EJB组件是,你可能需要实现一个简单的stub容器。
5.2用mock objects进行孤立测试
   除了采用stub之外,你还可以写一个模拟协作类的类,用他创建的对象来代替要测试的类的协作对象,我们称这样的对象为mock object(模拟对象)。模拟对象有两种方式:
5.2.1静态mock object
编写模拟类,并用模拟类创建的对象来代替协作对象与待测试类的对象进行交互,需要编写模拟类。
5.2.2动态mock object
采用运行期动态的生产模拟对象来与待测试类的对象进行交互。由于mock object是动态生成的,因此不需要编写模拟类。目前已经有很多这样的动态mock object的框架,如JMockEasyMock。不论你采用哪一种动态mock object框架,一般都遵循以下的步骤:
(1).创建mock实例。
(2).记录对mock调用的期望
(3).重放(replay)状态。
(4).mock实例作为交互对象来参加测试
(5).调用使用mock的测试用例
(6).验证mock行为
5.3用Cactus进行容器内测试
采用stub或者mock object编写的TestCasePlain JUnit TestCase,只能验证单个类的行为,而不能验证真个系统的各个组件之间的交互,要验证这样的交互是否正确,比如一个请求是否正确的转发给相应的组件?一个控制器是否正确的访问某个业务对象?一个DAO是否正常的访问了数据库?你的数据连接是否能正常连接到目标数据库呢?这些是Plain Junit TestCase没法测试的,对于这样的代码,我们需要编写集成单元测试,借助ApacheCactus可以很轻松的编写这样的TestCase
6 测试覆盖率
       测试覆盖率对单元测试具有重要的意义,但是不要误用。测试覆盖报告最好用来检查哪些代码没有充分的测试。当您检查覆盖报告时,找出较低的值,并了解为什么特定的代码没有经过充分的测试。然后采取相应的解决方法。测试覆盖报告一般还可以用来:
1.估计修改已有代码所需的时间
2.评估代码质量
3.评定功能测试
       切记:不要为了覆盖率而覆盖,应该为了确定代码的功能编写测试,而不是为了提高覆盖率而编写测试。
以下是一个真实项目的2006-8-24日的包com.numen.ralos.web.action测试覆盖报告:
1.测试覆盖报告
7.最佳实践
1.            测试用例应当经济,同时又保持自描述性
2.            必要时对测试套件进行重构
3.            避免编写有副作用的测试
4.            连续的回归测试:测试系统自动化
5.            一次只测试一个对象
6.            测试要尽可能地小,执行速度快
7.            反向测试
8.            等价划分
9.            测试自动化
8.Springspring的经验
 这部分内容取自Expert One-on-One™J2EE™ Development without EJB™[5]
1.            小步前进。一次只实现一部分功能。
2.            如果你认为一段待命的测试不够充分,请立即为它加上更多的测试。
3.            测试的名字要有意义,显示除它们的用途。
4.            不要依赖于测试用例的前后顺序,也不要编程创建测试套件。
5.            确保整个测试套件能够快速运行。
6.            把任何代码提交到源代码管理系统之前,完整的运行测试套件。
7.            如果用到了配置文件,从classpath来加载它。
8.            编写ant脚本来运行所有的测试,以及进行测试覆盖率分分析。
9.参考资料
[1] Vincent Massol. JUnit in action.Manning.2004
[2] David Astels. Test-Driven Development:A Practical Guide(2004年度美国《软件开发》杂志Jolt大奖 )
[3] Robert C.Martin. Agile Software Development:Principles,Patterns,and Practices(2003年度美国《软件开发》杂志Jolt大奖 )
[4] Kent Beck. Test-Driven Development By Example.2002
[5] Rod Johnson.etc. Expert One-on-One™J2EE™ Development without EJB™.2004
 

TDD开发实践一:文件过滤器

2006-12-10


TDD以及单元测试的重要性就不多说了啊,还是看看实际的开始实践吧
测试用例如下:


  1. /** 
  2.  * Copyright(c) 2005 Ceno Techonologies, Ltd. 
  3.  * 
  4.  * History: 
  5.  *   2006-7-4 下午01:49:06 Created by guyang 
  6.  */  
  7. package com.numen.sessionframework.common.io;  
  8.   
  9. import java.io.File;  
  10.   
  11. import com.numen.sessionframework.common.io.ExtensionFileFilter;  
  12.   
  13. import junit.framework.TestCase;  
  14.   
  15. /** 
  16.  *  
  17.  * @author <a href="mailto:hyysguyang@163.com">guyang</a> 
  18.  * @version 1.0 2006-7-4 下午01:49:06 
  19.  */  
  20. public class ExtensionFileFilterTest extends TestCase {  
  21.   
  22.     /* 
  23.      * (non-Javadoc) 
  24.      *  
  25.      * @see junit.framework.TestCase#setUp() 
  26.      */  
  27.     protected void setUp() throws Exception {  
  28.         super.setUp();  
  29.     }  
  30.   
  31.     /* 
  32.      * (non-Javadoc) 
  33.      *  
  34.      * @see junit.framework.TestCase#tearDown() 
  35.      */  
  36.     protected void tearDown() throws Exception {  
  37.         super.tearDown();  
  38.     }  
  39.   
  40.     /** 
  41.      * Test method for 
  42.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#addExtension(java.lang.String)}. 
  43.      */  
  44.     public void testNullAndEmptyInputAddExtension() {  
  45.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  46.         filter.addExtension(null);  
  47.         assertEquals("The size of extension list should be zero"0, filter.getExtensionList().size());  
  48.         filter.addExtension("");  
  49.         assertEquals("The size of extension list should be zero"0, filter.getExtensionList().size());  
  50.     }  
  51.   
  52.     /** 
  53.      * Test method for 
  54.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#addExtension(java.lang.String)}. 
  55.      */  
  56.     public void testSingleExtensionAddExtension() {  
  57.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  58.         filter.addExtension(".gif");  
  59.         assertEquals("The size of extension list should be one"1, filter.getExtensionList().size());  
  60.         assertEquals("The size of extension list should be '.gif'"".gif", filter.getExtensionList().get(0));  
  61.         assertTrue("The extension list should contain '.gif' extension.", filter.getExtensionList().contains(".gif"));  
  62.     }  
  63.   
  64.     /** 
  65.      * Test method for 
  66.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#addExtension(java.lang.String)}. 
  67.      */  
  68.     public void testHaveInvalidExtensionAddExtension() {  
  69.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  70.         filter.addExtension("dfasdf,.dgs¥……5÷4¥sdg,.hgdfhsagh");  
  71.         assertEquals("The size of extension list should be two"2, filter.getExtensionList().size());  
  72.         assertTrue("The extension list should contain '.dgs¥……5÷4¥sdg' extension.", filter.getExtensionList().contains(  
  73.                 ".dgs¥……5÷4¥sdg"));  
  74.         assertTrue("The extension list should contain '.hgdfhsagh' extension.", filter.getExtensionList().contains(".hgdfhsagh"));  
  75.     }  
  76.   
  77.     /** 
  78.      * Test method for 
  79.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#addExtension(java.lang.String)}. 
  80.      */  
  81.     public void testNormalExtensionAddExtension() {  
  82.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  83.         filter.addExtension(".dfasdf,.dgs¥……5÷4¥sdg,.hgdfhsagh");  
  84.         assertEquals("The size of extension list should be three"3, filter.getExtensionList().size());  
  85.         assertTrue("The extension list should contain '.dfasdf' extension.", filter.getExtensionList().contains(".dfasdf"));  
  86.         assertTrue("The extension list should contain '.dgs¥……5÷4¥sdg' extension.", filter.getExtensionList().contains(  
  87.                 ".dgs¥……5÷4¥sdg"));  
  88.         assertTrue("The extension list should contain '.hgdfhsagh' extension.", filter.getExtensionList().contains(".hgdfhsagh"));  
  89.     }  
  90.   
  91.     /** 
  92.      * Test method for 
  93.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#removeExtension(java.lang.String)}. 
  94.      */  
  95.     public void testNullAndEmptyRemoveExtension() {  
  96.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  97.         filter.addExtension(".txt");  
  98.         filter.removeExtension(null);  
  99.         filter.removeExtension("");  
  100.         assertEquals("The size of extension list should be one"1, filter.getExtensionList().size());  
  101.         assertTrue("The extension list should contain '.txt' extension.", filter.getExtensionList().contains(".txt"));  
  102.     }  
  103.   
  104.     /** 
  105.      * Test method for 
  106.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#removeExtension(java.lang.String)}. 
  107.      */  
  108.     public void testSingleRemoveExtension() {  
  109.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  110.         filter.addExtension(".txt");  
  111.         filter.removeExtension(".txt");  
  112.         filter.removeExtension("");  
  113.         assertEquals("The size of extension list should be zero"0, filter.getExtensionList().size());  
  114.     }  
  115.   
  116.     /** 
  117.      * Test method for 
  118.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#removeExtension(java.lang.String)}. 
  119.      */  
  120.     public void testHaveInvalidExtensionRemoveExtension() {  
  121.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  122.         filter.addExtension(".gfdsags,.hdfhdsh,.txt");  
  123.         filter.removeExtension(".txt");  
  124.         filter.removeExtension("aaaa");  
  125.         assertEquals("The size of extension list should be two"2, filter.getExtensionList().size());  
  126.     }  
  127.   
  128.     /** 
  129.      * Test method for 
  130.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#removeExtension(java.lang.String)}. 
  131.      */  
  132.     public void testNormalExtensionRemoveExtension() {  
  133.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  134.         filter.addExtension(".gfdsags,.hdfhdsh,.txt");  
  135.         filter.removeExtension(".hdfhdsh");  
  136.         filter.removeExtension(".gfdsags");  
  137.         filter.removeExtension(".txt");  
  138.         assertEquals("The size of extension list should be 0"0, filter.getExtensionList().size());  
  139.     }  
  140.   
  141.     /** 
  142.      * Test method for 
  143.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#accept(java.io.File)}. 
  144.      */  
  145.     public void testNoExtensionAccept() {  
  146.         File file = new File("");  
  147.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  148.         assertFalse("The gif extension should is not allowed", filter.accept(file));  
  149.     }  
  150.   
  151.     /** 
  152.      * Test method for 
  153.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#accept(java.io.File)}. 
  154.      */  
  155.     public void testInvalidExtensionAccept() {  
  156.         File file = new File("gsdg");  
  157.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  158.         assertFalse("The gsdg extension should is not allowed", filter.accept(file));  
  159.     }  
  160.   
  161.     /** 
  162.      * Test method for 
  163.      * {@link com.ceno.solar.web.tools.ExtensionFileFilter#accept(java.io.File)}. 
  164.      */  
  165.     public void testNormalExtensionAccept() {  
  166.         File file = new File(".gif");  
  167.         ExtensionFileFilter filter = new ExtensionFileFilter();  
  168.         assertFalse("The gif extension should is not allowed", filter.accept(file));  
  169.         filter.addExtension(".gif");  
  170.         assertTrue("The gif extension should is allowed", filter.accept(file));  
  171.         filter.addExtension("dgsagdsa,dgsagsag.gdsag,.gif");  
  172.         assertTrue("The gif extension should is allowed", filter.accept(file));  
  173.   
  174.     }  
  175. }  

相应的目标代码如下:

  1. /** 
  2.  * Copyright(c) 2005 Ceno Techonologies, Ltd. 
  3.  * 
  4.  * History: 
  5.  *   2006-7-4 下午01:20:47 Created by guyang 
  6.  */  
  7. package com.numen.sessionframework.common.io;  
  8.   
  9. import java.io.File;  
  10. import java.io.FileFilter;  
  11. import java.util.ArrayList;  
  12. import java.util.List;  
  13.   
  14. /** 
  15.  * 文件过滤器,用于过来特定的文件扩展名。 
  16.  *  
  17.  * @author <a href="mailto:hyysguyang@163.com">guyang</a> 
  18.  * @version 1.0 2006-7-4 下午01:20:47 
  19.  */  
  20. public class ExtensionFileFilter implements FileFilter {  
  21.     /** 
  22.      * 扩展名列表 
  23.      */  
  24.     private List<String> extensionList = new ArrayList<String>();  
  25.   
  26.     /** 
  27.      * 用于分隔用户扩展名的分隔符 
  28.      */  
  29.     private String separator = ",";  
  30.   
  31.     public ExtensionFileFilter() {  
  32.     }  
  33.   
  34.     /** 
  35.      * todo 缺少方法注释,通常写“构造”。 
  36.      *  
  37.      * @param extensions 添加要过滤的扩展名,多个扩展名之间用分隔符隔开 
  38.      */  
  39.     public ExtensionFileFilter(String extensions) {  
  40.         this.addExtension(extensions);  
  41.     }  
  42.   
  43.     /** 
  44.      * 添加要过滤的扩展名,多个扩展名之间用分隔符隔开 
  45.      *  
  46.      * @param extensions 要过滤的扩展名 
  47.      */  
  48.     public void addExtension(String extensions) {  
  49.         if (null == extensions || "".equals(extensions)) {  
  50.             return;  
  51.         }  
  52.         String[] tempExtentionS = extensions.split(separator);  
  53.         for (String extension : tempExtentionS) {  
  54.             if (isValidExtension(extension)) {  
  55.                 this.extensionList.add(extension);  
  56.             }  
  57.         }  
  58.     }  
  59.   
  60.     /** 
  61.      * 判断是否是有效的扩展文件名 
  62.      *  
  63.      * @param extension 扩展名 
  64.      * @return todo 缺少返回值注释 
  65.      */  
  66.     private boolean isValidExtension(String extension) {  
  67.         return !(null == extension || "".equals(extension) || !extension.startsWith("."));  
  68.         // return extensions.indexOf(".")>=0;  
  69.     }  
  70.   
  71.     /** 
  72.      * 删除不需要过滤的扩展名,多个扩展名之间用分隔符隔开 
  73.      *  
  74.      * @param extensions 扩展名 
  75.      */  
  76.     public void removeExtension(String extensions) {  
  77.         if (null == extensions || "".equals(extensions)) {  
  78.             return;  
  79.         }  
  80.         String[] tempExtentionS = extensions.split(separator);  
  81.         for (String extension : tempExtentionS) {  
  82.             if (isValidExtension(extension) && extensionList.contains(extension)) {  
  83.                 extensionList.remove(extension);  
  84.             }  
  85.         }  
  86.     }  
  87.   
  88.     public boolean accept(File file) {  
  89.         if (file.isDirectory()) {  
  90.             return false;  
  91.         }  
  92.   
  93.         String name = file.getName();  
  94.         int index = name.lastIndexOf(".");  
  95.         if (index == -1 || index == name.length() - 1) {  
  96.             return false;  
  97.         }  
  98.         return this.extensionList.contains(name.substring(index));  
  99.     }  
  100.   
  101.     /** 
  102.      * @return the extensionList 
  103.      */  
  104.     public List<String> getExtensionList() {  
  105.         return extensionList;  
  106.     }  
  107.   
  108.     /** 
  109.      * @param extensionList the extensionList to set 
  110.      */  
  111.     public void setExtensionList(List<String> extensionList) {  
  112.         this.extensionList = extensionList;  
  113.     }  
  114.   
  115.     /** 
  116.      * @return the separator 
  117.      */  
  118.     public String getSeparator() {  
  119.         <span
运行jetty:run时不能保存静态文件的解决办法


在windows中,当使用org.mortbay.jetty.maven-jetty-plugin运行jetty:run时,不能保存javascript、html、css等静态文件,该问题是由于windows的file lock导致,解决办法如下:

修改你所使用的maven-jetty-plugin依赖的对应版本(如6.1.4)的jetty.jar中org/mortbay/jetty/webapp/webdefault.xml line144-147部分内容:
 <init-param>
      <param-name>useFileMappedBuffer</param-name>
      <param-value>true</param-value>
    </init-param> 
为:
 <init-param>
      <param-name>useFileMappedBuffer</param-name>
      <param-value>false</param-value>
  </init-param> 

保存即可。

对于参数
useFileMappedBuffer的解释请参考:line86——line92:
 <!--   useFileMappedBuffer                                                -->
  <!--                    If set to true (the default), a  memory mapped    -->
  <!--                    file buffer will be used to serve static content  -->
  <!--                    when using an NIO connector. Setting this value   -->
  <!--                    to false means that a direct buffer will be used  -->
  <!--                    instead. If you are having trouble with Windows   -->
  <!--                    file locking, set this to false.                  -->


考虑到该jetty.jar其他产品可能会使用,在线上系统中,应该需要改回去,改为默认值true,因此,建议,不要更改jetty.jar中的webdefault.xml文件,而是保存更改之后的文件到某一个位置,以供以后使用,这样对以后发布系统也不会造成影响,如:保存到E:\webdefault.xml.
然后在maven-jetty-plugin的配置中添加上webDefaultXml的配置,如下:

        <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.4</version>
                <configuration>
                    <webDefaultXml>E:/webdefault.xml</webDefaultXml>
                    <contextPath>/</contextPath>
                    <scanIntervalSeconds>15</scanIntervalSeconds>
                    <webAppSourceDirectory>
                        src/main/webapp
                    </webAppSourceDirectory>
                    <scanTargets>
                        <scanTarget>
                            src/main/webapp
                        </scanTarget>
                    </scanTargets>
                    <connectors>
                        <connector
                            implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>${jetty.run.port}</port>
                        </connector>
                    </connectors>
                </configuration>
            </plugin>

这样就可以解决不能保存静态资源的问题,对于开发期间效率提高很大。
 基于commons-net的POP3Client读取gmail邮件内容

/**
 * Copyright 2007-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * History: 2008-8-7 下午11:40:15 Created by Numen
 */
package com.fdcmap.lord.mail;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.net.SocketFactory;

/**
 *
 * @author <a href="mailto:hyysguyang@gmail.com">Numen</a>
 * @version 1.0 2008-8-7 下午11:40:15
 */
public class DefaultSSLSocketFactory implements SocketFactory {

    private SSLSocketFactory socketFactory;

    private SSLServerSocketFactory serverSocketFactory;

    public DefaultSSLSocketFactory() {
        this.socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        this.serverSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
    }

    @Override
    public ServerSocket createServerSocket(int port) throws IOException {
        return serverSocketFactory.createServerSocket(port);
    }

    @Override
    public ServerSocket createServerSocket(int port, int backlog) throws IOException {
        return serverSocketFactory.createServerSocket(port, backlog);
    }

    @Override
    public ServerSocket createServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
        return serverSocketFactory.createServerSocket(port, backlog, bindAddr);
    }

    @Override
    public Socket createSocket(String host, int port) throws UnknownHostException, IOException {
        return socketFactory.createSocket(host, port);
    }

    @Override
    public Socket createSocket(InetAddress address, int port) throws IOException {
        return socketFactory.createSocket(address, port);
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
            throws UnknownHostException, IOException {
        return socketFactory.createSocket(host, port, localAddr, localPort);
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException {
        return socketFactory.createSocket(address, port, localAddr, localPort);
    }
}


public class Main{
public static void main(String[] args) {
        new Main().readGmailContent();
    }
   
    private void readGmailContent() {
        POP3Client client = new POP3Client();
         client.setSocketFactory(new DefaultSSLSocketFactory());
         client.connect("pop.gmail.com", 995);
         client.login("test@gmail.com", "111111");
         POP3MessageInfo[] messages = client.listMessages();
         for (POP3MessageInfo messageInfo : messages) {
         System.out.println(IOUtils.toString(client.retrieveMessage(messageInfnumber)));
         }
    }

}

eclipse 3.4  RC2已经发布

eclipse 3.4  RC2已经发布,功能上应该不会有变动了,eclipse社区真是强大.

当前eclipse的IMA已经开始了,但是Q4E当前的trunk上面的dev已经可以支持基本的功能,基本上可以和eclipse紧密集成,相信要不了多久,即可成为又一利器。Q4E当前的开发非常的活跃,版本发布的也非常快,新的功能支持越来越强大,基本上把m2eclipse远远的甩在后面了。

之前用过的IDE中对maven支持最好的莫过于IDEA,然而很不幸的是idea是收费,更重要的是idea在ubuntu里中文输入一直有问题,此外,当时eclipse的q4e和idea的maven支持比起来还是小儿科,没办法用,这也是前段时间从ubuntu郁闷的跑回windows的原因。但现在看来至少eclipse的对maven的支持Q4E已经足够了,相信很快即可超越idea。由于当前所有的开发都是基于maven构建,对我来说,当前,谁对maven支持最好,我就用谁,所以我还是继续用eclipse,毕竟,开源社区的力量真的很强大。

idea虽然强大,但还是有很多致命弱点,最重要的就是收费,加上ubuntu上中文输入有问题,然而eclipse也是非常强大,这些IDE都很强大,关键还是人用的怎末样。如果哪一天,用任何一个ide都不用去动鼠标,那时,即便是一个很小巧的文本编辑器,你的效率也会比擅长用鼠标的人效率高很多。这也是为什么emacs这么流行这么强大的原因。

这段时间最大的进步也许就是控制自己不去用鼠标,还好,现在基本已经习惯了,这个绝对也是开发工具箱中的一把绝世好剑。

现在可以考虑转到ubuntu了
真是一大快事。
 eclipse 3.4已经发布,m2eclipse更新也很快,感觉比q4e好用很多,还是比较习惯开console里面的maven打印出来的信息,不习惯Q4E的event的方式,而且也不是很喜欢Embedded Maven的方式,很久以前比较了一下Q4E和M2Eclipse,当时    Q4E的确要强大的多,而且开发很活跃,没想到,现在M2eclipse也同样更新的很快,甚至比Q4E还要快,codehaus出的东西,一向都很强大。

工欲善其身必先利其器

提高Java技能的几种简单有效的方法中作者,提到一些方法和措施,这里我也列举几个我认为最重要的,这些也是我这几年来一直在努力遵循实践的。

我列举几个我认为重要的:
一,软件工程技术(大的方向,这个是基础)
1. OO(OOA/OOD/OOP 等,绝不限于这些另如DDD之类的,当然很多这些基于OO技术)
2. 测试
3. 构建
4. scm
5. 架构(这个和OO有重叠)
上面是软件开发的基石

二 通用技术
1.开发模型:RUP,AD,瀑布模型等
2.构建技术:包括工具:make,ant,maven,rake等
3.最佳实践:XP里提到的很多最佳实践其实都很有用:尤其是TDD,Refactor,CI 。

三:工具
1. java/c++/c#等
2.junit/testng/cppunit 等
3.make,ant,maven.
4.TDD,Refactor,CI
5.一个好的IDE,如IDEA,eclipse,netbeans等
6.一个好的文本编辑器,如:vi,emacs,ultraedit

四  开源
1.阅读开源代码,如tomcat,jetty,spring,jboss等
2.参与开源社区
3.参与开源项目

五.订阅邮件列表

请注意,上述这些都是按照我所认为的优先顺序排列的,也是这几年工作中不断实践的,当然,这里面有些是贯穿于整个生命周期。 
The content on this page is provided by a Google Notebook user, and Google assumes no responsibility for this content.