07_project_03_java_code_style

第六十五章 Java编程规范(Java Programming Style)

编程规范(Programming Style),有时又称为Code Style,是一套“约定俗成”的编程规则。这套规则可由开发人员自行制定、或者由公司、开发团队制定。编程规范并不能提高或者改善程序运行的性能(Performance),但是,使用编程规范能够帮助提高代码的可读性(Readability),改善团队内部的交流与沟通、提高代码质量、避免常见的编码错误。因此,在参与大型项目或者团队开发时,学习和使用编程规范非常重要。

判断一套编程规范是否为一套好的编程规范是一个主观问题。开发人员或者团队可能有着不同的观点和看法。在制定一套编程规范时,开发人员可根据自身喜好和业界习惯来确立规则。一套编程规范大致会包括以下几类规则:

  1. 源代码布局(Layout of Source Codes),例如:各个部分在源文件中出现的顺序,源代码缩进(Indentation)、空格符(White Space)的使用等。
  2. 大小写规则(Capitalization):例如:如何使用大小写字符(Uppercase/Lowercase Characters)来命名类名称、变量名称等用户定义的名字。
  3. 命名规则(Naming Convention):例如:如何为类(Class)、方法(Method)/函数(Function)构造一个有意义的名字等;
  4. 其他:例如:如何使用注释、代码对齐(Alignment)等。

Java作为最流行的一种编程语言有着众多编程规范版本。目前,较为流行且适用于通用Java工程的一套编程规范为Google Java Style Guide。所以,我们在下面的内容中,简要的介绍一下Google Java Style中重要且常用的规则。如果读者对其感兴趣的话,我们建议阅读Google Java Style Guide的原文。如果读者对其他类似的Java编程规范感兴趣,或者希望做一些横向比较的话,还可以参考Sun Code Conventions

1. 源代码文件基础规则

规则1:源代码文件名应与文件中包含的最外层类(Top-Level Class)的名字相同,后缀名为.java。文件名和类名大小写敏感(Case Sensitive)。

简单的说,如果开发人员开发了一个新文件,定义了公有类Student,那么,这个文件的名称应为Student.java。

规则2:在源代码文件中,除了换行符以外,开发人员应使用空白字符(ASCII值为0x20)作为空白符。源代码文件中不应使用制表符(\t)。

这一规则大多应用于源文件的排版和缩进。开发人员应使用空格,而不是使用制表符或者其他空白字符。因为,在不同的编程环境下,制表符的空格距离是不同的。例如:如果使用制表符缩进的话,很可能会出现源代码在一个环境下是竖向对齐的,但是在另一个环境下却不对齐。为了避免此类情况,本规则建议全部使用空白符。

规则3:转移字符应使用它们的易读格式,而不应使用它们的8进制格式。

例如:制表符应使用\t,而不应使用八进制\011或者unicode格式\u0009。

public class Example {
  public static void main(String[] args) {
    System.out.println("Name\tAge\tGrade");
	System.out.println("Name\011Age\011Grade");  // 不推荐这种方法
	System.out.println("Name\u0009Age\u0009Grade"); // 不推荐这种方法
  }
}

2. 源代码文件结构规则

规则4:一个源代码文件应依次包含以下内容:(a) 版权(Copyright)或者许可证(License)信息;(b) package语句;(c) import语句;(d) 主类的定义。它们之间由一个空行分隔。 如下面的代码示例所示。

/* 
 * Copyright info
 */

package com.littlewaterdrop.bean;

import java.util.List;

public class Student {
}

规则5:包语句(Package Statement)不换行。行长度的规则不适用于包语句。

简单的说,无论包名有多长,包语句应在一行内完成。

package com.littlewaterdrop.could.be.a.very.long.package.name;

规则6:在import语句中,不建议使用通配符导入(Wildcard import)。导入语句不换行,行长度不适用于导入语句。

这条规则明确指出不建议在import语句中使用通配符"*"。开发人员应明确指出所需导入的类,如下面的代码示例所示。

import java.util.List;
import java.util.*;  // 不推荐这种方法

目前,是否在import语句中使用通配符"*"仍然是一个"争论"的问题,仁者见仁,智者见智。只不过,Google Java Style选择了禁用通配符导入的方法。其实,这两种导入方法各有利弊,小水滴将其总结如下,供读者思考,开发人员可视自身情况选择。

首先,在讲解它们的区别之前,小水滴需要指出的是:是否使用通配符导入不会影响程序运行的性能,即对run-time没有任何影响,编译出的.class文件并不包含通配符"*"信息。读者可阅读.class文件结构文章查看.class文件中的内容。

使用通配符导入具有如下优点:

  1. 使用通配符导入的是包中所有的类。因此,人们常常称"通配符导入包"。
  2. 如果在源代码中需要使用同一个包中的多个类的话,使用通配符导入能避免冗长的导入语句。Robert Martin在他的书中也建议使用通配符导入以避免冗长的导入语句。

Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, 1st edition, Pearson, 2008. ISBN: 9780132350884, ISBN-13: 978-0132350884。 中文版:Robert C. Martin, 代码整洁之道, 人民邮电出版社, 2010. ISBN: 9787115216878。

