5. 代码风格与文档:引言¶
5.1. 概述与目标¶
完成本模块的学习后,学生将能够:
描述遵循风格指南以及软件开发标准和约定的软件工程好处
应用命名、格式和注释约定及最佳实践
按照现行标准和约定格式化代码并进行缩进
编写格式正确的 JavaDoc 注释
准备有效的内部文档/注释
评估所写代码中内部文档的质量
5.2. 建议阅读:¶
附录 A(文档与编程风格) 来自 Data Structures and Abstractions with Java, 4th edition by Frank M. Carrano and Timothy Henry
5.3. 代码风格与文档简介¶
关于风格的一点说明 构建软件是一个复杂且通常需要协作完成的过程,它涉及:
多种编程语言、环境、库和相关技术
众多有着各自独特需求和交互方式的相关方(stakeholders)
众多开发者以及开发和项目团队
各种各样的开发者技能组合,以及开发者个人各异的编码"风格"、实践和偏好
软件开发过程与许多其他复杂工作一样,通常遵循一个生命周期。虽然存在各种生命周期方案,但多数通常都包含规划、需求收集与分析、设计、开发/实现、测试、部署与集成以及维护等阶段。
开发者使用的编程语言、环境、库和技术也在不断演进。函数、功能和做法会逐渐过时或"废弃"(deprecated),最终被更新、更受青睐的方案所取代。
开发者编写代码后,将其交给他人进行审查、审批,并迁移(部署)到"生产环境",或与其他代码集成。大多数情况下,最终产出的软件都会进入软件开发生命周期中的"维护"阶段。一旦进入该阶段,它可能还要经历后续的代码审查、日常维护和修改。
"几乎没有软件能由原作者维护其整个生命周期。" - Oracle 1996
5.4. 挑战¶
这些纷繁复杂的情况以多种方式影响着软件开发:当这些复杂性加上软件相关方不断变化的需求时,就形成了一种环境,在此环境中,开发者能否快速适应变得至关重要。
这种"适应"可能意味着开发者需要学习新的编程范式、语言和库,并培养出审查和理解他人编写的代码(或很久以前自己编写的代码)的能力。
这引发了诸多问题,包括:
如何帮助开发者以尽可能少的精力和压力采纳并使用新的编程语言、方法和技术?
在应对崭新或似曾相识的代码时,可以采取哪些措施来促进开发者的适应?
可以提供哪些支持来帮助开发者快速、有效地理解新代码?
我们通过使用风格指南和软件文档来解决上述许多问题。
5.5. 风格指南¶
风格指南本质上是一套标准、约定、指导原则和实践做法,开发者在编写代码、格式化代码和编写软件解决方案文档时应当遵循它们。此类指南通常还服务于软件开发的其它方面,包括软件设计和测试。
遵循风格指南和代码约定可以提高代码的可读性和可维护性。
需要指出的是,并不存在唯一的一套标准、约定和指导原则,相反,你很可能会遇到一个层次化的体系。例如,某种编程语言的创作者会提供文档来说明标准、约定和一般用法。使用该语言(或遵循该语言所承载的编程范式)的开发者社区可能会采纳并传播额外的标准和约定。此外,在某个组织、团队或项目内部,还可能有其他的具体标准、约定和指导原则,叠加在层级体系中更上层的标准之上。
要点
开发者需要快速有效地理解新的(或久已被遗忘的)代码和新技术
风格指南有助于完成这一任务
开发者应当养成理解并采纳与其软件开发环境相关的风格指南所传达的标准、约定和实践做法的习惯
5.6. 命名:名字里有什么¶
名字传达了相当多的信息。按照开发者的本意来理解它们,可以加快软件开发任务的进度;而偏离本意地理解则会拖慢进度。糟糕或不一致的命名会让开发者难以阅读和理解代码,常常导致开发时间变长、软件缺陷(bug)、集成问题甚至系统崩溃。
请记住,你或你的同事将来很可能需要重新审视先前发布的代码。只要可能,你都会希望为负责这些未来任务的开发者提供一些帮助,让其更快地适应,尤其是那个人很可能就是你自己!
名字有助于这一过程:一个名字可以让人一眼看出某个软件组件的意图或用途,并区分它是包、类、方法、字段、变量还是常量。
恰当且一致的命名可以帮助开发者快速对软件内部的工作方式形成心理图景或模型,从而更有效地理解各个软件组件、它们的角色和用途、预期的交互、逻辑以及整体的执行流程。
5.7. 命名约定¶
命名时,谨慎选择名字非常重要。
名字应尽量体现并涵盖其所代表的类、方法、变量或概念。
不要选择可能误导他人的名字,使对方对方法的功能或变量的用途产生错误理解。例如,如果你有一个类型为 int 的变量并把它命名为 counter,你不会希望它保存一个随机数或数值剧烈变化的数字。
对于 counter 这样的名字,审查代码的开发者会期望它的值逐步递增,就像在数数组的下标一样。
要点
命名应当让人立刻明白你的类、方法、字段、变量等的用途。
5.8. 基本命名规则¶
命名标识符(变量、类、方法等的名称)时,可以使用大写字母 { A-Z }、小写字母 { a-z }、数字 { 0-9 } 和下划线 { _ }。
标识符应始终以字母开头。
标识符不能与语言关键字相同,例如 final、class、public 都是 Java 保留字(也称为 Java 语言关键字)。它们不能用作标识符。
public void rotate90Degrees()
通常不使用下划线来分隔单词(首选 camelCase 驼峰式写法)。不过,在分隔全大写变量(保留给常量变量使用)时,会使用下划线。
public static final int STUDENT_ID = 1234567;
名字必须能说明其用途。例如,假设我们想把 2015 年存储为一个整数。把它存放在变量 ‘x’ 中并不是一个好主意,因为如果以后需要用到它,就不清楚 ‘x’ 代表什么。也就是说,
int x = 2015;
应该改成:
int year = 2015;
名称不能使用 Java 中的关键字/保留字。完整的保留字列表请参见 https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html 。例如:创建对象时使用关键字 new。但是,下面的两个示例都会导致语法错误,因为 new 是保留关键字:
要点
名字可以由字母和数字组成,并且应当有意义,但不能使用 Java 保留字。
5.9. 源文件与目录¶
源文件名应与类名加上 .java 扩展名一致。如果我们有一个名为 Student 的类,那么对应的源文件应该是 Student.java。请记住,Java 区分大小写。
与类名一样,源文件名中不应包含空格。
5.10. 包¶
包名用点/句点 '.' 分隔。从左往右读,靠左的包名包含右侧依次出现的包名(即包名逐层嵌套)。包名通常全部使用小写字母。
你常会看到一个例子是:import java.util.ArrayList。类 ArrayList 位于包 java.util 中。通常使用域名(URL)作为包名,但网址会被反过来写。
例如:
com.mywebsite.myapp 表示来自 mywebsite.com 的名为 myapp 的包。
5.11. 类¶
类名在大多数情况下应该是 名词 。名字应当简洁且具有足够的描述性,能够充分体现其所指代的事物或概念。
类名不含任何空格,并且每个单词的首字母都大写(UpperCamelCase)。与其他标识符不同,类名的首字母尤其要大写。
示例:
HelloWorld
AddIntegers
Employee
Game
Player
5.12. 接口¶
接口名应遵循与类名相同的规则。它们应与类名一样具有足够的描述性,并像类名一样首字母大写。某些软件开发环境会借助名称来区分接口与其他类。本课程就采用这种做法。例如,如果我们要为 Bag 数据结构定义接口,就会使用 BagInterface 这个名字。
5.13. 方法¶
方法通常以描述某个对象行为或功能的 动词 来命名。
方法名以小写字母开头,不含空格,每个单词(第一个除外)的首字母都大写(lowerCamelCase)。
方法名与其参数括号之间没有空格。
示例:
5.14. 变量¶
变量名的规则与方法名类似。变量名以小写字母开头,不含空格,每个单词(第一个除外)的首字母都大写。
示例:
result
studentName
totalCost
5.15. 常量¶
常量名应全部使用大写字母。当由多个单词组成时,应用下划线分隔。
示例:
MAX
DEFAULT_WIDTH
TAX_RATE
CONVERSION_RATE
5.16. 命名要诀与禁忌¶
camelCase:可以!
所有标识符都采用 camelCase。类和接口名的首字母大写,变量和方法名的首字母小写。
示例:
public class HelloWorld
public interface Employee
public double calculateGPA()
int year = 2015;
匈牙利命名法:不行!
匈牙利命名法是在变量前加上一个表示其类型的前缀。在 Java 开发中,它并不是首选风格。虽然过去在某些开发环境中被广泛使用,但在当今许多开发场景中已不常用。
示例:
int iYear = 2015; // This should be year, not iYear!
5.17. 命名小结¶
标识符类型 |
命名规则 |
示例(加粗) |
|---|---|---|
package(包) |
全部小写 |
util |
class(类) |
以大写字母开头,之后的每个单词也以大写字母开头 |
ArrayList |
methods(方法) |
遵循 lowerCamelCase 约定 |
myMethodName() |
variables(变量) |
遵循 lowerCamelCase 约定 |
myVariableName |
constants(常量) |
全部大写,多个单词须用 ‘_’ 分隔 |
static final int MIN_WIDTH = 4 |
interface(接口) |
像类名一样首字母大写 |
interface Storing |
5.18. 代码审查¶
跟随视频,动手练习与探索
- 完成下面描述的任务,然后观看命名视频。下载视频中的 java 文件,到你自己的 Eclipse 中运行和探索。你可以下载本示例的独立 *.java 文件。要运行独立的 *.java 文件,你需要:
新建一个 Eclipse 项目,然后
在项目中创建一个名为 "example" 的包(类顶部声明的包名必须与文件在 Eclipse 项目中所在的包一致),最后
将独立的 *.java 文件下载并导入到已创建的包中。
编写符合规范和约定的代码是一项宝贵的技能,它能极大地促进你作为开发者的成功,以及你与其他开发者良好协作的能力。每位开发者都需要学会如何审查和评估自己编写的代码以及他人编写的代码,以确保其符合质量标准,并找出可以改进的地方。
在本活动中,你将扮演一名初级开发者(Jr. developer),负责审查另一位开发者编写的代码。
审查 record.java 中的代码
回顾之前讨论过的命名约定和实践
以批判的眼光审查代码,看看能否发现命名方面的问题点以及改进的机会
观看视频,看看你列出的问题点和改进机会与我们的审查中发现的是否一致
5.19. 检查点 1¶
5.20. 格式¶
关于格式的重要性
"写程序采用特定风格并不仅仅是一个审美问题。更重要的是,以传统方式编写程序有着心理学上的依据:程序员们强烈期望其他程序员会遵循这些话语规则。如果这些规则被违反,那么程序员长期以来建立起来的期望所赋予的效力实际上就被抵消了。本文中对新手、进阶学生程序员以及专业程序员所做的实验结果,为上述主张提供了明确的支持。"
-- Elliot Soloway 与 Kate Ehrlich - 编程知识的实证研究(1984)
正确且一致的格式能提高代码的可读性,使其更易于审查、理解、调试和维护。理想情况下,格式和整体布局应当清晰地传达代码的逻辑结构,从而帮助开发者建立关于代码、其行为以及执行流程(即编程语句被执行的顺序)的心理模型。
看看下面的两个示例代码片段。哪一个更容易调试?你能找出其中的错误吗?
//Example 1:
public class Employee {
private String name;
private double hourlyRate;
public Employee(String name) {
this.name = name;
}
public Employee(String name, double hourlyRate) {
this.name = name;
this.hourlyRate = hourlyRate;
}
public String toString() {
return ("I am an employee named "+name);
}
或
//Example 2:
public class Employee {
private String name;
private double hourlyRate;
public Employee(String name) {
this.name = name;
}
public Employee(String name, double hourlyRate) {
this.name = name;
this.hourlyRate = hourlyRate;
}
public String toString() {
return ("I am an employee named "+name);
}
5.21. 缩进¶
缩进体现结构和层次,能快速展示作用域以及代码块与其中所包含代码之间的关系。
通常,一个缩进为 4 个空格。
出于多种原因,强烈不建议使用制表符(Tab),其中很重要的一点是不同开发环境的制表符设置不同。当代码在多个团队之间共享时,这可能成为问题,最终导致缩进不一致、难以阅读的一团文本。
请注意,有些工具可以将制表符替换为空格(相关模块会进一步讨论)。
在 Java 中,花括号内的代码构成一个代码块。代码块应逐层缩进,每一层嵌套都比上一层多缩进,以更清楚地体现嵌套关系。最外层的结构完全不应缩进。
// Example 1
public class CircleCalculation {
public static final double PI = Math.PI;
public static void main(String[] args) {
double radius;
double area;
. . .
if (radius > 0) {
. . .
}
}
}
//Example 2:
public class MyExampleB {
public static void main(String[] args) {
System.out.println("start of main");
methodA();
System.out.println("end of main");
}
public static void methodA() {
for (int i = 0; i < 10; i++) {
System.out.print("hello "+i);
}
System.out.println("end of loop");
}
}
5.22. 本课程的格式设置 / 配置 Eclipse 格式化¶
在准备和提交作业时,你应确保代码格式正确:缩进规范、用空格代替制表符等。这会让你的代码在不同用户和环境之间更具可移植性。Eclipse 提供了一个格式化工具来帮你完成此事。启动该工具后,它会根据既定设置自动格式化你的代码。第一次实验课(Lab)会详细介绍该功能的设置步骤,请务必完成设置过程。
注意!
每次想要格式化代码时,你必须手动启动格式化工具。在向 Web-CAT 提交作业之前,你应该先格式化代码。
5.23. 行长¶
超过 80 个字符的行应拆成 2 行(或更多行),并在第一行下方缩进。
过长的行会影响可读性,迫使开发者在做代码审查时左右滚动。此外,有些工具对长行的处理也不太好。最好避免过长的行。
你的 IDE 可以帮助你。在 Eclipse 中:
转到 Preferences -> General -> Editors-Text Editors。勾选 "Show print margin",并在 "Print margin column" 中输入 80。
5.24. 花括号¶
在 Java 中格式化花括号时,我们遵循 Kernighan 和 Ritchie(K & R)风格,这种风格有时也被称为"埃及括号"(Egyptian brackets)。
在 K & R 风格中,左花括号应位于代码块(一组用花括号括起来的语句)开始行的末尾,即在左花括号之前不换行,而在左花括号之后要换行。
右花括号应另起一行,并缩进到与代码块首行对齐。
在示例 1 中,注意右花括号是如何与 Java 关键字 public 对齐的。
//Example 1: note how the closing brace is aligned to match the
//Java keyword public.
public class MyExampleClass {
...
}
//In Example 2, note how the `for` loop closing brace is aligned to match
//the Java keyword `for` and the closing brace for `methodA` is aligned to
//match the Java keyword `public`.
public static void methodA() {
for (int i = 0; i < 10; i++) {
System.out.print("hello "+i);
} // end of for loop
System.out.println("end of loop");
} // end of method
你可以浏览 Sun Microsystems 资源中的 6.4 节和第 7 节(https://www.oracle.com/technetwork/java/codeconventions-150003.pdf ),或 Google 资源中的第 4 节(https://google.github.io/styleguide/javaguide.html ),以了解更多细节。
虽然还有其他做法,但对于本课程中你编写的任何代码,这些是首选方案。
// Example for while loop
while (x > 5) {
x = x - 1;
}
对于循环体内只有单条语句的 if 语句和循环,最好始终使用花括号,而不是仅靠缩进。
//Example 1: This is the preferred style
if ( x > 5 ) {
x = 5;
}
// over this approach...
//Example 2:
if ( x > 5 )
x = 5; // This works the same as Example1 but it’s not good style!
5.25. 逗号及其他运算符后的空格¶
运算符( +、-、*、/ )和相等性符号( <、>、<=、=>、== )两侧都应留一个空格。
示例:
x + 3
3 / 2
x == y
m <= n
逗号右侧应有空格,左侧则没有。
示例:
graphOrderedPair(4, 6);
5.26. 空行¶
空行能提高可读性,尤其是在组织或区分逻辑相关的代码段时。习惯上,会在方法之间、以及方法的局部变量与方法中的第一条语句之间添加空行。
5.27. 换行与续行缩进¶
跨多行的语句应进行缩进,使所有后续行都在第一行下方对齐缩进。对齐占多行的代码时也遵循这一约定。
if ( ... ) {
System.out.println("The volume of a sphere whose radius is " +
radius + "inches is " + volume +
" cubic inches.");
}
5.28. 软件文档概述¶
软件文档应包含有助于开发者阅读和理解程序的信息,并在适当的时候,为开发者提供足够的背景、上下文以及某些实现决策背后的理由,以方便未来的维护和修改。
这类背景和上下文可能记录在外部文档(程序清单之外的文档)或内部文档(程序清单之内的文档)中。
注释用于内部文档。注释应当对代码进行概述,并提供代码本身不易体现的额外信息。
作为一条规则,你应始终追求"自文档化代码"(Self-Documenting Code)。当开发者做到以下几点时,通常就能实现:
在适当位置加入简短且有描述性的注释
始终如一地遵循公认的风格指南
确保程序具有良好的逻辑结构
以直接且易于理解的方式实现代码逻辑
5.29. JavaDoc 注释¶
一个名为 JavaDoc 的工具确立了一些通用的注释约定,它可以提取代码中的信息,并利用这些信息创建头部注释和 API 文档。JavaDoc 注释出现在类、接口或方法声明之前,也出现在可见(public)字段的声明之前。所有可见(即非 private)字段都需要 JavaDoc 注释。
它们始终以 /** 开始,以 */ 结束。
JavaDoc 标签始终以 @ 开头,可以包含在 JavaDoc 注释中,用来记录任何参数、返回类型、前置条件等。javadoc 工具可以根据你的代码生成标签。所有标签都应包含一段简洁的描述。例如,如果你有 @param 标签,就应该描述该参数的作用。
与其他注释不同,JavaDoc 注释(以 /** 开头的注释)是公开的(可被外部访问)。其他注释,如 // 和 /* Comment */,则是私有的。
5.30. 描述一个类¶
类注释(javadoc 注释)以 /** 开始,以 */ 结束,中间写明类的细节/用途。注释块中的每一行都以 * 开头。开头的 /** 和结尾的 */ 应垂直对齐,注释块中的每个 * 也应垂直对齐。
类注释应始终包含以下内容:
对类的简洁描述
使用 @author 标签写明你的姓名和 PID
使用 @version 标签写明日期和/或版本。
类的注释块应出现在类声明之前、所有 import 语句之后。
类的描述通常使用两个标签:@author 表示谁编写了该文件,@version 表示该文件或项目的"版本"。你可以在 @author 标签中使用全名,也可以只使用用户名。在本课程中,将文件编写日期作为 @version 标签中的版本信息是完全可以的。
使用 @author 和 @version 等标签时,请确保将它们放在文档注释中每行的开头。
示例:
import java.util.ArrayList;
/**
* This class represents a student’s information such as GPA,
* current number of credit hours achieved, and the courses
* that the student is currently enrolled in.
*
* @author Jane Doe (jdoe)
* @version 2015.02.02
*/
public class Student {
...
}
具有泛型类型参数的类,应在类描述与 @author 标签之间使用 @param 标签列出。
示例:
/**
* This is an implementation of the Arraylist data structure using an
* array.
*
* @param <E> The type of object stored in the arraylist.
*
* @author Jane Doe (jdoe)
* @version 2015.02.02
*/
public class ArrayBasedArrayList<E> implements ArrayListInterface<E> {
...
}
5.31. 为公有字段/实例变量和静态变量编写文档¶
回顾
类的实例变量和静态变量统称为 字段 (fields)
公有字段的 JavaDoc 注释格式与类注释类似,只是内容不同。
它们以 /** 开始,以 */ 结束,中间写明字段的细节/用途。注释块中的每一行都以 * 开头。开头的 /** 和结尾的 */ 应垂直对齐,注释块中的每个 * 也应垂直对齐。
字段注释应紧跟在可见(public)字段的声明之前,并且应始终包含对字段用途的简洁描述,以及有关其使用的任何特殊信息。
示例:
/**
* Something about the purpose of the following field SALES_TAX_RATE
*/
public static final int SALES_TAX_RATE = 15;
5.32. 为方法编写文档¶
方法注释(JavaDoc 注释)的格式与类注释相同,只是里面的信息可能不同。例如,你仍然需要描述方法的功能,但不必包含 @author 和 @version 标签。不过,你可能需要其他标签(见下文)。
方法注释应包含以下内容:
对方法所实现功能的简洁描述
只要有参数,就使用 @param
只要有返回值,就使用 @return
当某个操作保证在特定条件下会抛出异常时,使用 @throws (并在方法签名中包含相应的 throws 子句)
只在真实的前置条件下使用 @precondition (再加上内部的 assert 或条件语句)。所谓前置条件,即方法绝不应该在这种条件下被调用(在这种情况下,方法的行为完全没有保证)
对于 mutator(修改器)方法,使用 @postcondition 来解释方法执行后对象所发生的状态变化。
对于可以从父类或接口继承的方法 javadoc,使用 @inheritDoc
注意:切勿让 @throws 和 @precondition 标签重叠。
切勿让 @throws 和 @precondition 标签重叠。某件事要么是前置条件(任何客户在任何情况下都不应在所述条件下调用该方法,而内部的 assert 或条件语句则作为开发/调试辅助手段来发现此类违规),要么是一种在所述条件下总会发生的保证行为(即在该条件下调用方法会有明确定义的结果,这个结果应写入 @throws 子句,并在内部用显式的 throws 语句实现)。
按照惯例,运行时/非受检异常( NullPointerException 、 ArrayIndexOutOfBoundsException 等)通常不会放在方法的 throws 子句中,而是作为前置条件的一部分;受检异常(FileNotFound、ClassNotFound 等)则放在 throws 子句中,并用 @throws 记录。不过,对前置条件(或会抛出运行时异常的情况)的记录更像是一个灰色地带。你只需要记录那些值得记录的情况,例如许多方法都可能抛出 NullPointerException ,我们不会把所有这些情况都记录下来。但也有例外,比如 IndexOutOfBoundsException 属于运行时异常,因此绝不会出现在 throws 子句中,但在它是由常见错误导致的结果时,有时也会用 @throws 标签记录。(例如 java.util.ArrayList.get(int) 或 java.lang.String.charAt(int) )本课程会给出明确的指导,并希望你的用法符合这些指导。更多信息见:https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#throwstag 。
你应该在你编写的每个方法或构造函数之前,放置一段描述性的 JavaDoc 注释:
/**
* This method calculates the student’s current cumulative GPA.
*
* @return gpa The student’s cumulative GPA.
*/
public double calculateGPA() {
...
}
5.33. Javadoc 标签¶
@author标签它标明程序员的姓名,是所有类和接口的必备项。请复习所提供的示例项目,这些项目应该可以通过 Eclipse 中的 "Project -> Download Assignment..." 获得。
@param标签方法参数应在方法的注释块中使用
@param标签进行文档化。格式为:标签,后跟所用变量名称及简短描述。参数应列在方法描述之后。如果方法有多个参数,则使用与参数数量相同的@param标签。这些标签应按参数在方法头中出现的顺序列出。务必将这些标签置于注释行的开头,并将所有同名标签归为一组(即所有 @param 标签应彼此相邻)。@return标签如果方法的返回类型不是 void,请使用 @return 标签来记录该方法返回的内容。
@return标签应出现在任何@param标签之后。@throws标签如果一个方法可能抛出受检异常,请使用
@throws标签命名 示例:/** * Calculates the slope from two points. * * @param x1 The first coordinate's x variable * @param y1 The first coordinate's y variable * @param x2 The second coordinate's x variable * @param y2 The second coordinate's y variable * * @return Returns the calculated slope value * @throws IllegalStateException if x1 < x2 */ public double findSlope(int x1. int y1, int x2, int y2) { ... }
5.34. 其他注释¶
使用内部/私有(非 JavaDoc)注释时,请确保注释的使用是高效的。如果你需要用注释来描述某个变量的用途,不妨直接修改变量名,使其更符合用途。如果你需要用注释来描述一段复杂的代码,不妨重写这些代码,让它更容易理解。有时候,没有注释比冗余的注释更好。在添加注释之前,始终先设法让代码本身更容易理解和清晰,因为一旦有了注释,读者要读的内容就更多了,而且反复阅读相同的内容也会让人厌烦。
单行注释以两个斜杠 // 开头,斜杠右侧的所有内容都是注释。单行注释有两种风格,两种都可以接受,但最好坚持使用一种,以保持一致。
注意
下面的示例并不是内部注释的良好用法。它们只是为了向你展示正确的语法和位置。请阅读上面关于使用内部注释的段落以获取解释。
第一种风格是把注释放在它所对应的那一行的行内:
public double tipCalculator(double mealCost) {
return mealCost * 1.15; //Final meal cost with 15% tip.
}
public double tipCalculator(double mealCost) {
//Final meal cost with 15% tip.
return mealCost * 1.15;
}
注释也可以以 /* 开头、以 */ 结束,当注释跨越多行时很有用:
/* This comment spans
multiple lines. */
5.34.1. 内部注释是最后不得已的文档手段¶
精心选择所有名字,让初读代码的人第一次理解就总是正确的。不要选择可能让读者对方法的功能或变量保存的信息产生误解的名字。选择糟糕的名字或绕来绕去的逻辑结构,然后试图用冗长的注释来解释,对可读性的改善微乎其微。这一点对方法来说更是如此,因为有一半时间,读者是在方法被调用的地方看到方法名,而不是在阅读方法本身的时候。如果方法应该做什么不是一目了然,那么无论你在方法里放了多少注释,都会影响所有调用该方法的代码的可读性。
努力使代码仅凭你选择的名字和使用的结构,就能自成一体地清晰易懂。
如果你觉得必须添加内部注释来解释某件事,请先问问自己什么需要解释。如果你需要解释某个名字指的是什么或打算如何使用它,请考虑换一个更好的名字。如果你不得不解释一系列复杂的 if 语句或其他绕来绕去的结构,请问问自己(或助教 TA)是否有更好的办法。只有在考虑过这些替代方案之后,才应该添加描述性注释。
5.34.2. 多余的注释比没有注释更糟¶
看看这些注释:
karel = new VPIRobot(); // Create a new robot
x = x + 1; // Add one to x
karel.move(); // move forward one step
这些都是无用注释的例子。许多学生给代码加注释只是为了"确保一切都记录在案",或者因为他们认为老师想看到的是大量注释。然而,这样的注释只会妨碍阅读代码。只有当注释表达了代码本身尚未体现的内容时,才应该添加注释。注释是可怜的读者必须费力阅读的又一段"代码",所以你需要仔细权衡它们的收益与阅读它们所需付出的代价。
5.35. 使用常量和引用值而非硬编码¶
有些时候,你可能会希望在代码中直接引用某个值。
这可能发生在以下情况:在图形用户界面(Graphic User Interface)上绘制形状时、用循环遍历数组或其他数据结构时、执行需要某个字面量或操作数的数学或业务操作时,或者引用某段数值范围的最小或最大界限时。
作为一条通用规则,你应始终权衡直接使用此类值所带来的利弊,这种做法被称为 硬编码 (hard coding,有时也写作 hard-coding 或 hardcoding)。
硬编码是一种不良实践,因为它假定这些值在软件的整个生命周期内保持不变,从而使代码缺乏灵活性,随着情况和相关方需求的变化,更新和维护都会变得困难。
例如,考虑在一个购物/电子商务(eCommerce)应用程序中实现税费计算,这要求软件在应用程序的多个类/区域中进行这些计算。
如果在每个需要计算税费的地方都硬编码税率,那么一旦税率发生变化,比如从 0.15(15%)变成 0.17(17%),你或你的开发者同事就需要通读全部代码,确保所有对 0.15(或 15/100)的引用都更新为新税率。
相对于硬编码,更可取的做法是使用 常量值 (不会改变的值)或可以被引用的值。
5.36. 常量¶
对于上面税率这个例子,更可取的做法是按如下方式创建一个字段常量:
final double TAX_RATE = 0.15;
total = subtotal * TAX_RATE
然后在计算中引用该常量。如果税率发生变化,你只需调整赋给该常量的值。
注意
如果某个常量只打算在单个类中使用,则应将其设置为 private。如果它需要在多个类之间使用,那么将它设置为 public static 可能会很有用。
5.37. 引用值¶
对于遍历数组或类似的其它任务,更可取的做法是引用一个值而不是硬编码。
因此,与其使用下面这种硬编码的方式:
int [] myArray = new int [4];
for (int i = 0; i < 4; i++ ) {
System.out.println( myArray[ i ] );
}
你应该使用下面这种更灵活的方式:
final int MAX = 4;
int [] myArray = new int [MAX];
for (int i = 0; i < myArray.length ; i++ ) {
System.out.println( myArray[ i ] );
}
或者,你也可以在循环中使用 MAX 而不是 myArray.length。
注意,使用常量和引用值能让代码更灵活、更易于维护。在循环条件中使用 myArray.length 而不是数值 4,会让代码更灵活,因为即使数组长度发生变化,这个引用值也始终与数组的真实长度一致。
编写代码时,你应始终采用当下最灵活的选择。
5.38. 类、字段和方法的访问修饰符与可见性¶
访问修饰符允许开发者指定其他类能否使用某个类的特定字段或调用其特定方法。
新手开发者常常忘记为类、字段和方法指定访问修饰符。
这是一个坏习惯,应尽量避免,因为省略访问修饰符可能会导致意外行为,破坏封装,甚至可能让外部类以非预期的方式访问字段和方法。
无论是描述软件设计,还是开发软件解决方案,你都应 始终 为所有类、字段和方法指定访问修饰符。
好的设计往往采用这样的做法:除非你明确希望外部类与某些字段和方法交互,否则一切都设置为 private。
注意
作为一条通用规则,你应将类的字段设置为 private,并按具体情况授予其他级别的访问权限。
关于访问修饰符和可见性的更多信息请参见:https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html
5.39. 审查清单¶
虽然到目前为止,作业和模块中已经多次提到风格问题,但这份清单可以帮助你在提交前审查代码时,心里始终装着风格这些事。
一般而言,我们应避免以下问题:
5.39.1. 命名¶
不符合规范约定的命名
未能充分体现并传达其所代表的概念,或其所代表类、方法、变量/概念用途的命名
过长或过短、描述性不足的命名,即糟糕的标识符(例如单个字符,或含糊的缩写、首字母、首字母缩写词)
5.39.2. 格式与缩进¶
缩进不一致/缺失
空格不足
被注释掉的代码行
代码中遗留的调试语句
一行多条指令或语句过长
5.39.3. 文档与注释¶
类描述缺失/不足
字段注释缺失/不足
方法注释缺失/不足
JavaDoc 标签缺失/不完整
args 参数说明缺失/不完整
错误/误导性的注释
多余的注释,或与代码本身相比没有更多描述性的注释
5.39.4. 其他风格要点¶
使用硬编码的值
缺失或不恰当的访问修饰符
不必要或未使用的字段/变量
5.40. 互动:代码风格与文档最终复习¶
5.41. 检查点 2¶
5.42. 相关资源¶
参考:
