Copyright (c) 1999 Scott Ambler, Ambysoft, Inc.

Java 编码准则是根据 Scott Ambler, Ambysoft Inc.(www.ambysoft.com)的许可证提供的。它们为包含在 Rational Unified Process 中进行了重新编排。


内容

1      介绍
       
1.1    最根本的准则

2    编码标准
     
2.1    命名约定
      2.2    注释约定
              2.2.1  Java 注释类型
              2.2.2  javadoc 的简要概述   

3    成员函数标准
     
3.1    命名成员函数
              3.1.1    命名存取成员函数
                         3.1.1.1    getter 方法
                         3.1.1.2    setter 方法
      3.2    命名构造函数
      3.3    成员函数可视性
      3.4    注明成员函数
               3.4.1    成员函数头
               3.4.2    内部注释
      3.5    编写清晰代码的技术
               3.5.1    注明代码
               3.5.2    分段或缩进代码
               3.5.3    在代码中使用空白区域
               3.5.4    遵循 30 秒规则
               3.5.5    编写简短的单命令行
               3.5.6    指定操作的顺序

4    字段和属性的标准
      4.1    命名字段
              4.1.1    命名组件(窗口小部件)
                         4.1.1.1    命名组件的取代方法:匈牙利表示法
                         4.1.1.2     命名组件的取代方法:匈牙利后缀表示法
                         4.1.1.3     设置组件名称标准
              4.1.2    命名常量
               4.1.3    命名集合
      4.2    字段可视性
               4.2.1    不要“隐藏”名称
      4.3    注明字段
      4.4    使用存取成员函数
              4.4.1    为什么使用存取函数?               
                          4.4.1.1   何时不使用存取函数
              4.4.2    命名存取函数
              4.4.3    存取函数的高级技术
                         4.4.3.1    延迟初始化
                         4.4.3.2    
常量存取函数
                         4.4.3.3    
集合存取函数
                         4.4.3.4    同时访问多个字段   
      4.5    存取函数可视性
      4.6    总是初始化静态字段

5    局部变量标准
      5.1    命名局部变量
                  5.1.1    命名流
                   5.1.2    命名循环计数器
                   5.1.3    命名异常对象
        5.2   声明和注明局部变量
               5.2.1     有关声明的一般注释

6    成员函数参数标准
     
6.1    命名参数
      6.2    注明参数
               6.2.1    对参数类型使用接口

7    类、接口、程序包和编译单元的标准
      7.1    类标准
                7.1.1    命名类
                7.1.2    注明类
                7.1.3    类声明
                7.1.4    最小化公共和保护接口
                            7.1.4.1    首先定义公共接口
      7.2     接口标准
                7.2.1    命名接口
                            7.2.1.1    取代方法
                7.2.2    注明接口
      7.3     程序包标准
                7.3.1    命名程序包
                7.3.2    注明程序包
      7.4     编译单元标准
                7.4.1    命名编译单元
                7.4.2    注明编译单元

8    错误处理和异常

9    其他标准和问题
      9.1    复用
      9.2    导入类
      9.3    优化 Java 代码
      9.4    编写 Java 测试装置

10    成功模式
       10.1    有效使用这些标准
       10.2    其他产生成功代码的因素

11    总结
       
11.1    Java 命名约定
        11.2    Java 注释约定
                   11.2.1    Java 注释类型
                   11.2.2    注明的内容
        11.3    Java 编码约定(常规)

12    参考

13    词汇表


1    介绍

本文档描述了编写稳定的 Java 代码的一系列标准、约定和准则。它们以安全可靠的软件工程原则为基础,使代码易于理解、维护和增强。此外,通过遵循这些编码标准,您作为一个 Java 开发人员的工作效率会有显著提高。经验证明,如果从一开始就花时间编写高质量的代码,在开发过程中,修改代码就会容易得多。最后,遵循一套通用的编码标准将带来更大的一致性,使开发团队的工作效率明显提高。

1.1    最根本的准则 

使用常识。找不到规则或准则时;规则明显不适用时;其他任何对象都失败时:请使用常识,检查基本原则。此规则优先于其他所有规则。常识是必需的


2    编码标准

Java 的编码标准非常重要,因为它们可提高开发团队各成员的代码的一致性。一致性的提高使代码更容易理解,这意味着它更容易开发和维护。这减少了您创建应用程序的总体成本。

您必须记住的是:您的 Java 代码会保留很长时间;在您转到其他项目后,它依然会保留。开发期间重要的目标是确保您可以将工作移交给另一个开发人员或者另一个开发团队,这样他们可以继续维护并改进以前的工作,而不必投入不必要的精力来理解代码。如果代码很难理解,则很有可能被废弃然后重写。

2.1    命名约定

我们贯穿标准都会讨论命名约定,因此让我们先讨论几个基本点,作个铺垫:

  1. 使用可以准确描述变量、字段和类的完整英文描述符;例如,使用类似 firstNamegrandTotalCorporateCustomer 这样的名称。尽管类似 x1y1fn 的名称很容易输入(因为它们很短),但它们不提供所代表的内容的任何指示信息,因而导致代码很难理解、维护和改进。
  2. 使用适用于域的术语。如果您的用户称 client 为 customer,那么就使用术语 Customer 来命名这个类,而不要使用 Client。许多开发人员会犯的一个错误是,不去使用行业或领域里已经存在而且很完美的术语,却生造出一些泛泛的词汇。
  3. 使用大小写混合,提高名称的可读性。总体使用小写字母,但是类和接口名称的第一个字母以及所有非首个单词的第一个字母应该大写。[KAN97]
  4. 尽量少用缩写,但如果一定要使用,那么就要谨慎地使用。这意味着您应该保持一列标准的简短形式(缩写),明智地从中选取,并且在使用时保持一致。例如,如果您要使用单词“number”的简短形式,那么选择 nbrnonum 中的一个,再注明您选择了哪一个(哪一个并不重要),然后保持只使用那一个。
  5. 避免使用长名称(最好少于 15 个字符)。尽管类名 PhysicalOrVirtualProductOrService 可能一时看上去不错,但是这个名称太长,您应当考虑将它重命名为短一点的名称,比如象 Offering
  6. 避免使用相似或者仅在大小写上有区别的名称。例如,不应同时使用变量名称 persistentObjectpersistentObjects 以及 anSqlDatabaseanSQLDatabase
  7. 避免使用下划线作为名称的首末字符。以下划线作为首末字符的名称通常为系统保留,不应在任何用户创建的名称中使用。更重要的是,下划线令人讨厌,很难输入,所以尽量避免使用。

2.2    注释约定

我们还将讨论注释约定,因此让我们先讨论一些基本点:

  1. 注释应该使代码更清楚。注明代码的目的是使代码更易于被同时参与程序设计的开发人员以及其他后继开发人员理解。
  2. 如果您的程序不值得注明,则可能也就不值得运行。[NAG95]
  3. 避免装饰;即,不要使用类似条幅的注释。二十世纪六十年代和七十年代,COBOL 程序员们养成了画框的习惯,典型的是用星号将他们的内部注释圈起来。当然,这让他们可以展现自己的美感,但坦白地说,它更是浪费时间,却对于最终产品的价值鲜有增益。您要写的是清晰的代码,而不是形式漂亮的代码。此外,由于很多显示和打印代码的字体是成比例的,但有些又不是,因此无论如何,都无法将那些框排列整齐。
  4. 保持注释简单。最好的注释应该是简单明了的注释。注释不必洋洋洒洒,只需提供足够的信息,使别人能够理解您的代码。
  5. 在编写代码之前,先编写注释。注明代码的最好方法是在编写代码之前先编写注释。这使您在编写代码之前可以想想代码将如何工作,而且这样可确保不会遗漏注释。另一种方法是边写代码边写注释。因为注释可以使代码更易理解,所以您可以在开发代码的过程中,利用这一点。如果打算花些时间写注释,那么您应至少在注释中提供点信息。[AMB98]
  6. 除完成什么操作以外,还要注明为什么要这么做。例如,下面的“示例 1”中的代码显示金额在 $1,000 以上(包括 $1,000)的订单可给予 5% 折扣。为什么这么做?是否有业务规则规定大订单可获得折扣?这是大订单的短期特别活动还是永久活动?原先的程序员真这么慷慨?这些仅当在某处(在源代码自身中或者在外部文档中)有注明时,您才会明白。

示例 1:

if ( grandTotal >= 1000.00)

{

grandTotal = grandTotal * 0.95;

}

2.2.1    Java 注释类型

Java 有三种注释风格: 

下表是对各种注释的建议用法的总结,并且提供了几个示例。

注释类型 用法 示例
文档 在接口、类、成员函数和字段声明之前紧靠它们的位置用文档注释进行注明。文档注释由 javadoc 处理,为一个类生成外部注释,如下所示。 /**
客户:客户是我们将服务和产品销售给的人或机构。
@author S.W. Ambler
*/
C 语言风格 使用 C 语言风格的注释将无用的代码注释掉。保留这些代码是因为用户可能改变想法,或者您只是想在调试中暂时不执行这些代码。 /*
这部分代码因为已被它之前的代码取代,所以已由 B.Gustafsson 于 1999 年 6 月 4 日注释掉。如果两年之后仍未用这些代码,将其删除。
. . . (源代码)
*/
单行 在成员函数内部使用单行注释对业务逻辑、代码段和临时变量声明进行注明。 // 按照 1995 年 2 月开始的
// 回赠促销活动,
// 给所有超过 $1000 的发票
// 提供 5% 的折扣。

一件很重要的事情是,您的组织应该制定一套如何使用 C 语言风格注释和单行注释的标准,并始终严格遵守。使用一种注释方式来注明业务逻辑,使用另一种方式注释掉旧的代码。业务逻辑使用单行注释,因为这样可以将注释和代码放在同一行上(这又叫做“直接插入”)。使用 C 语言风格的注释来注释掉旧的代码,因为这样可以同时注释掉数行。C 语言风格注释看起来很像文档注释,所以为了防止混淆,不应在别处使用。

注意行末注释-[MCO93] 强烈反对使用行内注释,即在一行的末尾加上注释。McConnell 指出,这种注释必须在代码的右端对齐,这样才能避免代码结构看起来混乱。结果,这些注释的格式难以划一,“如果您使用了很多这样的注释,则要花大量时间去将它们排列整齐。这些时间并没有花在更多地了解代码上,而完全花在了敲击空格键和 Tab 键这种冗长乏味的工作上。”他又指出,行末注释也难以维护。因为当该行程序的代码加长时,它们会将这些注释挤出该行,而如果您将它们排齐,您不得不对余下的注释做同样的工作。

2.2.2        javadoc 的简要概述

