Delphi 5 寫碼標準

Based on the Coding Standard Document by Steve Teixeira, Xavier Pacheco

編譯:蔡煥麟
日期:Oct-27-2001, Aug-7-2002


目錄

1.0 簡介

2.0 一般程式碼格式

2.1 縮格
2.2 邊界
2.3 Begin...End

3.0 Object Pascal

3.1 括號
3.2 保留字與關鍵字
3.3 程序與函式
3.4 變數

3.4.1 變數命名與格式
3.4.2 區域變數
3.4.3 全域變數

3.5 型態

3.5.1 大小寫慣例
3.5.2 浮點數型態
3.5.3 列舉型態(Enumerated Types)
3.5.4 Variant 和 OleVariant 型態
3.5.5 結構型態

3.6 陳述句(statements)

3.6.1 if 陳述句
3.6.2 case 陳述句
3.6.3 while 陳述句
3.6.4 for 陳述句
3.6.5 repeat 陳述句
3.6.6 with 陳述句

3.7 結構化異常處理

3.7.1 try...finally
3.7.2 try...except

3.8 類別(classes)

3.8.1 命名/格式
3.8.2 欄位(Fields)
3.8.3 方法 (Methods)

  • 命名/格式
  • 靜態方法(Static Methods)
  • 虛擬/動態方法(Virtual/Dynamic Methods)
  • 抽象方法(Abstract Methods)
  • 屬性存取方法(Property-Access Methods)
3.8.4 屬性(Properties)

4.0 檔案(File)

4.1 Project Files
4.2 Form Files
4.3 Data Module Files
4.4 Remote Data Module Files
4.5 Unit Files

4.5.1 一般單元結構
4.5.2 一般用途的單元
4.5.3 元件單元

4.6 檔案表頭(File Headers)

5.0 Forms and Data Modules

5.1 Forms

5.1.1 Form 型態命名標準
5.1.2 Form 實體命名標準
5.1.3 自動建立的 Forms
5.1.4 建立 Modal Form 的函式

5.2 Data Modules

5.2.1 Data Module 型態命名標準
5.2.2 Data Module 實體命名標準

5.3 Remote Data Modules

6.0 套件(Packages)

6.1 執行時期 v.s. 設計時期套件
6.2 檔案命名標準

7.0 元件(Components)

7.1 使用者自訂元件
7.2 元件實體命名慣例

7.2.1 元件型態字首(Component Type Prefixes)
7.2.2 元件修飾詞名稱(Component Qualifier Name)

8.0 補充說明


1.0 簡介

這份文件修改自 Delphi 5 Developer's Guide 裡面的 coding standards,該文所建議的寫碼風格與 Borland 大致相同,我在編寫這份文件時也做了微幅的修改,刪除一些不斷重複的字句,例如,「某某名稱在命名時應該與它的目的相符」這類一般性的原則,只要是「命名」,不管是專案名稱,檔案名稱,元件,函式,變數....都應該取有意義的名稱。

這份文件不可能照顧到所有細節,但是比較重要的部分都提到了,另外一篇 "Delphi 5 元件型態字首" 則包含了元件命名的字首建議清單,應該也有參考價值。

依照文件中的規範來撰寫程式,不但可使程式碼比較清晰易讀,也能夠讓整個開發團隊的程式風格保持一致,這樣小組成員在看別人的程式碼時會輕鬆一些。如果這些風格和你原有的習慣相差很大,或者你正好接手維護別人的程式碼,以下工具可能對你有用處:

DelForExp - 可以幫你重新編排 Delphi 程式碼。

GExperts - 包含許多提高 Delphi 生產力的工具,其中 Code Proofreader 能夠自動幫你修正程式碼的大小寫失誤。

2.0 一般程式碼格式

2.1.縮格

每層的縮格為兩個空白字元,不要使用 Tab 字元(Tools | Editor options 裡面的 Use tab character 選項不要勾選),以免程式碼到了別台機器或用其他文字編輯器顯示、列印時走了樣。

