Java编程指南建议.docx

上传人:b****8 文档编号:11209300 上传时间:2023-02-25 格式:DOCX 页数:12 大小:26.39KB
下载 相关 举报
Java编程指南建议.docx_第1页
第1页 / 共12页
Java编程指南建议.docx_第2页
第2页 / 共12页
Java编程指南建议.docx_第3页
第3页 / 共12页
Java编程指南建议.docx_第4页
第4页 / 共12页
Java编程指南建议.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

Java编程指南建议.docx

《Java编程指南建议.docx》由会员分享,可在线阅读,更多相关《Java编程指南建议.docx(12页珍藏版)》请在冰豆网上搜索。

Java编程指南建议.docx

Java编程指南建议

Java编程指南

——摘自《ThinkinginJava3rdEdition》

 

设计

1.优雅终将得到回报。

从短期利益来看,要想对问题提出优雅的解决方案,似乎需要投入更多的时间,不过一旦它能够工作,就能够很容易地适应新的环境,而不是要花上数以时计,甚至以天计或以月计的辛苦代价时,这时你将得到回报(尽管没人能够准确衡量这些回报)。

你得到的程序不仅易于编写和调试,而且还易于理解和维护,这就是其价值所在。

明白这一点需要经验,因为你在精心设计程序的时候生产率不会很高。

要抵抗住浮躁心态的诱惑;欲速则不达。

2.先能运行,再求快速。

即使你确信某段代码非常重要,它将成为整个系统的瓶颈,也必须遵守这一点。

别着急。

先尽可能简化设计,让系统运转起来。

如果性能不够理想,再求助于性能分析工具。

你几乎总会发现,你“以为的”那些瓶颈,其实都不是真正的问题所在。

要把时间用在刀刃上。

3.谨记“分而治之”原则。

如果待解决的问题过于复杂,先设想一下程序的基本操作,并且假定已经有一小段“神奇代码”能够处理最困难的部分。

这段“神奇代码”可以看成一个对象,你可以编写程序使用它,然后再回过头来研究这个对象,把它最困难的部分包装成其它对象,依此类推。

4.区分类的编写者和使用者(客户端程序员)。

作为“客户”,类的使用者不需要也不希望知道类的底层工作方式。

类的编写者必须是程序设计方面的专家,这样编写出来的类有才可能在新手使用的情况下,仍然能够稳定运行。

请把类想象成对其它类的“服务提供者”。

程序库只有在内部实现对用户来说是透明的情况下,才会易于使用。

5.编写类的时候,类的名称要非常清晰,使得注释成为多此一举。

你的目标应该是提供给客户端程序员简单明了的接口。

为此,在恰当的时候可以考虑方法重载,以得到直观且易于使用的接口。

6.你的分析和设计所产生的系统中的类、它们的公共接口,以及类之间(尤其是与基类之间)的联系,必须达到最少。

如果你在设计中产生了过多的类,请回顾一下,这些代码在程序的整个生命周期中能产生效益吗?

如果并非如此,你就要付出维护的代价。

对于不能提高生产率的任何东西,开发团队的成员不会自觉地进行维护;这也是许多设计方法无能为力的地方。

7.尽量让所有东西自动化。

首先编写测试代码(在你编写类之前),并把它和要测试的类放在一起。

你可以使用某种构建工具,来自动运行测试。

你也许会用到Ant,它是Java构建工具的事实标准。

这样,只要执行测试程序,所有改动就可以自动获得验证,有了错误也可以立刻发现。

因为你信赖测试框架所具有的安全性,所以当你发现新的需求时,会大胆地进行全面修改。

请记住,程序语言最大的改进,来自类型检查、异常处理等机制所赋予的内置测试行为。

但这些功能只能协助你到达某种程度,其它工作还需要你自己完成。

要开发一个健壮的系统,你得自己编写测试用例来验证类或程序的性质。

8.在编写类之前先编写测试代码,以验证这个类是否设计完备。

如果你写不出测试代码,就说明其实你还不清楚类的功能。

此外,在编写测试代码的过程中,通常还能够发现类需要具有的额外特性或限制。

而这些特性和限制并不总是能够通过分析和设计得到。

