Based on the Coding Standard Document by Steve Teixeira, Xavier Pacheco
編譯:蔡煥麟
日期:Oct-27-2001
1.0 簡介2.0 一般程式碼格式2.1 縮格 3.0 Object Pascal3.1 括號 4.0 檔案(File)
5.0 Forms and Data Modules
6.0 套件(Packages)
7.0 元件(Components)
8.0 補充說明 |
這份文件修改自 Delphi 5 Developer's Guide 裡面的 coding standards,該文所建議的寫碼風格與 Borland 大致相同,我在編寫這份文件時也做了微幅的修改,刪除一些不斷重複的字句,例如,「某某名稱在命名時應該與它的目的相符」這類一般性的原則,只要是「命名」,不管是專案名稱,檔案名稱,元件,函式,變數....都應該取有意義的名稱。
這份文件不可能照顧到所有細節,但是比較重要的部分都提到了,另外一篇 "Delphi 5 元件型態字首" 則包含了元件命名的字首建議清單,應該也有參考價值。
依照文件中的規範來撰寫程式,不但可使程式碼比較清晰易讀,也能夠讓整個開發團隊的程式風格保持一致,這樣小組成員在看別人的程式碼時會輕鬆一些。如果這些風格和你原有的習慣相差很大,或者你正好接手維護別人的程式碼,以下工具可能對你有用處:
DelForExp - 可以幫你重新編排 Delphi 程式碼。
GExperts - 包含許多提高 Delphi 生產力的工具,其中 Code Proofreader 能夠自動幫你修正程式碼的大小寫失誤。
每層的縮格為兩個空白字元,不要使用 Tab 字元(Tools | Editor options 裡面的 Use tab character 選項不要勾選),以免程式碼到了別台機器或用其他文字編輯器顯示、列印時走了樣。
邊界設定為 80 個字元(此為 Delphi 預設值),當程式碼太長而超過邊界時應予以折行,不過對於那些剛好有一個英文字跨在邊界上的情形則可以通容。折行時請盡量斷在逗號或者運算子後面,折下來的那行程式碼應該要往右縮格(兩個空白字元)。
begin 和 end 應該寫在新的一行,且兩者要對齊。下面的第一個例子是錯誤的示範,第二個是正確的:
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 要寫在新的一行也可以。
〔回目錄〕
左括號的右邊和右括號的左邊不應該有空白,參考下面的例子:
CallProc( AParameter ); // 錯誤 CallProc(AParameter); // 正確
Object Pascal 的保留字與關鍵字應該全部都用英文小寫。
函式名稱第一個英文字母應該大寫,其後使用 Camel-capped 格式以便於閱讀。以下是錯誤示範:
procedure thisisapoorlyformattedroutinename;
適當的名稱應該像下面這個:
procedure ThisIsMuchMoreReadableRoutineName;
函式名稱應該要能代表它的功能,讓人能夠不看內部細節就知道該函式大概在做什麼。當函式執行了某項動作,我們通常會以一個動詞為首來命名,例如:
procedure FormatHardDrive;
設定某個變數值的函式應該以 Set 為字首:
procedure SetUserName;
取得某個變數值的函式應該以 Get 為字首:
procedure GetUserName: string;
相同型態的參數應該合併在一個語句中:
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);
變數名稱應和它們的目的相符。
迴圈控制變數通常以 i, j ,k 來命名,當然你也可以用像是 UserIndex 之類有意義的名稱。
布林變數的名稱應該要能充分表達 True 或 False 的意思。
函式的區域變數恨其他變數的命名規則一樣。暫時性的變數也要有適當的名稱。
當有必要的時候,在一進入函式時就要初始化區域變數。AnsiString 型態的區域變數會被自動初始化為空字串,interface 和 dispinterface 型態的區域變數會自動被初始化為 nil,而 Variant 和 OleVariant 區域變數則會被自動初始化為 Unassigned。
你應該盡量避免使用全域變數,當你真的需要時才使用全域變數,而且盡量在一個單元的範圍內使用,如果一個全域變數會被多個單元使用,你應該將它移到一個共用的單元內。
〔回目錄〕
如果型態的名稱屬於保留字,就全部小寫。Win32 API 的型態則全用大寫,一些比較特殊的型態,你應該遵循 Windows.pas 或其他 API 單元裡面的寫法。至於其他的型態,通常是名稱的第一個字母大寫,其餘採用 Camel-capped 方式,參考以下範例:
var MyString: string; // string 是保留字. WindowHandle: HWND; // Win32 API 的型態. AForm: TMyForm; i: Integer; // 用小寫的 integer 也無妨.
不要使用 Real 型態,它是為了相容於舊的 pascal 程式而保留的。一般使用浮點數時,請使用 double 型態,它是 IEEE 定義的標準資料格式。如果需要更大範圍的數值,可使用 Extended,但它是屬於 Intel 規格,而且 Java 不支援。只有當浮點數實踐佔用的位於組大小有意義時才使用 Single(例如使用另一種語言寫成的 DLL 時)。
列舉型態的名稱必須以字母 'T' 開頭,藉此突顯它是一個型態。列舉型態中的識別字名稱應該以 2∼3 個和其型態相關的小寫英文字母開頭,例如:
TSongType = (stRock, stClassical, stCountry, stAlternative, stHeavyMetal, stRB);列舉型態的變數名稱可以直接將型態的第一個字母 'T' 拿掉來命名,或是其他具有特殊意義的名稱,例如:FavoriteSongType1, FavoriteSongType2。
通常不建議使用這兩種型態,它們通常被使用在只有執行時期才能確定其型態的時候,例如 COM 和資料庫程式設計。你應該只有在撰寫 COM 相關程式的時候才使用 OleVariant,因為 Variant 可以有效率地儲存 Delphi 字串,而 OleVariant 則必須將所有字串轉換成 OLE 字串(WideChar 字串),而且不會被參考計數--它們總是被複製成新的字串。
陣列型態的名稱開頭冠上 'T',如果要宣告指標陣列的型態,名稱開頭應該冠上 'P',而且要宣告在陣列型態的前面,參考範例:
type PCycleArray = ^TCycleArray; TCycleArray = array [1..100] of integer;陣列的變數在命名時可以直接將開頭的 'T' 拿掉作為變數名稱。
和陣列型態的規則一樣,參考下面的範例:
type PEmployee = ^TEmployee; TEmployee = record EmployeeName: string EmployeeRate: Double; end;
〔回目錄〕
〔回目錄〕
異常處理常被用於捕捉錯誤和資源回收,例如,假設你你配置了一項資源,你可以在 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;
你可以用此敘述來捕捉異常並進行額外的處理。一般來說,你不會只為了顯示錯誤訊息而使用 try...except,因為 Application 物件會自動幫你顯示錯誤訊息。如果你在 except 裡面處理完該做的事情之後想要再將錯誤傳出去,可以使用 raise 指令。
〔回目錄〕
類別的型態名稱以大寫 'T' 開頭,例如:
type TCustomer = class(TObject)
類別實體的名稱可以直接將前面的 'T' 去掉:
var Customer: TCustomer;
註:元件的命名規則請參考「元件」章節。
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 區段中。
屬性可以讓外界存取類別的私有成員,其命名方式是將私有成員名稱前面的 'F' 去掉。由於屬性代表的是資料,所以名稱應該是名詞而非動詞。
通常屬性是單數,如果是陣列型態的屬性,其名稱應該使用複數。
〔回目錄〕
專案名稱應該要能描述其功能,例如,The Delphi 5 Developer's Guide 的 Bug Manager 的專案名稱就取名為 DDGBugs.dpr。
Form 的檔案名稱以 Frm 作結尾,例如,AboutFrm.pas。主視窗通常會以 MainFrm.pas 命名。
Data Module 的檔案名稱以 DM 作結尾,例如,CustomerDM.pas 或 CustomerDm.pas。
Remote Data Module 的檔案名稱以 RDM 作結尾,例如,CustomerRDM.pas 或 CustomerRdm.pas。
註:MTSDataModule 也是 Remote Data Module 的一種,因此可以使用相同的命名方式。
在 interface 區段的 uses 子句應該只包含 interface 會用到的單元,在 implementation 區段的 uses 子句應該只包含 implementation 會用到的單元,沒有用到的單元應該將其移除。
如其名稱所揭示的,這部分是提供外界存取的介面,在此區段中所宣告的型態,變數,函式都可能會被其他單元使用,因此不希望被外界存取者應置於 implementation 區段內。
此區段所宣告的型態,變數,函式等都只能被此單元使用。
不要把很花時間的工作放在此區段中,否則會拖慢應用程式載入的時間。
在 initialization 區段中配置的資源必須在此處釋放。
例如,一個工具類的單元可能被命名為 BugUtilities.pas(或 BugUtils.pas),而一個專門存放全域變數的單元可能叫做 AppGlobals.pas。
記住這類共用單元可能被專案中所有的程式單元及套件使用,為了避免名稱衝突,在為檔案命名時不要用太普遍的名稱。
元件單元應該要被集中存放在一個獨立的目錄,不應該放在專案目錄下跟其他檔案混在一起。
檔案表頭通常提供了檔案的用途,版權宣告,作者,注意事項...等資訊。你可以採用類似下面的寫法:
(*--------------------------------------------------------------------------- 說明 簡要說明此檔案的用途。 注意事項 版權宣告 修改歷史 ---------------------------------------------------------------------------*)
如果有輸入或輸出的參數,以及跟外部互動的介面也可以在檔頭中註明。
〔回目錄〕
以 TXxxxForm 的方式命名,例如:
TAboutForm = class(TForm)
主視窗通常會這麼寫:
TMainForm = class(TForm)
輸入客戶基本資料的表單可能像這樣:
TCustomerEntryForm = class(TForm)
將型態名稱前面的 'T' 去掉作為 Form 的實體名稱,參考下面的範例:
型態名稱 |
實體名稱 |
TAboutForm | AboutForm |
TMainForm | MainForm |
TCustomerEntryForm | CustomerEntryForm |
只有主視窗才應該被自動建立,除非有特殊的原因,否則其他視窗都應該以動態的方式建立。
當你要使用一個 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.
以 TXxxDataModule 或 TXxxDmod 的方式命名,例如:
TCustomerDmod = class(TDataModule)
將型態名稱前面的 'T' 去掉作為 Data Module 的實體名稱,例如:
var CustomerDmod: TCustomerDmod;
命名方式可以和一般的 Data Modules 一樣使用 TXxxDmod,或者用 TXxxRdmod。例如:
var CustomerRmod: TCustomerRdmod;
〔回目錄〕
執行時期套件應該只包含其中各元件所需參考的單元或套件,其他只有在設計時期才會用到的程式碼或單元,例如屬性/元件編輯器等,就應該放在設計時期套件裡,此外,用來註冊元件的單元也應該放在設計時期套件裡。
套件的檔案名稱名應該仿照以下的例子:
其中 iii 表示用來作為識別的三個字元,可能是你的姓名,公司名稱,或是其他個體的縮寫。字元 vv 表示此套件所適用的 Delphi 版本號碼。而 lib 和 std 則用來區別設計時期和執行時期。
假設某個套件同時有設計時期和執行時期兩種套件,他們會有相似的檔案,例如 Delphi 5 Developer's Guide 的套件會命名為:
〔回目錄〕
元件的命名方式和之前「類別」一節當中提到的命名方式類似,差別僅在於元件名稱前面會冠上三個字元以作為識別,這三個前置字元可以是你個人、公司、或任何個體的名稱縮寫,例如,在 Delphi 5 Developer's Guide 裡面有一個時鐘元件,他的定義是:
TddgClock = class(TComponent)
注意這三個前置字元都是小寫英文字母。
元件所屬的單元裡面應該就只包含一個主元件,所謂「主元件」就是指那些會放到 Delphi 元件盤上面的元件,其他輔助性質的元件則可以跟主元件放在同一個單元裡面。
用來註冊元件的程序應該被抽離出來,並集中到一個獨立的檔案當中,這個專門用來註冊元件的單元同時也可以用來註冊屬性編輯器,元件編輯器,專家(Delphi experts)....等等。
由於註冊元件的動作只有在設計時期套件中才會執行,因此註冊單元應該只被放在設計時期套件裡,而不會出現在執行時期套件裡。註冊單元的檔案名稱建議您以下列型式命名:
XxxReg.pas
其中 Xxx 表示用來作為識別的三個字元,可能是你的姓名,公司名稱,或是其他個體的縮寫。例如,在 Delphi 5 Developer's Guide 裡面用來註冊元件的單元其檔名就是 DdgReg.pas。
元件的命名採用類似「匈牙利表示法」的命名慣例,元件的名稱分為「元件型態字首」以及「元件修飾名稱」兩個部分。
所謂「元件型態字首」,就是一組代表元件型態的英文小寫字母,例如,TButton 的型態字首為 btn,TEdit 的型態字首為 edt。你可以依照下列的步驟挑選出合適的字首:
請參考另一篇 "Delphi 5 元件型態字首" 以取得更多元件型態字首的資訊。
元件修飾詞名稱應該要符合其作用,例如,一個用來關閉視窗的按鈕應該被命名為 "btnClose",輸入員工姓名的文字盒應被命名為 edtEmpName。
〔回目錄〕
種類 | 元件名稱 | 檔案名稱 |
Form | Stk1100Form | Stk1100Frm.pas |
Data Module | Stk1100Dmod | Stk1100Dm.pas |
Remote Data Module | Stk1100Rdmod | Stk1100Rdm.pas |
MTS Object/Remote DataModule | Stk1100Object | Stk1100Obj.pas |
〔回目錄〕