2.2.邊界

邊界設定為 80 個字元(此為 Delphi 預設值),當程式碼太長而超過邊界時應予以折行,不過對於那些剛好有一個英文字跨在邊界上的情形則可以通容。折行時請盡量斷在逗號或者運算子後面,折下來的那行程式碼應該要往右縮格(兩個空白字元)。

2.3.Begin...End

beginend 應該寫在新的一行,且兩者要對齊。下面的第一個例子是錯誤的示範,第二個是正確的:

for I := 0 to 10 do begin  // 錯誤
for I := 0 to 10 do        // 正確
begin

但是當 begin 是接在 else 後面時則可以破例寫在 else 後面:

if (I > 9) then
begin
  ...
end
else begin
  SomeStatement;
end;

當然,else 之後的 begin 要寫在新的一行也可以。

回目錄


3.0 Object Pascal

3.1.括號

左括號的右邊和右括號的左邊不應該有空白,參考下面的例子:

CallProc( AParameter );  // 錯誤
CallProc(AParameter);    // 正確

3.2 保留字與關鍵字

Object Pascal 的保留字與關鍵字應該全部都用英文小寫。

3.3 程序與函式

3.3.1 命名/格式

函式名稱第一個英文字母應該大寫,其後使用 Camel-capped 格式以便於閱讀。以下是錯誤示範:

procedure thisisapoorlyformattedroutinename;

適當的名稱應該像下面這個:

procedure ThisIsMuchMoreReadableRoutineName;

函式名稱應該要能代表它的功能,讓人能夠不看內部細節就知道該函式大概在做什麼。當函式執行了某項動作,我們通常會以一個動詞為首來命名,例如:

procedure FormatHardDrive;

設定某個變數值的函式應該以 Set 為字首:

procedure SetUserName;

取得某個變數值的函式應該以 Get 為字首:

procedure GetUserName: string;

3.3.2 形式參數

相同型態的參數應該合併在一個語句中:

procedure Foo(Param1, Param2, Param3: Integer; Param4: string);

參數名稱應該符合它們所代表的意義。參數名稱可以在前面加上大寫字母 'A':

procedure SomeProc(AUserName: string; AUserAge: integer);

以 'A' 開頭的參數命名方式是一項不成文的慣例,這麼做可以避免當參數名稱和屬性或欄位名稱相同時所造成的混淆。

以下對於參數順序的建議在使用「暫存器呼叫慣例」時特別有幫助(在效能上)。

procedure SomeProc(APlanet, AContinent, ACountry, AState, ACity);

以上關於參數排列的建議可能會有許多例外情形,例如事件處理函式,它總是會以 Sender 最為第一個參數。

如果參數值不會被函式修改,應該在參數前面冠上 const,特別是以下這些型態:record, array, ShortString,  interface。

如果你使用的兩個單元中有相同的函式名稱,當你呼叫該函式時,被叫用的是在 uses 子句中排在後面的單元的函式。為了避免這種情況,你可以明白地指出要呼叫哪個單元的函式,例如:

SysUtils.FindClose(SR);

Windows.FindClose(Handle);

3.4 變數

3.4.1 變數命名與格式

變數名稱應和它們的目的相符。
迴圈控制變數通常以 i, j ,k 來命名,當然你也可以用像是 UserIndex 之類有意義的名稱。
布林變數的名稱應該要能充分表達 True 或 False 的意思。

3.4.2 區域變數

函式的區域變數恨其他變數的命名規則一樣。暫時性的變數也要有適當的名稱。

當有必要的時候,在一進入函式時就要初始化區域變數。AnsiString 型態的區域變數會被自動初始化為空字串,interface 和 dispinterface 型態的區域變數會自動被初始化為 nil,而 Variant 和 OleVariant 區域變數則會被自動初始化為 Unassigned。

3.4.3 全域變數