测试代码也可作为使用类的范例。

9.所有软件设计中的问题,都可以通过“从概念上引入额外的概念上的间接层次”得到简化。

这是软件工程领域的基本原则1,也是抽象的依据。

而抽象正是面向对象程序设计的主要性质。

在面向对象编程中,我们也可以这么说:

“如果代码过于复杂,那么就引入更多的对象。

10.引入的间接层次要有意义(与准则9相应)。

这里所指的意义可以像“将常用代码放入一个方法内”这么简单。

如果你加入了无意义的间接层次(通过抽象或封装等等),那就会和没有引入间接层一样糟糕。

11.尽可能使类原子化。

每个类要具有简单明了的用途,用它来向别的类提供服务。

如果类或系统设计得过于复杂,请将它分割成几个较简单的类。

一个最明显的判断依据就是类的大小:

如果类很大,那它很可能负担太重,就应该被分割。

建议重新设计类的线索有:

1)复杂的switch语句:

请考虑使用多态。

2)有许多方法,各自处理类型极为不同的操作:

请考虑划分成不同的类。

3)有许多成员变量,用来表示类型极为不同的属性:

请考虑划分成不同的类。

4)其它建议请参考《Refactoring:

ImprovingtheDesignofExistingCode》,MartinFowler著,(Addison-Wesley1999)。

12.当心冗长的参数列表。

参数列表过长将使得方法调用变得难以编写、阅读和维护。

你应该试着将方法放到更合适的类中,并/或使用对象作为参数。

13.不要一再重复。

如果某段代码不断出现于许多派生类方法中,应将该段代码置于基类的某个方法中,然后在派生类方法中进行调用。

这样不仅可以减少代码数量,也易于修改。

有时候,找出这种通用代码还可以为接口增加实用的功能。

在不牵涉继承的情况下,也可能会遇到这种情况:

如果类中的几个方法使用了重复的代码,请把这些代码移到某个方法里,然后在别的方法中进行调用。

14.小心switch语句或嵌套的if-else语句。

这通常预示着“以编程方式判断类型”的代码,也就是说究竟会执行哪一段程序代码,将依据某种类型信息来判断(开始的时候,可能不清楚确切的类型)。

通常可以使用继承和多态机制来替代此类代码;多态方法在调用时会自动进行类型检查,这样更可靠,扩展起来也更容易。

15.从设计观点来看,要找出变动的因素,并使它和不变的因素分离。

也就是说,找出系统中可能会改变的元素,将它们封装于类中,这样就不会被迫重新设计系统。

你可以在《ThinkinginPatterns(withJava)》(从www.BruceE下载)学习到大量的此类技术。

16.不要依靠子类化来扩展基础功能。

如果类接口中的某个元素非常重要,那么它应该被放进基类,而不是在继承时添加。

如果你依靠派生来添加方法,也许你应该重新考虑整个设计。

17.更少意味着更多。

从类的最小接口开始,尽量在能够解决问题的前提下让它保持简单明了。

先别急着考虑类被使用的所有方式。

一旦它被实际使用,你自然会明白该如何扩展接口。

不过,一旦类被使用后,你就不能在不影响用户代码的情况下缩减接口。

不过加入更多方法倒没什么问题,这不会对用户代码造成影响,它们只需重新编译即可。

即使用新方法取代了旧方法的功能,也请你保留原有接口(如果你愿意的话,可以在底层实现中将功能进行合并)。

如果你要通过“加入更多参数”来扩充原有接口,可以用新参数写一个重载的方法;这样,就不会影响对原有方法的调用。

18.大声朗读你的类,确保它们符合逻辑。

使得基类和派生类之间保持“是一个”(is-a)的关系,让类和成员对象之间保持“有一个”(has-a)的关系。

19.在判断应该使用继承还是组合的时候,考虑一下是否需要向上转型成基础类型。

如果不需要,请优先考虑组合(也就是使用成员对象)。

这样可以消除对多个基类的需求。

如果你采用继承,用户会假定它们可以被向上转型。

20.采用字段来表示数值的变化,使用方法重载来表示行为的变化。

