Morning@Weblog

8/13/2005

如何让新人快速融入团队

Filed under: — site admin @ 11:09 am

如何让新人快速融入已有团队,这是团队发展的必修课。正好,最近部门招了些新同事,实践中,让我也有了些许心得。我以为,如下三点实践是具有普遍性的,同时也是值得采纳的:

mentor制度。团队中身经百战、阅历丰富的老成员应该担当起新员工的mentor来,不求多,一帮一即可。mentor的作用有二:
 @ 对于技术新手,大拿们有义务为其指点迷津,答疑解惑,或者甚而对于己所不能者,起码也可以当作倾听者,不时发表自己的见地,这就是XP中的Pair Programming了;
 @ 对于技术以外,mentor也需要通过言传身教,将团队“积习”传授于新人,比如:开发过程、做事习惯、或者大而话之的“团队文化”;
其实,intern的表现对于mentor来讲也会起到督促和鞭策的效果,毕竟身为mentor,如果连自己都还是半桶水,又如何“震得住”intern呢。所以,自认为还有些心虚者就赶紧在私底下补补课吧:)

实战演练。为新人们设计一个有针对性的实战性练习,将必要技能与开发方法融入其中,有专人(老员工)负责监督与跟踪,并在练习结束后给出评价。当然,练习者也要做些总结。期间,结对的mentor也可以适时的做些指导。比如:前阶段在我的提议下,部门给新员工分配了一个JPetStore的改良练习,要求新人们组建起一个虚拟的XP小团队,采用快速迭代的方式,共同协作完成既定任务。从反馈效果来看还算不错。不要认为这样的练习没有必要,相反,为了让新人们快速成长,这样的实验性项目是最好的切入点。你可以大胆的将很多实际工作中需要掌握的技能和工具融入其中,让练习者有切身体会并学有所成。而真正的实际工作,其环境往往不会有如实验练习那么理想,因为要顾及到开发进度和客户压力等诸多外界因素,新工具新技术新方法的引入往往比较谨慎,而人们也往往不能做到时刻专注,长进的效果也就必然会打折扣。作为这一观点的印证,在我前面提到的,部门在给新员工做实战练习的同时,也为另一部门的几位老员工安排了同样的功课,最后从完成的效果来看,则明显差于新员工组建的临时团队。

安排力所能及的独立性任务。在熟识了团队氛围之后,就该给新人安排真正的工作任务了。此时,不必急于将与现有系统耦合较多的任务交给他/她来完成,这样只会让新人一头雾水。比如在产品研发的团队中,可以安排完成一些具有相对独立性的,与既有系统关系不大的Feature。这样,便可以进一步弥补“实战练习”中的缺失与不足,进一步增进团队融合。随着对系统的逐渐深入了解,那时再将复杂任务交于他们也尤未晚矣。此时,新人也便成了老人:)

8/9/2005

团队的Wiki开始真正发挥效用了

Filed under: — site admin @ 11:45 am

团队的Wiki开始真正发挥效用了,这让我感到很欣慰,记得这是我半年多前一手搭建起来的。想来一件新事物从引入到被大家自然而然的认可,并非是很容易的事情。

如今,有的同事会主动想到,要在Wiki上开辟专门的栏目,来统一管理团队交流之用的文档和资料,这是意识上的提升。于是,Wiki就担当起了它本该有的功能和职责——团队的知识文档库。得益于Wiki的“轻量”特点,其实做起来也挺简单的:我使用的是JSPWiki的“改良版”。因为集成了blog,因此所有人都可以将一些日常零散的内容作为个人blog来发布。日后当积累到一定程度,既可在Wiki上单开栏目,并将blog做整理,只要写一个简单的导向链接即可。如果是专门的,正式的文档,则可以直接作为栏目文章,而不是做导向链接。

好的Wiki应该还具备:版本管理、mail通知、RSS订阅、PDF格式导出等诸多功能。这其中,尤以版本管理甚为重要。因为有了它,实际上,Wiki就成了一个SCM系统了,那么基于文档的变更管理就也可以成为Wiki的职责范围了。

8/2/2005

实践TDD&Refactoring的一个典型案例

Filed under: — site admin @ 7:02 pm

前段时间在做部门平台的再工程,其中有一项工作是去除原先核心模块的EJB代码。这实际上是一个典型的重构过程,因为它非常符合重构的定义——在保证外部功能不变的情况下,对现有代码的调整。于是,我在每次修改代码之前,都会先将原有代码所反应出来的外部逻辑用测试用例表达出来,从而保障修改过程是在严格的契约控制之下进行的。这可以说是在我开始实践TDD&Refactoring至今,所遇到的最为典型的一个案例了。

不过期间也发现一些问题,比如:既有代码本身的易测性对TDD的施行是至关重要的。一个难于测试的软件系统,会导致编写测试用例异常困难(比如代码中大量的使用Factory或Single模式)。开始阶段,我花费了大量时间在编写测试用例上(实际上,在代码修改上花费的时间所占比重并不大)。其中的一个测试用例,直到最后完工时,才跑通,整个周期非常之长。于是这就很容易导致一种倾向:即使有心实践TDD/Refactoring者,在经历了一段费劲周折而进展甚微的过程之后,就干脆放弃了测试,冒险直接修改代码。

结合TDD,重构遗留系统的过程中遇到的另一个常见问题是,系统的耦合导致测试用例很难写。这一点在我的这次实践中也有所体现。也许针对某一个类的测试会牵涉多方“利益”,此时,最为有效的办法就是借助于mock test。假如只关心系统某个部分的测试,那么就务必要利用mock object来将之隔离。当然mock object的编写也是需要计入成本的。同时,这也教会我们,在编写实现代码时,务必要考虑可测试性,诸如针对接口编程这样的实践是一定要贯彻执行。

6/10/2005

FDD与XP的异同

Filed under: — site admin @ 6:25 pm

from Feature Driven Development and Extreme Programming

- Both processes are highly iterative and results oriented.
- Both people focused instead of document focused.
- Together with new tools and techniques are enabling and encouraging analysis, design, code, test and deployment to be done concurrently

[Team size]
XP is designed to work with projects that can be built by teams of two to ten programmers.
FDD was first used with a team of 16-20 developers of varying abilities, cultural backgrounds and experience.

[Metaphor and Model]
XP process begins with the Business writing stories on index cards. The whole project is guided by a system metaphor.
The enormous difference is FDD’s additional development of an overall domain object model. According to the car driving analogy, a domain object model is the map to guide the journey which can prevent you from driving around in endless circles. Reducing the time spent refactoring increases the time that can be spent adding new features.

[Collective Ownership or Class Ownership]
Collective ownership usually degenerates into non-ownership as the number of people involved grows.
Feature teams solve the problems that XP has mentioned, while keeping the well established benefits of individual code ownership:
- the feature team owns all the code that needs changing for a particular feature.
- The irritating development is caught at code inspection by the feature team and rejected.
- Owners of closely associated classes frequently work in the same feature team. Knowledge is clustered rather than randomly scattered.

[Inspections and Pair Programming]
XP uses pair programming to provide a continuous level of design and code inspection.
FDD promotes more formal inspections by feature teams. It is not unusual to see two members of a feature team working together where care is needed.

[Testing]
FDD takes unit testing almost for granted as part of Build By Feature. It is acceptable to use XP unit testing techniques in an FDD environment. FDD does not specify this because technology and resources differ so much between projects. In some circumstances it is very difficult to produce a set of completely isolated, independent tests that run in a reasonable amount of time.

[Reporting]
XP leaves tracking to the project managers, encouraging them to minimize the overhead of collecting data and use large visible wall charts.
Tracking By Feature in FDD describes a low-overhead, highly accurate means of measuring progress

6/9/2005

process, from forground to background

Filed under: — site admin @ 11:07 pm

A well-defined and lightweight process. Team members apply them several times, make refinements, and commit the process to memory. It becomes second nature to them. It becomes a good habit. It allows the team to carry out the basic steps, focusing on the content and results rather than process steps. The process itself moves from foreground to background.

软件过程的实践,应该是:经过若干次重复实践,而后精化,并逐渐形成记忆以成习惯。最终,要使得团队成员能够将注意力集中于结果与内容上,而非过程步骤本身;要使得过程规则从台前转入幕后。从规则到习惯,这是必由之路。由此也可以看到,好习惯的积累与传承,需要多次重复实践与磨合,需要所有团队成员的协作,这是十分难能可贵的。顾而,一个好的团队的形成,也并不是一件容易的事情。

6/3/2005

关于团队对于软件过程的基本认同

Filed under: — site admin @ 11:43 am

[ 这里的文字来自于昨天在BJUG maillist中与网友Jerry lin讨论的关于敏捷方法的一点个人意见]

在一个摸索前行的XP团队中,部分XP实践都是在过程中不断积淀而来的。因而在对XP还没有多少实践体会之前,要求团队成员在多大程度上认同某种过程和制度,那是有疑问的,因为一切规则都还没有最终成形,团队也还没有找到适合自己的开发模式,何况XP也并未教授人们具体的行为细则。

不过,要说最基本的认同,XP,或者AM,有着"先天"的优势,那就是在它的"敏捷宣言"里提到的四点,这充分迎合了软件开发活动中的绝对主体——程序员们的口味。这是一个起点,而后,剩下的路都还要大家自己去走。