你應該盡量避免使用全域變數,當你真的需要時才使用全域變數,而且盡量在一個單元的範圍內使用,如果一個全域變數會被多個單元使用,你應該將它移到一個共用的單元內。

回目錄


3.5 型態

3.5.1 大小寫慣例

如果型態的名稱屬於保留字,就全部小寫。Win32 API 的型態則全用大寫,一些比較特殊的型態,你應該遵循 Windows.pas 或其他 API 單元裡面的寫法。至於其他的型態,通常是名稱的第一個字母大寫,其餘採用 Camel-capped 方式,參考以下範例:

var
  MyString: string;    // string 是保留字.
  WindowHandle: HWND;  // Win32 API 的型態.
  AForm: TMyForm;      
  i: Integer;          // 用小寫的 integer 也無妨.

3.5.2 浮點數型態

不要使用 Real 型態,它是為了相容於舊的 pascal 程式而保留的。一般使用浮點數時,請使用 double 型態,它是 IEEE 定義的標準資料格式。如果需要更大範圍的數值,可使用 Extended,但它是屬於 Intel 規格,而且 Java 不支援。只有當浮點數實踐佔用的位於組大小有意義時才使用 Single(例如使用另一種語言寫成的 DLL 時)。

3.5.3 列舉型態(Enumerated Types)

列舉型態的名稱必須以字母 'T' 開頭,藉此突顯它是一個型態。列舉型態中的識別字名稱應該以 2∼3 個和其型態相關的小寫英文字母開頭,例如:

TSongType = (stRock, stClassical, stCountry, stAlternative, stHeavyMetal, stRB);

列舉型態的變數名稱可以直接將型態的第一個字母 'T' 拿掉來命名,或是其他具有特殊意義的名稱,例如:FavoriteSongType1, FavoriteSongType2。

3.5.4 Variant 和 OleVariant 型態

通常不建議使用這兩種型態,它們通常被使用在只有執行時期才能確定其型態的時候,例如 COM 和資料庫程式設計。你應該只有在撰寫 COM 相關程式的時候才使用 OleVariant,因為 Variant 可以有效率地儲存 Delphi 字串,而 OleVariant 則必須將所有字串轉換成 OLE 字串(WideChar 字串),而且不會被參考計數--它們總是被複製成新的字串。

3.5.5 結構型態

陣列型態的名稱開頭冠上 'T',如果要宣告指標陣列的型態,名稱開頭應該冠上 'P',而且要宣告在陣列型態的前面,參考範例:

type
  PCycleArray = ^TCycleArray;
  TCycleArray = array [1..100] of integer;

陣列的變數在命名時可以直接將開頭的 'T' 拿掉作為變數名稱。

和陣列型態的規則一樣,參考下面的範例:

type
  PEmployee = ^TEmployee;
  TEmployee = record
    EmployeeName: string
    EmployeeRate: Double;
  end;

回目錄


3.6 陳述句

3.6.1 if 陳述句

3.6.2 case 陳述句

3.6.3 while 陳述句

3.6.4 for 陳述句

3.6.5 repeat 陳述句

3.6.6 with 陳述句

回目錄


3.7 結構化異常處理(Structured Exception Handling)

異常處理常被用於捕捉錯誤和資源回收,例如,假設你你配置了一項資源,你可以在 finally 子句撰寫釋放資源的程式碼以確保資源會被釋放。

3.7.1 try...finally

在不需要理會錯誤的情形下,可以使用此敘述來確保資源被回收,但是每一個配置資源的動作都應該對應一個 try...finally 敘述,以下的程式碼可能會引發問題:

SomeClass1 := TSomeClass.Create;
SomeClass2 := TSomeClass.Create;
try
  {do some code }
finally
  SomeClass1.Free;
  SomeClass2.Free;
end;

比較安全的寫法應該是:

SomeClass1 := TSomeClass.Create;
try
  SomeClass2 := TSomeClass.Create;
  try
    {do some code }
  finally
    SomeClass2.Free;
  end;