也就是说,如果你发现某个类中含有一些状态变量,而类的方法会根据这些状态变量表现出不同的行为,那么或许你就应该重新设计,在子类和方法的重载中表达这种行为上的差异。

21.小心重载。

方法不应该把参数值作为执行代码的条件。

在这种情况下,你应该编写两个或多个重载方法作为替代。

22.使用异常体系——最好是从Java标准异常体系中派生出特定的异常类。

这样,处理异常的用户便可以在捕获基本异常之后,编写处理程序来捕获指定的异常。

即使你派生了新的异常类,以前的客户端代码仍然能通过基础类型来捕获这个异常。

23.有时候仅仅使用聚合就能完成工作。

比如飞机上的“旅客舒适系统”,它包括若干分离的部件:

座椅、空调、视频设备等等,你需要在飞机对象上产生许多这样的部件。

需要把它们声明为私有成员,然后构建一个全新的接口吗?

不,在这种情况下,这些部件也属于公共接口的一部份,所以你应该加入的是公有成员对象。

这些对象具有各自的实现,所以仍然是安全的。

注意,仅仅使用聚合并不是常用的解决方案,但有时候的确能解决问题。

24.从客户端程序员和程序维护者的角度进行思考。

你设计的类要尽可能地易于使用。

在设计类的时候,你应该预先考虑可能的变化,并使这些变化以后可以轻易完成。

25.当心“巨型对象综合症”。

习惯于过程式程序设计的程序员,在刚接触面向对象程序设计领域的时候,往往会遇到这样的问题。

因为他们最终还是习惯于编写过程式程序,并将它们放进一个或几个巨型对象中。

注意,除了应用程序框架之外,对象应该代表程序中的概念,而不是程序本身。

26.如果你只能采用某种别扭的方式才能实现某个功能,请将这个部份局限在某个类内部。

27.如果你只能采用某种不可移植的方式才能实现某个功能,请将其抽象成服务,并局限在某个类内部。

这样一个附加的间接层次,就可以防止不可移植的部份扩散到程序的其它部分。

这个惯用法的一个具体应用就是Bridge设计模式。

28.对象不应仅仅用来持有数据。

它还应该具有精心定义的行为。

在某些情况下使用“数据对象”是恰当的,但只有在通用容器不适用时,才会刻意使用数据对象来包装、传输大批数据项。

29.在原有类的基础上编写新类时,首先考虑组合。

只在必要情况下才使用继承。

如果在可以使用组合的地方仍然选择了继承,就会为设计引入不必要的复杂度。

30.使用继承和方法重载来表达行为上的差异,使用字段来表示状态的变化。

一个要不得的极端例子,就是派生出不同的类来表示不同颜色,而不是用一个color字段来表示颜色。

31.当心“变异性”(variance)。

语意不同的两个对象可能会拥有相同的动作(或者职责)。

这里自然而然会产生一种诱惑:

仅仅为了从继承中获得某些好处,就让其中一个类成为另一个类的子类。

这就是所谓“变异性”,即在没有任何正当理由的情况下,人为地制造出一个其实并不存在的超类/子类关系。

一个较好的解决办法是写一个共用的基类,把两个类都作为它的派生类,这样它们将共享同一个接口;这种方法会占用更多空间,但你仍旧可以从继承中得到好处,也许还能在设计上获得重要发现。

32.注意继承期间的限制。

最清晰的设计,将在派生类里添加新的功能。

如果在继承过程中去掉旧功能,而没有添加新功能,这样的设计就值得怀疑。

不过,也有例外情况,如果你在使用旧的类库,那么在子类中对原有类进行限制,就比重组整个类继承层次更有效率。

这时,在原有类基础上的新类就符合它应有的地位。

33.使用设计模式来消除那些“纯粹的功能性代码”。

也就是说,如果你的类只应该产生一个对象,请不要马上到程序的开头处,写下“只能产生一个对象”这样的注释。

你应把它包装成单件(singleton)。

如果主程序中有繁多且混乱的“用来创建对象”的代码,请使用类似“工厂方法”的创建型模式,以封装创建动作。

