Java語言編碼規範

一隻小螞蟻吆發表於2020-04-06
1 介紹(Introduction)
1.1為什麼要有編碼規範
編碼規範對於程式設計師而言尤為重要,有以下幾個原因:

- 一個軟體的生命週期中,80%的花費在於維護
- 幾乎沒有任何一個軟體,在其整個生命週期中,均由最初的開發人員來維護
- 編碼規範可以改善軟體的可讀性,可以讓程式設計師儘快而徹底地理解新的程式碼
- 如果你將原始碼作為產品釋出,就需要確任它是否被很好的打包並且清晰無誤,一如你已構建的其它任何產品

為了執行規範,每個軟體開發人員必須一致遵守編碼規範。


1.2版權宣告
本文件反映的是Sun MicroSystems公司,Java語言規範中的編碼標準部分。主要貢獻者包括:Peter King,Patrick Naughton,Mike DeMoney,Jonni Kanerva,Kathy Walrath以及Scott Hommel。

本文件現由Scott Hommel維護,有關評論意見請發至shommel@eng.sun.com


2.檔名
這部分列出了常用的檔名及其字尾。

2.1檔案字尾
Java程式使用下列檔案字尾

檔案類別 檔案字尾
Java原始檔 .java
Java位元組碼檔案 .class

2.2常用檔名
Java程式使用下列檔案字尾

檔案類別 檔案字尾
Java原始檔 .java
Java位元組碼檔案 .class


3.檔案組織
一個檔案由被空行分割而成的段落以及標識每個段落的可選註釋共同組成。超過2000行的程式難以閱讀,應該儘量避免。"Java原始檔範例"提供了一個佈局合理的Java程式範例。

3.1jAVA原始檔
每個Java原始檔都包含一個單一的公共類或介面。若私有類和介面與一個公共類相關聯,可以將它們和公共類放入同一個原始檔。公共類必須是這個檔案中的第一個類或介面。

Java原始檔還遵循以下規則:

- 開頭註釋(參見"開頭註釋")
- 包和引入語句(參見"包和引入語句")
- 類和介面宣告(參見"類和介面宣告")


3.1.1開頭註釋
所有的原始檔都應該在開頭有一個C語言風格的註釋,其中列出類名、版本資訊、日期和版權宣告:

/*
* Classname
*
* Version information
*
* Date
*
* Copyright notice
*/


3.1.2包和引入語句
在多數Java原始檔中,第一個非註釋行是包語句。在它之後可以跟引入語句。例如:

package java.awt;

import java.awt.peer.CanvasPeer;


3.1.3類和介面說明
下表描述了類和介面宣告的各個部分以及它們出現的先後次序。參見"Java原始檔範例"中一個包含註釋的例子。

類/介面宣告的各部分 註解
1 類/介面文件註釋(/**……*/) 該註釋中所需包含的資訊,參見"文件註釋"
2 類或介面的宣告
3 類/介面實現的註釋(/*……*/)如果有必要的話 該註釋應包含任何有關整個類或介面的資訊,而這些資訊又不適合作為類/介面文件註釋。
4 類的(靜態)變數 首先是類的公共變數,隨後是保護變數,再後是包一級別的變數(沒有訪問修飾符,access modifier),最後是私有變數。
5 例項變數 首先是公共級別的,隨後是保護級別的,再後是包一級別的(沒有訪問修飾符),最後是私有級別的。
6 構造器
7 方法 這些方法應該按功能,而非作用域或訪問許可權,分組。例如,一個私有的類方法可以置於兩個公有的例項方法之間。其目的是為了更便於閱讀和理解程式碼。


4.縮排排版
4個空格常被作為縮排排版的一個單位。縮排的確切解釋並未詳細指定(空格 vs. 製表符)。一個製表符等於8個空格(而非4個)。

4.1行長度
儘量避免一行的長度超過80個字元,因為很多終端和工具不能很好處理之。