(昨天在sina上看到Martin Fowler北京之行的活动报道,面对主流媒体,还有高校与国家机构的负责人参加。我想,Martin Fowler的这次来华,对于中国软件开发的现状,以及敏捷方法在中国的推广和传播,或许是影响深远的:)

5/29/2005

重构,错误,测试

Filed under: — site admin @ 10:56 pm

Made a mistake in a refactoring and chose to forge ahead, writing another test to isolate the problem.

这句话摘自Kent Beck的TDD一书。重构与测试一直以来都被认为是休戚与共,息息相关的。这一点也贯穿于TDD实践的始终。在我看来,上面这句话的指导意义在于:
1) 如果将软件开发中的重构行为比作排布与时间轴上的若干活动序列,那么TDD不只是孤立的讨论某个时间切片上的测试技法,TDD同时也为从一个重构活动到另一个重构活动的“迁移”提供了指导。当重构发生错误时,请适时的引入测试来隔离该错误。我以为,除了isolate the problem,这里还有catch and track the problem的意味。
2) 将重构时发生错误作为一个适时加入测试用例的契机。以往,我觉得对于一个“遗留”系统,加入测试用例的最好时机是后期测试出现bug的时候,所谓“亡羊补牢”。而事实上,开发过程中间的每个环节也存在补写测试用例的时机。这里便是一个恰到好处的引入点。

下面这段话,从反面进一步说明了重构与测试的关系。与上一句到是有种“遥相呼应”的感觉。

Write the tests you wish you had. If you don’t, you will eventually break something while refactoring. Then you’ll get bad feelings about refactoring and stop doing it so much.

4/19/2005

a way of known-to-unknown

Filed under: — site admin @ 6:12 pm

我们通常所说的软件设计与开发过程,无论是top-down(自顶向下)还是bottom-up(自底向上),都是一种过于简单化的认识。由此产生的对TDD的错误认识也就变得可以理解了。关于这一点,Kent Beck在他的TDD一书中讲的非常透彻:

A program grown from tests can appear to be written top-down, because you can begin with a test that represents a simple case of the entire computation. A program grown from tests can also appear to be written bottom-up, because you start with small pieces and aggregate them larger and larger.

Neither top-down nor bottom-up really describes the process helpfully. First, a vertical metaphor is a simplistic visualization of how programs change over time. “Growth” implies a kind of self-similar feedback loop where the environment affects the program and the program affects the environment. Second, if we have to have a direction in our metaphor, “known-to-unknown” is a helpful description. Known-to-unknown implies that we have some knowledge and experience on which to draw, and that we expect to learn in the course of development. Put these two together and we have programs growing from known to unknown.

4/9/2005

XPlanner impression

Filed under: — site admin @ 10:33 pm

XPlanner,从其字面意思即可大致了解其用途:作为XP实践的专用的计划订制与任务跟踪工具。XPlanner没有覆盖到所有的XP实践,主要集中于Planning Game方面,包括:User Story和Iteration Planning,同时还部分的融入了Pair Programming。XPlanner的定位,也就决定了它的局限性,不能作为一个XP过程实践的完整解决方案。

初次使用XPlanner的第一感觉是——清爽干净。不像某些软件,一开始就在页面上罗列很多功能,让人摸不着头脑。简洁的界面布局,风格一致的功能排布(这一点有些像AnthillPro),除了那些个图表最初让人有点费解之外,剩下的功能对于一个了解XP背景的人而言,完全可以在没有说明文档的前提下很快的掌握其大致功能(BTW:XPlanner的文档也确实有些简陋)。

基本功能包括:
- 简单的用户信息及帐号管理;
- 简单的项目管理,支持多个项目并行管理;
- 支持为每个项目定义多个迭代周期;
- 支持为每个迭代周期指定多个User Story,User Story的摘要信息包括:提交人(即XP中的customer),估计时间、实际执行时间、剩余时间、优先级(由customer定义),以及该Story的跟踪者;
- 支持为每个User Story定义多个Task,Task的种类有:Feature、Deb(a)t(e)、Defect、F(unction)Test、A(cceptance)Test、Overhead。Task的摘要信息包含:估计时间、实际执行时间、任务承担者等。可以分阶段为Task指定起始时间、结束时间、执行时延,这样可以反映“将一个Task细分为多个阶段”的实际情况,还可以根据情况为每个阶段定义最多两个执行人(在这里体现了pair programming实践)
- 支持在不同层次上(Project/Iteration/User Story/Task)将XPlanner数据导出成xml、pdf等格式;
- 在用户使用Xplanner期间,它会自动记录用户操作(谁什么时候做了什么事情),以及系统级事件(Container Event),作为跟踪项目过程执行情况的一个依据;
- 在定义Project/Iteration/User Story/Task时,其描述信息可以使用Wiki的语法格式。因而,也可以在描述中加入URL,以指向Xplanner之外的Web页面。在XPlanner中,与第三方工具的结合使用,主要也就是通过这样的简单形式加以实现的;
- XPlanner中有一个比较有意思的功能叫做Integration。它用于在软件版本(手工)集成期间,为所有的team member提供一个形象而直观的dashboard,以反映当前的集成情况。
1) 首先由集成者发起一个Integration操作,这样,其他人就可以通过XPlanner看到当前的集成是由谁在做的
2) 与当前集成相关的人员可以通过XPlanner所提供的功能“加入”到waiting line中来,而已经加入的,则可以选择leave line。
3) 最后,集成者可以选择finish或cancel当前的集成工作,这个集成过程被跟踪并记录下来。
- XPlanner利用Progress Bar来反映Iteration/User Story/Task的当前完成情况,还提供了诸多简单的图标统计功能:Matrics,Charts,来反映项目当前的执行情况。

这里是XPlanner的网站地址,http://www.xplanner.org

3/17/2005

不要老说“以后”

Filed under: — site admin @ 9:41 am

我时常听人说类似这样的话:“以后我们要逐渐建立统一的异常处理机制”,“以后我们要对程序中出现的重复代码做出改进”,……

举一个生活中的例子,我和我的邻居在做饭时的一个显著差别是,我喜欢在做完饭之后即刻刷锅,而我的邻居总是想在吃完美味可口的饭菜之后再处理这些杂务,而事情的结果往往是到第二天开始做饭之前,锅碗瓢盆依旧。

不要将对错误的改正希望寄托于“以后的某个时间点”。通常,在一个缺乏经验的小型开发团队中,没有人能够想到会主动发起这样的“改良运动”。解决方法有三:

- 首先是意识上的提高,主观拒绝类似“以后”这样的字眼;
- 随手可做的事情就随手做,在新加入模块/代码的时候,不要重复已有的过错,虽然明明知道那样不对,比如重复代码,不良设计等等,而是要尽可能做到统一,充分考虑体系的一致性,概念完整性;
- 在迫于压力的前提下(如果客观上这是真实的,并非主管的),可以考虑某种为将来做好打算的过渡手段;

3/15/2005

有感于软件过程实践的彼此关联性

Filed under: — site admin @ 7:37 pm

软件过程中的很多实践都是彼此关联的。在试图努力做好某项实践的时候,你也不得不承认,你需要同时考虑其他的关联点。尤其当你在某项实践上所花费的时间相当客观的时候,为了使这样的投入不致于失去意义,就迫使你更有理由关注其关联点。以下就是几个例子:

为了使Refactoring能够更快捷、方便地进行,你的代码必须有良好的风格和大家都同意的代码标准(coding standard)。

如果你们成对编程(pair Programming),那么你们就能更好地处理复杂的Refactoring,也使得Refactoring出错的可能性大大减小。

更进一步,测试(testing)能够使得Refactoring更加安全。

3/14/2005

规则的启示

Filed under: — site admin @ 8:44 pm

规则不是强制性的,而是辅助性的,其目的在于以一种"将侵入性降至最低"的方式来提醒大家如何工作。一旦规则发挥了作用,境况得到了改善,那么你就可以进一步修改规则,让开发进行得更顺利。最后,当规则成为习惯之后,就可以把它们完全置之脑后了。不过此前,如果没有规则,你可能无法开始改善近境况,不会有任何起步的机会。

3/8/2005

objective and replicable test

Filed under: — site admin @ 10:16 pm

在unit testing方面有这样的一个“公式”:objective test + replicable test = simple test programme,可以看出objective和replicable是unit testing的最关心的两样东西。objective意味着每个TestCase都应该是有目的的,并非人云亦云的“每个class都应该进行unit testing”;replicable则意味着TestCase应该是可重用的。

1/26/2005

Subversion搭建及使用中的问题

Filed under: — site admin @ 11:15 am

网上见到的多是关于Subversion搭建的简单讲解,也许有关于“问题的讨论”更为有价值一些。

在Subversion实际的搭建和使用过程中遇到了几个问题,记录如下:

我在实际搭建时主要参考的是蔡焕麟的《Subversion for Windows 安裝指南》一文,但是在配置完毕重启Apache时总是提示失败(The request operation has failed!),后来发现是少拷贝了一个dll:intl.dll。不过我的操作系统是Windows 2000,而先前在一台XP机器上却没有问题,也许蔡先生的使用环境多半是XP,所以没有遇到这样的问题。