finally
  SomeClass1.Free;
end;

3.7.2 try...except

你可以用此敘述來捕捉異常並進行額外的處理。一般來說,你不會只為了顯示錯誤訊息而使用 try...except,因為 Application 物件會自動幫你顯示錯誤訊息。如果你在 except 裡面處理完該做的事情之後想要再將錯誤傳出去,可以使用 raise 指令。

回目錄


3.8 類別(classes)

3.8.1 命名/格式

類別的型態名稱以大寫 'T' 開頭,例如:

type
  TCustomer = class(TObject)

類別實體的名稱可以直接將前面的 'T' 去掉:

var
  Customer: TCustomer;

註:元件的命名規則請參考「元件」章節。

3.8.2 欄位(Fields)

類別中的欄位,指的就是私有成員(private members),他們的命名規則和一般的變數相同,但是必須在前面冠上大寫 'F' ,表示他們是 fields。

3.8.3 方法(Methods)

Methods 的命名規則和一般函式相同。

當你不希望某個 method 被子類別改寫(override)的話,可以將它宣告為靜態方法。

當你預期某個 method 會被子類別改寫的話,應將它宣告為 virtual。而 dynamic 的使用時機是當一個類別有很多子類別的時候,例如一個類別有 100 個子類別,而這些子類別都會用到某個 method,但是很少會去改寫它,那這個 method 就應該被宣告為 dynamic 以節省記憶體。

註:每一個類別的 virtual methods 需要以一個虛擬方法表(VMT, Virtual Method Table)來記錄。

抽象方法應該只被用於抽象類別中,它們主要是用來定義基礎類別的介面。

存取屬性的方法就是你經常在元件的原始碼中看到的 Get/Set 方法,它們各自對應到屬性的讀取和寫入動作,寫入的方法名稱是以 Set 開頭,參數值的名稱通常以 Value 命名。參考以下範例:

TSomeClass =class(TObject)
private
  FSomeField: Integer;
protected
  function GetSomeField: Integer;
  procedure SetSomeField(Value: Integer);
public
  property SomeField: Integer read GetSomeField write SetSomeField;
end;

這些 Get/Set 方法應該放在 private 或 protected 區段中。

3.8.4 屬性(Properties)

屬性可以讓外界存取類別的私有成員,其命名方式是將私有成員名稱前面的 'F' 去掉。由於屬性代表的是資料,所以名稱應該是名詞而非動詞。

通常屬性是單數,如果是陣列型態的屬性,其名稱應該使用複數。

回目錄


4.0 檔案

4.1 Project Files

專案名稱應該要能描述其功能,例如,The Delphi 5 Developer's Guide 的 Bug Manager 的專案名稱就取名為 DDGBugs.dpr。

4.2 Form & Frame Files

Form 的檔案名稱以 Fm 作結尾,例如,AboutFm.pas。主視窗通常會以 MainFm.pas 命名。

Frame 的檔案名稱以 Frm 作結尾,例如,DataFrm.pas。

4.3 Data Module Files

Data Module 的檔案名稱以 Dm 作結尾,例如 CustomerDm.pas。

4.4 Remote Data Module Files

Remote Data Module 的檔案名稱以 RDM 作結尾,例如,CustomerRDM.pas 或 CustomerRdm.pas。

註:MTSDataModule 也是 Remote Data Module 的一種,因此可以使用相同的命名方式。

4.5 Unit Files

4.5.1 一般單元結構

在 interface 區段的 uses 子句應該只包含 interface 會用到的單元,在 implementation 區段的 uses 子句應該只包含 implementation 會用到的單元,沒有用到的單元應該將其移除。

如其名稱所揭示的,這部分是提供外界存取的介面,在此區段中所宣告的型態,變數,函式都可能會被其他單元使用,因此不希望被外界存取者應置於 implementation 區段內。