消除那些“纯粹的功能性代码”不仅可以让你的程序更容易理解和维护,也可以使你的代码更强壮,以防止那些善意的维护者无心造成的破坏。

34.当心因“过分分析而导致无从下手”的情况。

请记住,在你获得所有信息之前,必须让项目持续向前推进。

而且理解未知部份最好也是最快的方式,就是尝试向前推进一步而不是在完全理解之后才开始工作。

除非已经找到了解决方案,否则你无法知道如何解决。

你在某个类或一组类中造成的错误,并不会伤害整个系统的完整性。

这是Java内置的防火墙,请让它们工作起来。

35.当你认为自己已经获得了一个优秀的分析、设计或实现时,作一次全面评审。

邀请团队之外的某个人(不一定非要是个顾问),也可以是公司里其它团队的成员。

请他从旁观者的角度评审你的工作,这样能够在尚可轻易地作出修改的阶段就发现问题,相比因此而付出的时间和金钱代价,这样的收益更高。

 

实现

1.一般来说,请遵守Sun的程序编写习惯。

相关规范可以从这里取得:

代码的免费工具,在找到一个免费的样式检查器。

2.无论使用何种编写风格,如果你的团队(或整个公司,那就更好了)能够加以标准化,那么的确会带来显著效果。

当有人的编写风格与标准不符时,每个人都可能会认为不妥,这就迫使他去进行改进。

标准化的价值在于,分析程序代码时将更省脑力,因而你可以专注于代码的实质意义。

3.遵守标准的大小写规范。

类名称的第一个字母应为大写。

数据成员、方法、对象(引用)的第一个字母应为小写。

标识符的字应该连在一起,所有非首字的第一个字母都应该大写。

例如:

ThisIsAClassName

thisIsAMethodOrFieldName

如果你在staticfinal基本类型的定义处指定了常量进行初始化,那么该标识符应全为大写(字之间用下划线连接),它代表一个编译期常量。

“包”是个特例,其名称均为小写,即使对中间的字也是如此。

域名后缀(com,org,net,edu等等)也应为小写。