另一个问题与导入有关。我在导入一个原先存于cvs的project时,使用了svs的import命令。但是svn进程在运行了一段时间之后似乎死锁了。后来查找资料发现似乎这个功能一般不常用,一种替代方法是先check out一个空代码库到本地工作目录,然后将要导入的项目添加进来,再用add命令提交。后来我用TortoiseSVN客户端提交成功,但是在提交时发现:除了cpu一直100%运转之外,TortoiseSVN对内存的占用也是一直在上升的(我怀疑这与其实现算法不无关系)。并且,越到后来其提交的速度也越慢。这样的现象在规模较大的project中体现较为明显,比如包含的文件太多(我的project大约有将近3000个文件),目录结构太复杂。我使用的笔记本内存是512M,如果是1G内存的机器,性能方面会好许多。但是,这也足见svn及其相关工具目前而言还不甚稳定。在遇到这样的问题时,可以采取“分批”提交的方式了,svn在面对小规模project的时候还是可以应付自如的。

另:
冰云说svn客户端在解决冲突方面还不如cvs好,不过我还没有实际体会。

subclipse插件的使用也似乎有些问题:在我的试验环境中,当恢复到以前版本时,如果事先做了代码重构,往往会丢失掉重构以前的文件。去看了一下subclipse的网站,似乎自去年2月发布0.9.0版本以后,就一直没有更新过了。有鉴于此,我建议team member在使用svn时,以使用客户端工具(而非subclipse)为宜,因为这样比较可靠一些。此外,subclipse的功能也比较有限。

1/25/2005

Subversion/Apache搭建过程纪录

Filed under: — site admin @ 9:25 am

根据网络资源整理,结合实际搭建情况,在此做一简短小结:
- 分别安装Apache和Subversion;
- 把<subversion_root>/httpd/下的mod_dav_svn.so和mod_authz_svn.so复制到<apache_root>/modules/
- 把<subversion_root>/bin/下的libdb42.dll、libeay32.dll、ssleay32.dll复制到<apache_root>/bin或<apache_root>/modules
- 手工编辑<apache_root>/conf/httpd.conf,取消下面两行的注释:

#LoadModule dav_module modules/mod_dav.so
#LoadModule dav_fs_module modules/mod_dav_fs.so

然后在所有module的最后面新增两行:

LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

- 手工创建svn的代码库目录<repo_path>
- 运行svnadmin命令,创建代码库:

svnadmin create <repo_path>

- 修改httpd.conf,在末尾增加:

<Location /<repo_url>/<project_name>>
 DAV svn
 SVNPath <repo_path>/<project_name>
</Location>

可以增加多个Location,每个Location对应一个project,它们都位于同一个svn代码库中;
- 也可以利用SVNParentPath来简化上述配置:

<Location /<svn_url>>
 DAV svn
 SVNParentPath <svn_repo>
</Location>

- 重启Apache,通过<svn_url>访问svn代码库,以验证配置是否正确;
- 运行svn的import命令,将代码导入svn代码库:

svn import . <svn_url> -m “…”

其中“.”为当前路径;
- (使用文件方式实现客户端验证)修改httpd.conf,增加用户验证相关配置:

<Location /<repo_url>/<project_name>>
 DAV svn
 SVNPath <repo_path>/<project_name>
 AuthType Basic
 AuthName “Subversion Repository”
 AuthUserFile <userfile_path>
 AuthzSVNAccessFile <accessfile_path>
 AuthGroupFile d:/passwd/groups
</Location>

AuthUserFile指定了用户密码,利用<apache_root>/bin下的htpasswd命令可以创建一个经过加密的用户密码文件:

<apache_root>/bin/htpasswd –c <userfile_path> <user_name>

如果要生成明文文件,可以加上-p参数。
明文的密码文件格式如下:

username:password

AuthGroupFile指定了群组关系,可以在文本文件中手工定义,格式如下:
groupname: username1 username2

AuthzSVNAccessFile指定了详细的权限控制,可以在文本文件中定义,格式类似如下:

[project_name:/]
* =
zhangs = r
lis = rw

除了基于文本文件的简单验证机制外,SVN还支持通过Windows域用户身份、数据库、LDAP等方式来验证。

1/24/2005

DBUnit使用零星小结——DataType

Filed under: — site admin @ 9:15 am

※ DataType

关于数据类型的表达,在DBUnit包中有一个专门的DataType包,里面定义了许多类,分别对应各类数据:
- NumberDataType
- IntegerDataType
- LongDataType
- FloatDataType
- DoubleDataType
- DateDataType
- TimeDataType
- TimestampDataType
- BooleanDataType
- …

它们都派生自抽象类AbstractDataType,而AbstractDataType最终又是派生自另一个抽象类DataType,这看起来有些怪异。DataType里面预定义了很多DataType的静态实例,做法于DataOperation如出一辙:

public static final DataType CHAR = new StringDataType("CHAR", Types.CHAR);
public static final DataType VARCHAR = new StringDataType("VARCHAR", Types.VARCHAR);
public static final DataType CLOB = new ClobDataType();
……

有两个类型比较特殊,ClobDataType是从StringDataType派生的,BlobDataType是从BytesDataType派生的,后二者再从AbstractDataType派生。

众多DataType的职责基本上就是对JDBC ResultSet和PreparedStatement的简单再封装,然后等待DBUnit的上层框架代码调用。关键的方法主要包括:getSqlValue、getSqlType、setSqlValue、typeCast、compare。以NumberDataType为例,其setSqlValue和getSqlValue方法如下:

public Object getSqlValue(int column, ResultSet resultSet)
throws SQLException, TypeCastException
{
BigDecimal value = resultSet.getBigDecimal(column);
if (value == null || resultSet.wasNull())
{
return null;
}
return value;
}
public void setSqlValue(Object value, int column, PreparedStatement statement)
throws SQLException, TypeCastException
{
statement.setBigDecimal(column, (BigDecimal)typeCast(value));
}

然后是一个DataTypeFactory接口和一个缺省实现类DefaultDataTypeFactory,负责根据不同type创建相应DataType实例。

1/21/2005

DBUnit使用零星小结——DatabaseOperation

Filed under: — site admin @ 9:25 am

近日,在使用DBUnit的时候,走马观花的零星阅读了部分实现代码,于是忍不住想做些小结:

※ DatabaseOperation
我们在使用DBUnit的时候,通常会在test case开始运行的时候做数据库记录的准备工作。比如从外部xml文件读入数据,然后做一个干净的插入操作:

IDatabaseConnection dbUnitConn = ...;
...
IDataSet dataSet = new FlatXmlDataSet(inputStream);
try
{
    DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet);
}
finally
{
    dbUnitConn.close();
}

类似的数据库操作还有不少,比如:DatabaseOperation.NONE,DatabaseOperation.REFRESH,DatabaseOperation.INSERT ,DatabaseOperation.DELETE,DatabaseOperation.DELETE_ALL。

那么这些预定义数据库操作是在哪里实现的呢?实际上DatabaseOperation在DBUnit中是一个抽象类,而许多具体操作则都是直接或间接从该类派生而来的,并且要实现其excute方法。这里有几个派生类值得注意。

CompositeOperation:内含一个DatabaseOperation[]类型的action数组,实际上是一个action容器,其excute方法会逐个调用每个action的excute。这么做是很tricky的,是compoiste模式的活用。

AbstractBatchOperation:实现了批操作,另有三个类都是从其派生的,它们分别是:InsertOperation、DeleteOperation、UpdateOperation。以下是其核心的执行逻辑:

// For each table
while (tableIterator.next())
{
    ITable table = tableIterator.getTable();
    // For each row
    int start = _reverseRowOrder ? table.getRowCount() - 1 : 0;
    int increment = _reverseRowOrder ? -1 : 1;
    for (int i = start; ; i = i + increment)
    {
        int row = i;
        // for each column
        Column[] columns = ... // get columns;
        for (int j = 0; j < columns.length; j++)
        {
            Column column = columns[j];
            statement.addValue(table.getValue(row,
                    column.getColumnName()), column.getDataType());
            statement.addBatch();
        }
        statement.executeBatch();
        statement.clearBatch();
    }
}

DummyOperation:对应DatabaseOperation.NONE,实际上位于DatabaseOperation内部的一个静态私有类,execute方法是空的。

最后,DatabaseOperation还定义了一组static实例:

public static final DatabaseOperation NONE = new DummyOperation();
public static final DatabaseOperation UPDATE = new UpdateOperation();
public static final DatabaseOperation INSERT = new InsertOperation();
public static final DatabaseOperation REFRESH = new RefreshOperation();
public static final DELETE = new DeleteOperation();
public static final DatabaseOperation DELETE_ALL = new DeleteAllOperation();
public static final DatabaseOperation TRUNCATE_TABLE = new TruncateTableOperation();
public static final DatabaseOperation CLEAN_INSERT = new CompositeOperation(DELETE_ALL, INSERT);

这里的CLEAN_INSERT就是用的CompositeOperation,然后分别包装了DeleteAllOperation和InsertOperation实例。

在这里,DatabaseOperation还同时扮演了Factory的角色。不过这样就在基类中暴露了子类信息,按理说是一种不良设计。但是针对这个具体场合,类结构本身就是比较固定的,也很容易维护,也就不用再多此一举的把问题复杂化了。

