Java項目開發規範

一、目的   

  對於代碼,首要要求是它必須正確,能夠按照程序員的真實思想去運行;第二個的要求是代碼必須清晰易懂,使別的程序員能夠容易理解代碼所進行的實際工作。在軟件工程領域,源程序的風格統一標誌着可維護性、可讀性,是軟件項目的一個重要組成部分。而目前還沒有成文的編碼風格文檔,以致於很多時候,程序員沒有一個共同的標準可以遵守,編碼風格各異,程序可維護性差、可讀性也很差。通過建立代碼編寫規範,形成開發小組編碼約定,提高程序的可靠性、可讀性、可修改性、可維護性、可繼承性和一致性,可以保證程序代碼的質量,繼承軟件開發成果,充分利用資源,使開發人員之間的工作成果可以共享。

    本文在參考業界已有的編碼風格的基礎上,描述了一個基於 JBuilder 的項目風格,力求一種統一的編程風格,並從整體編碼風格、代碼文件風格、函數編寫風格、變量風格、註釋風格等幾個方面進行闡述。(這些規範並不是一定要絕對遵守,但是一定要讓程序有良好的可讀性)


二、整體編碼風格

1、縮進

    縮進建議以4個空格爲單位。建議在 Tools/Editor Options 中設置 Editor 頁面的Block ident爲4,Tab Size 爲8。預處理語句、全局數據、標題、附加說明、函數說明、標號等均頂格書寫。語句塊的"{"、"}"配對對齊,並與其前一行對齊,語句塊類的語句縮進建議每個"{"、"}"單獨佔一行,便於匹對。JBuilder 中的默認方式是開始的"{"不是單獨一行,建議更改成上述格式(在 Project/Default Project Properties 中設置 Code Style 中選擇 Braces 爲 Next line)。