此區段所宣告的型態,變數,函式等都只能被此單元使用。

不要把很花時間的工作放在此區段中,否則會拖慢應用程式載入的時間。

在 initialization 區段中配置的資源必須在此處釋放。

4.5.2 一般用途的單元

例如,一個工具類的單元可能被命名為 BugUtilities.pas(或 BugUtils.pas),而一個專門存放全域變數的單元可能叫做 AppGlobals.pas。

記住這類共用單元可能被專案中所有的程式單元及套件使用,為了避免名稱衝突,在為檔案命名時不要用太普遍的名稱。

4.5.3 元件單元

元件單元應該要被集中存放在一個獨立的目錄,不應該放在專案目錄下跟其他檔案混在一起。

4.6 檔案表頭(File Headers)

檔案表頭通常提供了檔案的用途,版權宣告,作者,注意事項...等資訊。你可以採用類似下面的寫法:

(*---------------------------------------------------------------------------
  說明

    簡要說明此檔案的用途。

  注意事項

  版權宣告

  修改歷史
---------------------------------------------------------------------------*)

如果有輸入或輸出的參數,以及跟外部互動的介面也可以在檔頭中註明。

回目錄


5.0 Forms and Data Modules

5.1 Forms

5.1.1 Form 型態命名標準

以 TXxxxForm 的方式命名,例如:

TAboutForm = class(TForm)

主視窗通常會這麼寫:

TMainForm = class(TForm)

輸入客戶基本資料的表單可能像這樣:

TCustomerEntryForm = class(TForm)

5.1.2 Form 實體命名標準

將型態名稱前面的 'T' 去掉作為 Form 的實體名稱,參考下面的範例:

型態名稱

實體名稱

TAboutForm AboutForm
TMainForm MainForm
TCustomerEntryForm CustomerEntryForm

5.1.3 自動建立的 Forms

只有主視窗才應該被自動建立,除非有特殊的原因,否則其他視窗都應該以動態的方式建立。

5.1.4 建立 Modal Form 的函式

當你要使用一個 Form 時,不要用 Delphi 幫你產生的 Form 變數,應該以一個函式來建立及顯示這個 Form,參考下面的範例:

unit UserDataFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TUserDataForm = class(TForm)
    edtUserName: TEdit;
    edtUserID: TEdit;
  private
    {Private declarations }
  public
    {Public declarations }
  end;

function GetUserData(var aUserName: string; var aUserID: Integer): Word;

implementation

{$R *.DFM}

function GetUserData(var aUserName: string; var aUserID: Integer): Word;
var
  UserDataForm: TUserDataForm;
begin
  UserDataForm := TUserDataForm.Create(Application);
  try
    UserDataForm.Caption := 'Getting User Data';
    Result := UserDataForm.ShowModal;
    if Result = mrOK then 
    begin
      aUserName := UserDataForm.edtUserName.Text;
      aUserID := StrToInt(UserDataForm.edtUserID.Text);
    end;
  finally
    UserDataForm.Free;
  end;
end;

end.

5.2 Data Modules

5.2.1 Data Module 型態命名標準

以 TXxxDataModule 或 TXxxDmod 的方式命名,例如:

TCustomerDmod = class(TDataModule)

5.2.2 Data Module 實體命名標準

將型態名稱前面的 'T' 去掉作為 Data Module 的實體名稱,例如:

var
  CustomerDmod: TCustomerDmod;

5.3 Remote Data Modules

命名方式可以和一般的  Data Modules 一樣使用 TXxxDmod,或者用 TXxxRdmod。例如:

var
  CustomerRmod: TCustomerRdmod;

回目錄


6.0 套件(Packages)

6.1 執行時期 v.s. 設計時期套件

執行時期套件應該只包含其中各元件所需參考的單元或套件,其他只有在設計時期才會用到的程式碼或單元,例如屬性/元件編輯器等,就應該放在設計時期套件裡,此外,用來註冊元件的單元也應該放在設計時期套件裡。