1/20/2005

自动测试 vs. 人工测试

Filed under: — site admin @ 11:08 am

来看一个经典的例子,通常的登录测试验证包含如下几个步骤:
- 获取登录页面
- 验证页面标题是否为登录页面
- 填写用户名和密码
- 点击确定按钮
- 验证页面标题是否为登录后的首页

也许这样的简单例子不足以让你觉得有必要采用自动测试技术,但是请考虑以下若干变体:
- 如果试图不通过登录而访问首页会怎样?
- 登录失败页面将重新导向登录界面
- 对于其他页面,是否保证了没有经过有效登录将不会显示。

虽然通常情况下,人工测试更为灵活,单次执行的代价也不高,并且相对于自动测试一开始就要编写一堆测试脚本而言,起步很低。但是,在测试需要反复多次执行的时候,人工测试就体现出其代价高昂与低可靠性的缺点了。以上面的登录验证为例,对于手工测试者而言,他需要记住所有这些场景并逐一验证,这是繁琐而易错的。

鉴于自动测试在这一点上的优势,如果不用花费太大代价就可以实现自动化测试,那么应该尽可能实现测试的自动化,因为它是有效保证代码质量的关键。

1/19/2005

功能测试 vs. 单元测试

Filed under: — site admin @ 9:47 am

持续集成所带给我的关键启示之一就是自动化测试的重要性。说到测试,一般包含功能测试和单元测试。对于两者的区别,以及何种情况下使用何种手段,这里到是有一个极为典型的例子,它说明:功能测试并不能完全取代单元测试,两者应该是齐头并进的。

场景描述:
- 一个web应用程序中要显示html表格,其数据来自于后台数据库;
- 表格的最大行数是20,一旦超过20行将会分页显示,如果没有任何数据,则会显示一条提示信息;

为此我们可以分几种情况来测试:
- 没有数据
- 1条数据
- 20条数据
- 21条数据
- 40条数据
- 41条数据

一个直观而笨拙的做法是通过操纵数据库来进行测试。但是,这么做不仅低效,同时还潜藏危险:如果基于同一数据库的两个test case并发执行会怎样?它们有可能彼此破坏测试数据。如果测试异常终止,数据库的状态是否能够回滚?

由此可见,整个过程用功能测试来完成是十分困难的。但是,如果用单元测试就可以轻松搞定,因为它可以脱离数据库而进行测试。

在本例中,我们可以利用单元测试来验证html表格的具体显示逻辑是否正确,然后再借助功能测试来验证表格显示逻辑是否被成功调用。

1/16/2005

CVS的使用,多个module还是一个module?

Filed under: — site admin @ 8:35 pm

针对软件的多个模块,有一种选择是在配置管理系统中为其各自建立一个项目,在CVS中就是为其建立不同的module。这样一来就会带来一个问题:如果模块之间彼此依赖,那么为了取得不同的软件模块,开发者就不得不记住哪个模块的哪个版本使用了其他模块的哪些版本。

尽管有些场合下,你不得不将源代码分项目组织,但是这种情况出现的几率要比你想象的小得多。实际上完全可以从一棵源代码树上分出多个模块来,上面那些问题完全可以通过构建脚本来加以解决(比如:ant),而不是去改变软件的存储结构。虽然你可以使用诸如anthill、cruisecontrol这样的持续集成平台来管理项目间的依赖,但是前提是你在自己的开发团队中采用了持续集成手段,并且这种做法依然是值得商榷的。我能想到的最简单的理由是,用一个项目组织也照样运转的好好的,何必多此一举呢?

1/15/2005

持续集成平台经验教训总结(4)

Filed under: — site admin @ 7:37 pm

(关于如何编写测试用例,也许需要专门的单独篇幅来讨论,这里只是阐述其重要性)

- 自动化测试在持续集成中的重要性

如果只是软件的自动编译,那么很多运行期错误还依然潜藏着。持续集成的排错能力很大程度上取决于测试技术,只有测试覆盖足够大,才能找出足够多的bug(当然不是全部bug)。所以一定要强调测试环节,这从一个侧面,也体现了测试的重要性。

测试包含单元测试和功能测试,应该尽可能提高自动化程度,这里尤指功能测试。针对web应用,可以利用Canoo webtest和HttpUnit等工具,实现自动的可重复的测试。

对于测试用例的编写,通过反馈来提高测试的质量(XP的一项核心价值)很具有指导意义:对于在单元测试中逃脱的bug,应当在修正错误的时候,同时补上相应的测试用例,以确保单元测试集不会再次把错误放过。并且,这个测试应该引导你去考虑编写更多的测试,以加强和完善测试集。

总之,运行更详尽的测试集可以大大提高持续集成的价值。同时,作为一种积累,一套完善的测试集,以及自动化的测试过程,从产品发展的长远角度来讲也是很有必要的。

1/14/2005

持续集成平台经验教训总结(3)

Filed under: — site admin @ 2:21 pm

- 关于自动构建的时间间隔

首先说说每日构建。两次构建的间隔是一天,由于一天里的代码变动不会很多,因此发现问题就会很快,问题的解决也会相对容易。这可以看作是分散了“捉虫”的总体代价,减少了团队成员在集成阶段“捉虫”所耗费的体能和时间。同时,一旦遇到构建失败,你也可以很容易的快速恢复到上一次成功构建的版本。微软在上千万行代码的项目中仍然坚持每日构建。

经验数据显示,集成的工作量是与两次集成间隔时间的平方成正比的。在XP社群中,每日构建被认为是最低要求。实际上,如果以往的集成力度不够的话,在版本临近发布的阶段,小于一天的高频度集成完全是可以接受的,如果有足够多的测试保证的话,如此频度的自动构建可以有助于我们在频繁更动和集成的同时,随时发现问题。这样我们就不用害怕要为随时可能到访的客户做软件演示了,也不用专门准备一台演示用服务器,装上一个主观认为没有问题的专为演示目的的“过时”版本了。

- 性能是问题吗?

对于每日构建而言,性能不是问题。既然平台已经成功搭建,所有程序都是跑在持续集成服务器上的,因而并不会影响每个团队成员各自的本地开发。

对于更短时间的集成频度,比如代码库一有更新就自动构建,就需要一台性能优良的专用服务器了。试想,当开发者将改动结果提交到代码库后,为了看到最近一次集成是否成功需要等待足足半个小时,那是无法忍受的。

有个问题值得注意,用于持续集成服务器自动执行的构建脚本,同时也用于本地开发。因而,在本地开发环境中,除非干净的构建(clean build),建议平时尽量采用增量构建,因为此类构建比较快速,并不对开发人员的开发进度构成太大影响。并且,脚本自身也应该提供足够的灵活度,可以有选择的编译某个部分,或者有选择的运行某些测试,以进一步缩短构建过程。当然,必要的干净构建依然是不能少的,否则某些bug就很容易在本地开发时漏掉。但是不管怎样,此类bug不会逃过持续集成服务器的眼睛。

1/13/2005

持续集成平台经验教训总结(2)

Filed under: — site admin @ 10:46 am

- 持续集成可以做什么?

完整的持续集成过程,应该是一个完全自动化的,并且是可重复的软件构建过程:自动从代码库中取得当前最新的软件快照(snapshot),然后编译、测试、打包、部署、运行。在这里要指明的是,代码库所包含的不单只是代码,还包括构建脚本、属性文件、安装脚本、以及在一台干净的机器上完成一次完整的构建过程所需要的一切素材。

这里的关键在于自动化,即:要让所有的事情都尽可能自动完成——一条命令就可以完成整个系统的构建,完成一整套测试。

- 只实现自动编译的持续集成平台意义不大

如果持续集成只实现了自动编译,那还是远远不够的,要实现这一功能,任何一个开发者只要随时在本地机器上运行一下相应的构建脚本就可以达到同样的目的。

持续集成只有加上足够的自动化测试,相关报告的自动生成,以及软件的自动部署和发布,才能愈加体现出其价值所在。否则,只是让平台编译一下整个软件以验证是否存在编译错误,其意义并不是很大。于是,我认为花很大力气去搭建一个这样的持续集成服务器,完全是没有必要的。

- 持续集成本身不能保证代码质量

有了持续集成并不能保证软件过程的良性改进,构建过程显然也无法保证当前最新的代码是可运行版本。当构建失败时给每个开发者发一封mail并不是最终的目的,相关负责人需要及时纠正错误,并且这最好能得到相应制度的保证。

1/12/2005

持续集成平台经验教训总结(1)

Filed under: — site admin @ 11:00 pm

- 持续集成平台存在意义是什么?

一般普遍地认为,持续集成平台的主要作用在于小版本的持续发布,以及在小版本成功发布的基础之上,团队成员对项目进度把握能力的增强和对预期目标信心的增强。是的,如果是单枪匹马,完全犯不着用持续集成平台,只有当多人协作开发的时候,持续集成才能发挥其应有的作用。因为,它可以发现多人代码集成过程当中的隐患,并且第一时间暴露问题。

- 没有持续集成平台是否可以呢?

