項目開發規范

一、目的   

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

    本文在參考業界已有的編碼風格的基礎上,描述了一個基于 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. 及少量的其它注釋,如自定義變量的注釋、代碼書寫時間等。

  注釋有塊注釋和行注釋兩種,分別是指:"/**/"和"http://"建議對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就是"神秘的數"。為什么稱它為神秘呢?因為別的程序員在程序里看到50這個數,不知道它的含義,只能靠猜了。

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

六、程序編寫風格

. exit()

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

. 異常

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

. 垃圾收集

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

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

 FileOutputStream fos = new FileOutputStream(projectFile);
 project.save(fos, "IDE Project File";
 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("Unexpected CloneNotSUpportedException: " + e.getMessage());
 }
}

. final 類

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

public void setPackets(int[] packets)
{
 this.packets = packets;
}
CounterSet(int size)
{
 this.size = size;
}

. byte 數組轉換到 characters

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

"Hello world!".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,應該在必要的時候再使用她,這是一個避免死鎖的好方法。

                                                                                  rivershan 原創于2002.11.5