Sun 公司的 Java Development Kit(JDK)中有一个名为 javadoc 的程序,它可以处理 Java 的代码文件,并且为 Java 程序产生 HTML 文件形式的外部注释。javadoc 支持一定数目的标记,即标记注释节开始的保留字。请参阅 JDK javadoc 注释获取更多详细信息。

标记 用于 目的
@author name 接口 说明给定代码片段的作者。每个作者使用一个标记。
@deprecated 成员函数 说明该类的应用程序编程接口 (API) 已被废弃,因此不应再使用。
@exception name description 成员函数 描述成员函数抛出的异常。每个异常使用一个标记,并且必须给出异常的完整类名。
@param name description 成员函数 用于描述传递到成员函数的参数,包括它的类型或类和它的用法。每个参数使用一个标记。
@return description 成员函数 描述成员函数的返回值(如果有)。您应当说明返回值的类型或类以及潜在用法。
@since 类和成员函数 说明自有 JDK 1.1 以来,该项已存在了多长时间。
@see ClassName 类、接口、成员函数和字段 在注释中生成指向所指定类的超文本链接。您可以(可能应该)使用全限定类名。
@see ClassName#member functionName 类、接口、成员函数和字段 在注释中生成指向所指定成员函数的超文本链接。您可以(可能应该)使用全限定类名。
@version text 类和接口 说明给定代码片段的版本信息。

注明代码的方式很大程度上影响您的工作效率以及所有维护和改进代码的后继开发人员的工作效率。在开发过程早期注明代码,会促使您在开始编写代码之前仔细考虑这些代码的逻辑,因而可以提高您的工作效率。而且,当您重新阅读数天前或者数星期前所写的代码时,您可以很容易地判断出当时您是怎么想的,因为这一切都有注明。


3    成员函数标准

切记:您今天所写的代码可能在今后的数年里仍在使用,而且很有可能由其他人来维护和改进。您必须尽可能地使您的代码清晰易懂,因为这会使代码易于维护和改进。

3.1    命名成员函数

成员函数的命名应使用全英文描述,大小写混合,并且所有非首个单词的第一个字母都大写。并且成员函数名称的第一个单词常常采用一个色彩强烈的主动动词。

示例:

openAccount()

printMailingLabel()

save()

delete()

这种约定常常使人一看到成员函数的名称就能判断它的功能。虽然这种约定要使开发人员多做一些输入工作(因为函数名常常较长),但是回报是代码可理解性的提高。

3.1.1    命名存取成员函数

在下一章中我们将更详细地讨论获取和设置字段(字段或属性)值的存取成员函数。下面总结了存取函数的命名约定。

3.1.1.1    getter 方法 

getter 方法是返回字段值的成员函数。您应当为字段名称加上前缀“get”,除非它是“布尔”字段,那么您给字段名称加前缀“is”而不是“get”。

示例:

getFirstName()

getAccountNumber()

isPersistent()

isAtEnd()

通过遵循该命名约定,您可以明确显示成员函数返回对象的字段,而对于布尔 getter 方法,您可以明确显示它返回 true 或 false。该标准的另一个好处是它遵循 Beans Development Kit(BDK)对 getter 方法成员函数使用的命名约定。[DES97] 主要的不利之处是“get”过多,需要额外输入。

替代 getter 方法的另一种命名约定:has 和 can

根据正确的英语约定,可作为替代的另一种方法是使用前缀“has”或者“can”取代“is”用于布尔 getter 方法。例如,当您阅读代码时,hasDependents()canPrint() 等 getter 方法名称就深有意义。该方法的问题是 BDK 不选用这种命名策略(至少尚未)。您可以将这些成员函数重命名为 isBurdenedWithDependents()isPrintable()

3.1.1.2    setter 方法

setter 方法(也称为可变方法)是修改字段值的成员函数。您应当为字段名称加上前缀“set”,而不用考虑字段类型。

示例:

setFirstName(String aName)

setAccountNumber(int anAccountNumber)

setReasonableGoals(Vector newGoals)

setPersistent(boolean isPersistent)

setAtEnd(boolean isAtEnd)

通过遵循该命名约定,您可以明确显示成员函数设置对象的字段值。该标准的另一个好处是它遵循 BDK 对 setter 方法成员函数使用的命名约定。[DES97] 主要的不利之处是“set”过多,需要额外输入。

3.2    命名构造函数

构造函数是在首次创建对象时执行所有必要初始化的成员函数。构造函数总是使用与它们的类相同的名称。例如,类 Customer 的构造函数将是 Customer()。请注意使用相同的大小写。

示例:

Customer()

SavingsAccount()

PersistenceBroker()

该命名约定由 Sun Microsystems 设定,要求严格遵守。

3.3    成员函数可视性

良好的程序设计应该尽可能减小类与类之间的耦合,一般原则是在设置成员函数的可视性时尽可能地加以限制。如果成员函数没必要设为公共(public),就设为保护(protected);没必要保护(protected),就设为私有(private)。

可视性 描述 正确用法
公共 公共成员函数可由任何其他对象和类中的任何其他成员函数调用。 当该成员函数必须由该函数所在的层次结构之外的对象和类访问时。
保护 受保护的成员函数可由定义它的类、该类的任何子类或者相同程序包内的任何类中的任何成员函数调用。 当成员函数提供的行为是类层次结构或程序包内部所需,而非外部所需时。
私有 私有成员函数只可由定义它的类中的其他成员函数调用,而该类的子类不可以调用。 当该成员函数提供特定于这个类的行为时。私有成员函数常常是重构的结果。重构也称“重组”,指类内其他成员函数封装某一个特定行为的做法。
缺省 缺省情况下(未指定可视性),函数只可由定义它的类中的其他成员函数或者相同程序包中的任何类调用。 当成员函数提供的行为是相同程序包中的类所需,而非外部以及子类所需时。

3.4    注明成员函数

您注明成员函数的方式经常决定成员函数是否可理解并由此是否可维护和可扩展。

3.4.1    成员函数头

每个 Java 成员函数都应包含某种称为“成员函数注释”的函数头,这些函数头在源代码的前面,用来注明所有重要的有助于理解函数的信息。该信息包含(但不限制为)以下内容:

  1. 成员函数做什么以及它为什么这么做。通过注明成员函数所执行的操作,可以使其他人更容易确定是否可以复用代码。注明执行这个操作的原因可使其他人更容易将代码放到程序的上下文中去。您还可以使其他人更容易确定是否应对代码片段实际进行新更改(有可能进行新更改的原因与您最初编写代码的原因相互冲突)。
  2. 什么成员函数必须作为参数传递。您还需要指示什么参数(如果有)必须传递给成员函数以及将如何使用它们。此信息是必需的,这样其他程序员可以了解要传递到成员函数的信息。2.2.2 javadoc 简要概述 一节中讨论的 javadoc @param 标记用于此目的。
  3. 成员函数返回什么。您需要注明成员函数返回的内容(如果有),这样其他程序员可以恰当地使用返回值或对象。2.2.2 javadoc 简要概述一节中讨论的 javadoc @return 标记用于此目的。
  4. 已知错误。有关成员函数的任何未解决的问题都应作出注明,以便让其他开发人员了解该成员函数的弱点和难点。如果一个类中的多个成员函数都存在同样的问题,那么这个问题应该注明为类的问题。
  5. 所有由成员函数抛出的异常。您应注明成员函数抛出的所有异常,以便使其他程序员明白他们的代码应该捕获什么。2.2.2 javadoc 简要概述一节中讨论的 javadoc @exception 标记用于此目的。
  6. 可视性决定。如果您觉得您对某个成员函数可视性的选择会遭到其他开发人员的质疑(例如可能您将某个成员函数设为公共,但是没有任何其他对象调用该成员函数),那么您应当注明您的决定。这将帮助其他开发人员了解您的想法,使他们不必浪费时间操心考虑您为什么带疑问地这样做。
  7. 成员函数如何更改对象。如果成员函数更改对象(例如银行帐户的 withdraw() 成员函数修改帐户余额),那么需要说明它。此信息是必需的,这样其他 Java 程序员可以精准地了解成员函数调用将如何影响目标对象。
  8. 避免使用含有信息的函数头 例如作者、电话号码、创建和修改日期以及单元位置(或文件名),因为这些信息很快就会过时。将所有权版权声明放到单元的最后。例如,读者不会想要浏览两三页对理解程序毫无帮助的文本,也不会想要浏览根本不包含任何程序信息的文本,比如版权声明。避免使用垂直条或者封闭的框架或框,这些东西只会增加视觉干扰,而且较难保持一致。请使用配置管理工具来保存单元历史记录。
  9. 如何在适当情况下调用成员函数的示例。最简单的确定代码片段如何工作的方法之一是查看示例。考虑包含一到两个有关如何调用成员函数的示例。
  10. 适用的前置条件和后置条件。前置条件是指一个成员函数可正确运行的限制条件;后置条件是指一个成员函数运行完以后的属性或声明。[MEY88] 前置条件和后置条件以很多方式描述您在编写成员函数过程中所作的假设 [AMB98],精确定义了成员函数的使用范围。
  11. 所有并行问题。并行对于许多开发人员是新鲜而复杂的概念,而对于有经验的并行程序员,它也是熟悉但复杂的主题。这样,如果您使用 Java 的并行编程功能,那么您需要完整地注明它。[LEA97] 建议:当一个类既包含了同步也包含了非同步的成员函数时,您必须注明成员函数依赖的执行上下文(尤其是当函数要求不受限访问时),这样可以让其他开发人员安全地使用您的成员函数。当一个实现 Runnable 接口的类的 setter 方法(即一个更新字段的成员函数)没有同步时,您应注明原因。最后,如果您覆盖某个成员函数或使它超负荷,并且更改它的同步性时,也应注明原因。
  12. 仅当注释可提高代码的清晰度时,才应加上注释。您无需给每个成员函数注明上面描述的所有方面,因为不是每个方面都适用于每个成员函数。但是,您要给每个编写的成员函数注明其中几个方面。

3.4.2    内部注释

除成员函数注释以外,您还需要在成员函数中加入注释来说明您的工作。目的是使成员函数更容易理解、维护和改进。