注意:用於文件中的例子應該使用更短的行長,長度一般不超過70個字元。


4.2換行
當一個表示式無法容納在一行內時,可以依據如下一般規則斷開之:

- 在一個逗號後面斷開
- 在一個操作符前面斷開
- 寧可選擇較高階別(higher-level)的斷開,而非較低階別(lower-level)的斷開
- 新的一行應該與上一行同一級別表示式的開頭處對齊
- 如果以上規則導致你的程式碼混亂或者使你的程式碼都堆擠在右邊,那就代之以縮排8個空格。

以下是斷開方法呼叫的一些例子:

someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);

var = someMethod1(longExpression1,
someMethod2(longExpression2,
longExpression3));
以下是兩個斷開算術表示式的例子。前者更好,因為斷開處位於括號表示式的外邊,這是個較高階別的斷開。


longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; //PREFFER

longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; //AVOID
以下是兩個縮排方法宣告的例子。前者是常規情形。後者若使用常規的縮排方式將會使第二行和第三行移得很靠右,所以代之以縮排8個空格


//CONVENTIONAL INDENTATION
someMethod(int anArg, Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}

//INDENT 8 SPACES TO AVOID VERY DEEP INDENTS
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
if語句的換行通常使用8個空格的規則,因為常規縮排(4個空格)會使語句體看起來比較費勁。比如:

//DON'T USE THIS INDENTATION
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { //BAD WRAPS
doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS
}

//USE THIS INDENTATION INSTEAD
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}

//OR USE THIS
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}


這裡有三種可行的方法用於處理三元運算表示式:


alpha = (aLongBooleanExpression) ? beta : gamma;

alpha = (aLongBooleanExpression) ? beta
: gamma;

alpha = (aLongBooleanExpression)
? beta
: gamma;

5.註釋

Java程式有兩類註釋:實現註釋(implementation comments)和文件註釋(document comments)。實現註釋是那些在C++中見過的,使用/*...*/和//界定的註釋。文件註釋(被稱為"doc comments")是Java獨有的,並由/**...*/界定。文件註釋可以通過javadoc工具轉換成HTML檔案。

實現註釋用以註釋程式碼或者實現細節。文件註釋從實現自由(implementation-free)的角度描述程式碼的規範。它可以被那些手頭沒有原始碼的開發人員讀懂。

註釋應被用來給出程式碼的總括,並提供程式碼自身沒有提供的附加資訊。註釋應該僅包含與閱讀和理解程式有關的資訊。例如,相應的包如何被建立或位於哪個目錄下之類的資訊不應包括在註釋中。

在註釋裡,對設計決策中重要的或者不是顯而易見的地方進行說明是可以的,但應避擴音供程式碼中己清晰表達出來的重複資訊。多餘的的註釋很容易過時。通常應避免那些程式碼更新就可能過時的註釋。

注意:頻繁的註釋有時反映出程式碼的低質量。當你覺得被迫要加註釋的時候,考慮一下重寫程式碼使其更清晰。

註釋不應寫在用星號或其他字元畫出來的大框裡。註釋不應包括諸如製表符和回退符之類的特殊字元。

5.1 實現註釋的格式

程式可以有4種實現註釋的風格:塊(block)、單行(single-line)、尾端(trailing)和行末(end-of-line)。

5.1.1 塊註釋

塊註釋通常用於提供對檔案,方法,資料結構和演算法的描述。塊註釋被置於每個檔案的開始處以及每個方法之前。它們也可以被用於其他地方,比如方法內部。在功能和方法內部的塊註釋應該和它們所描述的程式碼具有一樣的縮排格式。

塊註釋之首應該有一個空行,用於把塊註釋和程式碼分割開來,比如:

/*
* Here is a block comment.
*/
塊註釋可以以/*-開頭,這樣indent(1)就可以將之識別為一個程式碼塊的開始,而不會重排它。

/*-
* Here is a block comment with some very special
* formatting that I want indent(1) to ignore.
*
* one
* two
* three
*/
注意:如果你不使用indent(1),就不必在程式碼中使用/*-,或為他人可能對你的程式碼執行indent(1)作讓步。

參見"文件註釋"

5.1.2 單行註釋

短註釋可以顯示在一行內,並與其後的程式碼具有一樣的縮排層級。如果一個註釋不能在一行內寫完,就該採用塊註釋(參見"塊註釋")。單行註釋之前應該有一個空行。以下是一個Java程式碼中單行註釋的例子:


if (condition) {

/* Handle the condition. */
...
}


5.1.3 尾端註釋
極短的註釋可以與它們所要描述的程式碼位於同一行,但是應該有足夠的空白來分開程式碼和註釋。若有多個短註釋出現於大段程式碼中,它們應該具有相同的縮排。

以下是一個Java程式碼中尾端註釋的例子:

if (a == 2) {
return TRUE; /* special case */
} else {
return isPrime(a); /* works only for odd a */
}


5.1.4 行末註釋
註釋界定符"//",可以註釋掉整行或者一行中的一部分。它一般不用於連續多行的註釋文字;然而,它可以用來註釋掉連續多行的程式碼段。以下是所有三種風格的例子:


if (foo > 1) {

// Do a double-flip.
...
}
else {
return false; // Explain why here.
}

//if (bar > 1) {
//
// // Do a triple-flip.
// ...
//}
//else {
// return false;
//}

5.2 文件註釋
注意:此處描述的註釋格式之範例,參見"Java原始檔範例"

若想了解更多,參見"How to Write Doc Comments for Javadoc",其中包含了有關文件註釋標記的資訊(@return, @param, @see):

http://java.sun.com/javadoc/writingdoccomments/index.html

若想了解更多有關文件註釋和javadoc的詳細資料,參見javadoc的主頁:

http://java.sun.com/javadoc/index.html

文件註釋描述Java的類、介面、構造器,方法,以及欄位(field)。每個文件註釋都會被置於註釋定界符/**...*/之中,一個註釋對應一個類、介面或成員。該註釋應位於宣告之前:

/**
* The Example class provides ...
*/
public class Example { ...

注意頂層(top-level)的類和介面是不縮排的,而其成員是縮排的。描述類和介面的文件註釋的第一行(/**)不需縮排;隨後的文件註釋每行都縮排1格(使星號縱向對齊)。成員,包括建構函式在內,其文件註釋的第一行縮排4格,隨後每行都縮排5格。

若你想給出有關類、介面、變數或方法的資訊,而這些資訊又不適合寫在文件中,則可使用實現塊註釋(見5.1.1)或緊跟在宣告後面的單行註釋(見5.1.2)。例如,有關一個類實現的細節,應放入緊跟在類宣告後面的實現塊註釋中,而不是放在文件註釋中。

文件註釋不能放在一個方法或構造器的定義塊中,因為Java會將位於文件註釋之後的第一個宣告與其相關聯。

6 宣告

6.1 每行宣告變數的數量

推薦一行一個宣告,因為這樣以利於寫註釋。亦即,

int level; // indentation level
int size; // size of table

要優於,

int level, size;

不要將不同型別變數的宣告放在同一行,例如:

int foo, fooarray[]; //WRONG!

注意:上面的例子中,在型別和識別符號之間放了一個空格,另一種被允許的替代方式是使用製表符:


int level; // indentation level
int size; // size of table
Object currentEntry; // currently selected table entry


6.2 初始化
儘量在宣告區域性變數的同時初始化。唯一不這麼做的理由是變數的初始值依賴於某些先前發生的計算。

6.3 佈局

只在程式碼塊的開始處宣告變數。(一個塊是指任何被包含在大括號"{"和"}"中間的程式碼。)不要在首次用到該變數時才宣告之。這會把注意力不集中的程式設計師搞糊塗,同時會妨礙程式碼在該作用域內的可移植性。

void myMethod() {
int int1 = 0; // beginning of method block

if (condition) {
int int2 = 0; // beginning of "if" block
...
}
}

該規則的一個例外是for迴圈的索引變數

for (int i = 0; i < maxLoops; i++) { ... }

避免宣告的區域性變數覆蓋上一級宣告的變數。例如,不要在內部程式碼塊中宣告相同的變數名:

int count;
...
myMethod() {
if (condition) {
int count = 0; // AVOID!
...
}
...
}


6.4 類和介面的宣告
當編寫類和介面是,應該遵守以下格式規則:

- 在方法名與其引數列表之前的左括號"("間不要有空格
- 左大括號"{"位於宣告語句同行的末尾
- 右大括號"}"另起一行,與相應的宣告語句對齊,除非是一個空語句,"}"應緊跟在"{"之後

class Sample extends Object {
int ivar1;
int ivar2;

Sample(int i, int j) {
ivar1 = i;
ivar2 = j;
}

int emptyMethod() {}

...
}

- 方法與方法之間以空行分隔

7 語句

7.1 簡單語句

每行至多包含一條語句,例如:

argv++; // Correct
argc--; // Correct
argv++; argc--; // AVOID!


7.2 複合語句
複合語句是包含在大括號中的語句序列,形如"{ 語句 }"。例如下面各段。

- 被括其中的語句應該較之複合語句縮排一個層次
- 左大括號"{"應位於複合語句起始行的行尾;右大括號"}"應另起一行並與複合語句首行對齊。
- 大括號可以被用於所有語句,包括單個語句,只要這些語句是諸如if-else或for控制結構的一部分。這樣便於新增語句而無需擔心由於忘了加括號而引入bug。

7.3 返回語句

一個帶返回值的return語句不使用小括號"()",除非它們以某種方式使返回值更為顯見。例如:

return;

return myDisk.size();

return (size ? size : defaultSize);


7.4 if,if-else,if else-if else語句
if-else語句應該具有如下格式:

if (condition) {
statements;
}

if (condition) {
statements;
} else {
statements;
}

if (condition) {
statements;
} else if (condition) {
statements;
} else{
statements;
}
注意:if語句總是用"{"和"}"括起來,避免使用如下容易引起錯誤的格式:

if (condition) //AVOID! THIS OMITS THE BRACES {}!
statement;


7.5 for語句
一個for語句應該具有如下格式:

for (initialization; condition; update) {
statements;
}
一個空的for語句(所有工作都在初始化,條件判斷,更新子句中完成)應該具有如下格式:

for (initialization; condition; update);
當在for語句的初始化或更新子句中使用逗號時,避免因使用三個以上變數,而導致複雜度提高。若需要,可以在for迴圈之前(為初始化子句)或for迴圈末尾(為更新子句)使用單獨的語句。

7.6 while語句

一個while語句應該具有如下格式

while (condition) {
statements;
}
一個空的while語句應該具有如下格式:

while (condition);

7.7 do-while語句

一個do-while語句應該具有如下格式:

do {
statements;
} while (condition);
7.8 switch語句
一個switch語句應該具有如下格式:


switch (condition) {
case ABC:
statements;
/* falls through */
case DEF:
statements;
break;

case XYZ:
statements;
break;

default:
statements;
break;
}

每當一個case順著往下執行時(因為沒有break語句),通常應在break語句的位置新增註釋。上面的示例程式碼中就包含註釋/* falls through */。

7.9 try-catch語句

一個try-catch語句應該具有如下格式:

try {
statements;
} catch (ExceptionClass e) {
statements;
}
一個try-catch語句後面也可能跟著一個finally語句,不論try程式碼塊是否順利執行完,它都會被執行。

try {
statements;
} catch (ExceptionClass e) {
statements;
} finally {
statements;
}

8 空白

8.1 空行
空行將邏輯相關的程式碼段分隔開,以提高可讀性。

下列情況應該總是使用兩個空行:

- 一個原始檔的兩個片段(section)之間
- 類宣告和介面宣告之間

下列情況應該總是使用一個空行:

- 兩個方法之間
- 方法內的區域性變數和方法的第一條語句之間
- 塊註釋(參見"5.1.1")或單行註釋(參見"5.1.2")之前
- 一個方法內的兩個邏輯段之間,用以提高可讀性

8.2 空格

下列情況應該使用空格:

- 一個緊跟著括號的關鍵字應該被空格分開,例如:

while (true) {
...
}

注意:空格不應該置於方法名與其左括號之間。這將有助於區分關鍵字和方法呼叫。
- 空白應該位於引數列表中逗號的後面
- 所有的二元運算子,除了".",應該使用空格將之與運算元分開。一元操作符和運算元之間不因該加空格,比如:負號("-")、自增("++")和自減("--")。例如:
a += c + d;
a = (a + b) / (c * d);

while (d++ = s++) {
n++;
}
printSize("size is " + foo + "\n");

- for語句中的表示式應該被空格分開,例如:
for (expr1; expr2; expr3)

- 強制轉型後應該跟一個空格,例如:
myMethod((byte) aNum, (Object) x);
myMethod((int) (cp + 5), ((int) (i + 3)) + 1);

9 命名規範
命名規範使程式更易讀,從而更易於理解。它們也可以提供一些有關識別符號功能的資訊,以助於理解程式碼,例如,不論它是一個常量,包,還是類。

識別符號型別 命名規則 例子
包(Packages) 一個唯一包名的字首總是全部小寫的ASCII字母並且是一個頂級域名,通常是com,edu,gov,mil,net,org,或1981年ISO 3166標準所指定的標識國家的英文雙字元程式碼。包名的後續部分根據不同機構各自內部的命名規範而不盡相同。這類命名規範可能以特定目錄名的組成來區分部門(department),專案(project),機器(machine),或註冊名(login names)。 com.sun.eng
com.apple.QuickTime.v2
edu.cmu.cs.bovik.cheese
類(Classes) 命名規則:類名是個一名詞,採用大小寫混合的方式,每個單詞的首字母大寫。儘量使你的類名簡潔而富於描述。使用完整單詞,避免縮寫詞(除非該縮寫詞被更廣泛使用,像URL,HTML) class Raster;
class ImageSprite;
介面(Interfaces) 命名規則:大小寫規則與類名相似 interface RasterDelegate;
interface Storing;
方法(Methods) 方法名是一個動詞,採用大小寫混合的方式,第一個單詞的首字母小寫,其後單詞的首字母大寫。 run();
runFast();
getBackground();
變數(Variables) 除了變數名外,所有例項,包括類,類常量,均採用大小寫混合的方式,第一個單詞的首字母小寫,其後單詞的首字母大寫。變數名不應以下劃線或美元符號開頭,儘管這在語法上是允許的。
變數名應簡短且富於描述。變數名的選用應該易於記憶,即,能夠指出其用途。儘量避免單個字元的變數名,除非是一次性的臨時變數。臨時變數通常被取名為i,j,k,m和n,它們一般用於整型;c,d,e,它們一般用於字元型。 char c;
int i;
float myWidth;
例項變數(Instance Variables) 大小寫規則和變數名相似,除了前面需要一個下劃線 int _employeeId;
String _name;
Customer _customer;
常量(Constants) 類常量和ANSI常量的宣告,應該全部大寫,單詞間用下劃線隔開。(儘量避免ANSI常量,容易引起錯誤) static final int MIN_WIDTH = 4;
static final int MAX_WIDTH = 999;
static final int GET_THE_CPU = 1;




10 程式設計慣例

10.1 提供對例項以及類變數的訪問控制

若沒有足夠理由,不要把例項或類變數宣告為公有。通常,例項變數無需顯式的設定(set)和獲取(gotten),通常這作為方法呼叫的邊緣效應 (side effect)而產生。

一個具有公有例項變數的恰當例子,是類僅作為資料結構,沒有行為。亦即,若你要使用一個結構(struct)而非一個類(如果java支援結構的話),那麼把類的例項變數宣告為公有是合適的。

10.2 引用類變數和類方法

避免用一個物件訪問一個類的靜態變數和方法。應該用類名替代。例如:

classMethod(); //OK
AClass.classMethod(); //OK
anObject.classMethod(); //AVOID!


10.3 常量
位於for迴圈中作為計數器值的數字常量,除了-1,0和1之外,不應被直接寫入程式碼。

10.4 變數賦值

避免在一個語句中給多個變數賦相同的值。它很難讀懂。例如:

fooBar.fChar = barFoo.lchar = 'c'; // AVOID!

不要將賦值運算子用在容易與相等關係運算子混淆的地方。例如:

if (c++ = d++) { // AVOID! (Java disallows)
...
}

應該寫成


if ((c++ = d++) != 0) {
...
}

不要使用內嵌(embedded)賦值運算子試圖提高執行時的效率,這是編譯器的工作。例如:

d = (a = b + c) + r; // AVOID!

應該寫成

a = b + c;
d = a + r;


10.5 其它慣例

10.5.1 圓括號
一般而言,在含有多種運算子的表示式中使用圓括號來避免運算子優先順序問題,是個好方法。即使運算子的優先順序對你而言可能很清楚,但對其他人未必如此。你不能假設別的程式設計師和你一樣清楚運算子的優先順序。

if (a == b && c == d) // AVOID!
if ((a == b) && (c == d)) // RIGHT

10.5.2 返回值
設法讓你的程式結構符合目的。例如:

if (booleanExpression) {
return true;
} else {
return false;
}

應該代之以如下方法:

return booleanExpression;
類似地:

if (condition) {
return x;
}
return y;

應該寫做:

return (condition ? x : y);



10.5.3 條件運算子"?"前的表示式
如果一個包含二元運算子的表示式出現在三元運算子" ? : "的"?"之前,那麼應該給表示式添上一對圓括號。例如:

(x >= 0) ? x : -x;

10.5.4 特殊註釋
在註釋中使用XXX來標識某些未實現(bogus)的但可以工作(works)的內容。用FIXME來標識某些假的和錯誤的內容

11 程式碼範例

11.1 Java原始檔範例

下面的例子,展示瞭如何合理佈局一個包含單一公共類的Java源程式。介面的佈局與其相似。更多資訊參見"類和介面宣告"以及"文擋註釋"。


/*
* @(#)Blah.java 1.82 99/03/18
*
* Copyright (c) 1994-1999 Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*/


package java.blah;

import java.blah.blahdy.BlahBlah;

/**
* Class description goes here.
*
* @version 1.82 18 Mar 1999
* @author Firstname Lastname
*/
public class Blah extends SomeClass {
/* A class implementation comment can go here. */

/** classVar1 documentation comment */
public static int classVar1;

/**
* classVar2 documentation comment that happens to be
* more than one line long
*/
private static Object classVar2;

/** instanceVar1 documentation comment */
public Object instanceVar1;

/** instanceVar2 documentation comment */
protected int instanceVar2;

/** instanceVar3 documentation comment */
private Object[] instanceVar3;

/**
* ...constructor Blah documentation comment...
*/
public Blah() {
// ...implementation goes here...
}

/**
* ...method doSomething documentation comment...
*/
public void doSomething() {
// ...implementation goes here...
}

/**
* ...method doSomethingElse documentation comment...
* @param someParam description
*/
public void doSomethingElse(Object someParam) {
// ...implementation goes here...
}
}

相關文章