InputStreamis=loaders[i].getInputStream();
if(is!
=null){
readXmlStream(is);
success=true;
}
}
}catch(IOExceptione){
log.debug("ErrorwhenfindResourcesByFile:
"+path,e);
}catch(SAXExceptione){
log.debug("ErrorwhenfindResourcesByFile:
"+path,e);
}catch(ParserConfigurationExceptione){
log.debug("ErrorwhenfindResourcesByFile:
"+path,e);
}
returnsuccess;
}
/**
*读取并解析一个XML的文件输入流,以Element的形式获取XML的根,
*然后调用buildFactory(Element)
构建工厂
*@paraminputStream文件输入流
*@throwsSAXException
*@throwsIOException
*@throwsParserConfigurationException
*/
protectedvoidreadXmlStream(InputStreaminputStream)throwsSAXException,IOException,ParserConfigurationException{
if(inputStream==null){
thrownewParserConfigurationException("Cann'tparsesourcebecauseofInputStreamisnull!
");
}
DocumentBuilderFactoryfactory=DocumentBuilderFactory.newInstance();
factory.setValidating(this.isValidating());
factory.setNamespaceAware(this.isNamespaceAware());
DocumentBuilderbuild=factory.newDocumentBuilder();
Documentdoc=build.parse(newInputSource(inputStream));
Elementroot=doc.getDocumentElement();
buildFactory(root);
}
/**
*用从一个XML的文件中读取的数据构建工厂
*@paramroot从一个XML的文件中读取的数据的根
*/
protectedabstractvoidbuildFactory(Elementroot);
完整代码在附件中。
在编写代码的过程中,通常有两种不同的方式。
一种是从下往上编写,也就是按照顺序,每分出去一个函数,都要将这个函数编写完,才回到主程序,继续往下编写。
而一些更有经验的程序员会采用另外一种从上往下的编写方式。
当他们在编写程序的时候,每个被分出去的程序,可以暂时只写一个空程序而不去具体实现功能。
当主程序完成以后,再一个个实现它的所有子程序。
采用这样的编写方式,可以使复杂程序有更好的规划,避免只见树木不见森林的弊病。
有多少代码就算大段代码,每个人有自己的理解。
我编写代码,每当达到15~20行的时候,我就开始考虑是否需要重构代码。
同理,一个类也不应当有太多的函数,当函数达到一定程度的时候就应该考虑分为多个类了;一个包也不应当有太多的类。
。
。
。
。
。
2)释义名称与注释
我们在命名变量、函数、属性、类以及包的时候,应当仔细想想,使名称更加符合相应的功能。
我们常常在说,设计一个系统时应当有一个或多个系统分析师对整个系统的包、类以及相关的函数和属性进行规划,但在通常的项目中这都非常难于做到。
对它们的命名更多的还是程序员来完成。
但是,在一个项目开始的时候,应当对项目的命名出台一个规范。
譬如,在我的项目中规定,新增记录用new或add开头,更新记录用edit或mod开头,删除用del开头,查询用find或query开头。
使用最乱的就是get,因此我规定,get开头的函数仅仅用于获取类属性。
注释是每个项目组都在不断强调的,可是依然有许多的代码没有任何的注释。
为什么呢?
因为每个项目在开发过程中往往时间都是非常紧的。
在紧张的代码开发过程中,注释往往就渐渐地被忽略了。
利用开发工具的代码编写模板也许可以解决这个问题。
用我们常用的MyEclipse为例,在菜单“window>>Preferences>>Java>>CodeStyle>>CodeTemplates>>Comments”中,可以简单的修改一下。
“Files”代表的是我们每新建一个文件(可能是类也可能是接口)时编写的注释,我通常设定为:
/*
*createdon${date}
*/
“Types”代表的是我们新建的接口或类前的注释,我通常设定为:
/**
*
*@author${user}
*/
第一行为一个空行,是用于你写该类的注释。
如果你采用“职责驱动设计”,这里首先应当描述的是该类的职责。
如果需要,你可以写该类一些重要的方法及其用法、该类的属性及其中文含义等。
${user}代表的是你在windows中登陆的用户名。
如果这个用户名不是你的名称,你可以直接写死为你自己的名称。
其它我通常都保持为默认值。
通过以上设定,你在创建类或接口的时候,系统将自动为你编写好注释,然后你可以在这个基础上进行修改,大大提高注释编写的效率。
同时,如果你在代码中新增了一个函数时,通过Alt+Shift+J快捷键,可以按照模板快速添加注释。
在编写代码时如果你编写的是一个接口或抽象类,我还建议你在@author后面增加@see注释,将该接口或抽象类的所有实现类列出来,因为阅读者在阅读的时候,寻找接口或抽象类的实现类比较困难。
/**
*抽象的单表数组查询实现类,仅用于单表查询
*@author范钢
*@seecom.htxx.support.query.DefaultArrayQuery
*@seecom.htxx.support.query.DwrQuery
*/
publicabstractclassArrayQueryimplementsISingleQuery{
...
2.可维护性
软件的可维护性有几层意思,首先的意思就是能够适应软件在部署和使用中的各种情况。
从这个角度上来说,它对我们的软件提出的要求就是不能将代码写死。
1)代码不能写死
我曾经见我的同事将系统要读取的一个日志文件指定在C盘的一个固定目录下,如果系统部署时没有这个目录以及这个文件就会出错。
如果他将这个决定路径下的目录改为相对路径,或者通过一个属性文件可以修改,代码岂不就写活了。
一般来说,我在设计中需要使用日志文件、属性文件、配置文件,通常都是以下几个方式:
将文件放到与类相同的目录,使用ClassLoader.getResource()来读取;将文件放到classpath目录下,用File的相对路径来读取;使用web.xml或另一个属性文件来制定读取路径。
我也曾见另一家公司的软件要求,在部署的时候必须在C:
/bea目录下,如果换成其它目录则不能正常运行。
这样的设定常常为软件部署时带来许多的麻烦。
如果服务器在该目录下已经没有多余空间,或者已经有其它软件,将是很挠头的事情。
2)预测可能发生的变化
除此之外,在设计的时候,如果将一些关键参数放到配置文件中,可以为软件部署和使用带来更多的灵活性。
要做到这一点,要求我们在软件设计时,应当更多地有更多的意识,考虑到软件应用中可能发生的变化。
比如,有一次我在设计财务软件的时候,考虑到一些单据在制作时的前置条件,在不同企业使用的时候,可能要求不一样,有些企业可能要求严格些而有些要求松散些。
考虑到这种可能的变化,我将前置条件设计为可配置的,就可能方便部署人员在实际部署中进行灵活变化。
然而这样的配置,必要的注释说明是非常必要的。
软件的可维护性的另一层意思就是软件的设计便于日后的变更。
这一层意思与软件的可变更性是重合的。
所有的软件设计理论的发展,都是从软件的可变更性这一要求逐渐展开的,它成为了软件设计理论的核心。
3.可变更性
前面我提到了,软件的变更性是所有软件理论的核心,那么什么是软件的可变更性呢?
按照现在的软件理论,客户对软件的需求时时刻刻在发生着变化。
当软件设计好以后,为应对客户需求的变更而进行的代码修改,其所需要付出的代价,就是软件设计的可变更性。
由于软件合理地设计,修改所付出的代价越小,则软件的可变更性越好,即代码设计的质量越高。
一种非常理想的状态是,无论客户需求怎样变化,软件只需进行适当地修改就能够适应。
但这之所以称之为理想状态,因为客户需求变化是有大有小的。
如果客户需求变化非常大,即使再好的设计也无法应付,甚至重新开发。
然而,客户需求的适当变化,一个合理地设计可以使得变更代价最小化,延续我们设计的软件的生命力。
1)通过提高代码复用提高可维护性
我曾经遇到过这样一件事,我要维护的一个系统因为应用范围的扩大,它对机关级次的计算方式需要改变一种策略。
如果这个项目统一采用一段公用方法来计算机关级次,这样一个修改实在太简单了,就是修改这个公用方法即可。
但是,事实却不一样,对机关级次计算的代码遍布整个项目,甚至有些还写入到了那些复杂的SQL语句中。
在这样一种情况下,这样一个需求的修改无异于需要遍历这个项目代码。
这样一个实例显示了一个项目代码复用的重要,然而不幸的是,代码无法很好复用的情况遍布我们所有的项目。
代码复用的道理十分简单,但要具体运作起来非常复杂,它除了需要很好的代码规划,还需要持续地代码重构。
对整个系统的整体分析与合理规划可以根本地保证代码复用。
系统分析师通过用例模型、领域模型、分析模型的一步一步分析,最后通过正向工程,生成系统需要设计的各种类及其各自的属性和方法。
采用这种方法,功能被合理地划分到这个类中,可以很好地保证代码复用。
采用以上方法虽然好,但技术难度较高,需要有高深的系统分析师,并不是所有项目都能普遍采用的,特别是时间比较紧张的项目。
通过开发人员在设计过程中的重构,也许更加实用。
当某个开发人员在开发一段代码时,发现该功能与前面已经开发功能相同,或者部分相同。
这时,这个开发人员可以对前面已经开发的功能进行重构,将可以通用的代码提取出来,进行相应地改造,使其具有一定的通用性,便于各个地方可以使用。
一些比较成功的项目组会指定一个专门管理通用代码的人,负责收集和整理项目组中各个成员编写的,可以通用的代码。
这个负责人同时也应当具有一定的代码编写功力,因为将专用代码提升为通用代码,或者以前使用该通用代码的某个功能,由于业务变更,而对这个通用代码的变更要求,都对这个负责人提出了很高的能力要求。
虽然后一种方式非常实用,但是它有些亡羊补牢的味道,不能从整体上对项目代码进行有效规划。
正因为两种方法各有利弊,因此在项目中应当配合使用。
2)利用设计模式提高可变更性
对于初学者,软件设计理论常常感觉晦涩难懂。
一个快速提高软件质量的捷径就是利用设计模式。
这里说的设计模式,不仅仅指经典的32个模式,是一切前人总结的,我们可以利用的、更加广泛的设计模式。
a.if...else...
这个我也不知道叫什么名字,最早是哪位大师总结的,它出现在Larman的《UML与模式应用》,也出现在出现在Mardin的《敏捷软件开发》。
它是这样描述的:
当你发现你必须要设计这样的代码:
“if...elseif...elseif...else...”时,你应当想到你的代码应当重构一下了。
我们先看看这样的代码有怎样的特点。
if(var.equals("A")){
doA();
}elseif(var.equals("B")){
doB();
}elseif(var.equals("C")){
doC();
}else{
doD();
}
这样的代码很常见,也非常平常,我们大家都写过。
但正是这样平常才隐藏着我们永远没有注意的问题。
问题就在于,如果某一天这个选项不再仅仅是A、B、C,而是增加了新的选项,会怎样呢?
你也许会说,那没有关系,我把代码改改就行。
然而事实上并非如此,在大型软件研发与维护中有一个原则,每次的变更尽量不要去修改原有的代码。
如果我们重构一下,能保证不修改原有代码,仅仅增加新的代码就能应付选项的增加,这就增加了这段代码的可维护性和可变更性,提高了代码质量。
那么,我们应当如何去做呢?
经过深入分析你会发现,这里存在一个对应关系,即A对应doA(),B对应doB()...如果将doA()、doB()、doC()...与原有代码解耦,问题就解决了。
如何解耦呢?
设计一个接口X以及它的实现A、B、C...每个类都包含一个方法doX(),并且将doA()的代码放到A.doX()中,将doB()的代码放到B.doX()中...经过以上的重构,代码还是这些代码,效果却完全不一样了。
我们只需要这样写:
Xx=factory.getBean(var);
x.doX();
这样就可以实现以上的功能了。
我们看到这里有一个工厂,放着所有的A、B、C...并且与它们的key对应起来,并且写在配置文件中。
如果出现新的选项时,通过修改配置文件就可以无限制的增加下去。
这个模式虽然有效提高了代码质量,但是不能滥用,并非只要出现if...else...就需要使用。
由于它使用了工厂,一定程度上增加了代码复杂度,因此仅仅在选项较多,并且增加选项的可能性很大的情况下才可以使用。
另外,要使用这个模式,继承我在附件中提供的抽象类XmlBuildFactoryFacade就可以快速建立一个工厂。
如果你的项目放在spring或其它可配置框架中,也可以快速建立工厂。
设计一个Map静态属性并使其V为这些A、B、C...这个工厂就建立起来了。
b.策略模式
也许你看过策略模式(strategymodel)的相关资料但没有留下太多的印象。
一个简单的例子可以让你快速理解它。
如果一个员工系统中,员工被分为临时工和正式工并且在不同的地方相应的行为不一样。
在设计它们的时候,你肯定设计一个抽象的员工类,并且设计两个继承类:
临时工和正式工。
这样,通过下