2、空格

    原則上變量、類、常量數據和函數在其類型,修飾名稱之間適當空格並據情況對齊。關鍵字原則上空一格,如:if ( ... ) 等。運算符的空格規定如下:"::"、"->;"、"["、"]"、"++"、"--"、"~"、"!"、"+"、"-"(指正負號)、"&"(引用)等幾個運算符兩邊不加空格(其中單目運算符係指與操作數相連的一邊),其它運算符(包括大多數二目運算符和三目運算符"?:"兩邊均加一空格,在作函數定義時還可據情況多空或不空格來對齊,但在函數實現時可以不用。","運算符只在其後空一格,需對齊時也可不空或多空格。不論是否有括號,對語句行後加的註釋應用適當空格與語句隔開並儘可能對齊。個人認爲此項可以依照個人習慣決定遵循與否。

3、對齊

    原則上關係密切的行應對齊,對齊包括類型、修飾、名稱、參數等各部分對齊。另每一行的長度不應超過屏幕太多,必要時適當換行,換行時儘可能在","處或運算符處,換行後最好以運算符打頭,並且以下各行均以該語句首行縮進,但該語句仍以首行的縮進爲準,即如其下一行爲“{”應與首行對齊。

    變量定義最好通過添加空格形成對齊,同一類型的變量最好放在一起。如下例所示:
int        Value;
int        Result;
int        Length;
DWORD      Size;
DWORD      BufSize;

 個人認爲此項可以依照個人習慣決定遵循與否。

4、空行

 不得存在無規則的空行,比如說連續十個空行。程序文件結構各部分之間空兩行,若不必要也可只空一行,各函數實現之間一般空兩行,由於每個函數還要有函數說明註釋,故通常只需空一行或不空,但對於沒有函數說明的情況至少應再空一行。對自己寫的函數,建議也加上“//------”做分隔。函數內部數據與代碼之間應空至少一行,代碼中適當處應以空行空開,建議在代碼中出現變量聲明時,在其前空一行。類中四個“p”之間至少空一行,在其中的數據與函數之間也應空行。

5、註釋

 註釋是軟件可讀性的具體體現。程序註釋量一般佔程序編碼量的20%,軟件工程要求不少於20%。程序註釋不能用抽象的語言,類似於"處理"、"循環"這樣的計算機抽象語言,要精確表達出程序的處理說明。例如:"計算淨需求"、"計算第一道工序的加工工時"等。避免每行程序都使用註釋,可以在一段程序的前面加一段註釋,具有明確的處理邏輯。

 註釋必不可少,但也不應過多,不要被動的爲寫註釋而寫註釋。以下是四種必要的註釋:
 
A. 標題、附加說明。

B. 函數、類等的說明。對幾乎每個函數都應有適當的說明,通常加在函數實現之前,在沒有函數實現部分的情況下則加在函數原型前,其內容主要是函數的功能、目的、算法等說明,參數說明、返回值說明等,必要時還要有一些如特別的軟硬件要求等說明。公用函數、公用類的聲明必須由註解說明其使用方法和設計思路,當然選擇恰當的命名格式能夠幫助你把事情解釋得更清楚。

C. 在代碼不明晰或不可移植處必須有一定的說明。

D. 及少量的其它註釋,如自定義變量的註釋、代碼書寫時間等。

  註釋有塊註釋和行註釋兩種,分別是指:"/**/"和"//"建議對A用塊註釋,D用行註釋,B、C則視情況而定,但應統一,至少在一個單元中B類註釋形式應統一。具體對不同文件、結構的註釋會在後面詳細說明。

6、代碼長度

 對於每一個函數建議儘可能控制其代碼長度爲53行左右,超過53行的代碼要重新考慮將其拆分爲兩個或兩個以上的函數。函數拆分規則應該一不破壞原有算法爲基礎,同時拆分出來的部分應該是可以重複利用的。對於在多個模塊或者窗體中都要用到的重複性代碼,完全可以將起獨立成爲一個具備公用性質的函數,放置於一個公用模塊中。

7、頁寬

 頁寬應該設置爲80字符。源代碼一般不會超過這個寬度, 並導致無法完整顯示, 但這一設置也可以靈活調整. 在任何情況下, 超長的語句應該在一個逗號或者一個操作符後折行. 一條語句折行後, 應該比原來的語句再縮進2個字符.

8、行數

 一般的集成編程環境下,每屏大概只能顯示不超過50行的程序,所以這個函數大概要5-6屏顯示,在某些環境下要8屏左右才能顯示完。這樣一來,無論是讀程序還是修改程序,都會有困難。因此建議把完成比較獨立功能的程序塊抽出,單獨成爲一個函數。把完成相同或相近功能的程序塊抽出,獨立爲一個子函數。可以發現,越是上層的函數越簡單,就是調用幾個子函數,越是底層的函數完成的越是具體的工作。這是好程序的一個標誌。這樣,我們就可以在較上層函數裏容易控制整個程序的邏輯,而在底層的函數裏專注於某方面的功能的實現了。


三、代碼文件風格

所有的 Java(*.java) 文件都必須遵守如下的樣式規則:

. 文件生成

對於規範的 JAVA 派生類,儘量用 JBuilder 的 Object Gallery 工具來生成文件格式,避免用手工製作的頭文件/實現文件。
 
. package/import

package 行要在 import 行之前,import 中標準的包名要在本地的包名之前,而且按照字母順序排列。如果 import 行中包含了同一個包中的不同子目錄,則應該用 * 來處理。

package hotlava.net.stats;

import java.io.*;
import java.util.Observable;
import hotlava.util.Application;  
 
這裏 java.io.* 使用來代替InputStream and OutputStream 的。

. 文件頭部註釋

文件頭部註釋主要是表明該文件的一些信息,是程序的總體說明,可以增強程序的可讀性和可維護性。文件頭部註釋一般位於 package/imports 語句之後,Class 描述之前。要求至少寫出文件名、創建者、創建時間和內容描述。JBuilder 的 Object Gallery 工具生成的代碼中會在類、工程文件中等自動添加註釋,我們也要添加一些註釋,其格式應該儘量約束如下:

/**
 * Title:  確定鼠標位置類
 * Description: 確定鼠標當前在哪個作業欄位中並返回作業號
 * @Copyright: Copyright (c) 2002
 * @Company: HIT
 * @author: rivershan
 * @version: 1.0
 * @time: 2002.10.30
 */
 
. Class

接下來的是類的註釋,一般是用來解釋類的。

/**
 * A class representing a set of packet and byte counters
 * It is observable to allow it to be watched, but only
 * reports changes when the current set is complete
 */ 
 
接下來是類定義,包含了在不同的行的 extends 和 implements

public class CounterSet
  extends Observable
  implements Cloneable

.Class Fields

接下來是類的成員變量:

/**
 * Packet counters
 */
 
protected int[] packets;
 
public 的成員變量必須生成文檔(JavaDoc)。proceted、private和 package 定義的成員變量如果名字含義明確的話,可以沒有註釋。

. 存取方法
 
接下來是類變量的存取的方法。它只是簡單的用來將類的變量賦值獲取值的話,可以簡單的寫在一行上。(個人認爲儘量分行寫)

/**
 * Get the counters
 * @return an array containing the statistical data.  This array has been
 * freshly allocated and can be modified by the caller.
 */
 
public int[] getPackets() 
{
  return copyArray(packets, offset); 
}

public int[] getBytes() 

 return copyArray(bytes, offset); 
}

public int[] getPackets() 

 return packets; 
}