当然,在没有持续集成的年代,人们照样开发软件,并且即使是现在,也依然有人在这么做。只是,这样是有风险和代价的,并且,即使有了持续集成平台,没有用好,同样回避不了代价和风险。临近发布阶段的加班加点难道真的是天经地义的吗?在为客户演示时提心吊胆难道真的是亘古不变的吗?我想未必,如果有好的计划管理和过程控制,就不会前松后紧,正如在第一段中所提到的,持续集成平台无疑是一个很好的辅助工具。既然采纳了持续集成平台,那么就应该协力把它尽量做好,而不是半途而废。

- 不经常check in的持续集成是没有意义的

这个结论很容易推得。试想如果每隔几星期才check in一次,那么每日构建在最近一次成功构建之后,要经过一段相当漫长的时间才有可能暴露问题。并且,一旦构建失败,往往是积重难返,因为它是累积了数周以来的全部错误。而剩下的时间里,平台都在报告着一个不痛不痒却又是隐藏危险的Success信号。

因此,对于每日构建而言,实际上隐含要求了每个开发者至少每天要check in一次代码。换言之,持续集成给开发者提出了隐性的开发习惯上的要求,进而一定程度上很自然的约束和规范了开发过程。

1/10/2005

关于CVS的使用,一点小结

Filed under: — site admin @ 4:01 pm

- 强烈建议不要从根部毫不犹豫的commit,因为这样会把很多临时的、私人的目录和文件提交到CVS里面,本着“对自己代码负责,并且尊重同事劳动成果”的原则,建议只对自己每次修改的部分(目录或文件)进行commit;
- 对某些不适合用binary方式提交,而eclipse的cvs插件缺省又会以binary方式提交的文件(比如jsp、tld文件),请在Preferences->Team->File Content中做好设置;
- 一旦发现有不应该提交成binary格式的文件,可以通过在文件上右键点选Team->Change ASCII/Binary Property…来“补救”,建议选择“ASCII with keyword substitution”方式,然后选择“Include files that are already shared in the repository”;
- 建议在commit之前最好先作一下同步,这样一方面可以明确自己要提交的内容,另一方面也可以事先知道存在的冲突。具体方法是,在要提交的目录或文件上右键点选“Team->Synchronize with Repository…”
- 如果因为某此无意间的误操作导致冲突发生,对于带有类似如下片段的文本文件(包括源代码文件),请使用手工编辑的方式解决冲突,然后再次提交:

< <<<<<< .mine
Salami
Mortadella
Prosciutto
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2

- 在开始新的任务之前,应该首先与CVS同步。也就是说,应该首先更新本地机器上的源代码。这是一种良好的开发习惯,在旧有的代码基础上编写代码,只会带来麻烦和混乱;
- CVS的代码库作为统一的代码存放地,保证了软件代码的一致性,绝对不能走回头路;

12/21/2004

CVS Repository里的私人物件

Filed under: — site admin @ 10:09 am

项目在进行到需要将多人的工作集成在一起的阶段时,有时会发现cvs repository里有一些“私人文件”,这当然是大家无意间commit上去的,但是即使现在清除了,很可能以后还会再次出现,因此:
- 如果这些私人物品并不重要,为了保持repository的统一和整洁,应该尽量删去;
- 如果这些私人物品确实重要,可以将其置于本地工作目录之外,以免不小心再次commit;
- 如果希望这些文件在本地的workspace中可见,以便于访问,不妨在eclipse中设置“虚拟”文件夹;
- 如果实在不希望把它们放到其他地方,那么只好在commit时十分小心了。

总之。自己的“私人文件”出现在他人的本地工作路径中,不是一件好事情。实际上,这也反映出一个问题,那就是集成间隔的粒度还是过粗,否则,便可以在更短的时间里发现这些conflicts,相应的清理工作量也会更小一些。

12/17/2004

DBUnit导出数据库的中文问题

Filed under: — site admin @ 5:51 pm

DBUnit在导出数据库记录时,默认情况下采用的是"UTF-8″编码,因此会导致乱码。我在mysql和oracle下分别做了试验,如果是用java application,针对mysql的url字串需要指定编码格式,比如:

jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=GB2312

而对于oracle,则只需要直接给出url就可以了。

查看了一下DBUnit的源码,FlatXmlDataSet的write方法有三种形式:

public static void write(IDataSet dataSet, Writer writer)
public static void write(IDataSet dataSet, Writer writer, String encoding)
public static void write(IDataSet dataSet, OutputStream out)

如果传入OutputStream对象,就会有问题(具体原因待查),而如果传入FileWriter,则一切正常:

IDataSet fullDataSet = connection.createDataSet();
FlatXmlDataSet.write(fullDataSet, new FileWriter("full.xml"), “GB2312″);

encoding参数可以不指定,DBUnit会自动判断,在我的机器上,最后生成的xml,encoding是"GB1030″。

另一方面,如果使用build脚本来实现导出功能,则会遇到一些麻烦:

首先,对于mysql的url字串,如果不将&替换成&,则会遇到The reference to entity “characterEncoding” must end with the ‘;’ delimiter的错误。

其次,通过查找DBUnit的源码发现,dbunit task中的export,是使用OutputStream作为FlatXmlWriter的传入参数的:

OutputStream out = new FileOutputStream(_dest);
FlatXmlWriter writer = new FlatXmlWriter(out);
writer.setDocType(_doctype);
writer.write(dataset);

不得已,只好将其改为FileWriter的,重新打包后即可彻底解决中文乱码的问题了。

12/16/2004

DBUnit的大概印象

Filed under: — site admin @ 12:06 pm

DBUnit的主要应用场合是database-driven project,不过这个似乎是普遍现象:现在的软件,但凡稍有些许规模的,又有几个不需要和数据库打交道的呢。

DBUnit的主要职责在于,保证在每个testcase的执行间隙,将数据库设置为某种我们所预期的状态,从而为下一个testcase的执行准备好database related pre-condition。我们平时强调,testcase应该尽量保持彼此间的独立性,减少依赖,这样可以使testcase变得稳定,同时也只有这样,testcase才真正能够说明问题。但是,在与数据库相关的项目中,testcase间的关联很可能会在数据库这一层暴露出来,这样一来,某个测试的失败可能直接导致database corrupt,进而改变了后续testcase的pre-condition,从而影响了这些testcase的正常执行,并且,这可能进一步导致database corrupt。DBUnit着眼于以数据为中心的testcase,包括数据的读写与验证,由于每次操作的只是数据库的某个局部片段,因此testcase间的数据准备所花费的时间并不像想象中那么多。

另外,DBUnit还有import/export的功能,可以将数据库中的记录导出至flat xml dataset,也可以将该dataset导入到数据库中。不过xml默认使用的是UTF-8编码,因此中文支持有问题。

DbUnit同时还可以帮助你验证数据库的snapshot是否与某一组预期的dataset相匹配。

不过个人觉得,像数据准备这样的事情,不用DBUnit的话,“手工”也可以做,一些开源项目就是这么干的,虽然稍微麻烦一点,不过可以做一些helper classes。

12/6/2004

关于Jira的Task

Filed under: — site admin @ 10:53 am

周末试用了一下Jira,感觉还不错,打算向team member推荐在下一个迭代周期开始的时候使用。

Task

根据我的理解,在Jira中所有要track的issue实际上都可以被称为“task”,只是有些是预先定义好的,比如:bug、feature、improvement。预定义的条目中还有一个直接叫“task”的,我把它理解成“generic task”。我们还可以通过定制自己的task,来加以扩展,比如:test、documentation等等。这为实际的issue tracking的表达,提供了很大的灵活性和适应性,同时也解答了我一直以来的一个疑问:实际的开发过程中,很多东西都是需要跟踪的,不光是bug,feature,improvement,还有一些其他性质的task,甚至是idea,否则很多工作就没法很好的开展。详见这里

当然,这里的预定义的“task”,似乎在概念上还有一点混乱,比如对于bug,按我的理解,叫bug fix似乎更好,因为task是一种达到指定目标的行为,而bug则是task的一个施行对象。

Sub Task、Issue Linking

Jira除了可以自定义task之外,还可以为task指定sub task,这样就可以将某个复杂task细分,从而具有更好的表达力。不过Jira当前似乎只支持一层sub task(最新版本是3.0),但是这样也已经很受用了。另外,对于某些彼此间有关联的task,我们还可以为其定义issue linking,这样我们就可以很好的跟踪一组具有相关性的task了。

12/1/2004

曲折的持续集成自动部署

Filed under: — site admin @ 3:27 pm

第一阶段:原本希望在持续集成平台上可以每天看到一个最新版本的可供访问的web app。最早时想利用jboss或者tomcat的热部署,可是后来发现这很不可靠,尤其涉及ejb。

第二阶段:于是就寄希望于在build脚本中加入自动启停jboss服务器的功能,以便完成干净部署之后,再行启动应用。不过。不过简单的利用exec task执行启动脚本,或者利用java task执行org.jboss.Main都会导致build脚本长时间执行直至jboss停止之后方才结束的现象,即使位于同一个JVM中亦是如此。这是因为ant与jboss同处一个进程所致。为此,我将脚本修改如下:

<target name="stop-jboss">
 <echo>Stoping JBoss</echo>
 <exec executable="${jboss.stop.shell}” spawn="true">
  <arg value="-S” />
 </exec>