代码内部注释有两种类型:C 语言风格的注释(/* 与 */)和单行注释(//)。正如上述所讨论的,应认真考虑给代码的业务逻辑选择一种风格的注释,而给要注释掉的无用代码选择另一种风格的注释。建议对业务逻辑采用单行注释,因为它可用于整行注释和行末注释。使用 C 语言风格的注释来注释掉不需要的代码行,因为这样仅用一处注释就可以轻松地去掉几行代码。此外,因为 C 语言风格的注释看上去非常类似文档注释,所以使用它们容易产生混淆,这样会使代码的可理解性降低。所以,应尽量减少使用它们。

在内部,您始终应注明:

  1. 控制结构。描述每个控制结构,例如比较语句和循环。您不应阅读控制结构中的所有代码才能确定它执行的操作;而应当只要查看紧靠它之前的一行或两行注释就能确定。
  2. 代码做什么以及为什么这样做。您始终可以查看代码片段,想出它执行的操作,但对于非显而易见的代码,您很难确定它为什么那样做。例如,您可以查看代码行,很容易就确定要将 5% 折扣应用于订单总额。这是容易的。但不容易的是想出应用该折扣的原因。显而易见,有某个业务规则指出要应用折扣,因此应当至少在您的代码中提及该业务规则,这样其他开发人员才可以理解代码这样做的原因。
  3. 局部变量。尽管我们将在第 5 章中更为详细地讨论它,成员函数中定义的每个局部变量还是应当在它自己的代码行上作出声明,并且通常应当带有描述它的使用的行内注释。
  4. 困难或复杂的代码。如果您无法重写代码,或者没有时间重写,那么您必须在成员函数中完整注明所有的复杂代码。一般原则是如果您的代码不是显而易见的,那么您就需要注明它。
  5. 处理顺序。如果代码中存在必须以定义顺序执行的语句,那么您应当确保注明了该事实 [AMB98]。最糟糕的事情是您对代码片段做了轻微的改动,却发现它不起作用了,于是花数个小时查找问题,最后发现原来是搞错了代码的执行顺序。
  6. 注明您的右花括号。经常您会发现控制结构中包含控制结构。尽管您应当避免像这样写代码,但有时您会发现这样写代码更好一些。问题是哪个右花括号(} 字符)属于哪个控件结构就令人费解了。幸运的是有些代码编辑器在您选择左花括号时,会自动突出显示相应的右花括号;不幸的是并非每个编辑器都支持这样的功能。我发现给右花括号加上行内注释(例如 //end if//end for//end switch)会使代码更容易理解。

3.5    编写清晰代码的技术

本节讲述几种有助于专业开发人员脱离庸手行列的技术。这些技术是:

3.5.1    注明代码 

请记住 - 如果您的代码不值得注明,那么它也就不值得保留。[NAG95] 如果您适当地应用本文档中建议的注释标准和准则,就可以大大提高代码的质量。

3.5.2    分段或缩进代码 

提高成员函数的可读性的一种方法是将它分段,或者换句话说,就是在代码块的范围内使代码缩进。一对花括号({} 字符)中的任何代码都形成一个块。基本构想是块中的代码应统一地缩进一个单位。

Java 约定似乎是左花括号放在块的所有者后的行上,而右花括号应缩进一级。[LAF97] 指出的要点是您的组织选择缩进样式然后必须遵守它。与 Java 开发环境用于它生成的代码使用相同的缩进样式。

3.5.3    在代码中使用空白区域 

在 Java 代码中添加一些空行(称为空白区域),通过将代码分为多个易于理解的小节,可使代码更容易阅读。[VIS96] 建议使用一个空行来分隔代码的逻辑组(例如控制结构),而使用两个空行来分隔成员函数定义。没有空白区域,就很难阅读和理解代码。

3.5.4    遵循 30 秒规则 

其他程序员应当能够在不到 30 秒内阅读完成员函数并完全理解它做什么、它为什么这么做以及它如何做。如果做不到这点,那么您的代码就显得太难而无法维护,这样就应当改进代码。30 秒钟,就 30 秒。一个实用的原则是如果成员函数超出了屏幕,那么它就可能太长了。

3.5.5    编写简短的单命令行 

您的代码应当每行执行一个操作。早期,在一行代码上获取尽可能多的功能还有意义。但无论何时您尝试在一行代码上执行多个操作,都会使代码更难理解。为什么这样做?原因是我们要使代码更容易理解,这样就更容易维护和改进它。就像一个成员函数应当执行一个操作(而且仅一个操作),您在一行代码上应当仅执行一个操作。

此外,您应当编写保持显示在屏幕上的代码 [VIS96]。您不应将编辑窗口滚动到右边才能阅读整行的代码,包括带有行内注释的代码。

3.5.6    指定操作的顺序 

提高代码可理解性的一个很容易的方法是使用括号(也称为“圆括号”)来指定 Java 代码中操作的确切执行顺序 [NAG95] 和 [AMB98]。如果您必须了解操作顺序才能让编程语言理解您的源代码,那么有些东西就存在严重错误。这个问题主要在您进行逻辑比较(集中对其他几个比较执行 AND 和 OR 运算)时出现。请注意如果您使用简短的单命令行(如先前所建议),那么这就不会成为问题。


4    字段和属性的标准

此处使用的术语字段是指 BDK 调用属性的字段 [DES97]。字段是描述对象或类的数据段。字段可能是基本的数据类型(例如字符串或浮点型),也可能是对象(例如客户或银行帐户)。

4.1    命名字段

您应当使用全英文描述符来给字段命名 [GOS96] 和 [AMB98],这样使字段代表的内容显而易见。集合形式(例如数组或向量)的字段应使用复数名称来指示它们代表多个值。

示例:

firstName

zipCode

unitPrice

discountRate

orderItems

4.1.1    命名组件(窗口小部件)

对于组件(接口窗口小部件)的名称,您应当使用以窗口小部件类型为后缀的全英文描述符。这使您容易识别组件的目的以及它的类型,从而更容易在列表中找到每个组件。许多可视编程环境都在 applet 或应用程序中提供所有组件的列表,这样如果每个组件都命名为 button1button2 等,就会产生混淆。

示例:

okButton

customerList

fileMenu

newFileMenuItem

4.1.1.1    命名组件的取代方法:匈牙利表示法 

“匈牙利表示法”[MCO93] 基于以下原理:字段应当使用 xEeeeeeEeeeee 进行命名,其中 x 表示组件类型,EeeeeEeeeee 是全英文描述符。

示例:

pbOk

lbCustomer

mFile

miNewFile

主要的好处是这是 C++ 代码通用的工业标准,所以许多人已遵循它。根据变量的名称,开发人员可以快速地判断它的类型以及如何使用它。主要的不利之处是前缀表示法变为:

4.1.1.2    命名组件的取代方法:匈牙利后缀表示法 

基本上这是其他两种选择的组合,它产生诸如 okPbcustomerLbfileMnewFileMi 这样的名称。主要的好处是组件的名称指示出窗口小部件类型,并且相同类型的那些窗口小部件在字母列表中没有分在一起。主要的不利之处是您仍未在使用全英文描述,这使标准更难记住,因为它偏离了正常情况。

4.1.1.3   设置组件名称标准 

无论您选择什么约定,您都要创建一列“正式”的窗口小部件名称。例如,给按钮命名时,您使用 Button 还是使用 PushButton,使用 b 还是使用 pb?请创建一个列表,然后将它提供给组织中的每个 Java 开发人员。

4.1.2    命名常量

在 Java 中,不发生变化的常量值通常作为类的静态最终字段实现。认可的约定是使用全英文大写单词,在词与词之间用下划线连接 [GOS96]。

示例:

MINIMUM_BALANCE

MAX_VALUE

DEFAULT_START_DATE

该约定的主要好处是帮助您区分常量和变量。稍后,我们将在本文档中看到可以通过不定义常量来大幅提高代码的灵活性和可维护性;正确的方法是您应当定义返回常量值的 getter 方法成员函数。

4.1.3    命名集合

集合(例如数组或向量)应使用复数名称来代表数组存储的对象的类型。名称应当是全英文描述符,所有非首个单词的第一个字母都大写。

示例:

customers

orderItems

aliases

该约定的主要好处是它帮助区分代表多个值(集合)的字段和代表单个值(非集合)的字段。

4.2    字段可视性

当字段声明为保护时,子类中的成员函数是有可能直接访问它们的,导致显著增大类层次结构中的耦合。这使您的类更难维护和改进,因此应当避免这种情况。永远不应直接访问字段;而应当使用存取成员函数(请参阅下面)。

可视性 描述 正确用法
公共 公共字段可由任何其他对象或类中的任何其他成员函数访问。 不要将字段设为公共。
保护 受保护的字段可由声明它的类或该类的子类中的任何成员函数访问。 不要将字段设为保护。
私有 私有字段只可由声明它的类中的成员函数访问,而不可由子类中的成员函数访问。 所有字段都应设为私有,由 getter 方法和 setter 方法成员函数(存取函数)访问。

对于非持久的字段(它们将不保存到永久存储器),您应当将它们标记为静态瞬态 [DES97]。这使它们符合 BDK 的约定。

4.2.1    不要“隐藏”名称 

名称隐藏是指给局部变量、参数或者字段所取的名字,与另一个更大范围内的局部变量、参数或者字段的名字相同(或相似)。例如,如果您有名为 firstName 的字段,则不要创建名为 firstName 的局部变量或参数,或者任何与它类似的名称(例如 firstNamesfirstName)。名称隐藏会使代码难以理解,并容易产生问题,因为其他开发人员(或您)在修改代码时,会误读代码,也会产生难以发现的错误。

4.3    注明字段

所有字段都应很好地加以注明,以便其他开发人员理解它。为了有效,您需要注明:

  1. 它的描述。您需要描述字段,这样人们就了解如何使用它。
  2. 所有适用的不变量。字段的不变量是字段总为真的状况。例如,字段 dayOfMonth 的不变量可能是它的值只能在 1 到 31 之间(显然,您可以使这个不变量变得更复杂,从而根据年份和月份限定字段的值)。通过注明对字段值的限定,您可帮助定义重要的业务规则,使代码更易理解。
  3. 示例。对于那些关联了复杂的业务规则的字段,您应提供几个示例值,使它们更易理解。一个示例经常象一个好图片:它抵得过上千个词语。
  4. 并行问题。并行对于许多开发人员是新鲜而复杂的概念,而实际上,对于有经验的并行程序员,它也是熟悉但复杂的主题。这样,如果您使用 Java 的并行编程功能,那么您需要完整地注明它们。
  5. 可视性决定。如果您声明字段为非私有,那么应当注明这样做的原因。字段可视性前面在 4.2 字段可视性一节讨论过;使用存取成员函数支持封装,接下来将在 4.4 存取成员函数的使用一节中讨论。最根本的一点是您最好有不将变量声明为私有的合理理由。

4.4    使用存取成员函数

除命名约定以外,还可通过适当地使用存取成员函数(这些成员函数提供更新字段或访问字段值的功能)来实现字段的可维护性。存取成员函数有两种:setter 方法(也称为可变方法)和 getter 方法。setter 方法修改变量的值,而 getter 方法获取变量的值。

虽然存取成员函数过去会增加代码的开销,但是现在 Java 编译器为照顾它们的使用进行了优化,因此不再有开销的问题。存取函数有助于隐藏类的实施细节。一个变量只能从两个控制点进行访问:一个是 setter 方法,另一个是 getter 方法。这样让需要作出修改的位置点最少,从而提高了类的可维护性。9.3 优化 Java 代码一节中讨论了 Java 代码的优化。

组织可以实施的一个最重要的标准是使用存取函数。一些开发人员不愿使用存取成员函数,因为他们不愿输入这几个额外必需的字符,例如对于 getter 方法,除字段名以外,还必须输入“get”和“()”。最根本的一点是,使用存取函数后提高的可维护性和可扩展性,足够证实使用存取函数的合理性。

存取函数是唯一可以访问字段的地方。适当使用存取成员函数的一个关键概念是:成员函数中,只有存取成员函数可以直接访问字段。的确,在定义字段的类的成员函数中直接访问私有字段是可能的,但是您不会这样做,因此这样会增加类中的耦合。

4.4.1    为什么使用存取函数? 

“好的程序设计会努力将程序部件与不必要、未计划或者不想要的外部影响分隔开来。存取修饰符(存取函数)给编程语言控制这一类的接触提供了一种明确并可检验的方法。”[KAN97]

存取成员函数用以下方法提高了类的可维护性:

  1. 更新字段。每个字段只有几个更新点,这使修改和测试字段都很容易。换句话说,字段已被封装。
  2. 获取字段值。您对如何访问字段和由谁访问字段有完全的控制权。
  3. 获取常量的值和类的名称。通过在 getter 方法成员函数中封装常量值和类名,当那些值/名称更改时,您就只需要更新 getter 方法中的值,而无需更新每行使用该常量/名称的代码。
  4. 初始化字段。使用延迟初始化可确保字段总是已初始化,并且仅当需要它们时才初始化。
  5. 减少子类和它的超类之间的耦合。当子类只通过它们相应的存取成员函数访问被继承的字段时,就可以更改超类中字段的实施,而不影响它的任何子类,从而有效地减少了它们之间的耦合。存取函数降低了那种超类更改会波及子类的“脆弱基类”的风险。
  6. 将变化封装到字段中。如果一个或多个字段的业务规则发生变化,您可以潜在地修改存取函数,就可提供规则变化之前的功能。这一点使您很容易对新的业务规则作出反应。
  7. 简化并行问题。[LEA97] 指出,如果采用了基于字段值的 waits 语句,那么 setter 方法成员函数提供了唯一的一个位置来包含 notifyAll。这让转向并行方式更加容易。
  8. 名称隐藏不再是一个大问题。虽然应该避免名称隐藏(即给局部变量使用与字段相同的名称),但是始终通过存取函数访问字段意味着您可以给局部变量使用任何您想要的名称。您不必担心字段名称的隐藏,因为您无论如何都不会直接访问它们。

4.4.1.1    何时不使用存取函数 

唯一可能您不想用存取函数的时候是当执行时间最重要时。但是,这实际上很少见,因为应用程序耦合性的增加会抵消不使用存取函数所带来的时间节省。

4.4.2    命名存取函数

getter 方法成员函数应使用名称“get” + 字段名,除非字段表示的是一个布尔值(true 或 false),这时 getter 方法使用名称“is” + 字段名。无论何种字段类型,setter 方法成员函数都应使用名称“set” + 字段名([GOS96] 和 [DES97])。注意字段名始终使用大小写混合,所有单词的第一个字母要大写。这种命名约定在 JDK 中保持一致的使用,对于 bean 开发也是必需的。

示例:

字段 类型 getter 方法名称 setter 方法名称
firstName
string
getFirstName()
setFirstName()
address
Address 
object
getAddress()
setAddress()
persistent
boolean
isPersistent()
setPersistent()
customerNo
int
getCustomerNo()
setCustomerNo()
orderItems
 
Array of 
OrderItem 
objects
getOrderItems()
 
setOrderItems()
 

 

4.4.3    存取函数的高级技术

存取函数不仅仅局限在获取和设置实例字段的值时使用。这一节讨论如何将存取函数应用于以下方面,以提高代码的灵活性:

4.4.3.1    延迟初始化 

在访问变量之前需要初始化变量。有两种方法初始化:在创建对象时初始化所有变量(传统方法)或者初次使用对象时进行初始化。 

第一个方法使用首次创建对象时调用的特殊成员函数,称为构造函数。尽管这种方法起作用,但证实很容易出错。当添加新的变量时,您很容易忘记更新构造函数。

另一种方法称为延迟初始化,其中字段由它们的 getter 方法成员函数初始化(如下所示)。请注意如何在 getter 方法成员函数内使用 setter 方法成员函数。注意:成员函数会检查分支数是否为零;如果是,那么会将它设置为适当的缺省值。

/** Answers the branch number, which is the leftmost

four digits of the full account number.

Account numbers are in the format BBBBAAAAAA.

*/

protected int getBranchNumber()

{

if ( branchNumber == 0)

{

// The default branch number is 1000, which

// is the main branch in downtown Bedrock.

setBranchNumber(1000);

}

return branchNumber;

}

对字段使用延迟初始化是十分普遍的,那些字段实际上是数据库中存储的其他对象。例如,当您创建新的库存项时,您没有必要从设置为缺省值的数据库访存任何库存项类型。而应使用延迟初始化来在初次访问该值时设置它,这样您只要在需要时从数据库中读取库存项类型对象。 

该方法对于包含不定期访问的字段的对象是有利的。如果您不打算使用,为什么要承担检索持久存储器的开销?

无论何时在 getter 方法成员函数中使用延迟初始化,您都应当注明缺省值为什么是当前值,就如我们在上面示例中看到的。如果这样做,字段在您的代码中是如何使用的就不再神秘,这可提高代码的可维护性和可扩展性。

4.4.3.2    常量存取函数

常见的 Java 代码模式是将常量值作为静态最终字段实施。该方法对于保证稳定的“常量”是有意义的。例如,类 Boolean 实施两个分别称为 TRUEFALSE静态最终字段,它们代表了该类的两个实例。这对于值可能从不更改的 DAYS_IN_A_WEEK 常量也是有意义的。

但是,许多所谓的业务“常量”随时间推移而发生变化,因为业务规则在变化。请考虑以下示例:Archon Bank of Cardassia(ABC)一直坚持帐户要获得利息,必须至少有 $500 的余额。为实施这一点,我们可以在类 Account 中添加一个名为 MINIMUM_BALANCE 的静态字段,以供在计算利息的成员函数中使用。这虽然可行,但不灵活。如果业务规则发生变化,要求不同种类的帐户有不同的最小余额(例如储蓄帐户的最小余额为 $500 ,而支出帐户的最小余额为 $200),那会出现什么情况?又如果业务规则要求第一年最小余额为 $500,第二年变为 $400,而第三年变为 $300,那又会出现什么情况?要是规则变为夏季 $500 ,但冬季 $250 呢?而且可能将来需要组合使用所有这些规则。

在这想要指出的是将常量作为字段实施是不够灵活的。远比这恰当的方法是将常量作为 getter 方法成员函数实施。上例中,采用一个名为 getMinimumBalance() 的静态(类)成员函数比一个名为 MINIMUM_BALANCE 的静态字段要灵活得多,因为在这个成员函数中我们可以实现不同的业务规则,并针对不同种类的帐户生成不同的子类。

/** Get the value of the account number. Account numbers are in the following 
    format: BBBBAAAAAA, where BBBB is the branch number and 
    AAAAAA is the branch account number.
*/
public long getAccountNumber()
{
	return ( ( getBranchNumber() * 100000 ) + getBranchAccountNumber() );
}
/**
	Set the account number. Account numbers are in the following 
	format: BBBBAAAAAA where BBBB is the branch number and
	AAAAAA is the branch account number.
*/
public void setAccountNumber(int newNumber)
{
	setBranchAccountNumber( newNumber % 1000000 );
	setBranchNumber( newNumber / 1000000 );
}

常量 getter 方法的另一个优点是,它们有助于提高代码的一致性。请考虑上述代码,它们不能正常工作。一个帐号由分支号和分支帐号连接而成。测试代码后,我们发现 setter 方法成员函数 setAccountNumber() 不能正确更新分支帐号:它选取最左边的三位数,而不是四位。那是因为我们采用了 1,000,000 而不是 100,000 来抽取字段 branchAccountNumber。如果照下面所示,对这个值采用一个来源,即常量 getter 方法 getAccountNumberDivisor(),我们的代码就会更一致,并且可以正常工作。

/**
	Returns the divisor needed to separate the branch account number from the 
	branch number within the full account number. 
	Full account numbers are in the format BBBBAAAAAA.
*/
public int getAccountNumberDivisor()
{
	return ( (long) 1000000);
}
/**
	Get the value of the account number. Account numbers are in the following 
	format: BBBBAAAAAA, where BBBB is the branch number and 
	AAAAAA is the branch account number.
*/
public long getAccountNumber()
{
	return ( ( getBranchNumber() * getAccountNumberDivisor() ) + getBranchAccountNumber() );
}
/**
	Set the account number. Account numbers are in the following 
	format: BBBBAAAAAA where BBBB is the branch number and
	AAAAAA is the branch account number.
*/
public void setAccountNumber(int newNumber)
{
	setBranchAccountNumber( newNumber % getAccountNumberDivisor() );

	setBranchNumber( newNumber / getAccountNumberDivisor() );
}

通过对常量使用存取函数,我们降低了代码出现问题的可能性,同时提高了系统的可维护性。当帐号的格式发生变化时(我们知道它肯定会发生变化),我们的代码很容易修改,因为我们同时隐藏和集中了构造或分解帐号所需的信息。

4.4.3.3    集合存取函数

存取函数的主要目的是将访问封装到字段中,以减少代码的耦合。集合(例如数组和向量)要比单值字段复杂,自然要求不只实现标准的 getter 方法和 setter 方法成员函数。特别是因为要对集合进行增减,所以需使用存取成员函数。在集合字段的适当处添加以下存取成员函数:

成员函数类型 命名约定 示例
集合的 getter 方法
getCollection()
getOrderItems()
集合的 setter 方法
setCollection() 
setOrderItems()
在集合中插入对象
insertObject()
insertOrderItem()
从集合中删除对象
deleteObject()
deleteOrderItem()
创建新对象并将它添加到集合中
newObject()
newOrderItem()

这种方法的优点是集合完全被封装,这允许您以后用另一个结构(可能是一个链接的列表或者一个 B 型树)来替换它。

4.4.3.4   同时访问多个字段

存取成员函数的一个优点是,它们使您能有效地实施业务规则。举例来说,请考虑有关形状(shape)的类层次结构。Shape 的每个子类通过“xPosition”和“yPosition”这两个字段表示位置,并且可以通过调用成员函数 move(Float xMovement, Float yMovement) 在屏幕上的二维平面中移动。为达到我们的目的,图形在任何时刻都不可以只在一个坐标轴方向上移动,而应同时沿 x 和 y 轴移动(成员函数 move() 的两个参数中的任何一个参数都可以使用值 0.0)。这就意味着 move() 成员函数应该是公共的,但是成员函数 setXPosition()setYPosition() 应该都是私有的,由 move() 成员函数正确调用。

另一个实施方法是,引入一个可以同时更新两个字段的 setter 方法成员函数,如下所示。成员函数 setXPosition()setYPosition() 将仍然是私有的,这样它们不可由外部类或子类直接调用(您必须加入一些如下所示的注释来指出它们不应直接调用)。

/** Set the position of the shape */
protected void setPosition(Float x, Float y)
{
	setXPosition(x);
	setYPosition(y);
}
/** Set the x position.  Important: Invoke setPosition(), not this member function. */
private void setXPosition(Float x)
{
	xPosition = x;
}
/** Set the y position of the shape
    Important: Invoke setPosition(), not this member function.
*/
private void setYPosition(Float y)
{
	yPosition = y;
}

4.5    存取函数可视性

始终尽可能地将存取函数设为保护(protected),这样只有子类可以访问字段。仅当某个“外部类”需要访问某个字段时,您才应当将相应的 getter 方法或 setter 方法设为公共。注意:getter 方法成员函数为公共而 setter 方法成员函数为保护的情况是很常见的。

有时您需要将 setter 方法设为私有,以确保某些不变量不变动。例如,Order 类可能含有一个字段表示 OrderItem 实例的集合,同时含有另一个名为 orderTotal 的字段表示整个订单的总额。orderTotal 是一个表示订购商品总额的有用字段。仅有的可以更新 orderTotal 值的成员函数是那些处理订购商品集合的函数。假设那些成员函数都在 Order 中实施,那么即使 getOrderTotal() 很可能为公共,您也应将 setOrderTotal() 设为私有。

4.6    总是初始化静态字段

静态字段(也称为类字段)应被赋予有效值,因为不能假定会在访问静态字段之前创建类的实例。

 


5    局部变量标准

局部变量是指在块(通常是一个成员函数)范围内定义的对象或数据项。一个局部变量的作用范围是定义它的块。局部变量的一些重要的编码标准集中在:

5.1    命名局部变量

一般而言,命名局部变量遵循与命名字段相同的约定,即使用全英文描述符,所有非首个单词的第一个字母都大写。

但是为方便起见,对于以下几种具体的局部变量,这个命名约定可以放宽:

5.1.1    命名流

当只有一个输入和/或输出流在成员函数中打开、使用然后关闭时,常见的约定是对这些流分别采用 InOut 来命名 [GOS96]。对于既用于输入又用于输出的流,采用 inOut 来命名。

一种常见的取代这种命名约定的方法是使用名称 inputStreamoutputStreamioStream 代替 InOutinOut(尽管这与 Sun 公司的建议相冲突)。

5.1.2    命名循环计数器

因为局部变量常用作循环计数器,并且它为 C/C++ 所接受,所以在 Java 编程中,可以使用 ijk 作为循环计数器 [GOS96]。如果您给循环计数器使用这些名字,那么要始终使用它们。

一种常见的取代方法是使用如 loopCounter 或只是 counter 这样的名字,但是这种方法的问题是在需要多个计数器的成员函数中,常常出现象 counter1counter2 这样的名称。最根本的一点是,ijk 作为计数器必须起作用;它们可以快速输入,因此被广泛接受。

5.1.3    命名异常对象

因为 Java 编码中异常处理很常见,所以使用字母 e 作为一般的异常符是可接受的 [GOS96]。

5.2    声明和注明局部变量

Java 中声明和注明局部变量有几种约定。这些约定是:

  1. 每行代码只声明一个局部变量。这与每行代码应只有一个语句相一致,并使得对每个变量采用一处行内注释成为可能。
  2. 用一处行内注释注明局部变量。行内注释是一种紧接在同一行代码后,用符号 // 标注出来的单行注释风格(它也称为行末注释)。您应注明局部变量做什么、它在哪里适用以及为什么要用它。这会使代码易于理解。
  3. 局部变量只用于一个目的。一旦将一个局部变量用于多个原因,就明显降低了它的一致性,使它难以理解。同时由于来自前面代码的局部变量旧值可能产生意外副作用,因此还增大了代码产生问题的可能性。的确,局部变量的复用需要较少的内存,因而更高效,但是复用局部变量降低了代码的可维护性,使代码更加脆弱。这常常让由于不必分配更多内存而带来的小节省变得不值得。

5.2.1    有关声明的一般注释

在代码行间(例如在 if 语句作用范围内)声明的局部变量对于不熟悉您的代码的人来说是很难找到的。

一种取代“在第一次使用局部变量之前声明它们”的方法是在代码的顶部声明它们。因为无论如何,成员函数都应该简短(请参阅 3.5.5 编写简短的单命令行,所以要去代码顶部判断局部变量用途,并不是很费劲。


6    成员函数参数标准

有关成员函数参数的重要标准集中在参数应如何命名和注明上。术语参数指成员函数的参数。

6.1    命名参数

参数命名应遵循与局部变量完全一样的约定。与局部变量一样,名字隐藏是个问题。

示例:

customer

inventoryItem

photonTorpedo

e

一个可行的取代方法(来自 Smalltalk)是使用局部变量的命名约定,但在名称之前加上“a”或“an”。加上“a”或“an”有助于让参数与局部变量和字段区分开来,避免名称隐藏的问题。这种方法较好。

示例:

aCustomer

anInventoryItem

aPhotonTorpedo

anInputStream

anException

6.2    注明参数

成员函数的参数是采用 javadoc @param 标记在成员函数的头注释中注明的。您应描述以下方面:

  1. 参数应用来做什么。您需要注明参数用来做什么,以便其他开发人员了解使用参数的完整环境。
  2. 任何限制或前置条件。如果某个参数的完整值范围不适用于成员函数,则应让该成员函数的调用者知道。可能成员函数只接受正数或者字符数少于 5 个的字符串。
  3. 示例。如果应传递什么样的参数不明显,那么应该在注释中给出一个或多个示例。

6.2.1    对参数类型使用接口

如果合适,不要只指定参数类型属于哪一类(例如 Object),而应指定使用哪个接口(例如 Runnable)。好处是,这种方法根据环境可更加具体(Runnable 比 Object 更加具体),或潜在地在支持多态性上是一个更好的方法。不应坚持某个参数是具体类层次结构中某个类的实例,而应指定它支持某个具体的接口,这意味着它只需要多态地适应您的需要即可。


7    类、接口、程序包和编译单元的标准

这一章集中讲述类、接口、程序包和编译单元的标准和准则。类是一个使对象实例化(创建对象)的模板。类包含字段和成员函数的声明。接口是公共特征符的定义,包括成员函数和字段;实施接口的类必须支持这些函数和字段。程序包是相关类的集合。最后,编译单元是声明类和接口的源代码文件。因为 Java 允许编译单元存储在数据库中,所以单个的编译单元不可与物理源代码文件直接相关。

7.1    类标准

类的重要标准基于:

7.1.1    命名类

标准的 Java 约定使用全英文描述符,名称第一个字母要大写,并且名称其余部分使用混合大小写。([GOS96] 和 [AMB98])

类名必须是单数形式。

示例:

Customer

Employee

Order

OrderItem

FileStream

String

7.1.2    注明类

以下信息应出现在文档注释中,紧接在类定义的前面:

  1. 类的目的。开发人员需要了解类的一般目的,以判断这个类是否满足他们的需求。养成注明与了解类有关的所有有用信息的习惯;例如它是某个模式的一部分吗?或者使用它有什么要引起注意的限制吗 [AMB98]?
  2. 已知错误。如果一个类有任何未解决的问题,您应注明它们,让其他开发人员了解这个类的缺点和难点。此外,还应注明为什么未解决这个问题。注意:如果问题只适用于某一个成员函数,那么它应直接与那个成员函数相关。
  3. 类的开发或维护历史。通常要包含一个历史记录表,列出对类所作更改的日期、作者和摘要。这样做的目的是让进行维护的程序员了解过去曾对类作出的所有修改,同时注明谁对类做了什么操作。
  4. 注明适用的不变量。不变量是一组有关实例或类的断言,这些断言在任何“稳定”时间都必须为“真”,而稳定时间指在对象或类上调用某个成员函数之前以及紧跟调用成员函数之后的时间段 [MEY88]。通过注明类的不变量,您让其他开发人员可以了解可如何使用类。
  5. 并行策略。任何实施接口 Runnable 的类都应充分描述它的并行策略。并行编程很复杂,许多程序员都未经历过,所以您需要额外投入时间来确保人们能够理解您的东西。注明您的并行策略以及为什么选择这个策略而不是其他策略,这很重要。常见的并行策略 [LEA97] 包括以下方面:
  • 同步对象
  • 停滞对象
  • 警戒对象
  • 版本化对象
  • 并行策略控制器
  • 接受者

7.1.3    类声明

一种让您的类容易被理解的方法是一致地声明它们。Java 中常见的方法是按以下顺序声明类:

  • 公共成员函数
  • 公共字段
  • 受保护的成员函数
  • 受保护的字段
  • 私有成员函数
  • 私有字段

[LAF97] 指出,构造函数和 finalize() 应该首先列出,可能是因为这些将会是另一个开发人员为了解如何使用类而首先查看的成员函数。此外,因为我们有一个将所有字段声明为私有的标准,所以声明顺序实际变为:

构造函数

finalize()

公共成员函数

受保护的成员函数

私有成员函数

私有字段

在每组成员函数中,按字母顺序列出成员函数是常见的。许多开发人员选择先在每组中列出静态成员函数,然后列出实例成员函数,接着在这两个子分组的每个子分组中按字母顺序列出成员函数。这两种方法都有效,您只需选用一种,然后保持使用它。

7.1.4    最小化公共和保护接口

面向对象设计的一个基本原则是使类的公共接口最小化。这样做有几个理由:

  1. 易于了解。要了解如何使用类,您只需了解它的公共接口即可。公共接口越小,类越容易了解。
  2. 减少耦合。当一个类的实例向另一个类的实例或者直接向这个类发送消息时,这两个类就成为耦合类。最小化公共接口可将耦合的可能性降到最低。
  3. 更大灵活性。这直接与耦合相关。无论何时您想改变公共接口中的成员函数的实施方法(可能您想修改成员函数的返回值),那么您可能必须修改所有调用该成员函数的代码。公共接口越小,封装性就越大,代码的灵活性也越大。

尽量使公共接口最小化这一点明显值得您努力,但经常不明显的是也应使受保护接口最小化。基本构想是从子类的角度来看,它所有超类的受保护接口实际上是公共的。任何在受保护接口中的成员函数都可由子类调用。因此,出于与使公共接口最小化同样的理由,应使类的受保护接口最小化。

7.1.4.1    首先定义公共接口

大多数有经验的开发人员都在开始编写类的代码之前,先定义类的公共接口。 

7.2    接口标准

接口的重要标准基于:

7.2.1    命名接口

Java 约定是用大小写混合的方式给接口命名,并且每个单词的第一个字母要大写。虽然象 SingletonDataInput 这样的描述性名词经常用来给接口命名,但是比较好的 Java 约定是用象 RunnableCloneable 这样的描述性形容词来命名 [GOS96]。

7.2.1.1    取代方法

在接口名称前加前缀字母“I”。[COA97] 建议在接口名称的前面附加上字母“I”,使名称变为象 ISingletonIRunnable 这样的名称。这种方法有助于将接口名称与类和程序包的名称区分开来。我喜欢这种潜在的命名约定,原因很简单:它使类图(有时也称为对象模型)更易阅读。这种方法的主要缺点是现有接口(例如 Runnable)不是采用这种方法命名的。这种接口命名约定在 Microsoft 的 COM/DCOM 体系结构中也很流行。

7.2.2    注明接口

以下信息应出现在文档注释中,紧接在接口定义的前面:

  1. 陈述目的。在其他开发人员使用接口之前,他们需要理解接口封装的概念。换句话说,他们需要了解接口的目的。一种测试是否需要定义接口的好方法是看您是否可以轻松地描述它的目的。如果描述起来有困难,那么很可能从一开始起就不需要这个接口。因为接口的概念在 Java 中较新,所以人们对如何正确使用它们还没有经验,结果很可能会滥用它们。
  2. 应如何使用以及不应如何使用接口。开发人员需要了解应如何使用接口以及不应如何使用接口 [COA97]。

因为成员函数的特征符在接口中定义,所以对于每个成员函数特征符,您应遵循第 3 章中讨论的成员函数注释约定。

7.3    程序包标准

程序包的重要标准基于:

  • 命名约定
  • 注释约定

7.3.1    命名程序包

关于程序包的命名有几条规则。按顺序来说,这些规则是:

  1. 标识以句点隔开。为了使程序包名称更具可读性,Sun 建议程序包名称中的标识以句点隔开。例如程序包名称 java.awt 含有两个标识:javaawt
  2. 来自 Sun 公司的标准 Java 分发程序包以标识“java”开头。Sun 公司保留这种权利,这样无论您的 Java 开发环境的零售商是谁,标准 Java 程序包的命名始终一致。
  3. 本地程序包的名称以非全大写的标识开头。本地程序包在您的组织内部使用,不分发给其他组织。举例来说,这些的程序包名称有 persistence.mapping.relationalinterface.screens
  4. 全局软件包的名称以您的组织的因特网保留域名开头。一个要分发给多个组织的程序包应包含来源组织的域名,并且顶级域类型要大写。例如,要分发前面的程序包,它们应命名为 com.rational.www.persistence.mapping.relationalcom.rational.www.interface.screens

7.3.2    注明程序包

您应有一个或多个外部文档,描述您的组织所开发的程序包的用途。对于每个程序包,应注明:

  1. 程序包的基本原理。其他开发人员需要了解程序包的一切可能信息,这样他们才能判断是否要使用它,以及如果是共享程序包,那么是否要改进或扩展它。
  2. 程序包中的类。在程序包中包含一列类和接口,每个类和接口都用一行文字加以描述,以便让其他开发人员了解这个程序包包含什么。

技巧:创建一个使用程序包名称命名的 HTML 文件,然后将这个文件放到程序包的适当目录中。这个文件应带有后缀 .html

7.4    编译单元标准

编译单元的标准和准则基于:

7.4.1    命名编译单元

编译单元(在这种情况下是一个源代码文件)应被赋予文件内声明的主要类或接口的名称。使用与程序包或类相同的名称命名文件,大小写也应相同。扩展名 .java 应作为文件名的后缀。

示例:

Customer.java

Singleton.java

SavingsAccount.java

7.4.2    注明编译单元

虽然应努力使一个文件中只包含一个类或接口声明,但是有时在一个文件中定义数个类(或者接口)也可理解。一般原则是,如果类 B 的唯一用途是封装只有类 A 需要的功能,那么类 B 可以与类 A 出现在同一个源代码文件中。结果,下面的注释约定适用于源代码文件,而不专门适用于类:

  1. 对于包含几个类的文件,列出每个类。如果一个文件包含多个类,则您应列出这些类,并且简要地描述每个类。
  2. 文件名和/或标识信息。文件名应包含在它的顶部。好处是如果代码被打印出来,您会知道代码的源文件是什么。
  3. 版权信息。如果适用,您应指出文件的所有版权信息。通常的做法是指出版权的年份和版权持有个人或机构的名称。注意:代码作者可能不是版权持有者。

8    错误处理和异常

一般而言,只对错误(逻辑和编程错误、配置错误、损坏的数据、资源耗尽等)使用异常。一般规则是系统在正常状态下以及无超负荷或硬件故障状态下,不应产生任何异常。

  1. 使用异常处理逻辑和编程错误、配置错误、损坏的数据和资源耗尽。 

尽早采用适当的日志记录机制来报告异常,包括产生异常时。

  1. 使从给定的抽象中导出的异常数最小化。

在大型系统中,在每个级别都处理大量异常会使代码难以阅读和维护。有时,异常处理会阻碍正常处理。

有以下几种方法使异常数最小化:

  • 仅导出几个异常,但提供 diagnosis 原语,从而支持查询故障抽象或错误对象,来获取有关发生的问题本质的更多详细信息。
  • 在对象中添加 exceptional 语句,并且提供原语来明确检查对象的有效性。
  1. 对于经常发生的可预计事件不要使用异常。

使用异常来代表不明确是错误的状态有几个不便之处:

  • 它易混淆。
  • 它通常在控制流中强制性地产生一些中断,而这些中断更难以理解和维护。
  • 它使代码调试变得更加麻烦,因为大多数源代码级的调试器在缺省情况下会标出所有异常。

例如,不要将异常用作由某个函数返回的某种形式的额外值(象搜索中的 Value_Not_Found);而应使用一个含有“out”参数的过程,或者引入一个意思为 Not_Found 的特殊值,或者在一个含有判别式 Not_Found 的记录中包装返回的类型。

  1. 不要使用异常实施控制结构。

这是前面规则的一个特例:异常不应用作某种形式的“goto”语句。

  1. 确保状态码有一个正确值。

当使用子程序返回的状态码作为“out”参数时,始终要确保“out”参数被赋了值,这可以通过将赋值语句作为子程序体的第一个可执行语句来实现。系统缺省认为所有状态都为成功或失败的。考虑子程序的所有可能出口,包括异常处理程序。

  1. 在本地执行安全检查;不要指望您的客户会去做这件事。

如果某个子程序无正确的输入就可能产生错误的输出,那么应在子程序中安装代码,来有控制地检测和报告无效输入。不要依赖于注释来告诉客户输入正确值。如果不检测无效参数,注定早晚有一天,那条注释会被忽略,导致难以调试的错误。


9    其他标准和问题

本章说明几个重要的标准和准则;它们涉及很广,所以足以单独列出一章。

9.1    复用

任何从外部来源购买或复用的 Java 类库或者程序包都应验证是 100% 纯粹的 Java 语言 [SUN97]。通过强调这一标准,确保了所复用的东西将在您想部署它的所有平台上工作。您可以从各种来源(无论是专门开发 Java 库的第三方开发公司,还是您的组织中的另一个部门或项目团队)获得 Java 类、程序包或 applet。

9.2    导入类

导入(import)语句在指出类名时,允许使用通配符。例如,语句

import java.awt.*;

会引入程序包 java.awt 中的所有类。实际上,这并不完全正确。实际情况是每个所使用的取自 java.awt 程序包的类都会在编译时被引入代码,但未使用的类并不会引入。尽管这听起来似乎不错,但是它降低了代码的可读性。更好的方法是完全限定代码所使用的类的名称 [LAF97];[VIS96]。更好的导入类的方法如以下示例所示:

import java.awt.Color
import java.awt.Button
import java.awt.Container

9.3    优化 Java 代码

优化 Java 代码是程序员最后而不是最先应考虑的事情之一。将优化放到最后是因为只要优化那些需要优化的代码。代码的一小部分常常占用了处理时间的大部分,这样的代码就应该优化。缺乏经验的程序员会犯的一个典型错误是总想优化他们所有的代码,甚至那些运行起来已经很快的代码。

  1. 不要浪费时间去优化那些没人会在意的代码!

当优化代码时应该寻找什么?[KOE97] 指出最重要的因素是固定开销和大输入量时的性能。理由很简单:固定开销决定了程序在小输入量时的运行速度,算法决定了大输入量时的运行速度。Koenig 的原则是,一个程序如果在小输入量和大输入量时都运行得很好,那么在中等输入量的情况下很可能也会运行得很好。

编写可在几种硬件平台和/或操作系统上运行的软件的开发人员需要知道不同平台的特性。那些可能要消耗相当数量时间的操作,例如处理内存和缓冲区的方式,在不同的平台之间常常区别较大。因此,经常会发现要根据平台来对代码进行不同的优化。

优化代码时要注意的另外一点是用户的优先事项问题,因为根据环境,人们会对一些特定的延迟非常敏感。例如,用户可能更喜欢那种立刻显示自身框架然后再等 8 秒钟装入数据的屏幕,而不是那种在 5 秒钟内装入数据再显示框架的屏幕。换句话说,只要能有即刻的响应,大多数的用户都乐意多等一点时间 - 在优化您的代码时,这是一个很重要的常识。

  1. 无需总是从用户的观点以让代码运行更快的方式优化代码。

虽然优化可能意味着您的应用程序或成功或失败,但不要忘了更主要的是让代码正确运行。切记,运行起来慢但正确的软件永远比运行起来快但不正确的软件更让人可接受。

9.4    编写 Java 测试装置

面向对象的测试是一个几乎被对象开发团队忽视的重要课题。事实上,无论用何种语言编写代码,总有人(或者您或者其他某人)必须对您编写的软件进行测试。测试装置指的是用于测试应用程序的成员函数的集合,这些函数中的一部分嵌在类本身中(称为嵌入式测试),另一部分嵌在专门的测试类中。

  1. 在所有的测试成员函数名称前都加上前缀“test”。这使您可以在代码中快速找到所有的测试成员函数。这种在测试成员函数名称前加前缀“test”方法的优点是,它让您在编译代码的产品版之前,能很容易地从源代码中去掉测试成员函数。
  2. 一致地给所有成员函数的测试成员函数命名。方法测试是一种验证单个成员函数如定义那样工作的测试。所有的测试成员函数都应按照“testMemberFunctionNameForTestName”的格式命名。例如,用来测试 withdrawFunds() 的测试装置成员函数将包含 testWithdrawFundsForInsufficientFunds()testWithdrawFundsForSmallWithdrawal()。如果要对 withdrawFunds() 执行一系列测试,可以选择编写一个名为 testWithdrawFunds() 的成员函数来调用所有函数。
  3. 一致地给所有类的测试成员函数命名。类测试是一种验证单个类如定义那样工作的测试。所有的类测试成员函数都应按照“testSelfForTestName”的格式命名。例如,用来测试 Account 类的测试装置成员函数将包含 testSelfForSimultaneousAccess()testSelfForReporting()
  4. 创建单点来调用类的测试。开发一个名为 testSelf() 的静态成员函数来调用所有类测试和方法测试成员函数。
  5. 注明测试装置成员函数。注明您的测试装置成员函数。注释应包含测试描述和测试的预期结果。

10    成功模式

手握标准文档并不会自动地提高您的开发效率。要取得成功,您必须下决心提高效率,这意味着您必须有效地应用这些标准。

10.1    有效使用这些标准

以下建议将帮助您更有效地使用本文档中所描述的 Java 编码标准和准则。

  1. 理解标准。花些时间去理解为什么每个标准和准则会使开发效率提高。例如,不要仅仅因为这些准则的要求,就在一行声明一个局部变量。而应该因为您明白它能使代码更易懂,才这样做。
  2. 信任这些标准。理解每个标准是一个开始,但您还需要信任这些标准。遵守标准不应仅仅是当您有时间才做的事,而应该一直遵守,因为您相信这是最好的编码方法。
  3. 当您编写代码时就应该遵守标准,而不应是事后想法。有注明的代码不仅在您编写程序时而且在编写完程序后,都更容易理解。在开发阶段和维护阶段,一致命名的成员函数和字段都更加容易处理。在开发和维护阶段,整洁的代码也更加容易处理。最根本的一点是,遵守标准将提高您开发过程中的效率,并且使您的代码更加容易维护(从而使维护人员的效率也更高)。如果从一开始您就写出整洁的代码,您在编写过程中也会受益。
  4. 使它们成为您的质量保证流程的一部分。代码检查的一部分应该是确保源代码遵守您的组织所采用的标准。应当以标准作为基础来培训和指导您的开发人员达到更高的工作效率。

10.2    其他产生成功代码的因素

  1. 面向人而不要面向机器编程。您的开发工作的主要目的应该是您的代码易被其他人理解。如果没人能理解它,它就一点优点也没有。应当使用命名约定。注明代码。同时应当给它分段。
  2. 首先设计,然后编写代码。您是否曾遇到过这样的情况:您的程序倚靠的部分代码需要修改?可能是要传一个新参数给成员函数,或者可能是要将一个类拆成几个类。为了确保您的代码与重新配置后的已修改代码还能一起工作,您额外做了多少工作呢?您的情绪如何?您是否问过自己“为什么有人在原先写这些代码时不停下来考虑考虑,这样就不会有修改了”?或者是否责怪过“他们应该先设计好代码”?显然,您有过这样的疑问或责怪。如果您在实际动手写代码之前,花时间想清楚怎样写代码,您很可能就可以少花些时间编写它。此外,预先想好可以潜在地减少将来修改代码所带来的影响。
  3. 一小步一小步地开发。一小步一小步地开发(先写几个成员函数,检测它们,然后再写几个),经常比一次性地写完所有代码然后修改它要有效得多。测试和修改 10 行代码远比累积测试和修改 100 行代码要容易得多;实际上,可以很有把握地说,同样是编写、测试和修改 100 行代码,10 行 10 行地做所花的时间还不到一口气做 100 行所花的时间的一半。 
    理由很简单。当测试您的代码并发现错误时,错误几乎总是在刚写完的新代码中(当然这假定新代码所基于的其余代码都是很可靠的)。在一小部分代码中寻找问题会比在一大段代码中寻找问题要快得多。通过一小步一小步逐步地开发,您可以减少查找错误所需的平均时间,这转而又减少了整个的开发时间。
  4. 使代码保持简洁。复杂的代码或许让人在智力上获得满足,但是如果别人读不懂,那就毫无用处。如果要求某人(可能甚至是您)第一次修改一段复杂的代码以纠正其中的错误或改进代码,那么很可能代码会被重写。实际上,您很可能已经因为代码难懂而重写过某人的代码。当您重写代码时,您是怎样认为代码的最初开发者呢?您认为那个人是天才还是怪物?写出那种后来要被重写的代码没什么可骄傲的,所以应遵循 KISS 法则:保持代码简单直白。
  5. 学习常见的模式、反模式和代码模式。有大量的分析、设计和流程模式与反模式以及编程代码模式,可供您提高开发效率。

11    总结

为方便起见,本章总结了在此列出的准则,并按照主题,将它们组织成几个一页大小的 Java 编码标准总结。这些主题是:

  • Java 命名约定
  • Java 注释约定
  • Java 编码约定

在我们开始总结这个白皮书余下的标准和准则之前,我想重申主要的要求:

当您违背某个标准时,请注明它。所有其他标准都可以违背,唯独这个标准不可以违背。如果违背某个标准,就必须注明为什么您要违背这个标准、违背这个标准可能产生什么影响以及标准在什么情况下可以违背。

11.1    Java 命名约定

除了以下几个特例之外,命名时应始终使用全英文描述符。此外,总体应使用小写字母,但类和接口名称的第一个字母以及所有非首个单词的第一个字母要大写。

一般概念:

  • 使用全英文描述符。
  • 使用适用于该域的术语。
  • 利用混合大小写使名称可读。
  • 尽量少用缩写,但如果要用,必须明智地使用。
  • 避免使用长名称(最好少于 15 个字母)。
  • 避免使用相似或只大小写有区别的名称。
  • 避免使用下划线。
命名约定 示例
自变量/
参数
传递的值/对象使用全英文描述,可能要在名称之前加上“a”或“an”前缀。重要的是选择一种并坚持用它。
customer 和 account
               - 或 -
aCustomer 和 anAccount

字段/
属性
字段使用全英文描述,第一个字母小写,所有非首个单词的第一个字母都大写。
firstName、lastName 和
warpSpeed
 
布尔型的 getter 方法成员函数 所有布尔型 getter 方法都必须以单词“is”作为前缀。如果您遵守前文所说的布尔字段的命名标准,那么您只需将字段名赋给它即可。
isPersistent()、isString() 和
isCharacter() 
使用全英文描述,所有单词的第一个字母都大写。
Customer 和 SavingsAccount
编译单元文件 使用类或接口的名称,或者如果文件中除了主类之外还有多个类时,加上后缀“.java”来指示它'是一个源代码文件。
Customer.java、
SavingsAccount.java 和
Singleton.java
组件/
窗口小部件
使用全英文描述来描述组件的用途,末尾应接上组件类型。
okButton、customerList 和 
fileMenu
构造函数 使用类的名称。
Customer() 和 SavingsAccount()
析构函数 Java 没有析构函数,而是当某个对象在作为垃圾回收时,调用成员函数 finalize()
finalize()
异常 通常使用字母“e”表示异常。
e
最终静态字段(常量) 全部使用大写字母,单词之间用下划线分隔。更好的方法是采用最终静态 getter 方法成员函数,因为它们大大提高了灵活性。
MIN_BALANCE 和 DEFAULT_DATE
getter 方法成员函数 在被访问字段的名称的前面加上前缀“get”。
getFirstName()、getLastName() 和 
getWarpSpeeed()
接口 使用全英文描述来描述接口封装的概念,所有单词的第一个字母都大写。习惯上,名字后面加上后缀“able”、“ible”或者“er”,但这不是必需的。
Runnable、Contactable、
Prompter 和 Singleton
局部变量 使用全英文描述,第一个字母小写,但不隐藏现有字段。例如,如果有一个字段命名为“firstName”,局部变量就不要命名为“firstName”。
grandTotal、customer 和 
newAccount
循环计数器 通常使用字母 ijk 或者名称 counter 都可以接受。
i、j、k 和 counter
使用全英文描述,大小写混合,所有单词的第一个字母都大写,其他都小写。对于全局程序包,将您的因特网域名反转,然后连接程序包名称。
java.awt、
com.ambysoft.www 和 
persistence.mapping
成员函数 使用全英文描述来描述成员函数用途,第一个单词尽可能使用主动动词,第一个字母小写。
openFile() 和 addAccount()
setter 方法成员函数 在被访问字段的名称的前面加上前缀“set”。
setFirstName()、setLastName() 和 
setWarpSpeed()

11.2    Java 注释约定

一个很好的可遵循的注释原则是:问问您自己,您如果从未见过这段代码,要在合理的时间内有效地明白这段代码,您需要哪些信息?

一般概念:

11.2.1    Java 注释类型

下表描述 Java 注释的三种类型,并给出使用建议。

注释类型 用法 示例
文档 在接口、类、成员函数和字段声明之前紧靠它们的位置用文档注释进行注明。文档注释由 javadoc 处理,为一个类生成外部注释,如下所示。 /**
客户:客户是我们将服务和产品销售给的人或机构。
@author S.W. Ambler
*/
C 语言风格 使用 C 语言风格的注释将无用的代码注释掉。保留这些代码是因为用户可能改变想法,或者您只是想在调试中暂时不执行这些代码。 /*
这部分代码因为已被它之前的代码取代,所以已由 B.Gustafsson 于 1999 年 6 月 4 日注释掉。如果两年之后仍未用这些代码,将其删除。
. . . (源代码)
*/
单行 在成员函数内部使用单行注释对业务逻辑、代码段和临时变量声明进行注明。 // 按照 1995 年 2 月开始的
// Sarek 回赠促销活动,
// 给所有超过 $1000 的发票
// 提供 5% 的折扣。

11.2.2    注明的内容

下表总结了所写 Java 代码的每个部分哪些需要加以注明。

注明的内容
自变量/
参数
参数的类型

参数应当用来做什么

任何限定或前置条件

示例


字段/属性
字段/属性描述

注明所有适用的不变量

示例

并行问题

可视性决定

类的目的

已知错误

类的开发和维护历史

注明适用的不变量

并行策略

编译单元 每个类或类中定义的接口,包含简要描述

文件名和/或标识信息

版权信息

getter 方法成员函数 如果适用,注明为什么使用延迟初始化
接口 目的

应如何使用它以及不应如何使用它

局部变量 用处或目的
成员函数:注释 成员函数做什么以及为什么这么做

什么必须作为参数传递给成员函数

成员函数返回什么

已知错误

任何由某个成员函数抛出的异常

可视性决定

成员函数如何更改对象

包含有关所有代码修改的历史

如何调用成员函数(如果适用)的示例

适用的前置条件和后续条件

成员函数:内部注释 控制结构

代码做什么以及为什么这样做

局部变量

困难或复杂的代码

处理顺序

程序包 程序包的基本原理

程序包中的类

11.3    Java 编码约定(常规)

有很多关于 Java 代码可维护性和可改进性的重要约定和标准。99.9% 的时间里,面向他人(您的开发同事)编程要比面向机器编程重要得多。使您的代码为别人所理解是最重要的。

约定目标 约定
存取成员函数 考虑对数据库中的字段使用延迟初始化

使用存取函数获取和修改所有字段

对“常量”使用存取函数

对于集合,添加成员函数来插入和除去项

尽可能将存取函数设为保护,而不是公共

字段 字段始终应声明为私有

不要直接访问字段,应使用存取成员函数

不要使用最终静态字段(常量),应使用存取成员函数

不要隐藏名称

始终初始化静态字段

最小化公共和保护接口

在开始写代码之前定义类的公共接口

按以下顺序声明类的字段和成员函数:

  • 构造函数
  • finalize()
  • 公共成员函数
  • 受保护的成员函数
  • 私有成员函数
  • 私有字段
局部变量 不要隐藏名称

每行代码只声明一个局部变量

用一处行内注释注明局部变量

紧接在使用局部变量之前声明这些变量

使用局部变量只执行一个操作

成员函数 注明代码

给代码分段

使用空白区域,控制结构之前用一个空行,成员函数声明之前用两个空行

成员函数应能在不到 30 秒内让人理解

编写简短的单命令行

尽可能地限制成员函数的可视性

执行操作的顺序


12    参考

参考代码 参考信息
[AMB98] Ambler, S.W. (1998). Building Object Applications That Work: Your Step-By-Step Handbook for Developing Robust Systems with Object Technology. New York: SIGS Books/Cambridge University Press.
[COA97] Coad, P. and Mayfield, M. (1997). Java Design: Building Better Apps & Applets. Upper Saddle River, NJ: Prentice Hall Inc.
[DES97] DeSoto, A. (1997). Using the Beans Development Kit 1.0 February 1997: A Tutorial. Sun Microsystems.
[GOS96] Gosling, J., Joy, B., Steele, G. (1996). The Java Language Specification. Reading, MA: Addison Wesley Longman Inc.
[GRA97] Grand, M. (1997). Java Language Reference. Sebastopol, CA: O. Reilly & Associates, Inc.
[KAN97] Kanerva, J. (1997). The Java FAQ. Reading, MA: Addison Wesley Longman Inc.
[KOE97] Koenig, A. (1997). The Importance--and Hazards--of Performance Measurement. New York: SIGS Publications, Journal of Object-Oriented Programming, January, 1997, 9(8), pp. 58-60.
[LAF97] Laffra, C. (1997). Advanced Java: Idioms, Pitfalls, Styles and Programming Tips. Upper Saddle River, NJ: Prentice Hall Inc.
[LEA97] Lea, D. (1997). Concurrent Programming in Java: Design Principles and Patterns. Reading, MA: Addison Wesley Longman Inc.
[MCO93] McConnell, S. (1993). Code Complete: A Practical Handbook of Software Construction. Redmond, WA: Microsoft Press.
[MEY88] Meyer, B. (1988). Object-Oriented Software Construction. Upper Saddle River, NJ: Prentice Hall Inc.
[NAG95] Nagler, J. (1995). Coding Style and Good Computing Practices. http://wizard.ucr.edu/~nagler/coding_style.html
[SUN96] Sun Microsystems (1996). javadoc - The Java API Documentation Generator. Sun Microsystems.
[SUN97] Sun Microsystems (1997). 100% Pure Java Cookbook for Java Developers: Rules and Hints for Maximizing the Portability of Java Programs. Sun Microsystems.
[VIS96] Vision 2000 CCS Package and Application Team (1996). Coding Standards for C, C++, and Java. http://v2ma09.gsfc.nasa.gov/coding_standards.html

13    词汇表

100% 纯粹(100% pure):Sun 公司的一个“许可标志”,指出某个 Java applet、应用程序或程序包可在任何支持 Java 虚拟机的平台上运行。

存取函数(Accessor):修改或返回字段值的成员函数。也称为存取修饰符。请参阅 getter 方法setter 方法

分析模式(Analysis pattern):描述如何解决业务或领域问题的建模模式。

反模式(Antipattern):一种解决常见问题的方法,可从时间上证实是否错误或效率很低。

自变量(Argument):请参阅参数

BDK:Beans 开发工具

块(Block):括在花括号中的零个或多个语句的集合。

花括号(Brace):即字符 {},分别称为左括号和右括号,用来定义一个块的开始和结束。

类(Class):从中实例化对象的定义或模板。

类测试(Class testing):确保类及其实例(对象)如定义那样工作的行为。

CMVC:配置管理和版本控制

编译单元(Compilation unit):磁盘上的物理源代码文件或者存储在数据库中的“虚拟”源代码文件,类和接口都在这个文件中声明。

组件(Component):诸如列表、按钮或窗口这样的界面窗口小部件。

常量 getter 方法(Constant getter):一个 getter 方法成员函数,它返回“常量”值,然后在必要时可能对该值进行硬编码或执行计算。

构造函数(Constructor):在对象创建后执行任何必要的初始化的成员函数。

包含(Containment):一个对象,它包含一起协作完成自身行为的其他对象。这可以通过使用内部类(JDK 1.1+)或对象中其他类的实例聚集(JDK 1.0+)来实现。

CPU:中央处理器

C 语言风格的注释(C-style comment):一种 Java 注释格式,使用 /* 和 */,取自 C/C++ 语言,它可用于创建多行注释。通常用来“注释掉”测试中不需要或不想要的代码行。

设计模式(Design pattern):描述如何解决某个设计问题的建模模式。

析构函数(Destructor):一个用来当一个对象不再需要时从内存中除去该对象的 C++ 类成员函数。因为 Java 管理自身的内存,所以这种成员函数是不需要的。但 Java 支持一个概念上类似的、名为 finalize() 的成员函数。

文档注释(Documentation comment):一种 Java 注释格式,使用 /** 和 */,可由 javadoc 处理,来给类文件提供外部注释。对接口、类、成员函数和字段的主要注释应当使用文档注释编写。

字段(Field):描述类或类实例的变量,它是一种文字数据类型或另一个对象。实例字段描述对象(实例),而静态字段描述类。字段也称为字段变量和属性。

finalize():在对象从内存中除去之前的垃圾回收过程中被自动调用的成员函数。这个成员函数的目的是执行所有必要的清除工作,例如关闭打开的文件。

垃圾回收(Garbage collection):不再使用的对象被自动地从内存中除去的自动内存管理。

getter 方法(Getter):一种返回字段值的存取成员函数。getter 方法可用来回答常量值;这种方法经常比将常量作为静态字段实施要好,因为它更加灵活。

HTML:超文本标记语言,这是一种用于创建 Web 页面的行业标准格式。

缩进(Indenting):请参阅分段

行内注释(Inline comment):用一行紧接在同一行代码之后来注明这行源代码的行注释方法。虽然也可采用 C 语言风格的注释,但此种情况下,更经常使用单行注释。

接口(Interface):包含成员函数和字段的公共特征符定义,实施接口的类必须支持该公共特征符。通过组合,提高了接口的多态性。

I/O:输入/输出

不变量(Invariant):一组有关实例或类的断言,这些断言在所有“稳定”时间都必须为“真”,例如在对象或类上调用某个成员函数之前以及之后的时间段。

Java:一种业界标准的面向对象的开发语言,它非常适合于开发因特网应用程序以及必须在多种计算平台上运行的应用程序。

javadoc:包含在 JDK 中的一个实用程序,用于处理 Java 源代码文件并生成一个 HTML 格式的外部文档,该文档基于代码文件中的文档注释描述源代码文件的内容。

JDK:Java Development Kit

延迟初始化(Lazy initialization):一种在第一次需要某个字段时在相应的 getter 方法成员函数中初始化该字段的技术。延迟初始化用于当一个字段并非经常使用时,以及字段需要大量的内存来存储或者需要从永久存储器中读出时。

局部变量(Local variable):在某个块(通常是某个成员函数)的范围中定义的变量。一个局部变量的作用范围是定义它的块。

成员函数(Member function):与某个类或某个类实例关联的可执行代码片段。将成员函数看作某个函数的面向对象的等价物。

成员函数特征符(Member function signature):请参阅特征符

方法测试(Method testing):确保成员函数如定义那样工作的行为。

名称隐藏(Name hiding):这指给某个字段、变量或参数使用与另一个更大范围中的同类相同(至少相似)的名称的做法。名称隐藏最常见的滥用方式是给局部变量取与实例字段相同的名称。应避免名称隐藏,因为它使您的代码更难理解并且容易出错。

超负荷(Overload):成员函数超负荷是指在一个类(或者子类)中成员函数被定义多次,而每次定义的唯一区别只是特征符的不同。

覆盖(Override):成员函数覆盖是指它在一个子类中被重新定义,其特征符与原先定义相同。

程序包(Package):相关类的集合。

分段(Paragraphing):一种在代码块的范围中让代码缩进一个单位,使其与块外的代码区别开来的技术,通常是缩进一个水平制表符的长度。分段有助于提高代码的可读性。

参数(Parameter):传递给成员函数的参数。参数可以是一种已定义的类型(例如字符串或整型),也可以是对象。

后续条件(postcondition):在成员函数运行结束后将为“真”的属性或断言。

前置条件(precondition):让成员函数可以正确运行的约束条件。

属性(Property):请参阅字段

setter 方法(Setter):设置字段值的存取成员函数。

特征符(Signature):参数类型的组合(如果有)以及它们必须传给成员函数的顺序。它也称为成员函数特征符。

单行注释(Single-line comment):一种 Java 注释格式,使用 //,取自 C/C++ 语言,它常用于业务逻辑的内部成员函数注释。

标记(Tag):用于标注文档注释指定部分的一种约定,文档注释将由 javadoc 处理,来生成具有专业化外观的注释。举例来说,标记有 @see@author

测试装置(Test harness):用于测试代码的成员函数集合。

UML: 统一建模语言,是一种业界标准的建模表示法。

可视性(Visibility):一种用来指示类、成员函数或字段的封装程度的技术。关键字 public、protected 和 private 可用来定义可视性。

空白区域(Whitespace):为提高代码可读性而在代码中添加的空行、空格和制表符。

窗口小部件(Widget):请参阅组件