public void setPackets(int[] packets) 

 this.packets = packets; 
}
 
其它的方法不要寫在一行上

. 構造函數

接下來是構造函數,它應該用遞增的方式寫(比如:參數多的寫在後面)。

訪問類型("public","private" 等.)和任何"static","final"或"synchronized"應該在一行中,並且方法和參數另寫一行,這樣可以使方法和參數更易讀。

public
CounterSet(int size)
{
   this.size = size;
}

. 克隆方法
 
如果這個類是可以被克隆的,那麼下一步就是 clone 方法:

public
Object clone()
{
 try 
   {
     CounterSet obj = (CounterSet)super.clone();
     obj.packets = (int[])packets.clone();
     obj.size = size;
     return obj;
   }  
   catch(CloneNotSupportedException e) 
   {
    throw new InternalError("Unexpected CloneNotSUpportedException: " 
          + e.getMessage());
   }
}

. 類方法

下面開始寫類的方法:

/**
 * Set the packet counters
 * (such as when restoring from a database)
 */
protected final
void setArray(int[] r1, int[] r2, int[] r3, int[] r4)
  throws IllegalArgumentException
{
 //
   // Ensure the arrays are of equal size
   //
   if (r1.length != r2.length || r1.length != r3.length || r1.length != r4.length)
  throw new IllegalArgumentException("Arrays must be of the same size");
   System.arraycopy(r1, 0, r3, 0, r1.length);
   System.arraycopy(r2, 0, r4, 0, r1.length);
}

. toString 方法

無論如何,每一個類都應該定義 toString 方法:

public
String toString()
{
 String retval = "CounterSet: ";
    for (int i = 0; i < data.length(); i++) 
    {
       retval += data.bytes.toString();
       retval += data.packets.toString();
    }
    return retval;
}

. main 方法

如果main(String[]) 方法已經定義了, 那麼它應該寫在類的底部.


四、函數編寫風格

. 函數的命名

通常,函數的命名也是以能表達函數的動作意義爲原則的,一般是由動詞打頭,然後跟上表示動作對象的名詞,各單詞的首字母應該大寫。另外,還有一些函數命名的通用規則。如取數,則用Get打頭,然後跟上要取的對象的名字;設置數,則用Set打頭,然後跟上要設的對象的名字;而對象中爲了響應消息進行動作的函數,可以命名爲On打頭,然後是相應的消息的名稱;進行主動動作的函數,可以命名爲Do打頭,然後是相應的動作名稱。類似的規則還有很多,需要程序員多讀優秀的程序,逐漸積累經驗,才能作出好的函數命名。

. 函數註釋

系統自動生成的函數,如鼠標動作響應函數等,不必太多的註釋和解釋;

對於自行編寫的函數,若是系統關鍵函數,則必須在函數實現部分的上方標明該函數的信息,格式如下:

/**
* 函數名:
* 編寫者:
* 參考資料:
* 功  能:
* 輸入參數:
* 輸出參數:
* 備  注:
*/

希望儘量遵循以上格式。


五、符號風格

. 總體要求

對於各種符號的定義,都有一個共通點,就是應該使用有實際意義的英文單詞或英文單詞的縮寫,不要使用簡單但沒有意義的字串,儘可能不使用阿拉伯數字,更切忌使用中文拼音的首字母。如這樣的名稱是不提倡的:Value1,Value2,Value3,Value4 …。

例如:
file(文件),code(編號),data(數據),pagepoint(頁面指針), faxcode(傳真號) ,address(地址),bank(開戶銀行),……

. 變量名稱

變量命名由(前綴+修飾語)構成。現在比較流行的是一套由微軟的一個匈牙利軟件工程師首先使用,並且在微軟推廣開來,現在被稱之爲匈牙利命名法的命名規則。匈牙利命名法規定,使用表示標識符所對應的變量類型的英文小寫縮寫作爲標識符的前綴,後面在使用表示變量意義的英文單詞或縮寫進行命名。下面是匈牙利命名法中的一些命名方式:

(1)生存期修飾:用l(local)表示局域變量,p(public)表示全局變量,s(send)表示參數變量

(2)類型修飾:用s(AnsiString)表示字符串,c(char)表示字符,n(number)數值,i(intger)表示整數,d(double)表示雙精度,f(float)浮點型,b(bool)布爾型,d(date)表示日期型.

例如:
li_length表示整形的局域變量,是用來標識長度的.ls_code表示字符型的局域變量,用來標識代碼.

. 控件名稱

控件命名由(前綴+修飾語)構成。前綴即爲控件的名稱。

按鈕變量  Button+Xxxxxxx    例如:ButtonSave,ButtonExit,ButtonPrint等
題標變量  Label+Xxxxxxxx    例如:LabelName,LabelSex等
數據表變量 Table+Xxxxxx      例如:TableFile,TableCount等
查詢變量  Query+Xxxxxx      例如:QueryFile,QueryCeneter等
數據源變量 DataSource+Xxx      例如:DataSourceFile,DataSourceCenter等
。。。。。。。。。。。。。。。。
(注:對於與表有關的控件“修飾語”部分最好直接用表名。)

. Package 的命名

Package 的名字應該都是由一個小寫單詞組成。

. Class 的命名

Class 的名字必須由一個或數個能表達該類的意思的大寫字母開頭而其它字母都小寫的單詞或縮寫組成,這樣能使這個 Class 的名稱能更容易被理解。

. Class 變量的命名

變量的名字必須用一個小寫字母開頭。後面的單詞用大寫字母開頭。對於類的成員變量,在對其標識符命名時,要加上代表member(成員)的前綴m_。例如一個標識符爲m_dwFlag,則它表示的變量是一個類型爲雙字的成員變量,它是代表一個標誌。

. Static Final 變量的命名

Static Final 變量的名字應該都大寫,並且指出完整含義。

. 參數的命名

參數的名字必須和變量的命名規範一致。

. 數組的命名

數組應該總是用下面的方式來命名:
byte[] buffer;  
 
而不是: 
byte buffer[];

. 方法的參數
 
使用有意義的參數命名,如果可能的話,使用和要賦值的字段一樣的名字:

SetCounter(int size)
{
 this.size = size;
}

. 神祕的數

首先要說什麼是神祕的數。我們在程序裏經常會用到一些量,它是有特定的含義的。例如,現在我們寫一個薪金統計程序,公司員工有50人,我們在程序裏就會用50這個數去進行各種各樣的運算。在這裏,50就是&quot;神祕的數&quot;。爲什麼稱它爲神祕呢?因爲別的程序員在程序裏看到50這個數,不知道它的含義,只能靠猜了。

在程序裏出現&quot;神祕的數&quot;會降低程序的可讀性,應該儘量避免。避免的方法是把神祕的數定義爲一個常量。注意這個常量的命名應該能表達該數的意義,並且應該全部大寫,以與對應於變量的標識符區別開來。例如上面50這個數,我們可以定義爲一個名爲NUMOFEMPLOYEES的常量來代替。這樣,別的程序員在讀程序的時候就可以容易理解了。

六、程序編寫風格

. exit()

exit 除了在 main 中可以被調用外,其他的地方不應該調用。因爲這樣做不給任何代碼代碼機會來截獲退出。一個類似後臺服務地程序不應該因爲某一個庫模塊決定了要退出就退出。

. 異常

申明的錯誤應該拋出一個RuntimeException或者派生的異常。 
頂層的main()函數應該截獲所有的異常,並且打印(或者記錄在日誌中)在屏幕上。

. 垃圾收集

JAVA使用成熟的後臺垃圾收集技術來代替引用計數。但是這樣會導致一個問題:你必須在使用完對象的實例以後進行清場工作。比如一個prel的程序員可能這麼寫:

 ...
 {
  FileOutputStream fos = new FileOutputStream(projectFile);
  project.save(fos, &quot;IDE Project File&quot;); 
 }
 ...
 
除非輸出流一出作用域就關閉,非引用計數的程序語言,比如JAVA,是不能自動完成變量的清場工作的。必須象下面一樣寫:

 FileOutputStream fos = new FileOutputStream(projectFile);
 project.save(fos, &quot;IDE Project File&quot;); 
 fos.close();

. Clone

下面是一種有用的方法: 
implements Cloneable

