今天介绍一下Trac,一个基于Web的bug管理系统。
Trac拥有强大的bug管理功能,并集成了Wiki用于文档管理。它还支持代码管理工具Subversion,这样可以在 bug管理和Wiki中方便地参考程序源代码。
bug管理
在Trac中,项目中出现的问题称为ticket。像bug、功能改进、 TODO等都可以写成一个ticket。
- 可设置ticket的优先级别。
- 可以设置ticket的里程碑,表明这个ticket应在何时完成,并能够通过条形图方式显示里程碑的完成度。
- 可以自定义条件生成bug报告,并可以通过SQL语句自定义报告的格式。
Subversion集成
通过Subversion集成,开发者可在Trac中以Web方式浏览代码库中各个版本的代码和代码的修改历史,并可方便地比较各个版本间的差别。
Wiki
支持常用的Wiki语法。同时增加了许多bug管理的专用标记,可以方便地创建到ticket、代码行甚至修改历史的链接。
Trac使用Python写成,后台使用SQLite或PostgreSQL数据库,因此构筑一个完整的Trac系统需要安装以下软件:
- Apache
- Python
- Subversion
- PostgreSQL(可选)
下回继续介绍Trac的安装方法。
最近好多人都在问我GoDaddy的域名转向设置方法。其实我没用过GoDaddy的域名转向,而且据说在中国国内无法访问域名转发服务。幸好GoDaddy对每个域名都提供了免费的虚拟主机,虽然是带广告的,不过我们可以用它来做自己的域名重定向。方法么,自然是用mod_rewrite了。
以下假设你的域名为example.com,并假设你要将 blog.example.com 重定向至 www.myblog.com/myname/,如用户访问 http://blog.example.com/archives/sample.html 则会自动转向至 http://www.myblog.com/myname/archives/sample.html。
下面这一段翻译自GoDaddy的帮助。我的免费空间正在使用,因此没法一步步抓图了。
- 首先登录GoDaddy,然后选择菜单Hosting & Server->My Hosting Account。
- 你会看到页面上有Free Hosting Credits的字样,单击旁边的Use Credit建立虚拟主机。
- 选择你要关联的域名 example.com,然后选择主机类型为 Linux,然后Continue。
- 单击 Set Up Account。
- 在 Hosting Manager 中单击 Accept 接受最终用户协议。
- 输入用户名和密码。
- 单击 Continue。
- 确认之后单击 Submit。
之后进入Hosting Manager就可以管理你的免费虚拟主机了。控制面板功能很简单,我就不多罗嗦了。下面就开始做域名重定向。
首先打开Domain Management,单击example.com下的Add Sub Domain,添加子域名blog,路径随意,这里选择< same as sub domain >。
大约半小时之后状态会变成 Setup。马上通过FTP登录虚拟主机,可以看到主目录下出现了blog子目录。在 /blog 下建立一个 .htaccess 文件,内容如下。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^/(.*) http://www.myblog.com/myname/$1 [R,L]
</IfModule>
GoDaddy服务器会缓存.htaccess文件状态,新建或删除.htaccess文件一般要等待10-30分钟之后才会生效(修改则会立即生效)。因此建议虚拟主机建好之后马上建立空的.htaccess文件以节约时间。
这样所有访问 http://blog.example.com/ 的请求都会被转向至 http://www.myblog.com/myname/ ,而且你可以通过修改 RewriteRule 来定义更为复杂的转向规则。
板桥里人 http://www.jdon.com 2004/08/25
最近,Spring很热闹,因为实现IoC模式和AOP(见本站专栏),然后又成立公司,吸取上次JBoss的教训,文档不敢收费,结果迎来了一片祝贺声。
Spring真正的精华是它的Ioc模式实现的BeanFactory和AOP,它自己在这个基础上延伸的功能有些画蛇添足。
其实说白了,大家"惊奇"的是它的IoC模式(使用AOP功能需要了解AOP,比较难),那么,Spring之类的Ioc模式是什么? 就是:你在编制程序时,只要写被调用者的接口代码,具体子类实例可通过配置实现。
Ioc模式是什么知道的人不多,但是,当他知道生成对象不用再使用new了,只要在配置文件里配置一下,他感到新鲜,其实这就是Ioc模式的实现,PicoContainer是另外一种真正轻量的Ioc模式实现,PicoContainer还是采取代码将对象注射一个小容器中,而Spring采取配置文件。
配置式编码其实有利有弊,编码本来可通过开发工具或编译器检查错误,但是过分依赖配置时,就会经常出现因为粗心导致的小错误,如果调试程序出错经常是因为配置文件中小写字母写成大写字母,不知道你是怎么心情?
Spring最近还发表了Spring without EJB的书,这倒是说了实话,Spring和EJB其实是相竞争,如同黑与白,如果硬是将两者搭配使用,显得不协调,更不是一些人所谓优化EJB调用的谣言,原因下面将会分析。既然Spring+EJB有缺陷,那么就直接使用Spring+Hibernate架构,但是又带来了新问题:无集群分布式计算性能,只能在一台机器上运行啊,具体分析见:可伸缩性和重/轻量,谁是实用系统的架构主选?
下面来分析所谓Spring+EJB的不协调性,正如水和油搅拌在一起使用一样。
目前,Spring+EJB有两种应用方式:
1. Spring不介入EJB容器,只做Web与EJB之间的接口,这个位置比较尴尬,Web层直接调用EJB的方法比较直接快捷,为什么要中间加个Spring?可实现Web缓存?使用性能更好的AOP框架aspectwerkz啊;实现Web和EJB解耦?这样的工具更多,自己都可以做个小框架实现,就不必打扰背着AOP和IOC双重重担的Spring了吧。
2. Spring介入EJB容器,这时,需要在你的ejb-jar.xml中配置beanFactoryPath值指向你为EJB配置的applicationContext.xml,那么你的EJB还需要继承Spring的SimpleRemoteStatelessSessionProxyFactoryBean。
好了,现在你的SLSB(无状态Session Bean)成为下面这个样子:
void updateUser(){
myService.updateUser(); //委托给一个POJO的方法,真正业务逻辑封装在这个POJO中
}
这样做有很多“优点”,当然最大“优点”是:
由于真正业务核心在POJO中实现,因此,只要改一下applicationContext.xml配置,这样,调试时,前台就可以直接调用POJO,不必通过EJB,调试起来方便了,这有一个前提:他们认为调试EJB复杂,其实不然,在JBuilder中,结合Junit,测试EJB如同测试POJO一样方便,这是其他分支,不在此讨论。当部署使用时,再改一下applicationContext.xml配置,指引前台调用到EJB。
似乎很巧妙,这里有两个疑问,首先,指引到EJB的改变是什么时候做?持续集成前还是后,在前在后都有问题,这里不仔细分析。
这种表面巧妙的优点带来最大的问题是:粗粒度事务机制。所谓粗粒度事务机制,最早见于Petstore的WEB调用EJB Command模式,在这个帖子中有讨论。
下面以代码描述什么是粗粒度事务机制:
ejb方法:
public void updateUser(){
service.updateUser();
}
service是一个POJO,具体方法可能是更新两个表:
public void updateUser(){
updateTabel1();//更新table1
updateTable2(); //更新table2
}
当updateTable2()抛出异常,updateTable1()是不回滚的。这样,table1中就有一条错误的多余的记录,而table2则没有这条记录。
那么,怎么做才能使两个表记录一致,采取事务机制,只有下面这样书写才能实现真正事务:
在EJB方法中写两个方法,因为EJB方法体缺省是一个事务。
public void updateUser(){
updateTabel1();//更新table1
updateTable2(); //更新table2
}
关于EJB自动的事务机制,最近也有一个道友做了测试,对于JBoss中容器管理的事务的疑惑。
如果你从事关键事务,就是带money相关操作的事务,这种粗粒度机制可能害苦你,那么,似乎有一种办法可弥补事务,不使用EJB容器事务(CMT),在service中使用Spring的事务机制。
如果使用Spring事务机制,业务核心又在POJO中实现,那么我有一个疑问:还要套上EJB干什么?至此,你终于明白,Spring本质是和EJB竞争的,如果硬套上EJB使用,只是相借助其集群分布式功能,而这个正是Spring目前所缺少的。
我太惊异Spring精巧的诡异了,他和EJB关系,正如异型和人的关系一样。
为了避免你每次使用Spring时想到粘糊糊的异型,不如Spring without EJB,这也正是Spring的初衷,也是它的一个畅销书名。
为让初学者更容易掌握Spring,在本站教学区新增一篇“Spring入门速成”,简单易懂,专门针对VIP会员的。
参考文章:
Ioc模式(又称DI:Dependency Injection)
IOC模式的思考和疑问
Ioc容器的革命性优点
Java企业系统架构选择考量
JBoss 5迎来Java的彻底的可配置时代
Spring AOP 和 IOC 容器入门
在这由三部分组成的介绍 Spring 框架的系列文章的第一期中,将开始学习如何用 Spring 技术构建轻量级的、强壮的 J2EE 应用程序。developerWorks 的定期投稿人 Naveen Balani 通过介绍 Spring 框架开始了他由三部分组成的 Spring 系列,其中还将介绍 Spring 面向方面的编程(AOP)和控制反转(IOC)容器。
Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
在这篇由三部分组成的 Spring 系列 的第 1 部分中,我将介绍 Spring 框架。我先从框架底层模型的角度描述该框架的功能,然后将讨论两个最有趣的模块:Spring 面向方面编程(AOP)和控制反转 (IOC) 容器。接着将使用几个示例演示 IOC 容器在典型应用程序用例场景中的应用情况。这些示例还将成为本系列后面部分进行的展开式讨论的基础,在本文的后面部分,将介绍 Spring 框架通过 Spring AOP 实现 AOP 构造的方式。
请参阅 下载,下载 Spring 框架和 Apache Ant,运行本系列的示例应用程序需要它们。
Spring 框架
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如图 1 所示。
图 1. Spring 框架的 7 个模块
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
-
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
-
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
-
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
-
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
-
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
-
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
IOC 和 AOP
控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器 (在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。
在典型的 IOC 场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起,决定什么时间调用方法。下表列出了 IOC 的一个实现模式。
类型 1 |
服务需要实现专门的接口,通过接口,由对象提供这些服务,可以从对象查询依赖性(例如,需要的附加服务) |
类型 2 |
通过 JavaBean 的属性(例如 setter 方法)分配依赖性 |
类型 3 |
依赖性以构造函数的形式提供,不以 JavaBean 属性的形式公开 |
Spring 框架的 IOC 容器采用类型 2 和类型3 实现。
面向方面的编程
面向方面的编程,即 AOP,是一种编程技术,它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。AOP 的核心构造是方面,它将那些影响多个类的行为封装到可重用的模块中。
AOP 和 IOC 是补充性的技术,它们都运用模块化方式解决企业应用程序开发中的复杂问题。在典型的面向对象开发方式中,可能要将日志记录语句放在所有方法和 Java 类中才能实现日志功能。在 AOP 方式中,可以反过来将日志服务模块化,并以声明的方式将它们应用到需要日志的组件上。当然,优势就是 Java 类不需要知道日志服务的存在,也不需要考虑相关的代码。所以,用 Spring AOP 编写的应用程序代码是松散耦合的。
AOP 的功能完全集成到了 Spring 事务管理、日志和其他各种特性的上下文中。
IOC 容器
Spring 设计的核心是 org.springframework.beans
包,它的设计目标是与 JavaBean 组件一起使用。这个包通常不是由用户直接使用,而是由服务器将其用作其他多数功能的底层中介。下一个最高级抽象是 BeanFactory
接口,它是工厂设计模式的实现,允许通过名称创建和检索对象。BeanFactory
也可以管理对象之间的关系。
BeanFactory
支持两个对象模型。
-
单态 模型提供了具有特定名称的对象的共享实例,可以在查询时对其进行检索。Singleton 是默认的也是最常用的对象模型。对于无状态服务对象很理想。
-
原型 模型确保每次检索都会创建单独的对象。在每个用户都需要自己的对象时,原型模型最适合。
bean 工厂的概念是 Spring 作为 IOC 容器的基础。IOC 将处理事情的责任从应用程序代码转移到框架。正如我将在下一个示例中演示的那样,Spring 框架使用 JavaBean 属性和配置数据来指出必须设置的依赖关系。
BeanFactory 接口
因为 org.springframework.beans.factory.BeanFactory
是一个简单接口,所以可以针对各种底层存储方法实现。最常用的 BeanFactory
定义是 XmlBeanFactory
,它根据 XML 文件中的定义装入 bean,如清单 1 所示。
清单 1. XmlBeanFactory
BeanFactory factory = new XMLBeanFactory(new FileInputSteam("mybean.xml"));
|
在 XML 文件中定义的 Bean 是被消极加载的,这意味在需要 bean 之前,bean 本身不会被初始化。要从 BeanFactory
检索 bean,只需调用 getBean()
方法,传入将要检索的 bean 的名称即可,如清单 2 所示。
清单 2. getBean()
MyBean mybean = (MyBean) factory.getBean("mybean");
|
每个 bean 的定义都可以是 POJO (用类名和 JavaBean 初始化属性定义) 或 FactoryBean
。FactoryBean
接口为使用 Spring 框架构建的应用程序添加了一个间接的级别。
IOC 示例
理解控制反转最简单的方式就是看它的实际应用。在对由三部分组成的 Spring 系列 的第 1 部分进行总结时,我使用了一个示例,演示了如何通过 Spring IOC 容器注入应用程序的依赖关系(而不是将它们构建进来)。
我用开启在线信用帐户的用例作为起点。对于该实现,开启信用帐户要求用户与以下服务进行交互:
- 信用级别评定服务,查询用户的信用历史信息。
- 远程信息链接服务,插入客户信息,将客户信息与信用卡和银行信息连接起来,以进行自动借记(如果需要的话)。
- 电子邮件服务,向用户发送有关信用卡状态的电子邮件。
三个接口
对于这个示例,我假设服务已经存在,理想的情况是用松散耦合的方式把它们集成在一起。以下清单显示了三个服务的应用程序接口。
清单 3. CreditRatingInterface
public interface CreditRatingInterface {
public boolean getUserCreditHistoryInformation(ICustomer iCustomer);
}
|
清单 3 所示的信用级别评定接口提供了信用历史信息。它需要一个包含客户信息的 Customer
对象。该接口的实现是由 CreditRating
类提供的。
清单 4. CreditLinkingInterface
public interface CreditLinkingInterface {
public String getUrl();
public void setUrl(String url);
public void linkCreditBankAccount() throws Exception ;
}
|
信用链接接口将信用历史信息与银行信息(如果需要的话)连接在一起,并插入用户的信用卡信息。信用链接接口是一个远程服务,它的查询是通过 getUrl()
方法进行的。URL 由 Spring 框架的 bean 配置机制设置,我稍后会讨论它。该接口的实现是由 CreditLinking
类提供的。
清单 5. EmailInterface
public interface EmailInterface {
public void sendEmail(ICustomer iCustomer);
public String getFromEmail();
public void setFromEmail(String fromEmail) ;
public String getPassword();
public void setPassword(String password) ;
public String getSmtpHost() ;
public void setSmtpHost(String smtpHost);
public String getUserId() ;
public void setUserId(String userId);
}
|
EmailInterface
负责向客户发送关于客户信用卡状态的电子邮件。邮件配置参数(例如 SMPT 主机、用户名、口令)由前面提到的 bean 配置机制设置。Email
类提供了该接口的实现。
Spring 使其保持松散
这些接口就位之后,接下来要考虑的就是如何用松散耦合方式将它们集成在一起。在 清单 6 中可以看到信用卡帐户用例的实现。
注意,所有的 setter 方法都是由 Spring 的配置 bean 实现的。所有的依赖关系 (也就是三个接口)都可以由 Spring 框架用这些 bean 注入。createCreditCardAccount()
方法会用服务去执行其余实现。在 清单 7 中可以看到 Spring 的配置文件。我用箭头突出了这些定义。
运行应用程序
要运行示例应用程序,首先必须 下载 Spring 框架 及其所有依赖文件。接下来,将框架释放到(比如说)磁盘 c:\,这会创建 C:\spring-framework-1.2-rc2 (适用于当前发行版本) 这样的文件夹。在继续后面的操作之前,还必须下载和释放 Apache Ant。
接下来,将源代码释放到文件夹,例如 c:\ 盘,然后创建 SpringProject。将 Spring 库(即 C:\spring-framework-1.2-rc2\dist 下的 spring.jar 和 C:\spring-framework-1.2-rc2\lib\jakarta-commons 下的 commons-logging.jar)复制到 SpringProject\lib 文件夹中。完成这些工作之后,就有了必需的构建依赖关系集。
打开命令提示符,将当前目录切换到 SpringProject,在命令提示符中输入以下命令:build
。
这会构建并运行 CreateCreditAccountClient
类,类的运行将创建 Customer
类对象并填充它,还会调用 CreateCreditCardAccount
类创建并链接信用卡帐户。CreateCreditAccountClient
还会通过 ClassPathXmlApplicationContext
装入 Spring 配置文件。装入 bean 之后,就可以通过 getBean()
方法访问它们了,如清单 8 所示。
清单 8. 装入 Spring 配置文件
ClassPathXmlApplicationContext appContext =
new ClassPathXmlApplicationContext(new String[] {
"springexample-creditaccount.xml"
});
CreateCreditCardAccountInterface creditCardAccount =
(CreateCreditCardAccountInterface)
appContext.getBean("createCreditCard");
|
结束语
在这篇由三部分组成的 Spring 系列 的第一篇文章中,我介绍了 Spring 框架的基础。我从讨论组成 Spring 分层架构的 7 个模块开始,然后深入介绍了其中两个模块:Spring AOP 和 IOC 容器。
由于学习的最佳方法是实践,所以我用一个工作示例介绍了 IOC 模式 (像 Spring 的 IOC 容器实现的那样)如何用松散耦合的方式将分散的系统集成在一起。在这个示例中可以看到,将依赖关系或服务注入工作中的信用卡帐户应用程序,要比从头开始构建它们容易得多。
请继续关注这一系列的下一篇文章,我将在这里学习的知识基础上,介绍 Spring AOP 模块如何在企业应用程序中提供持久支持,并让您开始了解 Spring MVC 模块和相关插件。
下载
描述 |
名字 |
大小 |
下载方法 |
Examples: source code, spring files, build scripts |
wa-spring1-SpringProject.zip |
9 KB |
FTP |
参考资料
关于作者
|
|
|
Naveen Balani 把他的大多数时间都花在了设计和开发基于 J2EE 的框架和产品上。他为 IBM developerWorks 撰写了各种不同的文章,涉及的主题包括:ESB、SOA、JMS、WebServices Architectures、CICS、AXIS、DB2 XML Extender、WebSphere Studio、MQSeries 和 Java Wireless Devices,以及 DB2 Everyplace for Palm、J2ME、MIDP、Java-Nokia、Visual Studio .Net 和无线数据同步。您可以通过 naveenbalani@rediffmail.com 与他联系。
|
前两天 xdoclet 发布了版本 1.2b3,里面包含了 hibernate 的自动生成 mapping file 的 module
今天试了试,居然成功了,很有意思。
用 hibernate 和 xdoclet 开发的流程如下:
1.写 Persistent class,并在 源程序里写上一些 hibernate 的 tag
2.用 xdoclet 根据 Persistent class 里的 tag 自动生成 mapping file
3.用 Hibernate 提供的 SchemaExport 类生成 数据库建表的 ddl
然后就不用考虑数据库的细节啦,哈哈。
中文的一篇文档:
值得关注的持久化技术: hibernate
http://www.huihoo.com/java/hibernate/
这里有老外写的详细的文档,介绍 hibernate 和 xdoclet ,里面也包含了代码下载:
http://www.meagle.com:8080/hibernate.jsp
我自己学习过程中也写了一个 demo,用 ant 运行,来我的 ftp 下载:
ftp://cinc.3322.org/pub/doc/code/hibernate/
hibernatedemo_20030622.zip 是没用 xdoclet 的例子
hibernate_xdoclet_demo_20030614.zip 是使用 xdoclet 的例子
HibernateExamplesMeagle.zip Meagle 那个网站的例子
hibernate:
http://sourceforge.net/projects/hibernate/
xdoclet:
http://sourceforge.net/projects/xdoclet/
1.
每日构建(
Daily Build
)
在项目中使用每日构建(
Daily Build
)是为了实现“持续集成”。传统构建只发生在项目发布前夕很短的一段时间,压力和风险不小。每日构建一定程度上缓解了这种压力,削平了曲线。好东西!但每日构建也需项目组的投入,学习和使用
Ant
(或
Nant
)就是一个不错的办法。
2.
单元测试(
Unit Test
)
在项目中使用单元测试(
Unit Test
)和每日构建在思想上有相似之处,在实践中又交相呼应。单元测试可以确保每个软件项目最小逻辑部件的正确性。在耦合始终存在的情况下,单元测试的作用其实也被放大了,对某个逻辑部件的测试自然也包括了对所引用其他部件的测试(虽然这并不是单元测试的初衷)。单元测试不是最重要的,但的确是非常重要的和必须的。单元测试也需要项目组的投入,编写单元测试用例是起码的,学习和使用
JUnit
(或
Nunit
)能帮上不小的忙。
3.
结合起来(
Do Them All
)
单元测试的最佳实施者是每个开发人员自己,也可以采用同行测试(
Buddy Test
)。他们阅读设计文档,编写单元测试用例、编写逻辑部件,然后把逻辑部件放到测试用例中测试,测试通过就继续往前走。单元测试的发起者是项目组中单个的开发者时,会使得单元测试此起彼伏,但问题是有些单元测试有很多都是无效的。原因很简单,因为不同开发者编写的逻辑部件之间的耦合只要存在,这样的单元测试就是无效的。参考《软件配置管理模式》一书中关于“活动开发线”模式的讲述:当在项目的一条码线(
Code Line
)上,测试与开发在时间上同存时,测试都是无效的,单元测试也不例外。
也许在开发者自行单元测试的同时,我们还应该定时的让所有的开发者都停下来,提交他们的代码,然后一起来进行单元测试。其实这不难,每天下班的时候他们都会自动的停下来,如果项目的配置管理还算可以的话,他们也都会在下班前提交代码,然后让我们来运行单元测试吧。
我们用
Ant
来进行每日构建。同样,我们也可以让
Ant+JUnit
帮助我们进行每日单元测试(
Daily Test
?)。每次听微软的讲师讲
MSF
,都要讲他们的开发是深夜
12:00
开始的,也就是“自动构建
+
自动测试”,第二天早上,开发组的人拿到自动生成的报告,该
code
的就
code
,该
debug
的就
debug
。(其实,这也就是一种测试驱动开发:
Test-Driven Development
,又一个很多人争议的软件方法。)这不难,是看公司要不要这么做。
4.
实践(
Practice
)
关于使用
Ant
实现每日构建的请看陋文:“一个轻巧的每日构建解决方案”。
在此基础上,在
Ant
的“
build.xml
”中加入单元测试任务即可,同时也可以生成单元测试报告。甚至可以将单元测试结果报告通过
email
发送给开发组成员。
先请出国内外的几篇好文:
“让编译和测试过程自动化”
“利用
Ant
和
JUnit
进行增量开发”
“每日创建之自动测试”
到今天(
2004-08-20
)为止,在
Google
上面以“
ant+junit
”可以搜出
127000
条记录,大家都做得很好。但上面
3
篇的确已经足够帮助还未实作的人快速的实现“每日构建
+
单元测试”了。
关于实践他们都说得很清楚,我也是在看了这些文档后完成实践的。我在自己的
Blog
上加点自己的东西吧,也就是在实现“每日构建
+
单元测试”时的小贴士。
1)
环境配置之<mail>
:
“让编译和测试过程自动化”一文的作者
Erik Hatcher
也是
Ant
的开发者之一。但此文有些早了,文中所提到的
Ant
的<mimemail>
已经合并到<mail>
中,<mimemail>
已经废置不用了。<mail>
任务依赖于
JavaMail
和
JAF
,如果没有用过的话,请到
SUN
公司的网站上下载,并在下载后完成以下配置(以我的目录结构为例):
CLASSPATH
:
C:\javamail-1.3.2ea\mail.jar;
C:\javamail-1.3.2ea\lib\mailapi.jar;
C:\javamail-1.3.2ea\lib\pop3.jar;
C:\javamail-1.3.2ea\lib\smtp.jar;
C:\jaf-1.0.2\activation.jar;
这样才能保证
Ant
以
email
发送构建和测试报告时的正确。
2)
环境配置之<junit><junitreport>
:
如果项目组在白天用的是
JBuilder
,其中集成了
JUnit
和
Ant
。(建议下载单独的
Ant
和
JUnit
)但当晚上
Ant
调用
JUnit
执行测试任务时,必须保证
,<junit><junitreport>
能被
Ant
所识别。所以,你要将
JUnit
所属的
3
个
*.jar
文件:
src.jar ,junit.jar,junitmail.jar
拷贝到
Ant
的库所在目录(以我的目录结构为例):
C:\ant\lib
这样才能保证,
Ant+JUnit
会为你生成漂亮的
html
格式的测试报告,并发送到你指定的
email
中。
5.
总结(Summary)
讨论了每日构建和单元测试的作用和结合起来使用的理由。配合软件开发配置管理模式提出了供参考的结合方法,并通过实践验证该方案的可行性和简单性。最后和
java
不太熟练的朋友分享了少量经验。
有没有听说过
SEMA
?这可是衡量一个软件开发组好坏的很深奥的系统。别介,等一下!别按那个联接!
给你六年你也搞不清这玩意。所以我自己随便攒了一套衡量系统,信不信由你,这系统,三分钟就可掌握。你可以把省下的时间去读医学院了(译注:美国的医学院可是要读死人的!)。
Joel
衡量法则
-
你们用不用源文件管理系统?
-
你们可以把整个系统从源码到CD映像文件一步建成吗?
-
你们每天白天都把从系统源码到CD映像做一遍吗?
-
你们有软件虫管理系统吗?
-
你们在写新程序之前总是把现有程序里已知的虫解决吗?
-
你们的产品开发日程安排是否反映最新的开发进展情况?
-
你们有没有软件开发的详细说明书?
-
你们的程序员是否工作在安静的环境里?
-
你们是否使用现有市场上能买到的最好的工具?
-
你们有没有专职的软件测试人员?
-
你们招人面试时是否让写一段程序?
-
你们是否随便抓一些人来试用你们的软件?
|
“
Joel
衡量法则”好就好在你只需照着逐条回答以上问题,然后
把所答为“是”的问题算成一分,再加起来
就可以了,而不需要去算什么每天写的程序行数或程序虫的平均数等等
。但咱丑话说在前面,可别用
“
Joel
衡量法则”去推算你的核电站管理程序是否可靠。
如果你们得了12分,那是最好,得了11分还过得去,但如果只得了10分或低于10分,你们可能就有很严重的问题了。严酷的现实是:大多数的软件开发公司只能得到2到3分。这些公司如果得不到急救可就玄了,因为像微软这样的公司从来就没有低过12分。
当然,一个公司成功与否不仅仅只取决于以上标准。比如,让一个管理绝佳的软件公司去开发一个没有人要的软件,那开发出来的软件也只能是没有人要。或反过来,一帮软件痞子以上标准一条也达不到,没准照样也能搞出一个改变世界的伟大软件。但我告诉你,如果不考虑别的因素,你只要能达到以上12条准则,你的团队就是一个可以准时交活的纪律严明的好团队。
1.
你们用不用源文件管理系统?
我用过商业化的源文件管理系统,我也用过免费的系统,比如
CVS
,告诉你吧,
CVS
挺好用。但如果你根本就没有用源文件管理系统,那你就是累死了也没法让你的程序员出活:他们没法知道别人在改动什么源文件,写错了的源文件也没法恢复。
使用源文件管理系统还有一大好处是,由于每一位程序员都把源文件从源文件管理系统里提出来放到自己的硬盘里,几乎不会发生丢失源文件的事,最起码我还没听说过。
2.
你们可以把整个系统从源码到CD映像文件一步建成吗?
这句话问的问题是:从你们最新的源码开始到建立起能够交出去的最后文件,你们有多少步骤要做?
一个好的团队应该有一个批处理程序一步便可将所有的工作做完,像把源文件提取出来,跟据不同的语言版本要求(英文版,中文版),和各种编译开关(
#ifdef
)进行编译,联接成可执行文件,标上版本号,打包成CD映像文件或直接送到网站上去,等等等等。
如果这些步骤不是一步做完,就有可能出人为差错。而且当你很接近产品开发尾声的时侯,你可能很急于把最后几个虫解决,然后尽快地交活。如果这时候你需要做20步才能把最终文件制出来,你肯定会急得要命,然后犯一些很不该犯的错误。
正因为这个原因,我工作的前一个公司从用WISE改用InstallShield:我们必需要让我们的批处理程序完全自动化地,在夜里,被NT scheduler起动把最终文件制成,WISE不能被NT scheduler启动而InstallShield可以,我们只能把WISE扔掉。(WISE的那帮家伙向我保证他们的下一代产品一定支持在夜里自动运行.)
3.
你们每天白天都把从系统源码到CD映像做一遍吗?
你们有没有遇到过这样的事情:一个程序员不小心把有毛病的源码放进源文件管理系统,结果造成最终文件没法制成。比如,他建立了一个新源文件但忘了把它放进源文件管理系统,然后他高高兴兴锁机回家了,因为在他的机器上整个编译得很好。可是别人却因为这没法工作下去了,也只好闷闷地回家了。
这种造成最终文件没法制成的情况很糟糕,但却很常见。如果每天在白天就把最终文件制一遍的话,就可以让这种事不造成太大危害。在一个大的团队里,要想保证有毛病的源码及时得到纠正,最好每天下午(比如午餐时)制一下最终文件。午餐前,每个人都尽可能地把改动的源文件放到源文件管理系统里,午餐后,大家回来,如果最终文件已经制成了,好!这时大家再从源文件管理系统里取出最新的源文件接着干活。如果最终文件制作出错,出错者马上修正,而别人还可接着用原有的没问题的源程序干活。
在我以前曾干过的微软
Excel
开发组里,我们有一条规定:谁造成最终文件制作出错,谁就得被罚去负责监视以后的最终文件制作过程,直到下一位造成最终文件制作出错的人来接任他。这样做不仅可以督促大家少造成最终文件制作出错,而且可以让每个人都有机会去了解最终文件制作过程。
如果想更多了解这个话题,可以读我的另一篇文章
Daily Builds are Your Friend.
4.
你们有软件虫管理系统吗?
不论你有任何借口,只要你写程序,哪怕只是一个人的小组,如果你没有一个系统化的管理软件虫的工具,你写的程序的质量一定高不了。许多程序员觉得自己可以记得自己的软件虫。没门!我从来记不住超过2到3个软件虫。而且第二天早上起床后忙着去买这买那,好不容易记住的软件虫早忘掉了。你绝对需要一个系统来管住你的那些虫。
软件虫管理系统功能有多有少。但最少要管理以下几种信息:
-
如何重复软件虫的详细步骤
-
正常情况(无虫)应是怎样
-
现在情况(有虫)又是怎样
-
谁来负责杀虫
-
问题有没有解决
如果你觉得用软件虫管理系统太麻烦,可以简化一下,建立一个有以上5列的表来用就行了。
如果想更多了解这个话题,可以读我的另一篇文章
Painless Bug Tracking.
5.
你们在写新程序之前总是把现有程序里已知的虫解决吗?
微软
Windows Word
的第一版的开发项目曾被认为是“死亡之旅”项目。好象永远也做不完,永远超时。所有人疯狂地工作,可怎么也完成不了任务。整个项目一拖再拖,大家都觉得压力大得受不了。最后终于做完了这个鬼项目,微软把全组送到墨西哥的
Cancun
去度假,让大家坐下来好好想想。
大家意识到由于项目经理过于强求程序员们按时交活,结果大家只能匆匆地赶活,写出的程序毛病百出。由于项目经理的开发计划并没有考虑杀虫的时间,大家只能把杀虫的任务往后推,结果虫越积越多。有一个程序员负责写计算字体高度的程序,为了图快,居然写一行“return 12;”了事。他指望以后的质检人员发现这段程序有毛病后报告他再改正。项目经理的开发计划事实上已变成一个列写程序功能的清单,而上面列的所谓程序功能迟早都会成为软件虫。在项目总结会上,我们称这种工作方法为“绝对劣质之路”。
为了避免再犯这个错误,微软制定了“零缺陷策略”。许多程序员嘲笑这个策略,觉得经理们似乎在指望靠行政命令来提高产品质量。而事实上“零缺陷策略”的真正含义是:在任何时候,都要把解决现有程序里的问题作为首要问题来抓,然后再去写新程序。
为什么要这样做呢?
一般说来,你越不及时地杀虫,杀虫的代价(时间和金钱)就会越高。比如,你写程序时打错了一个字,编译器马上告诉你,你很容易就把它改正。你刚写好的程序在第一次运行时发现了一个问题,你也很快就能解决它,因为你对你刚写的程序还记忆犹新。如果你运行你的程序时发现了一个问题,可这个程序是几天以前写的,你可能就需要折腾一会儿,还好,你还大致记得,所以不会花太长时间。但如果你在你几个月以前写的程序里发现了问题,就比较难解决了,因为你已经忘了许多细节。这时候,你还没准儿正忙着杀别人程序里的虫呐,因为这家伙到加勒比海阿鲁巴岛度假去了。这时候,解决这一堆问题的难度不亚于从事尖端科学研究。你一定得小心翼翼地,非常系统化地从事,而且你很难知道多长时间你才能把问题解决。还有更糟糕的,你的程序已交到用户手里了,才发现问题,那你就等着套腰包吧。
总结起来,就一条:越早解决问题,越容易解决。
另外还有一个原因,刚写的程序里发现问题,你能够比较容易地估算解决它的时间。举个例子,如果我问你写一段程序去把一个列表排序需要花多长时间,你可以给我一个比较确切的估计。如果你的程序,在Internet Explorer 5.5安装以后,工作不正常。我问你要多长时间把这个问题解决,你恐怕都估计不出来,因为你根本就不知道是什么原因造成了这个问题。你可能要花三天时间才能解决,也有可能只花两分钟。
这个例子告诉我们,如果你的开发过程中有许多虫没有及时解决,那你的开发计划肯定不可靠。反过来,如果你们已经把已知的虫全部解决了,要做的事只是写新的程序,那你的开发计划就会比较准确。
把已知的虫全部解决,这样做还有一个好处:你可以对竞争对手快速反击。有些人把这叫着“让开发中的产品随时处在可以交给用户的状态”。如果你的竞争对手推出一个新的功能想把你的客户抢走,你可以马上在你的产品里加上这个功能,立刻将新产品交付用户,因为你没有一大堆积累下来的问题要解决。
6.
你们的产品开发日程安排是否反映最新的开发进展情况?
为什么我们需要开发日程安排?如果你的程序对公司的业务很重要,那公司就必须知道你的程序何时能写完。满世界的程序员都有一个通病,那就是他们都搞不清自己何时才能写完要写的程序。他们都只会对管理人员嚷嚷:“等我做好了就做好了!”
不幸的是,程序写完了,事远远没完。作为一个公司,在发行产品之前,还有许许多多的事情要做:何时做产品演示?何时参加展览会?何时发广告?等等。所有的这一且都依赖于产品的开发日程安排。
定下产品开发日程安排,还有一个很关键的好处:它逼着你只做叫你做的功能,甩掉那些可要可不要的功能,否则这些可要可不要的东西有可能把你缠住。请看
featuritis
。
定下产品开发日程安排,按照它开发,这并不难做,请看我的另一篇文章
Painless Software Schedules
,这篇文章告诉你一种制订产品开发日程的好方法。
7.
你们有没有软件开发的详细说明书?
写软件开发的详细说明书就像是绣花:人人皆知是好东西,可没谁愿意去做。
我不知道这是为什么,也许是因为多数程序员天生就不喜欢写文章。其结果是,一个开发组里的程序员们,宁可用程序来沟通,也不愿写文章来表达自己。他们喜欢上来就写程序,而不是写什么详细说明书。
在产品的前期设计过程中,如果你发现了一些问题,你可以轻易地在说明书里该几行字就行了。一旦进入了写程序的阶段,解决问题的代价就要高得多了,不仅仅是时间上的代价,而且也有感情上的代价,因为没人愿意将自己做成的东西扔掉。所以这时候解决问题总有一些阻力。
没有产品开发详细说明书就开始写程序,往往会导致程序写的乱七八糟,而且左拖右拖不能交付使用。我觉得这就是Netscape遇到的问题。前四个版本的程序越写越乱,以至管理人员作出一个愚蠢的决定:把以前的程序统统扔掉,重新写。后来他们在开发Mozilla时又犯了同样的错误。产品越做越乱,完全失控,花了几年的时间才进入内部测试阶段。
我最得意的理论是:如果让程序员们接受一些写文章的训练如
an intensive course in writing
,他们就可能会改变一下不写说明书的坏习惯,而以上所说的糟糕的例子就有可能少发生。
另一个解决问题的办法是:雇一些能干的项目主任,专职写产品开发详细说明书。
不论采用以上哪种方法,道理只有一个:在没有产品开发详细说明书之前,决不可写程序。
如果想更多了解这个话题,可以读我的
四篇文章
。
8.
你们的程序员是否工作在安静的环境里?
当你让你的智囊们工作在安静,宽敞,不受人打扰的环境里,他们往往能更快地出活,这已是不争的事实。有一本经典的讲软件开发管理的书
Peopleware
把这个问题阐述得很清楚。
问题在于,我们都知道最好不要打断这些智囊们的思路,让他们一直处于他们的最佳状态中,这样他们就能全神贯注,废寝忘食地工作,充分发挥他们的作用。作家,程序员,科学家,甚至篮球运动员都有他们的最佳状态。
问题还在于,进入这个最佳状态不容易。我觉得平均起来,需要15分钟才能进入最佳状态,达到最高工作效率。有时侯,当你疲劳了或已经高效率地干了许多工作了,你就很难再进入这个状态,只好干点杂事打发时间,或上网,玩游戏等。
问题更在于,你很容易就被各种各样的事打扰,被拽出你的最佳状态:噪音啦,电话啦,吃午饭啦,喝杯咖啡啦,被同事打扰啦,等等。如果一个同事问你一个问题,只花你一分钟,可你却被拽出你的最佳工作状态,重新回到这个状态需要花半小时。你的工作效率因此而受到很大影响。如果让你在一个嘈杂的大房间里工作(那帮搞网站的家伙还就喜欢这样),边上的推销员在电话里大叫大嚷,你就很难出活,因为你进入不了你的最佳工作状态。
作为程序员,进入最佳工作状态更难。你先要把方方面面的细节装在脑子里,
任何一种干扰都可能让你忘掉其中某些东西。你重新回来工作时,发现好些东西记不起来了(如你刚用的局部变量名,或你刚才的搜索程序写到哪里了等)你只好看看刚写的程序,回忆一下,慢慢地回到你刚才的最佳工作状态。
我们来做一个简单的算数。假设一个程序员被打扰一下,哪怕只有一分钟,他却需要花15分钟才能回到最佳工作状态(统计资料显示如此)。我们有两个程序员:杰夫和愚夫,
坐在一个大办公区里工作。愚夫想不起来用什么函数去进行Unicode 字符串复制。他可以花30秒查一下,或者花15秒问杰夫。由于他坐在杰夫的旁边,他就选择去问杰夫。杰夫被打扰了一下,耽误了他15分钟,节省了愚夫15秒钟。
现在,我们把他们俩用墙和门隔开,让他们俩分坐在不同的办公室里,愚夫又想不起来什么涵数名,自己查一下要花30秒;问杰夫,要花45秒,因为他要站起来走过去问(对这帮程序员来说,这可不是件简单的事,看看他们的体质就知道为什么了)。所以他选择自己去查。愚夫损失了30秒钟,可是杰夫少损失了15分钟。哈哈!
9.
你们是否使用现有市场上能买到的最好的工具?
用可编译语言写程序恐怕是这世界上为数不多的还不能随便抓一个破计算机就可以做的事。如果你用于编译的时间超过几秒钟,你就应该换一台最新最快的计算机了。因为如果编译时间超过15秒,程序员们就会不耐烦,转而去上网看一些无关的东西比如
The Onion
,弄不好一看就是好几个小时。
调试图形界面软件时,用只有一个显示器的计算机不仅不方便,有时甚至是不可能。用有两个显示器的计算机,要方便许多。
程序员们经常不可避免地要去画一些图标或工具栏图。多数程序员没有一个好的图形编辑器可用。用微软的“画笔”软件去画图标简直是笑话,可事实上大家还就在这样做。
1982年,我家定购了IBM的PC机(IBM生产的最早的个人计算机,是现代流行的标准化的个人计算机的祖宗)。我们家可能是以色列最早拥有这种PC机的几个家庭之一。当时我们跑到了仓库等着电脑从港口运过来。事实上,我说服我爸购买的是带有全套附属设备的个人计算机(译者按:有些现在很便宜的附属设备,那时候都是非常昂贵的),这些附属设备包括两个软盘驱动器,128K内存,一个针式点阵打印机(用来快速打印草稿)和一个运转起来发出机关枪扫射声音的兄弟牌的雏菊轮式打印机(译者按:原文为Daisy Wheel printer,是一种已经淘汰的打印机,原理类似于老式的机械打字机,可以产生清晰的英文字符)。附属的软件也很齐全,PC-DOS 1.0(最早的PC操作系统),75美元的参考书册,包括BIOS的完整源代码。 一个汇编语言编译器(Macro Assembler),非常棒的IBM单色显示器,可以显示80列宽的字符,而且支持小写字母显示。整套配置大概花了10000美元。这些钱包括以色列的荒谬的进口税。呵呵,那时候我家可真舍得花钱啊!
因为当时“每个人”都知道BASIC是给小孩玩的语言,用这种语言只能使你写出非结构化的垃圾代码,然后你的的脑子也会被这种语言变成Camembert产的奶酪(Camembert cheese,法国的一种奶酪,实心,圆饼状,灰色,有一个拳头大小)。所以我们又花了600美元买了一个IBM公司PASCAL语言开发包,需要3张软盘才装的下。PASCAL编译器运行分别需要第一号软盘,和第二号软盘,PASCAL链接器需要第三号软盘。我写了一个简单的输出文字“你好,世界”程序然后编译链接这个程序,总共花了8分钟。
嗯,8分钟好像太长了。我写了一个脚本程序来自动化整个过程,把时间缩减为7.5分钟。这样好一点了。但是我想设计一个可以玩奥赛罗的程序。(译者按:奥赛罗原文为Othello,一种棋类游戏,规则见http://www.ugateways.com/bof4.html)这个游戏总是能打动我。我不得不花很多时间等待编译器编译我的程序。“就是这样的,”一个专业程序员告诉我,“我们通常在办公室里房放上sit-up board(译者按:一种健身器材,可以在上面做仰卧起坐或者有氧操什么的) ,当PASCAL编译器开始运行时,我们就开始锻炼。我这样编程了几个月后,我的身材不要太棒喔!”
后来有一天,丹麦程序员写了一个很灵的叫做Compas Pascal的程序。Philippe Kahn(Borland公司的创始人)买下了它,更名为Borland Turbo Pascal。Turbo Pascal好得简直难以想象,因为它能做IBM Pascal能做的所有事情,但是只要33K内存。而且还额外提供一个编辑器。 这还不是最棒的。最棒的是编译一个小程序只需要不到一秒。这就好比一个你从来没有听说过的公司生产了通用公司别克轿车的克隆版,这种克隆车可以每小时行驶一百万英里,但是只消耗了一滴汽油。一只小小的蚂蚁喝下这点汽油也不会撑死。
突然,我的编程效率变得高的多了
那时我开始明白了“REP循环”(Rep loop)这个概念. REP是“读入,求值,打印(Read, Eval, Print)”的缩写。这个概念解释了Lisp(一种编程语言,用于人工智能)解释器的基本原理:它“读入”你的输入,计算你的输入得到结果,打印结果。下面给一个例子:我输入一些东西,Lisp解释器计算,然后输出结果。
在一个稍微大点的规模上,当你写代码时,你也处于一个REP循环的宏版本中,这个循环就是“编码-编译-测试”。你编写代码,把代码编译成可执行的文件,然后测试它,看一下运行起来怎么样。
关键一点是当你写一个程序时,你的工作过程是循环的。一个循环所花时间越短,你的生产力就越高,当然最短时间不会小于编译器运行的时间。 这就是一个程序员为什么总是要一个真正够快的硬件而编译器开发者们总是不断使他们的编译器运行更快的正式的纯计算机科学角度的原因。Visual Basic的办法是当你输入代码时,它就开始进行代码的语法解析,这样程序解释运行时速度很快。Visual C++的办法是增量编译(incremental compiles),预编译头文件(precompiled headers)和增量链接(incremental linking)。
但是一个大型的团队有多个开发人员和测试人员,你碰到了同样的循环,可能不同点就是有更多的文档要写(可是这还只是草稿,天哪!)。一个测试人员发现了bug并报告,然后开发人员修复了这个bug。那么测试人员得到修正后的代码需要多少时间?在一些软件开发机构,这样的报告-修正-再测试循环(Report-Fix-Retest loop)可能需要几个礼拜。如果一个循环需要这么长的时间,通常意味着该机构生产力很低。想让整个开发过程运转得更平滑一点,你必须想方设法使得报告-修正-再测试循环(Report-Fix-Retest loop)花的时间更少。
一个好的办法是每日构建(daily builds)。 每日构建意味着自动地,每天,完整地构建整个代码树、(译者按:“代码树”,原文为source tree,意思是将整个项目源代码的目录,子目录,文件的位置尽可能事先固定下来,这样在开发过程中各个模块间,各个文件间的相对位置都不会混乱。源代码树指的就是一个项目所有的已经组织好的代码文件。通常代码树应该用版本控制软件管理起来。虽然这个概念很基本,但是据我的观察,国内还是有软件公司在这方面做的不够好的,所以有必要解释一下。)
自动地 - 因为你设定代码每天在固定的时间构建。在Unix环境下使用cron,在windows下使用“任务计划”。
每天 - 或者更频繁. 当然每天构建的次数越多越好啦。但是有时候构建次数还是有上限的,原因和版本控制有关系,等会儿我会谈到的。
完整地 -很可能你的代码有多个版本。多语言版本,多操作系统版本,或者高端低端版本。每日构建(daily build)需要构建所有这些版本。并且每个文件都需要从头编译,而不是使用编译器的不完美的增量编译功能。
以下是每日构建(daily build)能带来的好处:
- 当一个bug被修正了,测试者可以很快得到最新的修正后的版本开始重新测试,以验证bug是否真正地被修复了。
- 开发人员可以更加确定他们对代码做的修改不会破坏1024个操作系统上的任何一个版本。验证这一点不需要在他们的机器上安装OS/2(IBM公司生产的PC机操作系统)。
- 那些每天将修改过的代码导入(check in)版本控制服务器的开发人员知道,他们对一个模块导入的修改不会拖别的开发人员的后腿。拖后腿的意思是,那些开发别的模块的程序员使用这个修改过的模块,出了问题,于是他们自己的模块也没有办法开发下去了。每日构建则不会有人拖后腿。如果把一个开发团队比作一台PC机,那么团队中的一个程序员对某个模块的修改导致其他人无法开发别的模块,相当于PC机发生了蓝屏。当一个程序员忘记把他(她)新建立的文件导入到repository(指版本控制服务器上的代码树)时,这种开发过程中的“蓝屏”会经常发生。因为在这个程序员自己的计算机上有这个文件,所以他(她)构建 这个程序没有问题。但是其他程序员是从版本控制服务器上导出(check out)代码的,由于服务器上没有这个文件,他们遇到了链接错误(link error),无法继续工作了。
- 外部团队(例如市场销售部门,进行beta测试的一些客户)可以获得一个比较稳定的版本,这样对他们开展自己的工作比较有利。
- 假如你将每日构建出的二进制文件(例如一个可执行程序,一个dll等等)存档管理,那么当你发现一个非常奇怪的,无法解决的bug时,你可以通过对这些文件进行二进制搜索(binary search)来确定什么时候这个bug第一次出现。如果有对代码进行了完善的版本控制,你也可以找出是谁在何时对代码进行的导入(check in)导致了这个bug。
- 当开发者修正了测试者报告的一个错误时,如果测试者同时报告了发现错误时的构建的版本,开发人员可以直接在那个版本中测试是否bug真正被修复了。(译者按:测试者报告出现了一个bug,只是报告了一个错误症状,而错误的原因可能有多个,每个原因可能在不同的模块中。前文中的方法是为了避免只修正了一个模块中一个原因,别的模块由于在变动,于是掩盖了而不是修复了bug)
以下是如何进行每日构建(daily build)的具体步骤。你需要用最快的电脑建立一个每日构建服务器。写一个脚本,可以自动从版本控制服务器中导出(check out)完整的代码,(你当然使用版本控制,不是吗?),然后对代码从头开始进行构建(build),要构建所有的版本。如果你有一个安装打包程序,也要在脚本中自动运行。所有会卖给最终用户的东西都要包括在构建过程中。把构建出来的版本放在各自的的目录里,不同时间构建的相同版本也应该按日期整理好,不要相互覆盖。每天固定的时间运行这样的脚本。
- 最关键的是所有这些步骤都应该由脚本自动化完成,从导出(check out)代码到将最终产品放在网上供用户下载(当然在开发阶段,产品是放在一台测试服务器上的)。要保证开发过程中的任何东西的任何记录是由文档记录的而不是由某个人的脑子来记录的,这是唯一的办法。否则你会碰到这样的情况,产品需要发布了,可是只有Shaniqua知道如何将产品打包的,可是他刚刚被巴士撞了。在Juno公司(本文作者工作过的公司之一),要进行每日构建,你唯一需要学会的东西就是双击每日构建服务器桌面上的一个快捷方式。
- 如果你在发行程序的当天发现了一个小bug,没有问题。修正它,然后重新运行每日构建脚本,现在你可以安安心心的发行程序了。当然,黄金定律是每日构建脚本应该是把所有的事情从头做一遍,遵循这个定律就不会有什么问题。
- 将你的编译器的警告参数设到最大(在微软的VC中是-W4 ; 在GCC中是-Wall),当遇到任何一个最微小的警告时就应该停下来。
- 如果每日构建失败了,可能整个开发团队的工作会因此进行不下去。当务之急是立刻找出原因,使得每日构建能成功进行下去。某天也许你会一天运行好几次每日构建脚本的。
- 每日构建一旦失败,应该自动地将失败用email通知整个团队。提取错误日志中的恰当部分并包括在email正文中也不是很难。每日构建脚本也可以将当前的状态报告整理成一个html文件,自动发布到一个所有人都可以访问的web服务器上,这样测试者可以很快知道那个版本的构建是成功的。
- 当我在微软的excel团队中工作时,我们的一个有效办法是,谁导致每日构建(daily build)失败,他(她)就得负责照看当日的每日构建(译者按:微软通常每日构建都在半夜),直到有另一个人导致构建失败而接替他(她)。这样做当然可以使每个开发者都小心不要因为自己代码的错误破坏了构建,更重要的是团队中的每个人都可以学会每日构建(daily build)的原理。
- 如果你的团队在同一个时区工作,在午饭时间进行每日构建(daily build)是个不错的主意。午饭前每个程序员导入(check in)代码,这样当程序员在吃饭时,构建系统在工作。当程序员吃完饭回来时,如果每日构建失败了,所有的人也都在,可以尽快找出失败的原因。当构建系统运作起来时,没有人再会担心别人导入(check in)代码会妨碍自己的工作了。.
- 如果你的团队同时在两个时区工作,计划好每日构建(daily build)的时间使得一个时区的工作不会影响另一个时区的工作。在Juno公司,纽约程序员在晚上7:00导入(check in)到版本控制服务器。如果他们的导入导致构建失败,印度Hyderabad市(译者按:印度科技重镇)的程序员在纽约时间晚上8:00以后的工作几乎无法进行下去。我们每天进行两次每日构建(daily build),每次构建的时间都在两地的程序员回家之前,这下就没有问题了。
更进一步的阅读:
- 一些关于每日构建工具的讨论
- 做每日构建是很重要的,它是 获得高质量代码的12个步骤之一。
- Windows NT team G. Pascal Zachary关于微软Windows NT团队每周构建的书中有一些很有趣的东西。超级明星(译者按:原文为showstopper,指特别受人喜爱,杰出之人或之物).
- Steve McConnell(译者按:著名的软件工程作家,Win2000开发组总负责人,其名著《Writing Solid Code》,《Debugging the Development Process》都有中文译本。) 写的关于每日构建(daily build)的文章在这里.
概要
通过每日构建或者持续集成,项目组可以及早发现集成问题。现在它的意义已经为众多的开发团队所认识。一些重要问题的处理,如构建版本的分类和提升、与自动测试系统和项目或缺陷管理系统的集成,能够使项目组从中得到更多的收益。本文将对这些方面进行描述,并介绍作为开放源代码构建自动化和管理工具的
luntbuild
在以上方面的努力。
1
.每日构建和存在的问题
1
.
1
每日构建的一般机理
如今,每日构建已经在许多项目中得到广泛的应用,以便及早发现集成问题。本文描述了建立每日构建系统中的一些想法,并
介绍开放源代码的每日构建工具
luntbuild
(
http://luntbuild.sf.net
)。
每日构建的机理很简单:
1.
定期(如每夜)检查源代码库
2.
如果源代码库中发生改变将触发构建和后续的冒烟测试
3.
给源代码库的当前构建作一个标签,以便将来可以提取当前构建进行重新构建
4.
给相应开发人员发送报告构建情况的
email
。
XP
方法的持续集成风格也具有与每日构建相同的机理
1
.
2
每日构建存在的问题
如果下列方面能够得到妥善处理,项目组可以从每日构建系统中获得除尽早发现集成问题之外的好处。
1
.为了给并行开发提供最大程度的构建支持,构建系统应该可以将源代码库中来自不同分支或有不同标签的模块组合起来进行构建。这一点对于大项目的开发尤其重要。
2
.历史构建应该能够进行很好的管理。在项目开发尤其是持续集成过程中会产生大量的构建版本,因此,应该能够将他们加以分类以确定一些重要的构建。实现的方法有两个:第一个方法是将一个已经存在的低级构建版本(如通过每日构建产生的一个版本)提升进高级构建类(如测试构建类)。由于分类是在构建完成之后进行的,这种方法被称为后构建标识。第二个方法是直接在指定的类中构建。由于构建类是在构建之前就确定了,这种方法被称为前构建标识。除了分类,系统还应该能查找或删除历史构建。系统可以很方便地找到指定版本、日期、状态等的构建,这就能够在大量的历史构建中搜寻而快速地找到指定的构建。通过删除不重要的历史构建,可以节省存储空间。
3
.当开发大项目时,需要构建记录不仅应该包含最终的分发包,还应该包含一些中间文件或是调试版本文件。因此,构建系统除了替项目组对源代码库进行集成校验外,还提供近期的开发基准或是调试环境。这样,将来可以很方便地通过发出一个命令实现下载特定构建的相关文件并更新开发环境。
4
.当控制访问不同的项目或者是系统的构建或功能时,特别是系统由多个项目构成或是可以被客户下载的等情况,需要使用访问控制机制进行访问控制。
5
.每日构建系统必须能够与项目管理、缺陷跟踪系统建立连接,这样才能方便地建立并保持构建和
bug/feature
之间的关系。例如,某开发人员提交一个
bug
给缺陷管理系统申明该
bug
将在下一个构建中改正,当下次构建完成之后构建管理系统将通知缺陷管理系统刚刚完成构建的版本号,从而缺陷跟踪系统能够知道该开发人员刚刚提交的
bug
在哪个版本中被提交。并且,当发布一个特定的版本时,构建系统可以与缺陷管理系统连接以获得本版本处于不同状态的的
bug/feature
列表和描述,并写入
release notes
中。
6
.每日构建系统必须可以与自动测试系统连接以便在构建完成后除单元测试外的进一步测试。例如,构建系统在生成一个新的构建版本之后将通知自动测试系统,接到通知后,测试系统将下载新的构建版本,将其安装并进行项目跟踪系统所要求的冒烟测试等。
实现以上提到的每日构建系统的各个功能并不象安装一个每日构建系统那么容易。在研究了已有的工具之后,我打算开发一个全新的自动构建工具——
luntbuild
,它可以逐步实现上述功能。当前发布的
1.0.2
版本除了具有每日构建系统或持续集成工具的基本功能之外,还可以实现上述的
1
、
2
、
3
功能。下文将介绍
luntbuild
,解释它在这些方面的功能。
2
.
Luntbuild
如何解决上述问题
Luntbuild
不仅仅是一个每日构建和持续集成工具,而是一个自动构建和管理的系统。它基于
apache ant
,需要存在一个
ant build
脚本。而通过提供一个
ant
脚本封装,
ant
脚本的编写会变得十分容易。在用户手册中详细说明了具体步骤。
2.1
对并行开发提供构建支持
Luntbuild
利用项目、视图、模块和计划的概念来自动化和管理构建。项目表示实际的项目以及对应的源代码库。模块表示一组具有版本号的目录以及文件,通常是一个特定分支上或者由特定标签标识的一个源代码目录路径。视图包含若干模块,而项目又包含若干视图。下面举例对此进行说明:
在
luntbuild
中配置一个
cvs
项目“
footbar
”,假定
footbar
包含“
src
”和“
web
”两个目录,“
src
”包含功能源代码而“
web
”包含
web
界面的
html
文件。我们首先创建一个由主分支上的“
src
”和“
web
”模块组成的“
development
”视图,该视图计划每晚构建。开发顺利进行,某日发布了“
foobar-1.0 build123
”并出售给客户。后来,客户反馈的
bug
与“
src
”目录中代码有关。为了解决这些
bug
,我们在源代码管理系统中为“
src
”目录创建“
1_0_bugfix
”分支,同时,在
luntbuild
的“
footbar
”项目下创建“
foobar1.0-bugfix
”视图,配置两个模块:“
1_0_bugfix
”分支上的“
src
”模块,和“
foobar-1_0-build123
”标签上的“
web
”模块。该视图的构建计划仍然是每天晚上。这样,新功能和改正
bug
的开发工作有单独的视图,并且可以分别按计划进行每日构建。
2.2
对历史构建管理的支持
在
luntbuild
中,计划不仅用于实现自动构建,还用于实现构建分类。计划被创建之后,可以与特定的视图相关联。这种视图
-
计划对被称为“构建计划”或“构建分类”。为了支持多个构建分类,一个视图可以与多个计划相关联。例如,你可以给开发视图关联三个计划:“每小时”、“每晚”和“发布构建”。“每小时”和“每晚”计划将自动被触发,而“发布构建”计划只能手动触发。为了提高构建速度,我们将“每小时”计划配置为增量构建;为了避免源代码库中的标签过多,我们将“每小时”计划的策略选为“不设标签”。“每小时”计划主要为“持续集成”而设置,这样,可以保证源代码库的一致性。为了提高可靠性,我们将“每晚”计划配置为干净构建;为了使所有的夜晚构建可再现,我们将“每晚”计划的策略选为“在构建成功时进行标签”。“每晚”计划为“更新开发环境”而设置,这将用于更新开发人员的开发环境。“发布构建”计划为维护所有已经发布出去的构建而设置,该构建类中的版本可以是手工触发该类中的构建而生成(即构建前标识),或者是采用构建后标识方法将其他构建类(如每晚构建类中的经过测试过的版本)提升到本类中。
除了构建分类和提升,
luntbuild
还可以严格或不严格匹配地查找某个版本号的构建,查找指定时间范围、状态或特定构建类中的构建。可以删除查找结果或是特定的构建版本。这样,就可以很方便地执行某些操作比如查找失败的历史构建并删除之。
而且对于某个特定的构建版本,可以删除它下面的文件,或者上传新的文件。从而达到为某个特定的构建版本打补丁的目的。
2.3
对设置最新开发环境的支持
在
luntbuild
中,特定构建版本所发布的文件是由用户提供的构建脚本决定的,
luntbuild
仅仅提供一个发布文件的根目录。所以您可以自由选择要在
luntbuild
中发布的文件。对于大项目来说,如果最新的开发环境包(如调试版本文件等)可以与最终分发包一起发布,开发人员就不需要花很长的时间来编译其他开发人员的代码以更新环境,这将带来极大的便利。
2.4
正在开发中的功能
所有的单体测试或冒烟测试应在构建脚本中进行,
luntbuild
捕获脚本运行的输出结果,并最终发送给为失败视图负责的开发人员(邮件策略是可以配置的,你可以选择每次构建都发送邮件或是不发送邮件)。将来,
luntbuild
会以图形的方式显示测试结果,这会比用原始的日志文件表示得更加清楚。
目前,
luntbuild
尚未实现访问控制。当在开放环境中使用
luntbuild
时,访问控制功能具有重要的意义,因此,现在正在设计这个功能,在日后发布的版本中将会实现。
Luntbuild
尚不能与第三方项目管理
/
缺陷跟踪系统或是自动测试系统连接。这项功能将在日后发布的版本中实现。第一步是提供
web-service
类
API
,使其他系统可以方便地访问
luntbuild
构建信息。