Java编程指南(建议).doc_第1页
Java编程指南(建议).doc_第2页
Java编程指南(建议).doc_第3页
Java编程指南(建议).doc_第4页
Java编程指南(建议).doc_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java编程指南Java 编 程 指 南摘自Thinking in Java 3rd Edition设计1. 优雅终将得到回报。从短期利益来看,要想对问题提出优雅的解决方案,似乎需要投入更多的时间,不过一旦它能够工作,就能够很容易地适应新的环境,而不是要花上数以时计,甚至以天计或以月计的辛苦代价时,这时你将得到回报(尽管没人能够准确衡量这些回报)。你得到的程序不仅易于编写和调试,而且还易于理解和维护,这就是其价值所在。明白这一点需要经验,因为你在精心设计程序的时候生产率不会很高。要抵抗住浮躁心态的诱惑;欲速则不达。2. 先能运行,再求快速。即使你确信某段代码非常重要,它将成为整个系统的瓶颈,也必须遵守这一点。别着急。先尽可能简化设计,让系统运转起来。如果性能不够理想,再求助于性能分析工具。你几乎总会发现,你“以为的”那些瓶颈,其实都不是真正的问题所在。要把时间用在刀刃上。3. 谨记“分而治之”原则。如果待解决的问题过于复杂,先设想一下程序的基本操作,并且假定已经有一小段“神奇代码”能够处理最困难的部分。这段“神奇代码”可以看成一个对象,你可以编写程序使用它,然后再回过头来研究这个对象,把它最困难的部分包装成其它对象,依此类推。4. 区分类的编写者和使用者(客户端程序员)。作为“客户”,类的使用者不需要也不希望知道类的底层工作方式。类的编写者必须是程序设计方面的专家,这样编写出来的类有才可能在新手使用的情况下,仍然能够稳定运行。请把类想象成对其它类的“服务提供者”。程序库只有在内部实现对用户来说是透明的情况下,才会易于使用。5. 编写类的时候,类的名称要非常清晰,使得注释成为多此一举。你的目标应该是提供给客户端程序员简单明了的接口。为此,在恰当的时候可以考虑方法重载,以得到直观且易于使用的接口。6. 你的分析和设计所产生的系统中的类、它们的公共接口,以及类之间(尤其是与基类之间)的联系,必须达到最少。如果你在设计中产生了过多的类,请回顾一下,这些代码在程序的整个生命周期中能产生效益吗?如果并非如此,你就要付出维护的代价。对于不能提高生产率的任何东西,开发团队的成员不会自觉地进行维护;这也是许多设计方法无能为力的地方。7. 尽量让所有东西自动化。首先编写测试代码(在你编写类之前),并把它和要测试的类放在一起。你可以使用某种构建工具,来自动运行测试。你也许会用到Ant,它是Java构建工具的事实标准。这样,只要执行测试程序,所有改动就可以自动获得验证,有了错误也可以立刻发现。因为你信赖测试框架所具有的安全性,所以当你发现新的需求时,会大胆地进行全面修改。请记住,程序语言最大的改进,来自类型检查、异常处理等机制所赋予的内置测试行为。但这些功能只能协助你到达某种程度,其它工作还需要你自己完成。要开发一个健壮的系统,你得自己编写测试用例来验证类或程序的性质。8. 在编写类之前先编写测试代码,以验证这个类是否设计完备。如果你写不出测试代码,就说明其实你还不清楚类的功能。此外,在编写测试代码的过程中,通常还能够发现类需要具有的额外特性或限制。而这些特性和限制并不总是能够通过分析和设计得到。测试代码也可作为使用类的范例。9. 所有软件设计中的问题,都可以通过“从概念上引入额外的概念上的间接层次”得到简化。这是软件工程领域的基本原则1,也是抽象的依据。而抽象正是面向对象程序设计的主要性质。在面向对象编程中,我们也可以这么说:“如果代码过于复杂,那么就引入更多的对象。”10. 引入的间接层次要有意义(与准则 9 相应)。这里所指的意义可以像“将常用代码放入一个方法内”这么简单。如果你加入了无意义的间接层次(通过抽象或封装等等),那就会和没有引入间接层一样糟糕。11. 尽可能使类原子化。每个类要具有简单明了的用途,用它来向别的类提供服务。如果类或系统设计得过于复杂,请将它分割成几个较简单的类。一个最明显的判断依据就是类的大小:如果类很大,那它很可能负担太重,就应该被分割。 建议重新设计类的线索有: 1) 复杂的 switch 语句:请考虑使用多态。 2) 有许多方法,各自处理类型极为不同的操作:请考虑划分成不同的类。3) 有许多成员变量,用来表示类型极为不同的属性:请考虑划分成不同的类。4) 其它建议请参考Refactoring: Improving the Design of Existing Code,Martin Fowler著,(Addison-Wesley 1999)。12. 当心冗长的参数列表。参数列表过长将使得方法调用变得难以编写、阅读和维护。你应该试着将方法放到更合适的类中,并/或使用对象作为参数。13. 不要一再重复。如果某段代码不断出现于许多派生类方法中,应将该段代码置于基类的某个方法中,然后在派生类方法中进行调用。这样不仅可以减少代码数量,也易于修改。有时候,找出这种通用代码还可以为接口增加实用的功能。在不牵涉继承的情况下,也可能会遇到这种情况:如果类中的几个方法使用了重复的代码,请把这些代码移到某个方法里,然后在别的方法中进行调用。14. 小心switch语句或嵌套的if-else语句。这通常预示着“以编程方式判断类型”的代码,也就是说究竟会执行哪一段程序代码,将依据某种类型信息来判断(开始的时候,可能不清楚确切的类型)。通常可以使用继承和多态机制来替代此类代码;多态方法在调用时会自动进行类型检查,这样更可靠,扩展起来也更容易。15. 从设计观点来看,要找出变动的因素,并使它和不变的因素分离。也就是说,找出系统中可能会改变的元素,将它们封装于类中,这样就不会被迫重新设计系统。你可以在Thinking in Patterns(with Java)(从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的程序编写习惯。相关规范可以从这里取得: /docs/codeconv/index.html(本书尽可能遵守了这些习惯)。众多Java程序员看到的程序代码,都遵循这些习惯。如果你固执地使用自己过去的编写风格,会给读你程序的人带来一些困难。不论你决定采用何种编写习惯,要确保在整个项目中保持一致。你可以在上找到一个用来重排Java 代码的免费工具,在找到一个免费的样式检查器。2. 无论使用何种编写风格,如果你的团队(或整个公司,那就更好了)能够加以标准化,那么的确会带来显著效果。 当有人的编写风格与标准不符时,每个人都可能会认为不妥,这就迫使他去进行改进。标准化的价值在于,分析程序代码时将更省脑力,因而你可以专注于代码的实质意义。3. 遵守标准的大小写规范。类名称的第一个字母应为大写。数据成员、方法、对象(引用)的第一个字母应为小写。标识符的字应该连在一起,所有非首字的第一个字母都应该大写。例如:ThisIsAClassNamethisIsAMethodOrFieldName如果你在 static final 基本类型的定义处指定了常量进行初始化,那么该标识符应全为大写(字之间用下划线连接),它代表一个编译期常量。“包”是个特例,其名称均为小写,即使对中间的字也是如此。域名后缀(com, org, net, edu等等)也应为小写。(这是Java 1.1和Java 2 之间的不同之处。)4. 不要为私有字段名称加上你自己的“修饰”符号。这通常以“前置下划线和字符”的形式出现。匈牙利命名法(Hungarian notation)是其中最糟糕的例子。在这种命名法中,你得加入额外字符来表示数据的类型、用法、位置等等。这就使你好像是在使用汇编语言,编译器不能提供任何额外帮助一样。这样的标记容易让人混淆,又难以阅读,也不易推广和维护。要是你觉得必须给名称加上修饰以防止混淆,你的代码也许已经过于复杂而令人迷惑,需要简化的其实是你的代码。5. 编写通用性的类时,请遵守标准形式(canonical form)。包括定义equals()、hashCode()、toString()、clone()(实现 Cloneable接口,或者选择其它对象复制策略,比如序列化),并实现Comparable和Serialiable接口。6. 对于那些“获得或改变私有字段值”的方法,请使用JavaBean的“get”、“set”、“is”等命名习惯。即使你当时并不认为自己在编写JavaBean。这样不仅可以把你的类当成Bean来使用,而且这也是这类方法的标准命名方式,它能够使读者更容易理解。7. 对于你编写的每一个类,请使用JUnit为它编写测试用例(参见网站)。在项目中使用类的时候,也不必移除类的测试代码,这样如果代码有所变动,你可以重新执行测试。测试代码也可以作为类的使用范例。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. 如果对象在某个特定范围内必须被清理(而不是作为垃圾被回收),请使用以下方法:先初始化对象,如果成

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论