Java 寫碼標準
整理:蔡煥麟
日期:Oct-4-2003
1.0 簡介
1.1 資料來源
這份文件中所建議的寫碼標準主要參考自下列文件:
大部分規則都相同,僅有極少部分的修改,例如:縮排使用 2 個空白字元。
1.2 為何需要寫碼標準?
- 軟體生命週期中,維護就佔了 80%。
- 幾乎沒有任何一個軟體從頭到尾(整個生命週期)都是由原始作者在維護。
- 寫碼慣例提高了程式的可讀性,讓程式設計師能更快地了解別人的程式碼。
- 如果程式碼是產品的一部分,你得確保它跟其他產品有相同的品質。
2.0 檔案名稱
Java 原始碼:*.java
Java bytecode:*.class
3.0 檔案組織
通用慣例:
- 一個檔案裡面若可區分成數個段落,各段落之間應以一個空白行隔開,並視需要加上註解。
- 檔案的內容若超過 2000 行,就應予以切割。
3.1 Java 原始碼
Java 原始碼檔案的組成分子,依照出現的順序排列為:
- 起始註解。
- package 和 import 敘述(先寫 package,再寫 import)。
- 類別或介面宣告。
3.1.1 起始註解
每個 Java 程式碼檔案的開頭都要有 C-style 的註解,參考下列範例:
英文範例:
/* * Description * * A brief description of the class/interface. * * History * * yyyy-mm-dd Author * What has been changed. * * Copyright notice */
中文範例:
/* * 說明 * * 簡短描述這個類別/介面。 * * 修改歷史 * * yyyy-mm-dd 作者姓名 * 改了哪些東西。 * * 版權聲明 */
3.1.2 類別和介面宣告
類別和介面各個組成分子,依照應該出現的順序排列如下:
順序 | 項目 | 備註 |
1 | 類別/介面的註解 | |
2 | class 或 interface 敘述 | |
3 | 類別或介面的實作註解(有需要才寫) | |
4 | 類別變數(宣告為 static 的變數) | 依照 public,protected, 和 private 的順序宣告。 |
5 | Instance 變數 | 依照 public,protected, 和 private 的順序宣告。 |
6 | 建構函式 | |
7 | finalize() | |
8 | 方法 | 相關功能的方法應該擺在一起,而不是以 public 或 private 存取範圍來區分。這樣安排的目的是為了增加程式的可讀性。 |
參考 10.0 Java 程式碼範例。
4.0 縮排(Indentation)
每次縮排為 2 個空格,並且使用 Tab 字元來縮排,亦即修改編輯器的設定,將 Tab 字元以 2 個空白字元來顯示(大部分的文書編輯器都有提供這類功能),注意不是按 Tab 鍵時輸入 2 個空白字元。
4.1 邊界
每道敘述不要超過 80 個字元,範例程式則建議在 70 個字元以下。
4.2 折行
程式碼若太長,會超出邊界時,可採取下列方式折行:
- 在逗號之後折行。
- 在運算元之前折行。
- 被折下去的那行敘述,跟前一道敘述中相同階層的表示式對齊。
- 如果上面的方式會造成程式碼太逼近右邊界(過度深層的縮排),可單純以 8 個空白字元縮排。
- 盡量在外層折行。
範例:
// 折行後,跟前一道敘述中同階層的表示式對齊。 var = someMethod1(longExpression1, someMethod2(longExpression2, longExpression3)); // 折行後以縮排兩次,以避免過度深層的縮排 var = someMethod(longExpression1, longExpression2, longExpression3, longExpression4, longExpression5); // 在外層折行,正確。 longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6; // 在內層折行,錯誤!應盡量避免。 longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6;
if ... else 的折行規則也是用兩次縮排,若只用一次縮排,if 敘述折行後會跟 if 區塊內的敘述對齊,使得不易辨識區塊主體,例如:
// 錯誤示範 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } // 正確寫法 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } // 若只縮排一次,則條件式和區塊主體之間要空一行 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); }
5.0 註解
通用慣例:
- 註解要能幫助人們閱讀及了解程式碼。
- 如果你的程式不值得寫註解,那你的程式可能也沒多大用處。
- 避免過度裝飾,例如:把註解加框,讓它看起來像一幅廣告。
- 要簡潔有力。
- 先寫註解,才寫 code。
- 好的註解要說明 why(為什麼要寫這段,為什麼要這樣寫...等等),而不只是說明 what (這段程式碼在做什麼)而已。
Java 的註解主要分成兩種:
- 實作註解(implementation comments),寫法跟 C++ 的註解一樣,有 /*...*/ 多行註解和 // 單行註解這兩種型式。
- 文件註解(documentation comments),是 Java 特有的註解型式,寫法是 /**...*/,這類註解可以被 javadoc 工具讀取並產生 HTML 說明文件。
實作註解主要是用來說明程式碼,文件註解則用來描述程式碼的規格。
註:如果程式當中到處都是註解,可能意味著程式碼的品質較差,當然這並不是要你把註解拿掉或乾脆不寫註解,而是希望你注意到,當你覺得若不寫這麼多冗長的註解就很難讓人了解程式碼時,你可能要考慮用更好的方式改寫這段程式碼。
5.1 實作註解
5.1.1 區塊註解(多行註解)
區塊註解可以用在檔案標頭、每個函式之前、或程式碼中的任何地方。區塊註解的前面應該要有一列空白。例如:
someMethod(); /* * Here is a block comment. */ hello();
不要用很多 * 把註解框起來,像下面這樣:
/*********************
?*??????????????????????????????? *
?*? 這是個錯誤示範?????????? *
?*??????????????????????????????? *
?*********************/
5.1.2 單行註解
比較短的註解可以使用單行註解,單行註解可以用 /*...*/ 或 // 兩種寫法,建議使用後者,它也常被用來將一段程式碼註解掉(comment out),參考下面的範例:
if (foo > 1) { // Do a double-flip. 注意前面要空一行 ... } else { return false; // 寫在這裡的叫做 end-of-line comments } //if (bar > 1) { // // // Do a triple-flip. // ... //} //else { // return false; //}
5.2 文件註解
請參考 http://java.sun.com/j2se/javadoc/writingdoccomments/index.html。
6.0 宣告
6.1 一行一個宣告
建議一行只寫一個宣告,這樣有利於撰寫註解,例如下面的寫法:
int level; // Indentation level int size; // Size of table
比這種寫法好:
int level, size;
最糟糕的是把不同用途的東西擺在同一行宣告,例如:
int foo, fooarray[]; // WRONG!
註:以上的寫法都是用一個空白字元來分隔型態和變數名稱,另一種可以接受的寫法是用 tab 字元,例如:
int level; // Indentation level int size; // Size of table Object currentEntry; // Currently selected table entry
6.2 初始化
儘量在宣告變數時就設定初始值。
6.3 宣告的位置
儘管 Java 允許你在任何地方宣告變數,把宣告集中在程式區塊一開始的地方(由 {...} 包住的程式區塊)會比較清楚。參考下面的範例:
void myMethod() { int int1 = 0; // 正確 if (condition) { int int2 = 0; // 正確 ... } int int3 = 0; // 錯誤,應該跟 int1 的宣告在一起 }
唯一的例外是 for 迴圈的索引變數,它可以直接宣告在 for 敘述裡面,例如:
for (int i = 0; i < maxLoops; i++) { ... }
注意不要讓區域變數蓋過外層的變數,例如:
int count; ... myMethod() { if (condition) { int count; // 錯誤!容易讓人混淆,不易除錯 ... } ... }
6.4 類別和介面的宣告
在撰寫類別和介面時,應遵守下列規則:
- 方法名稱和左括號 '(' 之間不要有空白。
- 左大括號 '{' 出現在同一行程式敘述的後面,而不是獨立一行。
- 右大括號 '}' 應該是單獨一行,而且要跟它對應的程式區塊對齊。唯一的例外是,若程式區塊中沒有任何敘述(空的區塊),'}' 應該緊跟在 '{' 後面。
- 方法和方法之間要空一行。
範例:
class Sample extends Object { int ivar1; int ivar2; Sample(int i, int j) { ivar1 = i; ivar2 = j; } int emptyMethod() {} ... }
7.0 敘述(statements)
7.1 簡單敘述
一行只能寫一個敘述,例如:
argv++; // 正確 argc++; // 正確 argv++; argc--; // 錯誤!
7.2 複合敘述
所謂的複合敘述,指的就是以大括號 '{...}' 包住的敘述,通用的規則是:
- 左大括號 '{' 出現在同一行程式敘述的後面,而不是獨立一行。
- 右大括號 '}' 應該是單獨一行,而且要跟它對應的程式區塊對齊。
- 當你在寫 if-else, for, while...等敘述時,不管其中的程式只有一行還是多行,一律都要用大括號包住,以免日後加入新的程式碼時忘了加括號。
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) { // else 也可以自己獨立一行 statements; } else { statements; }
注意:不管單行還是多行敘述,都要用使用大括號。
7.5 for 敘述
for 敘述應依照下列格式撰寫 :
for (initialization; condition; update) { statements; }
空的 for 敘述:
for (initialization; condition; update);
initialization 裡面用到的變數不要超過三個。
7.6 while 敘述
while 敘述應依照下列格式撰寫 :
while (condition) { statements; }
空的 while 敘述:
while (condition);
7.7 do-while 敘述
do-while 敘述應依照下列格式撰寫 :
do { statements; } while (conditions);
7.8 switch 敘述
switch 敘述應依照下列格式撰寫 :
switch (condition) { case ABC: // case 不需要縮排 statements; // 往下繼續執行 case DEF: statements; break; case XYZ: statements; break; default: statements; break; // 若 default 是最後一個 case,這裡的 break 可不寫。 }
當一個 case 裡面沒有寫 break 時,程式會繼續往下執行,這時必須在這個 case 往下執行的地方加上註解,就像上面的範例一樣,這樣可以避免粗心的程式設計師誤解程式流程。
7.9 try-catch 敘述
try-catch 敘述應依照下列格式撰寫 :
try { statements; } catch (ExceptionClass e) { statements; }try { statements; } catch (ExceptionClass e) { statements; } finally { statements; }
8.0 空白
8.1 空白行
適當地加入空白行可以區分程式段落,提高可讀性。以下情況應該加入一行空白行:
- 方法和方法之間。
- 區域變數和第一行程式敘述之間。
- 程式區塊之前。
- 單行註解之前。
- 在一個方法裡面,可邏輯切割的程式段落之間(增進可讀性)。
以下情況應該加入兩行空白行:
- 類別/介面之間。
- 在一個原始碼檔案裡面,可邏輯切割的程式段落之間。
8.1 空白字元
以下情況應該使用空白字元:
- 關鍵字和左括弧之間,例如 :
while (true) { ... }
- 參數列中的逗號後面,也就是逗號和參數名稱之間。
- 所有二元運算子,除了 . 之外,都要用空白字元將運算元隔開,例如:
a += c + d; a = (a + b) / (c * d); while (d++ = s++) { n++; } prints("size is " + foo + "/n");
- for 敘述裡面的表示式要以空白隔開,例如:
for (expr1; expr2; expr3)
- 轉型時,例如:
myMethod((byte) aNum, (Object) x); myMethod((int) (cp + 5), ((int) (i + 3)) + 1);
9.0 命名慣例
9.1 Java 命名慣例
通用慣例:
- 使用完整的、能夠望文生義的英文。
- 使用跟該領域相關的術語。
- 大小寫混合使用,以增加可讀性。
- 名稱盡量不要超過 15 個字元。
- 不要只用大小寫的差異來辨別兩個變數名稱,這樣容易造成混淆。
項目 | 命名慣例 | 範例 |
Parameters 參數 | 使用完整的英文來描述傳遞的變數/物件,可以在前面冠上 'a' 或 'an',不管用哪種方式,最重要的是定下來以後就要遵守它。 | customer, account 或 aCustomer, anAccount |
Fields/properties 欄位/屬性? | 使用完整的英文,第一個單字(word)的第一個字母小寫,其餘的單字的第一個字母大寫。 | firstName, lastName |
傳回布林型態的 getter 成員函式 | 開頭一律冠上 'is'。 | isDead, isString, isFailed |
Classes 類別 | 使用完整的英文,類別名稱的每個單字的第一個字母大寫,其餘字母為小寫。 | Customer, SavingAccount |
Compilation unit files 檔案 | 以類別名稱作為主檔名,附屬檔名為小寫的'.java'。 | Customer.java, SavingAccount.java |
Exceptions 異常 | 通常以小寫字母 'e' 代表異常物件。 | e |
Final static fields (consants) 常數 | 全部使用大寫英文字母,每個單字以底線分隔。 | MIN_BALANCE, DEFAULT_DATE |
Getter 成員函式 | 把要存取的欄位名稱前面冠上 'get'。 | getFirstName(), getLastName() |
Interfaces 介面 | 跟類別的命名規則相同,使用完整的英文,介面名稱的每個單字的第一個字母都大寫,其餘小寫。如果可能的話,盡量使用形容詞,也就是 'able','ible' 等字尾的名稱(不是絕對必要)。 | Runnable, Contactable, Singleton |
Local variables 區域變數 | 使用完整的英文,第一個單字(word)的第一個字母小寫,其餘的單字的第一個字母大寫。注意:應避免和類別的欄位名稱相同,以免產生混淆,例如,你有一個欄位名稱是 'firstName',那麼在成員函式中的區域變數就不要取相同的名稱。 | grandTotal, customer, newAccount |
Loop counters 迴圈計數器 | 通常以 'i', 'j', 'k' 或 'counter' 來命名。 | i, j, k, cnt, counter |
Packages 套件 | 使用完整的英文,通常全都小寫。為了避免和其他套件名稱衝突,一般會將公司或組織的 internet 網域名稱順序顛倒過來,再加上套件名稱,不用把網域名稱的每個部分都加進去,用一個或兩個就行了。 | java.awt, com.company.utility com.apple.quicktime.v2 |
Member functions 成員函式 | 使用完整的英文,第一個單字(word)的第一個字母小寫,其餘的單字的第一個字母大寫,盡可能以動詞開頭。 | openFile(), addAccount() |
Setter 成員函式 | 把要存取的欄位名稱前面冠上 'set'。 | setFirstName(), setLastName() |
區域變數的命名可以稍有彈性,對於比較不重要的變數,可以採取下列簡短的命名方式,但請特別注意,不可濫用,原則上仍應該盡量以完整的英文來命名:
變數類型 | 建議的命名慣例 |
offset | off |
length | len |
char | c 或 ch |
float | f |
Object | o 或 obj |
String | s 或 str |
10.0 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... } }
11.0 其他建議
- 程式中如果要用到很多次字串相加,應改用 StringBuffer,以增加程式執行的效能,因為字串相加會需要額外建立 String 物件的時間和記憶體資源。
- 隨時注意可能會引發 null pointer assignment 異常的程式碼,考慮物件參考為 null 時應如何處理。
- 要使用 String.equals() 來比較某個 String 物件和字串常數時,將字串常數寫在左邊,這樣可以避免 String 物件為 null 時所引發的錯誤,例如:
if ("Michael".equals(s))
- 盡量捕捉明確的異常類別,例如:FileNotFoundException,不要全都使用 Exception。
- 不要用物件參考存取類別的靜態成員。
- 有特殊意義的數值應定義成常數,不要把數值寫死在程式裡。你可以用 final static 來定義常數。