public
Object clone()
{
 try 
 {
  ThisClass obj = (ThisClass)super.clone();
  obj.field1 = (int[])field1.clone();
  obj.field2 = field2;
  return obj;
 } 
 catch(CloneNotSupportedException e) 
 {
  throw new InternalError(&quot;Unexpected CloneNotSUpportedException: &quot; + e.getMessage());
 }
}

. final 類

絕對不要因爲性能的原因將類定義爲 final 的(除非程序的框架要求) 
如果一個類還沒有準備好被繼承,最好在類文檔中註明,而不要將她定義爲 final 的。這是因爲沒有人可以保證會不會由於什麼原因需要繼承她。
 
. 訪問類的成員變量
 
大部分的類成員變量應該定義爲 protected 的來防止繼承類使用他們。 
注意,要用&quot;int[] packets&quot;,而不是&quot;int packets[]&quot;,後一種永遠也不要用。

public void setPackets(int[] packets) 

 this.packets = packets; 
}
CounterSet(int size)
{
 this.size = size;
}

. byte 數組轉換到 characters

爲了將 byte 數組轉換到 characters,你可以這麼做:

&quot;Hello world!&quot;.getBytes();

. Utility 類

Utility 類(僅僅提供方法的類)應該被申明爲抽象的來防止被繼承或被初始化。

. 初始化
 
下面的代碼是一種很好的初始化數組的方法:

objectArguments = new Object[] 

 arguments 
};

. 枚舉類型
 
JAVA 對枚舉的支持不好,但是下面的代碼是一種很有用的模板:

class Colour 
{
   public static final Colour BLACK = new Colour(0, 0, 0);
   public static final Colour RED = new Colour(0xFF, 0, 0);
   public static final Colour GREEN = new Colour(0, 0xFF, 0);
   public static final Colour BLUE = new Colour(0, 0, 0xFF);
   public static final Colour WHITE = new Colour(0xFF, 0xFF, 0xFF);
}
 
這種技術實現了RED, GREEN, BLUE 等可以象其他語言的枚舉類型一樣使用的常量。 他們可以用 '==' 操作符來比較。 
但是這樣使用有一個缺陷:如果一個用戶用這樣的方法來創建顏色 BLACK

new Colour(0,0,0)

那麼這就是另外一個對象,'=='操作符就會產生錯誤。她的 equal() 方法仍然有效。由於這個原因,這個技術的缺陷最好註明在文檔中,或者只在自己的包中使用。

. 混合使用 AWT 和 Swing 組件

如果要將 AWT 組件和 Swing 組件混合起來使用的話,請小心使用。實際上,儘量不要將他們混合起來使用。

. 滾動的 AWT 組件

AWT 組件絕對不要用 JscrollPane 類來實現滾動。滾動 AWT 組件的時候一定要用 AWT ScrollPane 組件來實現。

. 避免在 InternalFrame 組件中使用 AWT 組件
 
儘量不要這麼做,要不然會出現不可預料的後果。

. Z-Order 問題

AWT 組件總是顯示在 Swing 組件之上。當使用包含 AWT 組件的 POP-UP 菜單的時候要小心,儘量不要這樣使用。


八、性能

 在寫代碼的時候,從頭至尾都應該考慮性能問題。這不是說時間都應該浪費在優化代碼上,而是我們時刻應該提醒自己要注意代碼的效率。比如:如果沒有時間來實現一個高效的算法,那麼我們應該在文檔中記錄下來,以便在以後有空的時候再來實現她。

 不是所有的人都同意在寫代碼的時候應該優化性能這個觀點的,他們認爲性能優化的問題應該在項目的後期再去考慮,也就是在程序的輪廓已經實現了以後。

. 不必要的對象構造

不要在循環中構造和釋放對象

. 使用 StringBuffer 對象

在處理 String 的時候要儘量使用 StringBuffer 類,StringBuffer 類是構成 String 類的基礎。String 類將 StringBuffer 類封裝了起來,(以花費更多時間爲代價)爲開發人員提供了一個安全的接口。當我們在構造字符串的時候,我們應該用 StringBuffer 來實現大部分的工作,當工作完成後將 StringBuffer 對象再轉換爲需要的 String 對象。比如:如果有一個字符串必須不斷地在其後添加許多字符來完成構造,那麼我們應該使用 StringBuffer 對象和她的 append() 方法。如果我們用 String 對象代替 StringBuffer 對象的話,會花費許多不必要的創建和釋放對象的 CPU 時間。

. 避免太多的使用 synchronized 關鍵字

避免不必要的使用關鍵字 synchronized,應該在必要的時候再使用她,這是一個避免死鎖的好方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章