进入Harmony 世界,类库开发最佳实践
2006 年 12 月 14 日
Apache Harmony 是 2005 年 5 月宣布的开放源码 Java SE 实现,本文是由 5 部分组成的 进入 Harmony 世界 系列文章的第四篇,这个系列主要介绍 Apache Harmony 项目的内部实现,最新发展现状和开源 Java 开发的模式,并鼓励和欢迎大家参与到 Harmony 的社区中来。
本文较详细地介绍了 Harmony 项目类库(API库)开发过程当中的经验,从架构设计和软件工程的角度,介绍了类库开发过程当中积累的类库模块的划分、测试优先的开发模式、结对编程、代码审核等等最佳实践。
类库模块划分
类库模块的划分,是 Harmony 项目类库总体设计上的主要特点。通过将庞大的 J2SE 类库划分为若干个相对独立而且小规模的功能模块,Harmony 的开发者简化了类库实现的过程,降低了开发类库的风险。类库模块的划分原则和具体划分方式,是项目进行和结构设计实践当中不断总结和归纳出来的。
Harmony 模块化背景和目标
实现一个完整的兼容的开源 J2SE,这是一种很棒的开创性的想法,但它的实现有着相当的难度,有很多的问题需要注意。开源软件的开发流程,具有一定的自由性和不确定性。通常的情况是,来自世界各地的开发者,在一个组织的统一协调下,分工合作,不定期的提交一些功能模块,然后集成到一个大的平台产品当中。这样的开发方式是开源软件的固有特征,但是对于 J2SE 这样的庞大体系,这样的开发模式会增加产品的风险。为了减小风险,便于平台的升级和维护,就要充分考虑以下问题:合理的划分平台,定义好模块之间的接口,以及降低耦合性。总结来说有以下几个目标:
- 符合 J2SE 基本结构
与现有的 J2SE 实现功能上兼容的同时,在基本的大的组件方面也要做到一致。如 JVM、类库、JIT 组件等等大规模组件必须对应一致。这样可以使 Java 类库的开发工作易于管理,开源社区的参与者可以集中开发他擅长的模块。
- 独立开发
为了开发的便利和风险的降低,平台的模块必须实现独立开发,各个模块之间尽量减少耦合,这样才可以发挥开源项目的优势,避免其缺陷。
- 模块替换
在 Java 的开源世界里,随时都有很多开源软件的出现,如果出现了某些优秀的开源 J2SE 组件,应该允许它们方便的替换进来;另外,如果发现本身某一个模块实现上的不足,也可以方便的替换出去,而不影响整个平台。
- 共享组件
除开提供完整的 J2SE 实现,这个项目的另外一个有价值之处就是在于为开源社区提供共享的 J2SE 组件。这个目标十分重大,因为一个完整的 J2SE 平台,本身就意味着了聚集了很多有价值的组件。所以,平台的设计必须确保这些组件可以方便的被共享和重用。
针对以上目标,在 Harmony 项目中采用了模块化的划分方式。在 Sun 公司的最初设计里面,根本没有考虑过对 Java 模块化,也没有开源的想法,所以原有的耦合性是比较大。而且,这个划分既要有全局的划分,即把整个 J2SE 划分为几个大的组件,如 JVM,API 类库,Tools 等等,也有对组件内部的划分,例如把类库进一步划分成一些模块,不同的模块实现不同的功能。
类库设计
完整的 J2SE 平台的模块化设计,首先应该在最抽象的层次,按照设计目标,划分出整体的模块组件,规定好它们的互联规范,比如对类库、虚拟机间接口的定义;然后,应该在比较具体的层次,在比较大的组件当中再次划分子模块,例如类库的进一步划分。
类库(Class Library)是 J2SE 当中最复杂和最常用的部分。它为 Java 开发者提供了丰富的编程接口,给 Java 应用程序提供了基础类库。Harmony 类库的目标,是要实现一个标准 J2SE 类库,和现有的 Sun 的 JDK 兼容一致。虽然不会增加特性,但是在完全不涉及 Sun 的源代码的前提下,仅仅依靠公开的 Java 文档,设计和开发这样的类库,依然是一项艰巨的工作。
Harmony Class Library 分为三十多个模块。如果我们从上下层次上看,它们主要上分为以下四个大类,主要模块如图所示。
图一 类库划分总图应该注意到,这个划分和 Sun 公司的 J2SE 结构图并不相同,这是因为,Harmony 为自己项目的特点,对已有的基于包(package)的类库结构按照功能重新分析和归类,有的模块是从包中划分出来,如 instrument 模块做 Java 虚拟机的类重定义和管理功能,它本来是 java.lang 包当中的子包,但是由于功能的独特,所以就被单独分为一块。
下面逐一介绍这四大类和主要的模块。
- 基础模块 (LUNI) 与 NIO
LUNI 是 Lang、Util、Net、IO 这些模块的首字母总称,他们和新的 NIO 模块一起表示类库的基础和核心部分,处于类库的底层。当然,LUNI 当中也有些独立分出模块的子包,如 instrument,management 模块,这些模块作为虚拟机监测工具类库,就不属于 LUNI。
之所以要把 LUNI 和 NIO 同看作为基础模块,是因为它们互相之间有着密切的联系。在实际实现中,它和 IO 和非常多的底层公用,两者有紧密的联系。如 NIO 当中的 SocketChannel 和 NET 中 Socket 基本上就可以共用同一套底层代码。
Lang 模块就是 java.lang 包中的基本模块。JVM 中的 Class、String、反射和引用等类虽然在 lang 包里面,但不属于这个模块(属于VMI); java.lang.instrument 和 JMX(Java Management Extension) 中的 java.lang.management 子包也不属于这个模块。Lang 模块主要包括基本的语言方面和类型方面的类,如基本类型的包装类(Byte, Boolean 等),基础的接口和抽象类(如 Comparable 接口和枚举抽象类 Enum)等等。另外,5.0 中的用于实现“元数据”技术的注释模块(java.lang.annotation)的新模块也属于这个模块。
Util 模块就是 java.util 包当中的基本模块(它并不包括 util 当中的任何子包,因为这些子包都各自有独立的功能,被分成了其他模块)。Util 模块包含了 Java 当中最常用的集合框架(Collection Framework)。集合框架当中定义了最常用的集合数据结构的表示接口,如 Set、Map、List 等;以及这些数据结构的不同具体实现,如 HashSet、TreeMap、LinkedList 等;还有一些集合工具类,如 Arrays,提供了基本的集合操作算法(插入、查找和排序等)。这个框架当中的接口和类极为重要,不仅被应用程序频繁使用,而且许多类库的实现也依赖它们。所以说,这个框架的性能和功能在一定程度上决定了类库的性能和功能。
Util 模块还包含了一些常用类。如时间和区域相关的 Calendar、TimeZone、Locale 等类;格式输入输出类 Formatter、Scanner;随机数生成类 Random;用于全球化的 ResourceBundle 类等等。
Net 模块包括与网络操作相关的模块,主要在 java.net 包里面。Java 语言最开始的一大亮点就是对网络的强大支持。在 Net 模块当中,网络当中的抽象概念,如 URL、Proxy、Socket(甚至有缺省的 http/ftp 等协议的实现)等都被方便的提供出来,使得开发者不用关心底层网络实现,大大提高了网络程序开发的能力。
IO 是基于流的输入输出模块,由于性能上的缺憾以及对异步 IO 的要求,NIO 作为一个新的 IO 模块加入了 Java 库。IO 和 NIO 联系紧密,在 NIO 出现以后,IO 当中的绝大部分都用 NIO 重新实现了。NIO 基于缓冲(Buffer),实现了异步 IO,对于文件、网络等基本 IO 都做了新的实现和优化。
NIO 是新的 IO 模块的缩写(除了字符集(Charset)以外的模块),它是 Java1.4 提出来的最重要的新特性之一。与旧的 IO、NET 模块相比,它支持非阻塞模式(non-blocking),提出了 Buffer 概念来替代数组,目的是提高整体的 IO 效率。
- 通用模块 (Common)
在 LUNI 的上面,是许多相对独立的模块组成的通用功能模块区,其中有很多是从 java.util 包当中分离出来的,这些模块一般是实现了 Java 某一重要方面的专门功能,这些功能适用广泛,可以为桌面应用程序和企业级服务程序提供丰富的数据结构和操作功能。
Concurrent 是 J2SE 5.0 版本提供的新组件。这个模块主要提供 Util 模块当中集合框架的线程同步功能,是一个帮助开发者简便实现线程安全的容器和集合的模块。这个模块虽然主要为集合框架提供支持,但是它的工作是多线程方面的,为了独立开发的需要,被单独分为一个模块。
Archive 模块实现了 zip 压缩文件的算法,提供了 jar 文件(使用 Zip 算法的压缩文件)生成、读取和管理的功能,为 Java 程序的压缩、发布和传播提供了便利。
Log 模块(logging)是提供程序日志信息管理的组件。这个模块实现了多种形式的日志信息表示方式(文件、控制台、网络流等),提供了多种日志权限的管理,为记录程序的运行状态和 debug 提供了便利。
Prefs 模块是提供用户配置信息管理的组件。这个模块比较特殊,因为它必须为不用 OS 平台提供不同的 Java 实现。
Regex 模块是提供 Java 正则表达式的组件。这个模块实现了类似 Perl 语言的正则表达式支持,提供了字符串验证、查找等功能。这个模块被 Java 类库和应用程序广泛使用,提高了软件开发中字符串操作的效率和可靠性。
Charset 模块是 java.nio 的子包,是提供 Unicode 字符集管理和转换的组件。虽然这个模块被定义在 nio 的包当中,但是它只是用到了 nio 的数据结构而已,功能上比较独立,Harmony 在设计时将它单独分开。
Text 模块是一个文本解析组件。这个模块实现了文本格式管理和解析功能。
SQL 模块是提供通用数据库 SQL 语言操作和查询的组件。这个模块提供了对于数据库查询和操作的一般框架和数据结构。
其他的通用模块还包括数学 Math 库、Java Beans 等等。
- 企业级模块 (Enterprise)
企业级模块区由一些比较高级的企业级应用模块组成。虽然它们被称为企业级模块,并不代表只被用于企业级系统开发当中,而是指它们一般被 J2EE 程序应用,涉及一些企业级的特性和功能,某些企业级模块在桌面应用当中也会用到,甚至于还十分基础和重要,如 Security 模块。
这个模块区的划分主要是因为平台实现的原因,因为这些模块有一些通用的特性与企业级应用相关,如 XML、RMI 等,这样符合设计的目标,利于独立化开发。
Security 模块既包括 java.security,也包括 javax.security,及其子包,它们定义了丰富的安全性概念和操作。这个模块当中的某些部分比较底层,甚至出现在 JVM 的类库当中,但是主要的功能还是用于企业级应用的安全验证,例如标准的公开密钥算法、授权证书、报文摘要等等,所以这个模块被分到了企业级模块类。
JNDI 模块存在与 javax.naming包及其子包当中,是一个典型的企业级应用模块,用于企业级命名空间管理;JMX 模块是一个 5.0 版本新引入的模块,存在于 javax.management 当中,实现了对 Java 平台自身的监控和管理,它的出现是 Java 在自身信息管理方面的发展。
SQL 模块提供了标准的 SQL 数据库访问构架;JDBC 模块存在于 javax.sql 当中,和 SQL 模块不同的是,它提供了一种具体的数据库访问功能,而非通用的框架,等等。
- 客户端模块 (Client)
客户端模块主要指的是桌面应用程序用到的模块,例如用户界面 swing、awt 和 applet 等等。这个模块在 Java 语言的早期比较流行,但是,这些桌面应用因为它们比较慢,而且用户界面比较差而不受重视,尤其是新的 Java 用户界面库 SWT 的出现以后。随着 Java 的进一步发展,AWT 和 SWING 也有了进一步的发展,最近 J2SE5 中,它们已经比较完善了。而且,Harmony 对 J2SE 平台兼容性的要求,也需要开发者去实现它们。
这些模块包括: applet 模块, awt 界面模块, swing 界面模块,print 打印功能模块;sound 声音处理模块。Swing 和 awt 界面模块占据了整个 J2SE 平台 Java 代码量的相当大一部分,据称几乎超过 50%。
测试优先开发模式
有了类库模块的划分,开发者就可以针对某一个模块,进行具体的开发了。在具体的类库开发过程当中,开发者必须保证代码的正确性,以及代码行为跟已有参考实现 RI(Reference Implementation,即现有的商业 J2SE 的实现,如 Sun 的 JDK)的兼容性。这些必须通过详细的单元测试来保证,在实践当中,Harmony 的开发者采取测试优先的开发模式来保证这一点。
测试优先或者称测试驱动(Test Driven)的原则,是为了保证软件的质量,将软件的测试优先考虑,在分析、设计、实现和集成等等开发步骤当中首先完成相应的测试代码,来保证软件产品质量的原则,这是 XP (极限编程)实践当中一个很重要的内容。
在 Harmony 项目当中,由于开源的 J2SE 实现必须和现有的商业的 J2SE 实现保持兼容,所以,测试优先原则受到了十分的重视。我们根据项目特点,在实现 API 库的开发过程中,提出了具体的测试优先实践方案,用于 API 实现的单元测试。开发一个具体的 Java 类时,采用以下的流程:
- 阅读规范
首先,开发者将仔细阅读这个类的 API 公开的规范文档,以充分了解这个类的基本情况和功能、其中每一个公开函数和公开域的含义和功能、必要的数据结构和算法、特殊情况的处理方法和异常抛出的规则,以及类与类的公开函数间的关系,等等。有了这样的了解,就可以对这个类的实现,以及在整个模块中它起的作用有一个详细的认识。
- 完成单元测试代码
根据以上了解,开发者将使用 JUnit 这个测试工具,编写相关的、尽可能完备的单元测试代码,覆盖文档上所提到的所有可能的输入输出情况和对象状态情况。
一般针对每一个类,开发者都要实现一个 JUnit 测试类(test class);针对这个类当中的每一个方法,要实现若干个测试方法,分别对应于不同的输入输出,对象状态,要覆盖所有正常和异常情况;针对规范中提到的其他特性,例如多线程同步和序列化支持等,也要给出相应正常和异常的测试。而对每一个包(package),都有一个测试包(test suit)。
在 Harmony 的进一步开发过程中,也有人提出使用 TestNG 等测试构架,用于 Harmony 这种大规模的总的单元测试,现在这个还在讨论中。
- 测试 RI 的实现
有了这些的测试代码,开发者可以开始测试 RI 的具体表现。开发者在 RI 的实现下运行测试,将不通过的测试进行分析和修改,确保测试全部通过。当然,有些时候,开发者可能发现 Sun 的实现并不符合规范要求,或者和基本逻辑不符。这个时候,开发者记录这个差异,然后保持测试结果与 API 规范的要求相一致。
这一步的主要目的是在分析和修改这些测试的过程当中,开发者可能会发现原有测试代码当中很多不完备和没有考虑到的情况,通过测试,可以进一步完善测试代码。
- 编写实现代码
当测试通过了以后,开发者等于已经从规范定义和代码实例两个方面掌握了这个类的行为和特性,可以开始正式编写开源的实现代码了。当然,在编写过程中,测试代码也可以进一步的完善。
- 测试实现代码
最后,开发者将开源的实现代码通过测试。在测试中,开发者需要不断的修改自己的实现,使得开源的实现和 RI 的实现一样,同样全部通过测试(当然,RI 的 Bug 部分除外)。这样,开发者最终得到了一份和 RI 的实现兼容的开源实现。
在此同时,我们还得到了一份可靠的单元测试代码。这份测试代码可以被反复的使用,并且实现测试的自动化,十分有利于项目的增量开发和持续集成中的回归测试。
测试优先的主要流程如图所示。
图二 测试优先流程
Harmony 项目当中使用的流程是一个测试优先原则的很好实践,在这里,测试不是一种“附加”的技巧,不是为了写测试而写测试,而是实现实际代码的必经之路。在现在一般的软件项目当中,虽然测试优先是一个被反复提倡和强调的概念,因为开发者如果不写测试,工程依然可以被开发出来(尤其是大量时间比较紧的项目),这时的测试就是一个可能被忽略的环节。但是这样会出现质量隐患;而在 Harmony 项目的特点就是,我们开发的软件必须要和现有的实现保持兼容,而且公开的文档很难直接指导实现工作,有很多的细节必须通过对现有实现的单元测试来得到答案,没有测试,开发根本无从下手,这时的测试就成了不可能被遗忘和忽略的一环。而且因为有了现有 RI 的实现,测试可以被提前开发出来,并且得到了进一步的优化和测试,这样,这个开源项目就真正实现了测试“优先”。
结对编程
有了以上的开发方式,Harmony 的开发者如何在团队协作的环境下来有效的实施这些原则和方法呢?在实践当中,开发者总结和归纳出了一套以“结对编程”为主的团队协作方式。
结对编程(Pair Programming)也是 XP 当中的一个重要实践,它的基本理论是:两个开发者结成开发对子,一起开发一个模块,一个人编码的时候,另外一个人在旁边进行检查和核对,随时对编码过程中的错误和疏忽做出纠正。这个开发模式似乎是在浪费人力资源,让两个开发者干一份工作,但是,在很多项目的开发当中,这种模式可以大大降低程序修改和返工的概率,提高质量,总的来说反而提高了人力资源的利用,因为两个开发者都在干有意义的事情,并没有浪费人力。
在 Harmony 项目当中,基于测试优先的原则,开发者提出了自己的结对编程实践方法,如图所示。两个开发者组成一个开发对子(Development Pair),一起阅读两个类的 API 规范文档, 然后,两人分别完成一个类的测试代码,接着,两人交叉换位,依据对方完成的测试代码,完成另外一个类的实现代码。
图三 结对编程
这样,虽然两人没有如传统结对编程里面提到的那样直接监督对方编程,但是通过测试代码,两人实现了相互独立的检查对方的工作,使得任何一个类都有两个人来完成,增加了交流机会和发现问题的机会,提高了产品的质量。这个方法基于测试工作的重要地位以及测试优先的工作流程,是保证开源项目品质的一个很有效的方法。
代码审核
对于这个项目而言,代码本身就是产品,需要开放出来给全世界的人看,所以代码的可读性和书写规范就变得十分重要。为此,开发者制定了统一而严格的规范(Checklist),使用公认的优秀的编码风格来实现这个平台,从变量命名的规范到程序结构的安排,这个规范十分清晰而完整。
为了保证这个 Checklist 被很好的落实,开发人员在 Development Pair 的基础上,在某些情况下(如新成员加入,对工作尚不熟悉时),添加了一个新的角色:代码审核员(QA)。
一般地,在需要 QA 的 Development Pair 当中,有 Level1 和 Level2 两个 QA 角色:Level2 的 QA 主要负责检查结构和代码的缺陷和隐患,例如可能的 Bug、不妥的设计结构等等;Level1 的 QA 主要负责检查除开 Bug 以外的表面上的问题,例如注释的书写问题、代码命名规范和容易引人混淆的代码书写等等。
这样的安排主要是为了加速培训新成员(对于 QA)以及保证代码质量(对于 Development Pair),在实践当中有着很好的效果。为了在一定时期内可以最大程度的调动成员的积极性,也为了让每个成员都有机会在不同的角色上面得到锻炼,开发人员设计了一个“轮换游戏”(Rotation Game),以一定周期内的表现来决定下一个周期各位开发人员的角色。
这个“游戏”的规则比较灵活,以三个 Development Pair 为例,Rotation Game 的规则如下图所示,其中蓝色表示代码开发人员,黄色表示 Level2 的 QA,红色表示 Level1 的 QA,每一个小“Pair”代表两个人的对子:
图四 轮换游戏
在实践当中,这样的 QA 代码审查和角色轮换的工作,大大提高了代码的质量,这样的工作方式和上述单纯的结对编程方式相结合,灵活应用,很好的保证了 Harmony 项目的代码质量要求。
除开人工审核以外,自动审核的作用也很有效。除了使用 Eclipse 内部的代码格式化功能以外,开发者还统一将 Eclipse 内部的代码编译要求提高,将所有被忽略的代码规范要求都设置成“警告”级别,使得代码中的很多小问题,例如注释的格式不对等等,都成为了警告,开发者原则上必须消除所有警告,如图所示;另外,开发者还使用和配置代码检查工具 Checkstyle,对代码的深层次问题,例如可能出现的内存泄漏,都做出了检查。
图五 代码审核自动化的手段之一
此外,开发者还定期召开“代码审核会议”,利用以前积累的经验规则,用人工检查的讨论方式,来改进代码质量。这也是开发者之间促进交流和沟通的一个很好方式。
除开静态的检查以外,开发者还会使用 Purify 等动态检查工具,用实际运行的方式来发现程序的缺陷,如内存泄漏问题等。
结束语
本文介绍了 Harmony 项目进行当中,尤其是在 API 库开发过程当中逐渐积累和总结出来的最佳实践经验。这些经验不是不变的,而是随着项目的进行,逐渐改进。
参考资料
学习
- 阅读 进入 Harmony 世界 系列文章的其它部分。
- 从 Apache Harmony 项目的 官方网站 中获得更多相关信息。
- 了解 Harmony 的类库 API 的覆盖率统计报告: JDK 1.4、JDK 1.5。
- 如何 参与 到 Apache Harmony 项目。
- 定制 bugzilla 进行项目管理(developerWorks Java,2006 年 10 月): 介绍了如何在 Apache Harmony 项目中使用流行的缺陷跟踪系统 ― Bugzilla。
- 追求代码质量 专栏:来自质量专家 Andrew Glover 关于测试覆盖工具方面的专家意见。
- 让开发自动化 专栏:专门探索软件开发过程自动化的实际应用。
- 揭开极端编程的神秘面纱 专栏:帮助您理解 XP,并解释为什么它这么重要。
获得产品和技术
- Apache Harmony 的二进制版本 下载。
- IBM Apache Harmony 开发包(VM Environment) 下载。
- IBM JDK 下载。
作者简介
罗毅 就读于南京大学计算机系,研究的领域包括软件过程和工作流,您可以通过alexluoyi@hotmail.com联系到他。 |
蔡一超,就读于南京大学软件学院 对java和Linux技术有浓厚的兴趣。您可以通过caiyichao@gmail.com联系到他。 |
胡睿,目前就职于 IBM 中国开发中心 Harmony 开发团队。 主要负责类库开发的技术工作,对 J2SE、J2EE、C/C++、全球化技术、软件测试等有浓厚的兴趣。另外,他对历史、电影、音乐与羽毛球等也十分热爱。您可以通过ruihu@cn.ibm.com联系到他。 |
以上是 进入Harmony 世界,类库开发最佳实践 的全部内容, 来源链接: utcz.com/p/205621.html