使用通配符导入也有许多缺点:

  1. 因为使用通配符将导入包中所有的类,增加了源代码编译的工作量。因为,在编译时,编译器会在CLASSPATH中查找目标类。导入的类越多;查询时间越长,因此,编译时间也会随着变长。
  2. 使用具体类名导入能够明确的指出该源代码文件中所依赖的类,使得代码依赖关系更加明确。而使用通配符导入则弱化了这一点,使得源代码变为依赖于一个包。
  3. 导入的类名会污染名字空间。如果恰巧在源代码中使用了相同的类名,编译器会报错。
  4. 当项目所依赖的代码库添加的新类,而新类又恰巧与工程中的类名相同时,会导致工程编译失败。这意味着当升级依赖代码库时,可能会导致项目代码无法正常工作。小水滴认为这一点是Google Java Style不推荐使用通配符导入的重要原因之一。

3. 源代码格式

规则7:建议在if、else、for、do和while语句中使用括号,即使该语句没有语句体或者语句体只包含一条语句。建议使用Kernighan&Ritchie格式。

这条规则是指在任何情况下,开发人员都需要使用括号,即使语句体中只包含一条语句。因为这样能够明确展示分支的起始处和结束处。左花括号,常称为开括号(open curly bracket),应与分支语句在同一行,并且在左花括号之后立即换行。右花括号,常称为闭括号(close curly bracket),应出现在新的一行中。如果右花括号后面紧随着的是else分支或者逗号,则后面的内容可以和右括号出现在同一行中。否则,右花括号之后应紧接着是换行符,新起一行。

while (condition) {
  execute();
}

规则8:缩进时,使用2个空格。

开发人员常常使用2个空格缩进或者4个空格缩进。我们已在规则2中介绍过不建议使用制表符"\t"。2个空格缩进或者4个空格缩进并没有很大的区别。

规则9:每行一条语句。

在源代码中,每一行最多使用一条语句。这样做的原因是增强程序的可读性,避免阅读时漏读语句。

规则10:单行的长度不应长于80字符或者100字符。

单行语句的长度不建议超过80个字符或者100个字符,因为,当语句过长时,编辑器可能会将语句转行显示,增加了阅读代码的难度。

规则11:一行只定义一个变量;只在需要时才定义变量。

这条规则与规则9相似,为了增强程序的可读性,不建议在一行中定义多个变量。

int m = 0;
int n = 0;
int i = 0, j = 0;  // 不推荐这种方法

规则12:在switch语句中,每个条件分支的标签独占一行;分支体缩进2个空格;如果在分支体内需要fall-through,则应给出注释解释其行为。每条switch语句都需要default分支块。

switch语句是一条非常特殊的语句。在某些情况下,它可能非常长(可能长达上千行)。所以,我们可以想象,在阅读switch语句是需要非常仔细。本条规则的目的是为了帮助开发人员更好的阅读和理解switch语句。注释"fall-through"能明确指出代码逻辑的走向;提供default分支是为了提醒开发人员处理所有可能的情况。

switch (input) {
  case 1:
    // fall through
  case 2:
    handleCaseOneAndTwo();
    // fall through
  case 3:
    handleCaseOneTwoOrThree();
    break;
  default:
    handleDefaultCase(input);
}

规则13:在类上使用的标注(Annotation)应独占一行,出现在文档块(Documentation Block)之后,类定义之前。

本规则是指如果类定义有头注释(Header Comments)的话,标注应放置在注释头之后,类定义之前。每个标注分别独占一行。

/**
 * @Author: Adam
 * @Description: This class is used for demonstrating the usage of annotations.
 * @Date: 2020-09-16
 */
@ToString
@AllArgsConstructor 
public class Student {
}

规则14:使用在成员方法或者成员变量上的标注可以一起放在一行内,出现在文档块(Documentation Block)之后,成员定义之前。

本条规则是指当成员变量或者成员函数设有头注释的话,标注应放在注释之后,成员定义之前。标注可以与成员定义放置在同一行。

@ToString
@AllArgsConstructor 
public class Student {
  /**
   * This is added by the feature that records students' names.
   */
  @Getter @Setter private String name = null;
  
  /**
   * This is added by the feature that records students' age.
   */
  @Getter @Setter private Integer age = null;
}

规则15:Modifier应按照以下顺序出现,这也是Java标准文档(Java Language Specification)推荐的顺序:public protected private abstract default static final transient volatile synchronized native strictfp。

4. 命名规则

规则16:包名应全部使用小写字母。当使用多个单词时,应直接将多个单词连接在一起。 例如:

package com.littlewaterdrop.tool;

规则17:类名应使用首字母大写的驼峰命名方式,即:每个单词的首字母大写,其他字母小写。类名称(Class Name)应使用名词(Nouns);接口名称(Interface Name)应使用名词(Nouns)或者形容词(Adjectives)。

