Unwrap the package statement's potential
(挖掘package声明的潜力)
Minimize project complexity and maximize code reuse
(最小化项目的复杂性,最大化代码的复用程度)
by Laurence Vanhelsuwe
总评
package声明是Java语言的一项非常强有力的特征。然而大多数的Java程序员,甚至那些富有经验的开发者,并没有正确的挖掘出它的潜力来。更糟的是,许多开发者错误的使用了package的声明,使得它在试图控制项目复杂性和代码的复用方面出现问题。感兴趣了吗?那就继续阅读下去,看看这样一个简单的语言特征是如何拥有如此巨大的反响的。
为了避免最高级别的package命名冲突,除了众所周知的并且通常由Sun微系统公司申明的package命名约定之外,很少有程序员彻底地理解这个既让人迷惑但又是简单的package声明了。大多数的编程者认为关键字package最多就是用来将项目中的class整理成组。他们简单的使用package声明来为每个项目创建单一的命名空间。但不幸的是,这种方式并不能经受时间或者范围的考验。
当一种过分简单化的package观念添加到以团队为范围(先不说企业为范围)的Java代码仓库中时,您就会逐渐地同时也是痛苦地发现,错误地创造和管理Java代码仓库中的package层级意味着十分的昂贵和费力。更糟的是,这些问题将随着您代码的逐渐庞大而变得越来越严重,特别地,还会给代码带来项目边界模糊的问题。
因此,从使用package声明的那一刻起,就必须正确的做一些选择和决议。
在这篇文章中,我将解释为什么许多的Java程序员没有正确的使用package关键字,然后演示一个可供选择的并且已经经受了时间考验的一些方式。
新手使用Java package的方式
在你最初使用Java来编写程序的时候,一般是根本不会使用包声明的。那个用来作为语言介绍的经典HelloWorld程序确实没有使用package,也没有以任何方式来讨论Java package或者package关键字:
// (没有使用package声明!)
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
你只是简单地声明你的类在缺省的package下(没有名字的package),所以你可以以最详细(verbose)的方式来运行你的Java代码(比如在你控制台的命令行):
> java HelloWorld
幸运的是,Java的设计并没有因这种代码执行的便利性而有任何的削弱。然而优雅并且强大地支持大规模的项目编码是程序设计的最高目标,这正是通过package这个特征来显现出来的。因此,以缺省package的方式来作为类声明在你真正实现项目的时候是并不足以支撑下去的。
类名冲突和package的诞生
随着您对Java越来越适应,您将会很快发现把所有的类都放置在缺省包下,就将受限于这个缺省包空间所能容纳的实际的类的数量。比如,假设您起初的一些用于练习的类起名为Main或者Program,而您的实际的项目同样需要一个名为Main或者Program的类来作为程序主入口,那么您就会发现类名起了冲突。要么删除些旧的类,要么通过把全局空间细分成多个命名空间来创建多个包空间以解决问题。
Java新手们特别的也是最终发现的关于包声明的问题在于当他们开始第二个Java项目,并且希望对他们第一个项目中的类和第二个项目中的类有一个清晰分离的时候。
很快的,为每个新项目创建新的包变成了程序员的第二个习性。不幸的是,许多的Java程序员对于真正的package的理解仅限于这一点上。但是以这种原始的方式在那些跨度比较长的项目中继续使用Java包特征无疑是远不能满足要求的,尤其是当代码量由小逐渐变得很大的时候。
代码复制:坚决说不
简单的为每个新项目创建新的包的一个长期的问题是代码的复制问题。代码复制是编写程序的地狱,这是因为:
# 维护的代价将由于螺旋状的上升而变得难以控制
# 代码难以阅读
# 代码变得越来越臃肿
# 系统性能可能会变得缓慢下来
我们都知道这些问题的根源:程序员那标志性的懒惰。这个过程通常是这样的:在我们工作的时候,我们会以自己的感觉认为这些是以前就已经做好的或者已经解决的(代码中逻辑的部分,或者整个方法,或者(希望不是这样!)整个类),于是我们高兴的把那些代码复制到新项目中来。这也就是剪切加复制编码的来源。
如果您对于自己的那些用近乎于一致的逻辑、方法甚至类所堆砌成的重复代码产生了难以控制的感觉,那么您就有必要对您每天使用的Java开发方法论进行反思,以释放包申明真正的力量。
The Big Bang...uh, I mean split [不能确切的翻译,故保留]
让我们从理论上来分析一下代码复制的问题,认定所有的代码复制是非法的并且根除它,任何重要的代码片断都应该出现并且只出现一次。这就是说,所有任何的
# 通用的逻辑
# 通用的数据分组
# 通用的方法/程序
# 通用的常量
# 通用的类
# 通用的接口
都不应当被申明在应用特定的包中。
这一关键的结论让我们有了如下的有关包结构的第一个黄金规则:
黄金规则一
永远不要将通用的代码直接混合在应用代码中!
比如:在com.company或者org.yourorg这一层上,将你的包层次再分为两个功能完全不同的分支:
1. 可复用的代码分支
2. 项目(应用)特定的分支
应用代码总是会用到通用的代码(类库以及程序),但是它们自己并不会包含这样的代码。相反的情况是:类库代码也永远不会包含任何应用特定的代码或者是和应用有依赖关系的代码。
如果你还从来没有考虑(编写)过这样两种不同类型的基本代码的话,那么你有必要在你日常的编码过程中考虑一下这种基本的代码归类(dichotomy)方法。这是在你的组织中应用代码复用能力,以及一次性的消除代码复制问题的关键所在。
这种代码的归类方法运用到package上的时候,逻辑上需要从最高级别上分为通用(复用)package的主分支以及非通用(非复用)(比如:应用特定的)主分支。
举个例子,在过去的五年中,我已经把org.lv这个最高级的命名空间分为了org.lv.lego和org.lv.apps这两个子空间。(lv并不代表什么,只是我最初的命名)这些基本的高级别的分支在以后的日子里面又分成了更多的子空间。比如lego分支目前分成如下的一些子空间:
org.lv.lego.adt
org.lv.lego.animation
org.lv.lego.applets
org.lv.lego.beans
org.lv.lego.comms
org.lv.lego.crunch
org.lv.lego.database
org.lv.lego.files
org.lv.lego.games
org.lv.lego.graphics
org.lv.lego.gui
org.lv.lego.html
org.lv.lego.image
org.lv.lego.java
org.lv.lego.jgl
org.lv.lego.math
org.lv.lego.realtime
org.lv.lego.science
org.lv.lego.streams
org.lv.lego.text
org.lv.lego.threads
请注意的是,基本上所有这些包结构的逻辑内容都自我证明了它们是经过仔细选择后的结果,并且也是从字面上就可以分辨出它们的含义(可以和java.*结构做对照)。这一点对于释放可复用代码真正的潜力作用是非常关键的,比如那些可复用的逻辑、程序、常量、类以及接口。很差的包命名,就象很差的类/接口的命名一样,会对它们的使用者产生歧义并且破坏资源的复用潜力。
在这些较深层次的包级别上,您仍然需要对如何进一步组织您的包结构非常的小心。
这里是第二条黄金规则:
黄金规则二
保持分等级的包结构
总是试图创建象平衡的、不规则形状的树结构那样的包层次。
如果你的包层次中的某些层次最终形成(退化为)线性结构,那表明了你并没有正确的使用Java包特性。经典的错误是简单的将项目包罗列在你的最高层应用包分支下面,比如在我的org.lv.apps包。这是错误的使用方式,因为它并不是层级结构的。线性罗列对于人脑来说是难以记忆很长时间的;而层级结构却正是适应我们大脑的神经系统网络结构的。
项目总是能由关键原则来进行归类,这原则或者说观念应该在你的Java包层次中反应出来。下面是我的org.lv.apps包目前是如何进行细分的:
org.lv.apps.comms
org.lv.apps.dirs
org.lv.apps.files
org.lv.apps.games
org.lv.apps.image
org.lv.apps.java
org.lv.apps.math
很明显,你们的细分肯定和我的不相同,但是重要的是从大处考虑并且在脑子里始终保留对将来的扩展能力。深层次的包结构是有益的。而浅层次的则不然。
……