(这是Java1.1和Java2之间的不同之处。

4.不要为私有字段名称加上你自己的“修饰”符号。

这通常以“前置下划线和字符”的形式出现。

匈牙利命名法(Hungariannotation)是其中最糟糕的例子。

在这种命名法中,你得加入额外字符来表示数据的类型、用法、位置等等。

这就使你好像是在使用汇编语言,编译器不能提供任何额外帮助一样。

这样的标记容易让人混淆,又难以阅读,也不易推广和维护。

要是你觉得必须给名称加上修饰以防止混淆,你的代码也许已经过于复杂而令人迷惑,需要简化的其实是你的代码。

5.编写通用性的类时,请遵守标准形式(canonicalform)。

包括定义equals()、hashCode()、toString()、clone()(实现Cloneable接口,或者选择其它对象复制策略,比如序列化),并实现Comparable和Serialiable接口。

6.对于那些“获得或改变私有字段值”的方法,请使用JavaBean的“get”、“set”、“is”等命名习惯。

即使你当时并不认为自己在编写JavaBean。

这样不仅可以把你的类当成Bean来使用,而且这也是这类方法的标准命名方式,它能够使读者更容易理解。

7.对于你编写的每一个类,请使用JUnit为它编写测试用例(参见www.junit.org网站)。

在项目中使用类的时候,也不必移除类的测试代码,这样如果代码有所变动,你可以重新执行测试。

测试代码也可以作为类的使用范例。

8.有时你需要通过继承,才能访问基类的保护成员。

这可能会令你觉得需要多个基类。

如果不需要向上转型,可以先派生一个新类以对保护成员进行访问,然后在需要访问那个保护成员的所有类中,将新派生的类作为成员对象,而不要直接使用继承。

9.应避免纯粹为了提高执行速度而采用final方法。

只有在程序能够运行,但速度不够快,并且性能分析工具显示对此方法的调用将成为瓶颈的时候,才应该考虑把方法标记为final。

10.如果两个类因某种功能性原因而产生了关联(比如容器和迭代器),那么请试着让其中一个类成为另一个的内部类。

这不仅强调了二者间的关联,而且把类嵌套到其它类中,就可以在同一个包中重复使用类的名称。

Java容器库在每个容器内都定义了一个内部类Iterator,这就为容器提供了统一的接口。

使用内部类的另一个原因是,把它作为私有实现的一部份。

这时,使用内部类是为了隐藏实现,而不是为了体现类之间的关联,也不是因为上面提到的命名空间污染问题。

11.在任何时候,你都要警惕那些相互之间高度耦合的类,请评估一下使用内部类为程序编写和维护带来的好处。

使用内部类不能消除类之间的耦合,而是使耦合关系更明显,使用起来也更方便。

12.不要跳入过早优化的陷阱。

过早优化其实并不明智。

尤其是在系统构建初期,先别为是否使用本地方法、是否将某些方法声明为final、以及是否调整代码效率等问题而烦恼。

你的主要目的应该是先证明设计的正确性。

即使在设计本身也有效率需求的情况下,也要遵循“先能运行,再求快速”的准则。

13.尽可能缩小对象的作用域,这样对象的可见范围和生存期也都会尽可能地小。

这就降低了出现“在错误的语境中使用对象,导致难以发现的错误”的机会。

假设你有个容器,以及一段遍历该容器的代码。

如果你复制该代码,并将它用于遍历新容器,你就很可能会以旧容器的大小做为新容器的访问上界。

然而,在遍历旧容器的时候如果超出了容器范围,编译期就会捕获错误。

14.使用Java标准库提供的容器。

精通它们的用法,将极大地提高你的工作效率。

优先选择ArrayList来处理顺序结构,选择HashSet来处理集合、选择HashMap来处理关联数组,选择LinkedList来处理栈(而不用Stack,即便你可以创建适配器来得到栈的接口)和队列(如书中所见,也可以通过适配器得到)。

当使用前三个的时候,你应该把它们分别向上转型为List,Set和Map,这样就可以在必要的时候以其它方式实现。

15.对一个健壮的程序而言,每一个部件都必须健壮。

在用Java编写类的时候,应充分运用所有能够提高程序健壮性的设施:

访问控制、异常、类型检查和同步控制等等。

这样,你在构建系统时就能够安全地进行进一步的抽象。

16.宁可在编译期发生错误,也不要在执行期发生错误。

试着尽可能在最靠近问题发生点的地方处理问题。

对于任何异常,都要在具有足够信息解决问题的最近的处理程序处进行捕获。

在当前阶段应尽可能地对异常做点什么;如果实在无法解决问题,应该重新抛出异常。

17.当心冗长的方法定义。

方法应该是简洁的功能单元,它要描述并实现类接口中的某个离散的部分。

过长且复杂的方法不仅难以维护,而且代价高昂。

或许它的负担太重了。

如果出现了这样的方法,应该把它分割成几个方法。

这种方法还提醒你或许要编写新类。

短小精悍的方法同样能够在类中被复用。

(有时候方法必须很大才行,但它们应当只做一件事情)

18.尽量使用“private”关键字。

一旦你把库的特征(包括方法、类、字段)标记为public,你就再也不能去掉它们。

要是你非要这么做的话,就会破坏别人的已有代码,使它们必须被重写或重新设计。

如果你只公开必要部份,那么其余部分就可以自由改变,而不用担心影响别人。

设计总是会演化,所以这种自由度非常重要。

在这种方式下,实现的变动对派生类造成的影响最小。

在处理多线程问题的时候,保持私有性尤其重要,因为只有私有字段可以受到保护,而不用担心被未受同步控制的使用所破坏。

只在包内访问的类,也应该具有private字段,但通常最好使用具有包内访问权限的方法对它们进行访问,而不是将这些字段声明为public。

19.大量使用注释,并使用javadoc的“文档注释语法”来生成程序的文档。

不过,注释应该体现代码的真正涵义;如果只是把代码已经明确表示的内容简单重复一遍,会令人厌烦。

请注意,通常Java类和方法的名称都很详细,这也在某种程度上降低了对注释的需求。

20.避免使用“魔术数字”,就是那种固定在代码里的数字。

要想对它们进行修改,那真是一场恶梦,因为你永远无法知道“100”究竟是代表“数组大小”还是“完全不同的其它东西”。

你应该使用带有描述性名称的常量,并在程序中使用这个常量名称。

这将使程序更易于理解和维护。

21.在编写构造器时,请考虑异常。

在最佳情况下,构造器不会有任何抛出异常的动作。

稍次的情况下,只允许类组合强壮的类,或者从强壮的类继承,这样异常被抛出的时候,并不需要清理。

在其它情况下,你必须在finally子句里清理组合过的类。

如果某个构造器一定会失败,恰当的动作就是抛出异常,这样调用者就不至于盲目地认为对象已被正确创建而继续执行。

22.在构造器中只作必要的动作:

将对象设定为正确状态。

要积极避免在构造器内调用其它方法(final方法除外),因为这些方法可能会被其他人所重载,这就可能会在构造期间得到意外的结果(详细情形请参考第7章)。

小型而简单的构造器抛出异常或引发问题的可能性要小得多。

23.客户端程序员用完对象之后,如果你的类需要任何清理动作,请将此动作放到某个精心定义的方法中,比如命名为dispose(),这样能清楚说明其用途。

此外,在类里放置一个布尔型标志,用来表明dispose()是否已经被调用过,这样finalize()可以检查其“终结条件”。

24.finalize()方法的职责只应该是,为了调试的目的而检查对象的“终结条件”。

在特殊情况下,可能会需要释放一些不能被垃圾回收器回收的内存。

因为垃圾回收器可能不会被调用来处理你的对象,所以你无法使用finalize()执行必要的清理。

基于这个原因,你得自己编写dispose()方法。

在类的finalize()方法中,请检查并确认对象已被清理,如果对象尚未被清理,这表明是一个编程错误,请抛出派生自RuntimeException的异常。

在使用这种模型前,请先确认finalize()在你的系统上可以正常工作(你可能需要调用System.gc()来确认此行为)。

25.如果对象在某个特定范围内必须被清理(而不是作为垃圾被回收),请使用以下方法:

先初始化对象,如果成功的话,立刻进入一个带有finally子句的try块,在finally子句中进行清理动作。

26.在继承的时候如果重载了finalize()方法,要记得调用super.finalize()。

(但如果你直接从Object类继承,就不需调用。

)你应该在被重载的finalize()方法中最后(而不是首先)去调用super.finalize(),这样能保证如果你还需要使用基类中的某个组件的话,它们还是合法的。

27.当你编写固定大小的对象容器时,它们要能够被传送给一个数组,尤其是从某个方法返回此容器时。

通过这种方式,你可以获得数组的“编译期类型检查”的好处,而且接收数组可能不需要“先将数组中的对象加以类型转换”就能够加以使用。

请注意,容器库的基类Java.util.Collection具有两个toArray()方法,它们都能够达到这个目的。

28.优先选择接口而不是抽象类。

如果你知道某些东西将成为基类,你应当优先把它们设计成接口;只有在必须放进方法定义或成员变量时,才把它改为抽象类。

接口只和客户希望的动作有关,而类则倾向于实现细节。

29.为了避免一个十分令人泄气的经验,请检查classpath,确保所有未放进包里的类的名称互不相同。

否则编译器会先找到另一个名称相同的类,它会认为这样没有意义,并报告错误信息。

如果你怀疑classpath出了问题,试着从classpath中的每个起点搜寻同名的.class文件。

你最好还是将所有类都放到相应的包里。

30.注意无心的重载错误。

如果你在重载基类方法时没有正确拼写方法的名称,其后果是增加了一个新方法,而没有重载原有的方法。

然而这样做完全合法,因此编译器或运行期系统不会向你报告错误信息;只是你的代码就无法正确工作了。

31.当心过早优化。

先能运行,再求快速。

只有在你必须(也就是说只有在证明某段代码确实存在性能瓶颈)时才这么做。

除非你已经使用性能分析工具找出了瓶颈所在,否则你只是在浪费时间。

性能调整的隐性成本在于它会令你的代码变得难以阅读和维护。

32.记住,代码被阅读的时间多于它被编写的时间。

清晰的设计能使程

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 哲学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1