驼峰式命名方式来源于Larry Wall等人所著的Programming Perl一书。其意思是当变量名称或者函数名称由多个单词组成时,如果将单词首字母大写,其他字母小写的话,这些单词会呈现出驼峰那样的高低起伏的形状。因此,这种命名方式被称为驼峰式命名。

如果将所有单词的首字母大写的话,这种方法被称为大驼峰法(Upper Camel Case),例如:StudentBean。如果将第一个单词的首字母小写,其他单词的首字母大写的话,这种方法被称为小驼峰法(Lower Camel Case),例如:myCourseSchedule。通常情况下,类名使用大驼峰命名法。

一般来讲,类用来表达一个事物,因此,它的名字通常为一个名词或者名词词组,例如:Student,ResearchGroup等。接口名称一般用来表示一个事物的共同属性或者能够从事某项行为的能力。因此,接口名称通常为一个名词或者名词词组、或者形容词、形容词词组。

public class Car {
}

public interface Drawable {
}

规则18:成员方法名称应使用首字母小写的小驼峰法命名,即:第一个单词全部小写,随后的单词首字母大写,其他字母小写。成员方法名称应使用动词(Verbs)或者动词词组(Verb phrases)。

成员方法建议使用小驼峰的命名方式。因为,成员方法表达的是一个行为动作。因此,成员方法的名称建议使用动词或者动词词组。

public class Car {
  public void paint(Color color) {
    // 将小汽车漆成指定的颜色。
  }
}

public interface Drawable {
  public void draw(Graphics2D graphics) {
    // 将对象画在一个二位的图形上。
  }
}

规则19:常量名称中的单词应全部大写,单词之间使用下划线连接。

为了与其他类型的名称区别开,常量并未使用驼峰命名法。常量名称建议全部使用大写,单词之间使用下划线连接,例如:FINAL_VARIABLE。当开发人员看到此种名称时,就能清晰的了解这个变量是常量,其值不可改变。

规则20:局部变量、函数入参应使用小写的驼峰命名方式。变量名称应使用名词(Nouns)或者名词词组(Noun Phrases)。

局部变量(Local Variables)或者函数参数建议使用小驼峰法命名。局部变量和函数参数又被称为临时变量,因为它们都是临时存在的。通常情况下,它们的生命周期很短。因为临时变量往往表达的是一个对象或者事物,建议由名词或者名词词组命名。

规则21:类型变量名称可使用以下两种方法中的其中一种。第一种方法:使用一个大写字母,例如: T、V、E等。第二种方法:使用类名加上一个大写字母,例如:RequestT、MessageT等

Java语言的类型变量(Type Parameter)是一个例外。从使用数量和使用场景上看,类型变量的使用比局部变量、函数参数的使用少得多。而且,在泛型编程(Generic Programming)的书籍和学习材料中,大多数用例都是使用T或者E来命名类型变量的。在Java标准文档中,常见的类型变量名称为E、T、V、R等。所以,建议开发人员也使用相似的规则命名类型变量。

5. 编程实践

规则22:可能时,尽量使用标注@Override。

尽量使用标注@Override是一个好的习惯。因为,如果开发人员出现手误,在覆盖(override)基类的方法时拼错了方法名称或者参数类型,Java编译器能够帮助开发人员检查出来。因为,如果函数名错误或者参数类型错误的话,这个方法会成为一个新方法,它无法覆盖基类中定义的方法。因此,在此情况下编译器会认为不应使用标注@Override而报错,提醒开发人员。

规则23:应捕捉并处理每一个异常分支。

捕捉和处理每一个异常分支是开发过程中的一个基本准则。也许开发人员认为不会发生的情况,在实际运行中是可能发生的。因此,一个好的方法是处理一切可能发生的场景。

try {
  Runtime.exec("/usr/bin/bash");
} catch (SecurityException ex) {
  handleSecurityException(ex);
} catch (IOException ex) {
  handleIOException(ex);
} catch (NullPointerException ex) {
  // it looks impossible, just continue    // 这是不推荐的行为
} catch (IllegalArgumentException ex) {
  handleIllegalArgumentException(ex);
}

规则24:当使用静态成员时,应使用类名来访问该静态成员,而不应使用对象引用或者表达式来引用该静态成员。

严格上来说,静态成员变量和静态成员方法是"绑定"在类上的,而不是对象上的。因此,使用类名来引用静态成员。

import java.util.List;
import java.util.Arrays;

public class Vehicle {
  public static List<String> getAllBrands() {
    return Arrays.asList("Ford", "Honda", "Mercedes-Benz");
  }
  
  public static void main(String[] args) {
    List<String> brands = Vehicle.getAllBrands();
	
	Vehicle car = new Vehicle();
	List<String> brandsOfCars = car.getAllBrands(); // 不推荐这种方式
  }
}

规则25:尽量不要使用并覆盖Object.finalize()方法。

本规则不建议使用finalize()方法是因为该方法容易被误用。其详细内容可参考对象释放过程(Object Finalization)

6. 小结

本章简要的介绍了编程规范的用途与意义,并且依次讲解了Google Java编程规范中重要的、常用的规则。我们将在下一章介绍如何使用工具Checkstyle来检查代码是否遵守了编码规则。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.