Apache Log4j 代码样式指南
介绍
本文档是 Log4j 项目针对 Java™ 编程语言源代码的编码标准的完整定义。它起源于 Google 编码标准,但包含反映 Log4j 社区愿望的修改。
与其他编程样式指南一样,涵盖的问题不仅包括格式的美学问题,还包括其他类型的约定或编码标准。但是,本文档主要关注我们普遍遵循的硬性规则,并避免给出无法明确执行(无论是人工还是工具)的建议。
术语说明
在本文件中,除非另有说明
- 术语类用于包容性地表示“普通”类、枚举类、接口或注释类型(
@interface
)。 - 术语注释始终指实现注释。我们不使用“文档注释”一词,而是使用常见的术语“Javadoc”。
其他“术语说明”将偶尔出现在本文档中。
指南说明
本文档中的示例代码是非规范性的。也就是说,虽然示例是 Log4j 样式的,但它们可能并不说明表示代码的唯一样式方法。示例中做出的可选格式选择不应作为规则强制执行。
源文件基础
文件名
源文件名由它包含的顶级类的区分大小写的名称加上.java
扩展名组成。
2.2 文件编码:UTF-8
源文件以UTF-8编码。
特殊字符
空白字符
除了换行符序列之外,ASCII 水平空格字符(0x20)是源文件中任何地方出现的唯一空白字符。这意味着
- 字符串和字符文字中的所有其他空白字符都已转义。
- 制表符不用于缩进。
特殊转义序列
对于具有特殊转义序列(\b
、\t
、\n
、\f
、\r
、\"
、\'
和 \\
)的任何字符,使用该序列而不是相应的八进制(例如 \012
)或 Unicode(例如 \u000a
)转义。
非 ASCII 字符
对于剩余的非 ASCII 字符,使用实际的 Unicode 字符(例如 ∞
)或等效的 Unicode 转义(例如 \u221e
),具体取决于哪个使代码更易于阅读和理解。
提示:在 Unicode 转义的情况下,有时甚至在使用实际 Unicode 字符时,解释性注释非常有用。
示例
示例 | 讨论 |
---|---|
String unitAbbrev = "μs"; |
最佳:即使没有注释也完全清楚。 |
String unitAbbrev = "\u03bcs"; // "μs" |
允许,但没有理由这样做。 |
String unitAbbrev = "\u03bcs"; // 希腊字母 mu,"s" |
允许,但笨拙且容易出错。 |
String unitAbbrev = "\u03bcs"; |
糟糕:读者不知道这是什么。 |
return '\ufeff' + content; // 字节顺序标记 |
良好:对不可打印字符使用转义,并在必要时添加注释。 |
提示:不要仅仅因为担心某些程序可能无法正确处理非 ASCII 字符而使代码可读性降低。如果发生这种情况,这些程序是损坏的,必须修复。
源文件结构
源文件由按顺序组成
- Apache 许可证
- 包语句
- 导入语句
- 正好一个顶级类
正好一个空行分隔每个存在的节。
Apache 许可证
Apache 许可证应位于此处。不应出现其他许可证。适用的其他许可证应在 NOTICE 文件中引用
包语句
包语句不换行。列限制(列限制:120)不适用于包语句。
导入语句
主树中没有通配符导入
通配符导入(静态或其他)不使用。
测试树中的静态通配符导入
对于测试导入(如 JUnit、EasyMock 和 Hamcrest),鼓励使用通配符静态导入。
不换行
导入语句不换行。列限制(列限制:120)不适用于导入语句。
排序和间距
导入语句分为以下几组,按此顺序排列,每组之间用一个空行隔开
- java
- javax
- org
- com
- 所有静态导入都在一个组中
在一个组内没有空行,导入的名称按 ASCII 排序顺序出现。(注意:这与导入语句按 ASCII 排序顺序不同;分号的存在会扭曲结果。)
可以在源代码分发中找到用于自动排序导入的 IDE 设置,位于src/ide
下。例如
- Eclipse:
src/ide/eclipse/4.3.2/organize-imports.importorder
- IntelliJ:
src/ide/Intellij/13/IntellijSettings.jar
类声明
正好一个顶级类声明
每个顶级类都驻留在它自己的源文件中。
类成员排序
类成员应按以下顺序分组>。
- 静态变量按以下所示顺序分组。在一个组内,变量可以按任何顺序出现。
-
- public
- protected
- package
- private
- 实例变量按以下所示顺序分组。在一个组内,变量可以按任何顺序出现
-
- public
- protected
- package
- private
- 构造函数
- 方法可以按以下顺序指定,但如果可以提高程序的清晰度,也可以按其他顺序出现。
-
- public
- protected
- package
- private
重载:从不拆分
当一个类具有多个构造函数或具有相同名称的多个方法时,这些构造函数或方法按顺序出现,中间没有其他成员。
格式
术语说明:块状结构指的是类、方法或构造函数的主体。请注意,根据数组初始化器,任何数组初始化器可以选择性地被视为块状结构。
大括号
大括号用于可选的地方
大括号用于if
、else
、for
、do
和while
语句,即使主体为空或只包含单个语句。
非空块:K & R 风格
大括号遵循 Kernighan 和 Ritchie 风格("埃及括号")用于非空块和块状结构
- 左大括号前没有换行。
- 左大括号后有换行。
- 右大括号前有换行。
- 右大括号后有换行,如果该大括号终止一个语句或方法、构造函数或命名类的主体。例如,如果大括号后面是
else
或逗号,则没有换行。
示例
return new MyClass() { @Override public void method() { if (condition()) { try { something(); } catch (ProblemException e) { recover(); } } } };
枚举类的一些例外情况在第 4.8.1 节中给出,枚举类。
空块:可以简洁
空块或块状结构可以在打开后立即关闭,中间没有字符或换行({}
),除非它是多块语句的一部分(直接包含多个块:if/else-if/else
或try/catch/finally
)。
示例
void doNothing() {}
块缩进:+4 个空格
每次打开一个新的块或块状结构时,缩进都会增加四个空格。当块结束时,缩进会返回到之前的缩进级别。缩进级别适用于整个块中的代码和注释。(参见第 4.1.2 节中的示例,非空块:K & R 风格。)
每行一个语句
每个语句后面都有一个换行符。
列限制:120
Log4j 的列限制为 120 个字符。除以下情况外,任何超过此限制的行都必须进行换行,如换行中所述。
例外情况
- 无法遵守列限制的行(例如,Javadoc 中的超长 URL 或超长的 JSNI 方法引用)。
package
和import
语句(参见包语句和导入语句)。- 注释中的命令行,这些命令行可以剪切粘贴到 shell 中。
换行
术语说明:当原本可以合法地占据单行的代码被分成多行时,通常是为了避免超过列限制,这种操作称为换行。
没有一个全面、确定的公式来显示确切如何在每种情况下换行。通常,有多种有效的方法可以换行相同的代码片段。
提示:提取方法或局部变量可以解决问题,而无需换行。
在哪里换行
换行的首要原则:优先在更高的语法级别换行。此外
- 当在非赋值运算符处换行时,换行符位于符号之前。(请注意,这与 Google 风格中用于其他语言(如 C++ 和 JavaScript)的做法不同。)
- 这也适用于以下“运算符类”符号:点分隔符(
.
)、类型边界中的与符号(<T extends Foo & Bar>
)以及 catch 块中的管道符(catch (FooException | BarException e)
)。
- 这也适用于以下“运算符类”符号:点分隔符(
- 当在赋值运算符处换行时,换行符通常位于符号之后,但两种方式都是可以接受的。
- 这也适用于增强型
for
(“foreach”)语句中的“赋值运算符类”冒号。
- 这也适用于增强型
- 方法或构造函数名称与其后面的左括号(
(
)保持在一起。 - 逗号(
,
)与其前面的标记保持在一起。
缩进续行至少 +8 个空格
换行时,第一行之后的每一行(每个续行)至少比原始行缩进 +8 个空格。
当有多个续行时,缩进可以根据需要进行调整,超过 +8 个空格。一般来说,如果两个续行以语法上平行的元素开头,则它们使用相同的缩进级别。
水平对齐部分讨论了使用可变数量的空格来将某些标记与前几行对齐的做法,这种做法是不鼓励的。
空白
垂直空白
单个空行出现在
- 之间类的连续成员(或初始化器):字段、构造函数、方法、嵌套类、静态初始化器、实例初始化器。
- 例外:在两个连续的字段之间(它们之间没有其他代码)的空行是可选的。这些空行根据需要使用,以创建字段的逻辑分组。
- 在方法体中,根据需要创建语句的逻辑分组。
- 可选地在类的第一个成员之前或最后一个成员之后(既不鼓励也不反对)。
- 根据本文档其他部分的要求(例如导入语句)。
多个连续的空行是允许的,但从不需要(也不鼓励)。
水平空白
除了语言或其他样式规则要求的地方,以及文字、注释和 Javadoc 之外,单个 ASCII 空格也仅出现在以下位置。
- 将任何保留字(如
if
、for
或catch
)与紧随其后的左括号((
)分隔开 - 将任何保留字(如
else
或catch
)与紧随其前的右大括号(}
)分隔开 - 在任何左大括号(
{
)之前,有两个例外String[][] x = {{"foo"}};
(根据第 8 项,{{
之间不需要空格)
- 在任何二元或三元运算符的两侧。这也适用于以下“运算符类”符号
- 连接类型边界中的与符号:
<T extends Foo & Bar>
- 处理多个异常的 catch 块中的管道符:
catch (FooException | BarException e)
- 增强型
for
(“foreach”)语句中的冒号(:
)
- 连接类型边界中的与符号:
- 在
,:;
或强制转换的右括号()
)之后 - 在开始行尾注释的双斜杠(
//
)的两侧。这里允许使用多个空格,但不需要。 - 在声明的类型和变量之间:
List<String> list
- 可选地在数组初始化器的两个大括号内
new int[] {5, 6}
和new int[] { 5, 6 }
都是有效的
注意:此规则从不强制要求或禁止在行首或行尾添加额外的空格,只针对内部空格。
水平对齐:从不强制要求
术语说明:水平对齐是指在代码中添加可变数量的额外空格的做法,目的是使某些标记出现在前几行中某些其他标记的正下方。
这种做法是允许的,但Google 风格从不强制要求。即使在已经使用水平对齐的地方,也不需要保持水平对齐。
以下是一个没有对齐的示例,然后是使用对齐的示例
private int x; // this is fine private Color color; // this too private int x; // permitted, but future edits private Color color; // may leave it unaligned
提示:对齐可以提高可读性,但会给未来的维护带来问题。考虑一下将来需要修改单行的更改。这种更改可能会使以前令人愉悦的格式变得混乱,而这是允许的。更常见的情况是,它会促使程序员(可能是你)也调整附近行的空白,可能触发一系列级联的重新格式化。这个单行更改现在有了“爆炸半径”。这在最坏的情况下会导致毫无意义的繁琐工作,但在最好的情况下,它仍然会破坏版本历史信息,减慢审查速度,并加剧合并冲突。
分组括号:推荐
可选的分组括号仅在作者和审阅者一致认为没有合理的可能性会导致代码在没有它们的情况下被误解,或者它们不会使代码更易读时才省略。不合理地假设每个读者都记住了整个 Java 运算符优先级表。
特定结构
枚举类
在每个枚举常量后面的逗号之后,换行符是可选的。
没有方法且其常量上没有文档的枚举类可以选择性地像数组初始化器一样格式化(参见数组初始化器)。
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
由于枚举类是类,因此所有其他类格式化规则都适用。
变量声明
每个声明一个变量
每个变量声明(字段或局部变量)只声明一个变量:int a, b;
之类的声明不被使用。
在需要时声明,尽快初始化
局部变量不习惯性地在包含它们的块或块状结构的开头声明。相反,局部变量是在它们第一次使用的地方附近声明的(在合理的范围内),以最小化它们的范围。局部变量声明通常具有初始化器,或者在声明后立即初始化。
数组
数组初始化器:可以是“块状”
任何数组初始化器可以选择性地像“块状结构”一样格式化。例如,以下所有都是有效的(不是详尽的列表)
new int[] { new int[] { 0, 1, 2, 3 0, } 1, 2, new int[] { 3, 0, 1, } 2, 3 } new int[] {0, 1, 2, 3}
没有 C 样式的数组声明
方括号是类型的一部分,而不是变量的一部分:String[] args
,而不是String args[]
。
switch 语句
术语说明:在switch 块的大括号内是一个或多个语句组。每个语句组由一个或多个switch 标签(case FOO:
或default:
)组成,后面跟着一个或多个语句。
缩进
与任何其他块一样,switch 块的内容缩进 +2 个空格。
在 switch 标签之后,会出现一个换行符,缩进级别增加 +2 个空格,就像打开一个块一样。下一个 switch 标签会返回到之前的缩进级别,就像关闭一个块一样。
贯穿:带注释
在 switch 块中,每个语句组要么突然终止(使用break
、continue
、return
或抛出异常),要么用注释标记,以指示执行将或可能继续到下一个语句组。任何传达贯穿概念的注释都足够(通常是// fall through
)。在 switch 块的最后一个语句组中,不需要此特殊注释。示例
switch (input) { case 1: case 2: prepareOneOrTwo(); // fall through case 3: handleOneTwoOrThree(); break; default: handleLargeNumber(input); }
存在默认情况
每个 switch 语句都包含一个default
语句组,即使它不包含任何代码。
注释
应用于类、方法或构造函数的注释出现在文档块之后,每个注释都单独列在一行上(即,每行一个注释)。这些换行符不构成换行(第 4.5 节,换行),因此缩进级别不会增加。示例
@Override @Nullable public String getNameIfPresent() { ... }
异常: 一个单独的无参数注解可以与签名的第一行一起出现,例如
@Override public int hashCode() { ... }
应用于字段的注解也出现在文档块之后,但在这种情况下,多个注解(可能带参数)可以列在同一行上;例如
@Partial @Mock DataLoader loader;
没有关于格式化参数和局部变量注解的特定规则。
注释
块注释风格
块注释的缩进与周围代码相同。它们可以是/* ... */
风格或// ...
风格。对于多行/* ... */
注释,后续行必须以与前一行*
对齐的*
开头。
/* * This is // And so /* Or you can * okay. // is this. * even do this. */ */
注释不用星号或其他字符绘制的框包围。
提示: 编写多行注释时,如果希望自动代码格式化程序在必要时重新换行(段落风格),请使用/* ... */
风格。大多数格式化程序不会重新换行// ...
风格的注释块中的行。
修饰符
类和成员修饰符(如果存在)按 Java 语言规范推荐的顺序出现
public protected private abstract static final transient volatile synchronized native strictfp
数值字面量
long
值的整数字面量使用大写L
后缀,绝不使用小写(避免与数字1
混淆)。例如,3000000000L
而不是3000000000l
。
命名
所有标识符的通用规则
标识符仅使用 ASCII 字母和数字,并在下面提到的两种情况下使用下划线。因此,每个有效的标识符名称都与正则表达式\w+
匹配。
在 Google 风格中,不使用特殊的前缀或后缀,例如name_
、mName
、s_name
和kName
。
按标识符类型划分规则
包名
包名全部小写,连续的单词简单地连接在一起(没有下划线)。例如,com.example.deepspace
,而不是com.example.deepSpace
或com.example.deep_space
。
类名
类名使用大驼峰命名法编写。
类名通常是名词或名词短语。例如,Character
或ImmutableList
。接口名也可能是名词或名词短语(例如,List
),但有时也可能是形容词或形容词短语(例如,Readable
)。
没有关于命名注解类型的特定规则,甚至没有公认的约定。
测试类以它们测试的类的名称开头,以Test
结尾。例如,HashTest
或HashIntegrationTest
。
方法名
方法名使用小驼峰命名法编写。
方法名通常是动词或动词短语。例如,sendMessage
或stop
。
JUnit 测试方法名中可能出现下划线,以分隔名称的逻辑组件。一种典型的模式是test<MethodUnderTest>_<state>
,例如testPop_emptyStack
。没有一种正确的方法来命名测试方法。
常量名
常量名使用CONSTANT_CASE
:所有字母都大写,单词之间用下划线分隔。但究竟什么是常量呢?
每个常量都是一个静态 final 字段,但并非所有静态 final 字段都是常量。在选择常量命名法之前,请考虑该字段是否真的感觉像一个常量。例如,如果该实例的任何可观察状态可以改变,它几乎肯定不是一个常量。仅仅打算永远不改变对象通常是不够的。示例
// Constants static final int NUMBER = 5; static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann"); static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable static final SomeMutableType[] EMPTY_ARRAY = {}; enum SomeEnum { ENUM_CONSTANT } // Not constants static String nonFinal = "non-final"; final String nonStatic = "non-static"; static final Set<String> mutableCollection = new HashSet<String>(); static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable); static final Logger logger = Logger.getLogger(MyClass.getName()); static final String[] nonEmptyArray = {"these", "can", "change"};
这些名称通常是名词或名词短语。
非常量字段名
非常量字段名(静态或其他)使用小驼峰命名法编写。
这些名称通常是名词或名词短语。例如,computedValues
或index
。
参数名
参数名使用小驼峰命名法编写。
应避免使用单字符参数名。
局部变量名
局部变量名使用小驼峰命名法编写,并且可以比其他类型的名称更自由地缩写。
但是,应避免使用单字符名称,除非是临时变量和循环变量。
即使是 final 和不可变的,局部变量也不被认为是常量,也不应该像常量那样进行样式化。
类型变量名
每个类型变量都以两种风格之一命名
- 单个大写字母,可选地后跟单个数字(例如
E
、T
、X
、T2
) - 以用于类的形式命名的名称(参见类名),后跟大写字母
T
(示例:RequestT
、FooBarT
)。
驼峰命名法:定义
有时,将英语短语转换为驼峰命名法有多种合理的方法,例如,当出现首字母缩略词或“IPv6”或“iOS”之类的非寻常结构时。为了提高可预测性,Google 风格指定了以下(几乎)确定性的方案。
从名称的散文形式开始
- 将短语转换为纯 ASCII 并删除所有撇号。例如,“Müller's algorithm”可能变为“Muellers algorithm”。
- 将此结果划分为单词,在空格和任何剩余的标点符号(通常是连字符)处分割。
- 推荐: 如果任何单词在常用用法中已经具有传统的驼峰命名法外观,将其拆分为其组成部分(例如,“AdWords”变为“ad words”)。请注意,像“iOS”这样的单词本身并不真正属于驼峰命名法;它违反了任何约定,因此此建议不适用。
- 现在将所有内容(包括首字母缩略词)都小写,然后只将以下内容的首字母大写
- ... 每个单词,以产生大驼峰命名法,或
- ... 除第一个单词之外的每个单词,以产生小驼峰命名法
- 最后,将所有单词合并成一个标识符。
请注意,原始单词的大小写几乎完全被忽略。示例
散文形式 | 正确 | 不正确 |
---|---|---|
"XML HTTP request" | XmlHttpRequest |
XMLHTTPRequest |
"new customer ID" | newCustomerId |
newCustomerID |
"inner stopwatch" | innerStopwatch |
innerStopWatch |
"supports IPv6 on iOS?" | supportsIpv6OnIos |
supportsIPv6OnIOS |
"YouTube importer" | YouTubeImporter YoutubeImporter * |
*可接受,但不推荐。
注意: 一些单词在英语中是模棱两可地用连字符连接的:例如,“nonempty”和“non-empty”都是正确的,因此方法名checkNonempty
和checkNonEmpty
也都是正确的。
编程实践
@Override:始终使用
只要合法,就用@Override
注解标记方法。这包括覆盖超类方法的类方法、实现接口方法的类方法以及重新指定超接口方法的接口方法。
异常: 当父方法是@Deprecated
时,可以省略@Override
。
捕获的异常:不忽略
除了下面提到的情况外,对捕获的异常不做任何处理是非常罕见的。(典型的响应是记录它,或者如果它被认为是“不可能的”,则将其重新抛出为AssertionError
。)
当在 catch 块中确实需要不采取任何措施时,在注释中解释了这样做的理由。
try { int i = Integer.parseInt(response); return handleNumericResponse(i); } catch (NumberFormatException ok) { // it's not numeric; that's fine, just continue } return handleTextResponse(response);
异常: 在测试中,如果捕获的异常名为expected
,则可以不加注释地忽略它。以下是确保被测方法确实抛出预期类型的异常的非常常见的习惯用法,因此这里不需要注释。
try { emptyStack.pop(); fail(); } catch (NoSuchElementException expected) { }
静态成员:使用类限定
当对静态类成员的引用必须限定时,它使用该类的名称进行限定,而不是使用该类的类型的引用或表达式。
Foo aFoo = ...; Foo.aStaticMethod(); // good aFoo.aStaticMethod(); // bad somethingThatYieldsAFoo().aStaticMethod(); // very bad
终结器:不使用
覆盖Object.finalize
极其罕见。
提示: 不要这样做。如果你一定要这样做,首先仔细阅读并理解Effective Java第 7 条,“避免终结器”,然后不要这样做。
Javadoc
格式
通用形式
Javadoc 块的基本格式如本例所示
/** * Multiple lines of Javadoc text are written here, * wrapped normally... */ public int method(String p1) { ... }
... 或者在本例中,单行示例
/** An especially short bit of Javadoc. */
基本形式始终是可接受的。当没有 at 子句存在时,可以使用单行形式,并且整个 Javadoc 块(包括注释标记)可以放在一行上。
段落
段落之间以及(如果存在)在“at 子句”组之前出现一行空行——即,仅包含对齐的引导星号(*
)的行。除第一个段落之外,每个段落都在第一个单词之前立即出现<p>
,后面没有空格。
At 子句
使用的任何标准“at 子句”都按@param
、@return
、@throws
、@deprecated
的顺序出现,这四种类型永远不会出现空描述。当 at 子句不能放在一行上时,续行从@
的位置缩进四个(或更多)空格。
摘要片段
每个类和成员的 Javadoc 都以一个简短的摘要片段开头。这个片段非常重要:它是文本中在某些上下文中(例如类和方法索引)出现的唯一部分。
这是一个片段——一个名词短语或动词短语,而不是一个完整的句子。它不以A {@code Foo} is a...
或This method returns...
开头,也不构成像Save the record.
这样的完整的祈使句。但是,该片段被大写并像完整的句子一样进行标点符号。
提示: 一个常见的错误是使用/** @return the customer ID */
的形式编写简单的 Javadoc。这是不正确的,应该改为/** Returns the customer ID. */
。
Javadoc 的使用位置
至少,每个public
类以及每个public
或protected
成员都应包含Javadoc,以下列出了一些例外情况。
其他类和成员根据需要包含Javadoc。当需要使用实现注释来定义类、方法或字段的总体目的或行为时,该注释应以Javadoc的形式编写。(这样更统一,也更利于工具使用。)
例外:不言自明的函数
对于像getFoo
这样“简单明了”的函数,Javadoc是可选的,因为在这些情况下,除了“返回foo”之外,真的没有其他有价值的内容需要说明。
重要:不要以这个例外为借口省略典型读者可能需要了解的相关信息。例如,对于一个名为getCanonicalName
的函数,如果典型读者可能不知道“规范名称”的含义,就不要省略它的文档(理由是它只会说/** 返回规范名称。 */
)。
例外:重写
重写超类型方法的函数并不总是包含Javadoc。