6.2 檔案命名標準

套件的檔案名稱名應該仿照以下的例子:

其中 iii 表示用來作為識別的三個字元,可能是你的姓名,公司名稱,或是其他個體的縮寫。字元 vv 表示此套件所適用的 Delphi 版本號碼。而 lib 和 std 則用來區別設計時期和執行時期。

假設某個套件同時有設計時期和執行時期兩種套件,他們會有相似的檔案,例如 Delphi 5 Developer's Guide 的套件會命名為:

回目錄


7.0 元件(Components)

7.1 使用者自訂元件

7.1.1 元件型態命名標準

元件的命名方式和之前「類別」一節當中提到的命名方式類似,差別僅在於元件名稱前面會冠上三個字元以作為識別,這三個前置字元可以是你個人、公司、或任何個體的名稱縮寫,例如,在 Delphi 5 Developer's Guide 裡面有一個時鐘元件,他的定義是:

TddgClock = class(TComponent)

注意這三個前置字元都是小寫英文字母。

7.1.2 元件單元

元件所屬的單元裡面應該就只包含一個主元件,所謂「主元件」就是指那些會放到 Delphi 元件盤上面的元件,其他輔助性質的元件則可以跟主元件放在同一個單元裡面。

7.1.3 使用註冊單元

用來註冊元件的程序應該被抽離出來,並集中到一個獨立的檔案當中,這個專門用來註冊元件的單元同時也可以用來註冊屬性編輯器,元件編輯器,專家(Delphi experts)....等等。

由於註冊元件的動作只有在設計時期套件中才會執行,因此註冊單元應該只被放在設計時期套件裡,而不會出現在執行時期套件裡。註冊單元的檔案名稱建議您以下列型式命名:

XxxReg.pas

其中 Xxx 表示用來作為識別的三個字元,可能是你的姓名,公司名稱,或是其他個體的縮寫。例如,在 Delphi 5 Developer's Guide 裡面用來註冊元件的單元其檔名就是 DdgReg.pas。

7.2 元件實體命名慣例

元件的命名採用類似「匈牙利表示法」的命名慣例,元件的名稱分為「元件型態字首」以及「元件修飾名稱」兩個部分。

7.2.1 元件型態字首(Component Type Prefixes)

所謂「元件型態字首」,就是一組代表元件型態的英文小寫字母,例如,TButton 的型態字首為 btn,TEdit 的型態字首為 edt。你可以依照下列的步驟挑選出合適的字首:

  1. 移除開頭的 'T' 字母,例如,"TButton" 就變成 "Button"。
  2. 移除所有的母音字母,並且全部改成小寫,例如,"Button" 就變成 "bttn","Edit" 變成 "edt"。
  3. 兩個連續的相同字母僅保留一個,例如,"bttn" 變成 "btn"。
  4. 若結果和其他的名稱衝突,可以開始加入母音字母,例如,"TBatton" 的字首和 "TButton" 一樣,因此可以將 "TBatton" 的字首改成 "batn"。

請參考另一篇 "Delphi 5 元件型態字首" 以取得更多元件型態字首的資訊。

7.2.2 元件修飾詞名稱(Component Qualifier Name)

元件修飾詞名稱應該要符合其作用,例如,一個用來關閉視窗的按鈕應該被命名為 "btnClose",輸入員工姓名的文字盒應被命名為 edtEmpName。

回目錄

8.0 補充說明

  1. 如果專案中的每個模組都已經賦予一個代號,例如 STK1100,STK1200...等,命名方式也可以採用變通的方法。參考下面的例子:
    種類 元件名稱 檔案名稱
    Form Stk1100Form Stk1100Frm.pas
    Data Module Stk1100Dmod Stk1100Dm.pas
    Remote Data Module Stk1100Rdmod Stk1100Rdm.pas
    MTS Object/Remote DataModule Stk1100Object Stk1100Obj.pas

回目錄