</target>
<target name="start-jboss-windows” if="is-windows">
 <echo>Starting JBoss(windows)</echo>
 <exec executable="cmd” spawn="true">
  <arg value="/C"/>
  <arg value="${jboss.start.shell}"/>
  <arg value=">NUL"/>
 </exec>
</target>
<target name="start-jboss-linux” if="is-linux">
 <echo>Starting JBoss(linux)</echo>
 <exec executable="sh” spawn="true">
  <arg value="${jboss.start.shell}"/>
 </exec>
</target>

第三阶段:我在本地的windows环境下似乎屡试不爽(诚然,没有做过太多测试)。可是后来移植到linux服务器之后,发现并非每次都能成功。后来发现是jboss在stop时,并未彻底从内存中消亡。进程依然存在,端口依然打开着。因此导致了再此启动失败。最后实在没辙了,采用强制措施——kill。

for PID in `ps -ef | grep jboss.Main | sed -e ‘/grep/d’ | awk ‘{ print $2 }’`
do
 kill -9 $PID
done

linux的shell确实十分强大,在管道过滤时,还可以支持正则表达式的过滤,总算是领教了。

第四阶段:jboss是没有问题了,可部署了anthillpro的tomcat又有了新问题。由于anthillpro在自动构建期间会产生大量输出,尤其是日志记录。这最终导致了tomcat的catalina.out文件巨大无比,从而使得web浏览器根本无法访问anthillpro的管理界面。本想控制一下anthillpro的日志输出级别(默认是debug),可是修改了log4j的配置文件之后似乎不起作用。后来干脆又采取了强硬措施——定期删除catalina.out文件。些好一个简单的shell脚本,如下:

LOGFILE=$TOMCAT_HOME/logs/catalina.out
if [ -f $LOGFILE ]
then
rm $LOGFILE;
echo “” >$LOGFILE
fi

然后利用crontab功能,在命令行下输入crontab -e,然后输入如下内容:

0 5 * * * /root/rmcatalinaout.sh

rmcatalinaout.sh就是刚才的shell脚本,这代表每天上午5点,该shell脚本将会被执行一次。该设置将以文件形式保存于/var/spool/cron目录下,文件名称即登录的用户名,比如:root。

看起来这个问题似乎是彻底解决了,虽然并不优雅,现在的我,只求不会再生枝节。

11/20/2004

time granulrity,issue tracking,tools vs. human

Filed under: — site admin @ 10:12 pm

time granulrity:

作为时间的一种度量单位,或小时记,或天记,都是灵活的和因人而异的。比如你完全可以将8个小时理解为1天。但是,粒度的确定并非没有原则:为任务分配时间是一种预估,这样的估计如果易变而不准确,那么多半也就失去了意义。一般而言,只有在对所做的工作十分了解的前提下,才能够准确估计。对于一个易变的的环境,陌生的环境,时常被打断的环境,或者时常冒出些“障碍”的环境(比如某个艰深的bug fix),时间的粒度就不亦过细,至少在迭代的初期是如此。随着时间的推移和经验的积累,估计的粒度也就会相应改进。

issue tracking

issue tracking至关重要,否则事态就可能滑入不可控的危险,team member就会失去对项目状况的全景认识,这也是我的切身体会。实际上,不单是bug,feature,defact,……,很多东西都是需要有tracking的。比如某此会议中的一个idea,如果没有某种形式的tracking,很容易就会消失在大家的记忆中,直到哪次再被大家带着一种似曾相识的感情从被遗忘的角落里重新提起。

tools vs. human

我始终觉得,在一个开发团队中,对于工具的使用,总是二等考量,更为重要的是人的因素。因为,即使有趁手的工具,也依然有可能存在缺乏交流的team member,以及由这些team member参加的冗长的缺乏主题的会议。

11/15/2004

To Maven or to Ant ?

Filed under: — site admin @ 9:52 pm

maven最具特色的两点是POM和repository,前者是描述项目对象的基础概念,对应于project.xml文件,而后者则很好的解决了项目依赖问题,包括对第三方库的依赖。相比而言,我觉得后一点更为重要一些。利用集中式的构建资源库,只需要指定jar包的名称和版本,maven就会自动下载对应的snapshot。不过,在我看来这一点用ant也可以做到,只是在maven这边属于内建功能,平滑而自然。而ant则需要更多的人工参与才能达到,不过这对于一个已经经过精心规划的ant-based项目而言,应该算不上什么,积累很重要。

maven的出现晚于ant,实际上其诸多思想都是借鉴ant而来的,并做了改进。它将不少ant中的潜规则“固化”于软件本身,比如目录结构的安排,又比如build.properties文件的加载。这些features为初涉maven者提供了便利。

另外,maven的jelly脚本带有procedure的味道,这与ant是有所不同的(当然ant中也可以内嵌脚本)。尽管适当的过程控制语义可以另问题妥善而方便的解决,不过请记住:无论maven也好,ant也好,毕竟都不是一门过程语言,不要赋予它们过多的责任。

11/14/2004

对Maven大致了解之后的一点整理结果

Filed under: — site admin @ 5:55 pm

Jelly>>
Maven利用Jelly标签库来进行脚本的编写工作。Jelly变量的定义规则与Ant的proeprty不同,赋值后仍可以修改。Jelly脚本是带有procedure痕迹的,比如<foreach>。

jeez标签库>>
jeez标签库已由Maven预先注册,jeez同时包含了ant和werkz标签。因而可以在脚本中直接使用合法的ant task,而无需指定前缀,从而简化了ant的迁移过程。

goal>>
goal有些类似ant的target,并且它是一个werkz标签。一个goal可以包含任何合法的ant标签。在执行指定插件的goal时,需要在goal前面加上插件名作为前缀。

插件>>
Mave利用插件来为紧凑的内核添加丰富的功能。Maven自带了许多插件包。

POM>>
Maven基于POM概念,POM对应于一个project.xml,利用POM的继承机制,我们可以将一些公用的定义内容单独放到一个project.xml中作为父POM,而在各自的子POM中,只编写各异的部分。

maven.xml>>
如果需要对Maven的执行过程做定制,则需要编写maven.xml文件。maven.xml依然使用Jelly脚本。maven.xml由一个<project>开始,内含若干<goal>,每个<goal>则引入脚本的执行逻辑。procedure smell就是在这里得以体现的。

Maven的属性处理机制>>
Maven依次调用下满的属性文件:
${project.home}/project.properties
${project.home}/build.properties
${user.home}/build.properties
Maven的属性覆盖规则正好与Ant相反,“最后定义有效”,由于-D依然具有最高的优先级,因此其处理顺序被排在了最后。

而插件的属性规则又刚好相反,Maven禁止插件的属性覆盖已经设置的属性。由于Maven是在处理完所有其他属性之后再开始处理插件的,因此,我们预先设置的某些属性,就可以反过来对插件的某些缺省属性做“覆盖”,这个规则很像Ant的“property immutable”规则。

Maven提供了缺省的一系列系统属性,对它们的修改,可以定制Maven的行为。

Repository机制>>
Maven采用集中管理库的方式。所有要引用的jar,都统一放到一个repository中,本地或者远程,并且可以实现自动下载,从而解决了项目间共享第三方依赖包的问题。这一点很值得借鉴。主repository位于Ibiblio站点,但是你也可以自己创建一 个远程repositories。

SNAPSHOT>>
建立SNAPSHOT依赖后,对Jar包的依赖可以实现即时的同步。

11/13/2004

对Ant又有了新的发现

Filed under: — site admin @ 5:57 pm

看来应该早一点看看有关Ant 1.6的新特性,关于MacroDef,Import,subant,虽然我现在对他们的使用还不太熟悉,但是我觉得这些正是可以解决我这一周来在使用Ant解决多工程构建与依赖时所遇到的困难。

MacroDef是一种简单的自定义Task的方法(一组现成Task的序列,有别于自己手工编写Task),其调用行为类似<antcall>,不过它还可以和<import>结合,并且显示输出上更友好。本质上感觉<antcall>更带有procedure的味道,而MacroDef则更为自然,也比较符合Ant的精神。

import的功能类似于原来的xml entity import,但是它在target overriding和special properties两个方面对原始的xml entity import做了很大的改进。看来《Java Dev. with Ant》确实有一点点过时了。

subant是批量执行build.xml的指定target的简便方法。

有了这些工具,我的build脚本编写也就不用那么辛苦了。看来过段时间等我有了更进一步的掌握之后,我得重构我的那帮build.xml哥们了。

11/9/2004

JCoverage使用中的几个问题

Filed under: — site admin @ 11:51 am

昨天和今天被JCoverage搞的头都大了,一番折腾下来,小结如下:
- 奇怪的库依赖,instrument的时候总是报ClassDefNoFound,说找不到log4j。开始百思不得其解,后来才发现在taskdef的时候应该在classpathref中指定log4j的所在路径,而不只是jcoverage的jar包路径。
- CreatePross时的IOException,一旦instrument的类超过某个数量(比如800个),就会出现这样的异常,堆栈显示错误的地方在org.apache.tools.ant.taskdefs.Java.fork。这恐怕和jcoverage自身有关,目前最简单的办法就是不要一次“注入”这么多类,可以分批依次“注入”。不然就得看instrument的源代码了。
- instrument无法识别匿名类。尽管以debug方式编译源码,可依然有WARN蹦出,说无法找到***$1的souce line number。
- 没有详细的帮助文档,恐怕开源的东西也真的不好苛求,不知道JCoverage的商业版本是否有所改善,包括前面那个IOException。
- 没有源代码报告的交叉引用,功能上讲这是一个比较大的欠缺,据说Clover就有此项功能。

11/5/2004

持续集成平台实现产品完整自动构建

Filed under: — site admin @ 10:59 am

这两天对AnthillPro持续集成平台在产品代码自动构建方面做了一些试验。

现在AnthillPro每天会自动构建一个产品的版本,源代码位于cvs中,同时还会生成相关的单元测试报告、压力测试报告,以及测试覆盖报告。每天,team member可以通过内部web直接查看这些报告。另外,成熟代码的对外打包发布也实现了自动完成。只要通过AnthillPro做一次No Schedule的Release Build,相应的zip包就会放到用户可以通过网页直接访问并下载的地方。

目前还有一点不足是,没有成功实现自动热部署。一旦实现构建过程中的热部署,那么对于web应用而言,自动构建成功之后,就意味着可以直接通过web方式访问站点。这对处于开发阶段的team member而言可能是很有价值的。我在本地的Windows环境已经调通,但是到了linux下就不灵了。我用的是tomcat提供的Manage API接口,也许是tomcat的热部署还不成熟,总之有待查证。

另外,作为补充,我还打算将CheckStyle加入构建过程,生成的报表同样可以直接访问到。接下来还有一项工作就是,试验AnthillPro环境下,针对大型复杂项目/产品的master build + child build工程构建方式。

基于Ant的集成开发方式——多层面的properties(续)

Filed under: — site admin @ 10:20 am

改动频繁的properties应该独立于build.xml,单独作为外部properties文件。尤其是用户相关的properties,也就是所谓的user-specific properties。这样的文件不用放到SCM中,而是放到各自本地机的user.home中。

长期不变的properties通常可以放到build.xml文件内的首部。这些properties也有存在的必要,开发阶段的偶尔调整是有可能的,它们的作用就像程序设计中的常量定义——改动一处,全盘皆变——方便而又易于维护。当然,这些properties应该是随build.xml文件一起保存在SCM中的。

更复杂的手段是:

除user-specific配置项外,针对项目的一些全局性配置提供project-specific的properties文件。这些配置项的易变性往往低于user-specific配置项,但是长期的开发周期中依然有可能变动,此类properties文件应该放入SCM中。一个最明显的例子就是project.version,该配置项的用途之一是生成可发布软件时作为路径或文件名的一部分,就像tomcat的home路径通常是jakarta-tomcat-

user-specific配置项和project-specific配置项允许互有交叉。当两者冲突时,user-specific可以覆盖project-specific,即前者的优先级更高。根据“immutable properties”原则,这就意味着,build.xml文件中应该首先加载user-specific properties。

根据实际情况,可能还有一类最易变的配置项。那就是每次build过程中都可能变化的properties,称之为build-specific properties。此时,我们可以利用Ant的命令行参数-D来进行配置。由Ant的特性我们可知,此类配置项的优先级是最高的。当然,一般情况下这种配置项还是少一些为好。

加上前面提到的位于build.xml文件内的properties,我将其暂命名为inner properties,各类proeprties的易变性和优先级情况如下:

类别 易变性 优先级 是否位于SCM中
build-specific properties No
user-specific properties No
project-specific properties Yes
inner properties Yes

综上,四种不同级别的properties,足够我们应付大多数项目构建了。

11/4/2004

基于Ant的集成开发方式——多层面的properties

Filed under: — site admin @ 11:12 am

开发阶段,不同用户环境下对程序的发布、部署要求可能不同,比如:配置参数的设置,系统的安装路径,第三方库的路径(假如不打算将第三方库加入发布的ZIP包内的话)。我们可以在编写build文件时借助定制properties来解决这个问题,即将一些配置项抽象为properties,也可以将这些properties单独放在一个外部文件里。在多人协作开发时,如果将这些易变的properties也加入CVS的话,则每此不同人员从CVS下载程序到本地时就必须再次更改,而这种更改可能要伴随CVS中相应文件的更新(build文件,或者是独立的properties文件),这显然是不合适的。

为此,我们可以利用每台本地机上的user.home路径,将properties文件放到那里。该文件不用放到CVS中,build时让Ant自动去目录下找该文件并加载。至于user.home路径,linux下为/home/,windows下一般为C:Document and Settings。其中为当前用户名。也可以在Ant中利用系统环境变量结合进行设置,这样更为灵活。windwos下的环境变量为HOMEPATH,linux下为HOME。

前面提到的properties文件单纯用于build过程,对于最终发布系统中所带的properties文件,我们也可以做类似的灵活性改进。首先编写一个properties的tmplate文件放入CVS中,在build文件的init target中先将tmplate文件copy成“实例”文件,然后将某些可配置项做字符串替换。我们可以将这些可配置项的替换值放到另一个properties文件中,而该文件则不放入CVS,也同样的放到user.home路径下。字符串替换可以用或者

当然,在发布完整的安装包时,可能也需要将这些散落在本地机的properties文件“加入”到安装包中。一种办法是,编写一个示范性的template文件,加入到CVS中,开发阶段并不使用该文件。在生成安装包时将该template文件也打入包中,同时,要在随产品发布的安装文档中加入相关说明。这样,用户就可以按照说明,手工copy一个“实例”文件,然后做一些针对性的配置。这种方法的不足在于要求用户做额外的工作,如果安装包自包含特性很好的话,那么用户也许只需要简单的运行一下run.bat(对于可运行版本而言),或者build.bat(即使是可运行版本也可能存在build过程,就像AnthillPro)。为此,我们需要在完成发布任务的Target中生成一个“final release”的properties文件,这样的文件只用于最终发布是打入包中。

11/2/2004

基于Ant的集成开发方式

Filed under: — site admin @ 10:26 am

开发阶段,不同用户环境下对程序的发布、部署要求可能不同,比如:配置参数的设置,系统的安装路径,第三方库的路径(假如不打算将第三方库加入发布的ZIP包内的话)。我们可以在编写build文件时借助定制properties来解决这个问题,即将一些配置项抽象为properties,也可以将这些properties单独放在一个外部文件里。在多人协作开发时,如果将这些易变的properties也加入CVS的话,则每此不同人员从CVS下载程序到本地时就必须再次更改,而这种更改可能要伴随CVS中相应文件的更新(build文件,或者是独立的properties文件),这显然是不合适的。

为此,我们可以利用每台本地机上的user.home路径,将properties文件放到那里。该文件不用放到CVS中,build时让Ant自动去目录下找该文件并加载。至于user.home路径,linux下为/home/,windows下一般为C:Document and Settings。其中为当前用户名。也可以在Ant中利用系统环境变量结合进行设置,这样更为灵活。windwos下的环境变量为HOMEPATH,linux下为HOME。

前面提到的properties文件单纯用于build过程,对于最终发布系统中所带的properties文件,我们也可以做类似的灵活性改进。首先编写一个properties的tmplate文件放入CVS中,在build文件的init target中先将tmplate文件copy成“实例”文件,然后将某些可配置项做字符串替换。我们可以将这些可配置项的替换值放到另一个properties文件中,而该文件则不放入CVS,也同样的放到user.home路径下。字符串替换可以用或者

当然,在发布完整的安装包时,可能也需要将这些散落在本地机的properties文件“加入”到安装包中。一种办法是,编写一个示范性的template文件,加入到CVS中,开发阶段并不使用该文件。在生成安装包时将该template文件也打入包中,同时,要在随产品发布的安装文档中加入相关说明。这样,用户就可以按照说明,手工copy一个“实例”文件,然后做一些针对性的配置。这种方法的不足在于要求用户做额外的工作,如果安装包自包含特性很好的话,那么用户也许只需要简单的运行一下run.bat(对于可运行版本而言),或者build.bat(即使是可运行版本也可能存在build过程,就像AnthillPro)。为此,我们需要在完成发布任务的Target中生成一个“final release”的properties文件,这样的文件只用于最终发布是打入包中。

11/1/2004

关于Code Coverage

Filed under: — site admin @ 4:56 pm

在整理有关Code Coverage的资料时,从一篇blog上看到的内容,现依据作者观点,整理思路如下:

代码覆盖率用于衡量测试程度充分与否,从而给开发者以信心:

- 代码覆盖率有助于度量测试质量。软件度量技术的不成熟,导致不存在放之四海而皆准的度量方案:考察产品发布后客户反馈的bug数量,很难界定测试和开发的责任,同时也已于事无补;而对于需求/功能覆盖率测试而言,实际的需求文档通常是不完整的,并且人员对需求理解也常常存在偏差。代码覆盖率这一重要指标,应该得到重视的。

- 代码覆盖率有助于风险分析。No risk, no test. 风险分析的准确性主要依赖于分析者的技术素养和其对系统的了解程度。代码覆盖率有助于分析者了解系统,在进行非正式风险分析时,不应该拒绝任何可能有用的信息。

- 开发人员更愿意阅读代码覆盖报告这样看起来比较专业的测试成果。

但是,100%的覆盖是不可能的;并且,即便能做到100%覆盖,也不能证明软件被充分测试,因为每种覆盖算法(路径、语句、方法)都有不足。

那么,覆盖率达到多少为宜?——将代码覆盖报告看作测试的线索即可,并不过分依赖于它。

10/28/2004

提升Ant build文件灵活性的方法(2)

Filed under: — site admin @ 10:43 am

有时候我们需要面对不同操作系统平台的部署和构建,比如在windows和unix平台下,我们可以分别编写两个build文件,不过更为灵活的作法是,利用ant的<os>,然后在target中加上if属性,这样就可以实现仅用一个build文件实现多平台的构建了:

<condition property="os.windows">
    <os family="windows"/>
</condition>
<condition property="os.unix">
    <os family="unix"/>
</condition>
<target name="..." if="os.windows">
    ... ...
</target>
<target name="..." if="os.unix">
    ... ...
</target>

BTW:看了anthillpro的build.xml,未免让人觉得有些遗憾,看起来作者是过分依赖<antcall>了,这是有悖于ant的精神的。

提升Ant build文件灵活性的方法

Filed under: — site admin @ 9:21 am

今天在看到了一条有关Ant使用技巧的blog,说到了利用filterset和filter实现在构建过程中即时替换token的技巧:

<target name="config">
    <copy todir="${build.prod.dir}">
      <fileset dir="${config.dir}">
        <include name="*.properties" />
        <include name="*.xml" />
      </fileset>
      <filterset begintoken="%" endtoken="%">
        <filter token="HIBERNATE.DRIVER"
                value="${hibernate.connection.driver_class}" />
        <filter token="HIBERNATE.URL"
                value="${hibernate.connection.url}" />
        ... ...
      </filterset>
    </copy>
</target>

这里要替换的token位于目标文件,以%开始和结束,而替换的字串(properties)则位于某个properties文件中。这是一种十分常见的提升ant build文件灵活性的方法。另外,利用<replace>也可以实现同样的效果:

<target name="mkpropertyfile">
     <copy file="etc/conf.properties.tmpl" tofile="etc/conf.properties" />
     <replace file="etc/conf.properties"
              replacefilterfile="${build.properties}" />
</target>

target首先根据template文件创建一个实例文件,然后再作替换。${build.properties}对应的文件包含了所有要替换的token及对应的value。并且replace还会根据目标文件的时间戳决定是否替换。
另外,filter也提供了从属性文件导入的方式。具体方法可以参考ant的在线文档

9/30/2004

顺利完成Anthill Pro平台搭建

Filed under: — site admin @ 10:18 am

大概前后花了两天半的时间,终于顺利完成了Anthill Pro平台的搭建。从查阅文档,到尝试搭建;从遇到很多莫名其妙的问题,到一一解决之后的首次Force Build成功,再到连续自动构建的顺利实现。整个过程,不能说饱偿辛酸,但也是颇费周折,不过其中的成就感也是蛮大的。并且,我还完成了一份看起来不错的文档,作为工作小结。

不过,Anthill Pro在有些地方还是不太让人满意的,比如文档滞后就是一个“严重”问题。另外,最新的beta版有几个bug,我在这上面折腾了好长时间,最后才发现是bug。

Anthill Pro可以做什么

  • 可以通过Authentication管理用户,包括增删改,以及为用户指定角色;
  • 可以通过Authorization管理角色,包括增删改;
  • 可以对不同Module的不同部分实现权限控制;
  • 对于Ant Builder而言:
  • 可以为构建的执行指定特定的target;
  • 可以支持两种方式的property传入:命令行;属性文件。后者又有两种方法:命令行下指定-propertyfile参数,或者用<property> task;
  • 可以为Ant的执行添加classpath和环境变量;
  • 利用Build Dependency可以解决项目之间构建的依赖问题。Anthill Pro会自动构建被依赖项目,以获得本次构建所需的必要数据;
  • 可以利用Schedule灵活控制构建的时机;
  • 可以利用Anthill Pro实现规范的产品版本命名方式:<prefix><buildno><postfix>,比如:1.0.0、version-1.9 alpha、version-1.0 (build 999),每次构建时Anthill Pro自动对最末一个数字加一;
  • 可以通过配置工具,随时调整和修改版本号的命名策略;
  • 可以利用邮件的方式有针对性的发送构建失败的通知;
  • 可以通过AnthillPro Intranet查看构建过程的日志记录;
  • 9/16/2004

    release之前要做的工作

    Filed under: — site admin @ 4:09 pm

    The steps that a team needs to cover when preparing a release usually include:

    1 Writing the documentation
    2 Writing any platform-specific bootstrap scripts, batch files, or programs
    3 Writing any installer scripts, using installation tools
    4 Checking all the source, documentation, and sundries into the source repository
    5 Labeling the source in the source code repository
    6 Running a clean build directly off the source repository image
    7 Running the complete test suite
    8 Packaging the software in a form suitable for distribution and installation

    from Java Development with Ant

    9/15/2004

    Tower of Babel

    Filed under: — site admin @ 11:04 am

    Teamwork里的成员彼此使用的词汇时常千差万别,不知道这种现象是否普遍。这是混乱之源,每每看到这样的现象心里总是会生起一股莫名的愤怒。有时想想真该花点时间来统一一下用词,最好能形成一个词汇表。不求包罗万象,至少也应该在一些关键词汇上有统一的说法,这样也便于形成风格一致的文档化的东西。

    代码的风格需要规范,交流的词汇同样也需要规范。另外,组员们也应该尊重词汇表制定者的劳动成果,而不是无视其存在,照样我行我素,麻木不仁,否则就难免Babel塔的下场。

    9/14/2004

    关于如何进行功能测试

    Filed under: — site admin @ 10:59 am

    上周有个同事在向我抱怨功能测试用例说明书太枯燥,用例彼此间太孤立。这里面有我的原因,毕竟我还算是个新手,并且我也希望不要遗漏任何待测的功能细节。不过,这到是启发了我。也许作为测试人员,应该采用如下的测试方式:首先通读用例文档,以理解测试意图(当然,此前对待测系统的使用更是要熟悉)。然后,选用几个典型案例进行测试,而不是照着文档顺序式的逐个测试,几番折腾之后,大概有若干测试条目已经完成,或打勾或打叉。至于剩下的,则再进行针对性的测试。我想这样的话,或许同事的抱怨会少一些。

    9/3/2004

    功能测试的困惑

    Filed under: — site admin @ 3:30 pm

    因为一直把自己的身份定位为开发人员,所以我在软件功能测试方面的经验几乎为零。不过,此番编写用例,也算是亲身体验了一次。终于明白了编写功能测试的测试用例绝非易事。 (more…)

    8/25/2004

    既有项目向ant迁移的十步法则

    Filed under: — site admin @ 12:09 pm

    编译自《Java Development with Ant》

    - 签入。安全起见,将所有内容签入,并标以BEFORE_ANT标志。
    - 清理。清除旧的.class文件以免混淆,将旧的JAR文件备份至安全的地方。此时项目中不应该有任何生成文件。
    - 明确交付内容。通过检查你现有的build工具,列一个有关于你的项目输出结果的清单,以及生成它们的步骤;再列一个Ant target的清单及其依赖关系。
    - 定义目录。定义你的目录结构以及用于引用这些目录的porperty名称。
    - 设计build文件。初步设计你的build文件,或是复用一个现成货。
    - 安置源文件。如果你需要放置源文件到新的目录,现在做。
    - 实现build文件。创建你已经定义好的build文件,或是定制一个现成货。
    - 以verbose运行build。以verbose标记运行build文件,验证其是否工作正常。
    - 添加一些测试。开始编写测试,如果先前没有的话。
    - 完善build文件。根据需要添加更多的target。

    8/24/2004

    项目构建的哲学,5个要点

    Filed under: — site admin @ 10:08 pm

    编译自《Java Development with Ant》

    - 要牢记以目标为出发点
    - 在构建过程中集成测试
    -提供自动化部署的支持
    - 让构建脚本文件可移植
    - 让构建脚本文件可定制
    (more…)

    8/19/2004

    Unit test best practices

    Filed under: — site admin @ 10:59 pm

    译自《Java Development with Ant》

    - 测试每一处可能出错的地方。这是XP所坚持的一条格言。
    - 一个优秀的测试是很难通过的。如果所有测试都顺利通过了,那么你的测试很可能不够充分。
    - 为你发现的每一个bug添加一个新的测试用例。
    - 一旦测试失败,通过编写更多的测试来捕获问题,而非马上求助于debugger。测试越多越好。
    - 以无效参数测试每个方法,而非仅是有效数据。工业级软件需要识别和处理非法数据,而且以错误数据通过测试往往是最说明问题的。
    - 在运行新的测试之前,先清除既往的测试结果。
    - 测试用例采用统一的命名规范:*Test.java。
    - 将测试代码与产品代码分开存放。以同样的包命名结构为两者提供各自独立的目录树。这使得测试与被测对象处于相同的包中,而创建时又保持各自独立。
    - 一旦产生错误(error)或失败(failure),终止创建过程。
    - 为测试起名要贴切。
    - 每个测试方法尽量测试一件事情。

    Powered by WordPress