本文檔主要是為Delphi開發人員提供一個原始碼書寫標準,以及程式和文件的命名標準,使他們在程式設計時有一致格式可遵循。這樣,每個程式設計人員寫的程式碼能夠被其他人理解。
縮排就是每級間有兩個空格。不要在原始程式碼中放置製表符。這是因為,製表符的寬度隨著不同的設定和程式碼管理實用程式(列印、文件及版本控制等)而不同。
透過使用Tools|Environment 選單,在Environment Options 對話框的General頁上,不要選取Use Tab Character 和Optional Fill 複選框,這樣,製表符就不會被儲存。
邊距設定為80個字元。原始碼一般不會因為寫一個單字而超過邊距,但本規則比較靈活。只要可能,長度超過一行的語句就應該用逗號或運算子換行。換行後,應縮排兩個字元。
begin 語句必須單獨佔一行。例如,下面第一行是錯誤的,而第二行正確:
for i:=0 to 10 do begin // 錯, begin 與for 在同一行
for i:=0 to 10 do // 對, begin 在另外一行中
begin
本規則的一個特殊情況是,當begin 為else 語句的一部分時,例如:
if some statement = then
begin
. . .
end
else begin
Some Other Statement;
end;
注意:end 語句總是單獨一行。當begin 不為else 語句的一部分時,對應的end 語句與begin 語句的縮排量相同。
我們通常使用“{...}”類型的區塊註釋,以前的“(*...*)”類型的區塊註釋用於臨時註釋掉暫不使用的程式碼,從Delphi 2開始支援“//”行註釋,如果決定不在支援Delphi 2.0以下的版本,可以使用「//」註釋。
在左括號與下一字元之間沒有空格。同樣,右括號與前一字元也沒有空格。下面的例子示範了正確與不正確的空格。
CallPRoc( Aparameter ); // 錯!
CallProc(Aparameter); // 正確!
不要在語句中包含多餘的括號。在原始碼中,括號只有在確實需要時才使用。下面的例子示範了正確與不正確用法:
if (I=42) then // 錯,括號是多餘的
if (I=42) 或 (J=42) then // 正確,必須使用括號
Object Pascal 語言的保留字和關鍵字總是完全的小寫。以下是Delphi 5保留字清單:
and | array | as | asm |
begin | case | class | const |
constructor | destructor | dispinterface | div |
do | downto | else | end |
except | exports | file | finalization |
finally | for | function | goto |
if | implementation | in | inherited |
initialization | inline | interface | is |
label | library | mod | nil |
not | object | of | or |
out | packed | procedure | program |
property | raise | record | repeat |
resourcestring | set | shl | shr |
string | then | threadvar | to |
try | type | unit | until |
uses | var | while | with |
xor | private | protected | public |
published | automated |
過程名稱應以大寫字母開始,且大小寫交錯以增加可讀性。下面是一個不正確的寫法:
procedure thisisapoorlyformattedroutinename;
改成這樣寫就對了:
procedure ThisIsMuchMoreReadableRoutineName;
只要可能,同一類型的形參應當歸並在一起:
procedure Foo(Param1,Param2,Param3:Imteger;Param4:string);
形參的順序主要要考慮寄存器呼叫規則。最常用的參數應作為第一個參數,並依使用頻率依序由左至右排。輸入參數位於輸出參數之前。範圍大的參數應放在範圍小的參數之前。例如:
SomeProc(aPlanet, aContinent, aCountry, aState, aCity).
有些則例外。例如,在事件處理過程中,TObject 類型的Sender 參數往往是第一個要傳遞的參數。
要讓記錄、陣列、短字串或介面類型的參數不能被過程修改,就應當把形參標以Const 。這樣,編譯器將以最有效的方式產生程式碼,確保傳遞的參數不可變。
如果其他類型的參數希望不被過程所修改,也可以標示Const 。儘管這對效率沒有影響,但這給過程的呼叫者帶來了更多的資訊。
局部變數用於過程內部,果需要的話,應在過程的入口處立即初始化變數。局部的AnsiString 類型的變數會自動被初始化為空字串,局部的介面和dispinterface類型的變數自動被初始化為nil,局部的Variant和OleVariant類型的變數自動被初始化為Unassigned。
一般不鼓勵使用全域變數。不過,有時候需要用到。即使如此,也應把全域變數限制在需要的環境中。例如,一個全域變數可能只在單元的實作部分是全域的。
全域資料如果將由許多單元使用,就應移動到一個公用單元裡被所有物件使用。全域資料可在宣告時直接初始化為一個值。注意,所有全域變數會自動進行零初始化,因此,請勿將全域變數初始化為諸如0 、nil、或Unassigned等空值。零初始化的全域變數在.EXE檔案中不佔空間。零初始化的資料保存在虛擬的資料段中,而虛擬資料段只在應用程式啟動時才分配記憶體。非零初始化的全域資料則在.EXE檔案中佔空間。
類型標識符是保留字,應全部小寫。 Win32 API 類型常常全部大寫,並且遵循諸如Windows.pas或其他API單元中關於特定類型名稱的規則。其他變數名,第一個字母應大寫,其他字母則大小寫交錯。以下是一些例子:
var
MyString: string; // 保留字
WindowsHandle: HWND; // Win32 API 類型
I: Integer; //在System單元中引入的類型標識
不鼓勵使用Real類型,因為它只是為了與舊的Pascal程式碼相容而保留的。通常情況下,對於浮點數應使用Double。 Double可被處理器最佳化,是IEEE定義的標準的資料格式。當需要比Double提供的範圍更大時,可以使用Extend。 Extend是intel專用的類型,java不支援。當浮點變數的物理位元組數很重要時(可能使用其他語言編寫DLL),則應使用Single。
一般不建議使用Variant和OleVariant。但是,當資料類型只有在運行期才知道時(常常是在COM和資料庫應用的程式中),這兩個類型對程式設計就有必要。當進行諸如自動化ActiveX控件的COM編程時,應使用OleVariant;而對於非COM編程,則應使用Variant。這是因為,Variant能夠有效地保存Delphi的原生字串,而OleVariant則將所有字串轉換為OLE字串(即WideChar字串),且沒有引用計數函數。
在if/then/else語句中,最有可能執行的情況應放在then子句中,不太可能的情況放在else子句中。為了避免出現許多if語句,可以使用case語句來代替。如果多於5級,不要使用if語句。請改用更清楚的方法。不要在if語句中使用多餘的括號。
如果在if語句中有多個條件要測試,應依照計算的複雜程度從右向左排。這樣,可以使程式碼充分利用編譯器的短路估算邏輯。例如,如果Condition1比Condition2快,Condition2比Condition3快,則if語句一般應這樣建構:
if Condition1 and Condition2 and Condition3 then
如果Condition3為False的機會很大,利用短路估算邏輯,我們也可以把Condition3放在最前面:
if Condition3 and Condition1 and Condition2 then
case語句中每種情況的常數應依數字或字母的順序排列。每種情況的動作語句應簡短且通常不超過4 - 5 行程式碼。如果動作太複雜,應將程式碼單獨放在一個過程或函數中。 Case語句的else子句只用於預設情況或錯誤偵測。
case語句遵循一般的縮排和命名規則。
建議不要使用Exit過程來退出while循環。如果需要的話,應使用循環條件退出循環。所有對while迴圈進行初始化的程式碼應位於while入口前,且不要被無關的語句隔開。任何業務的輔助工作都應在循環後立即進行。
如果迴圈次數是確定的,應當用for語句代替while語句。
repeat語句類似while循環,且遵循同樣的規則。
with語句要小心使用。若要避免過度使用with語句,尤其是在with語句中使用多個物件或記錄。例如:
with Record1,Record2 do
這些情況很容易迷惑程式設計人員,且導致除錯困難。
with語句也遵循本章關於命名和縮排的規則。
異常處理主要用於糾正錯誤和保護資源。這意味著,凡是分配資源的地方,都必須使用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;
如果你希望在發生異常時執行一些任務,可以使用try...except。通常,沒有必要為了簡單地顯示一個錯誤訊息而使用try...except,因為application物件能夠自動根據上下文做到這一點。如果要在子句中啟動預設的異常處理,可以再次觸發異常。
不鼓勵使用帶有else子句的try...except,因為這將阻塞所有的異常,包括你沒有準備處理的異常。
過程與函數名應有意義。進行一個動作的過程最好在名稱前加上表示動作的動詞為前綴。例如:
procedure FormatHardDrive;
設定輸入參數值的過程名稱應以Set 為其前綴,例如:
procedure SetUserName;
取得數值的過程名稱應以Get 為其前綴,例如:
function GetUserName:string;
所有形參的名稱都應表達它的用途。如果適當的話,形參的名稱最好以字母a 為前綴,例如:
procedure SomeProc(aUserName:string; aUserAge:integer);
當參數名稱與類別的特性或欄位同名時,前綴a 就有必要了。
當兩個單元中含有相同名稱的過程時,如果呼叫這個過程,實際被呼叫的是Uses 子句中較後出現的那個單元中的過程。為避免這種情況,可在方法名前加上想要的單元名,例如:
SysUtils.FindClose(SR);
或Windows.FindClose(Handle);
變數的名稱應當能夠表達出它的用途。循環控制變數常為單一字母,諸如I 、J 或K 。也可以使用更有意義的名稱,例如UserIndex。布林變數名必須能清楚表示出True 和False 值的意義。
局部變數遵循其他變數的命名規則。
全域變數以大寫字母“G”打頭,並遵循其他變數的命名規則。
枚舉型別名必須代表枚舉的用途。名稱前要加T字元作為前綴,表示這是個資料型態。枚舉類型的標識符列表的前綴應包含2 - 3 個小寫字符,來彼此關聯。例如:
TSongType=(stRock, stClassical, stCountry, stAlternative, stHeavyMetal, stRB);
枚舉類型的變數實例的名稱與型別相同,但沒有前綴T ,也可以給變數一個更特殊名稱,諸如:FavoriteSongTypel、FavoriteSongType2等等。
數組類型名應表達出該數組的用途。類型名必須加上字母“T”為前綴。如果要宣告一個指向陣列類型的指針,則必須加字母P 為前綴,且聲明在類型宣告之前。例如:
type
PCycleArray = ^TCycleArray;
TCycleArray=array[1..100] of integer;
實際上,數組類型的變數實例與類型名稱相同,但沒有“T”前綴。
記錄類型名應表達出記錄的用途。類型名必須加上字母T為前綴。如果要宣告一個指向記錄類型的指計,則必須加字母P為前綴,且其宣告在類型宣告之前。例如:
type
PEmployee = ^TEmployee;
TEmployee = record
EmployeeName: string;
EmployeeRate: Double;
end;
類別的名稱應表達出類別的用途。一般的類別名稱前要加字母“T”,如果是介面類別那麼類別名稱前要加“I”,錯誤異常類別的類別名稱前要加“E”,而類別參考類型(Class-reference type)則要在類別名後加“Class”。例如:
type
TCustomer = class(TObject);
ICustomer = interface;
TCustomerClass = class of TCustomer
ECustomerException = class(Exception);
類別的實例名稱通常與類別名稱相同,只不過沒有前綴“T”。
var
Customer: TCustomer;
注意:關於元件的命名,請參閱「元件類型」。
欄位的命名遵循與變數相同的規則,只不過要加前綴F ,表示這是欄位。
所有欄位必須為私有。如果要在類別的作用域之外存取字段,可藉助於類別的屬性來實現。
方法的命名遵循與過程和函數相同的規則。
當你不希望一個方法被衍生類別覆寫時,應使用靜態方法。
當你希望一個方法能被衍生類別覆蓋,應使用虛擬方法(virtual)。如果類別的方法要被多個衍生類別直接或間接地使用,則應當用動態方法(dynamic)。例如,某一個類別含有一個被頻繁覆蓋的方法,並有100個派生類,則應將方法定義為動態的,這樣可以減少記憶體的開銷。
如果一個類別要建立實例,則不要使用抽象方法。抽象方法只能在那些從不建立實例的基底類別中使用。
所有屬性存取方法應定義在類別的私有或保護部分。屬性存取方法遵循與過程和函數相同的規則。用於讀取的方法應加上「Get」前綴,用於寫入的方法應加上「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;
儘管不是必須,但還是建議你使用寫入存取方法來存取代表私有欄位屬性。
屬性作為私有欄位的存取器,遵循與欄位相同的命名規則,只不過沒有F前綴。屬性名應為名詞,而不是動詞。屬性是數據,而方法是動作。數組屬性名應為複數,而一般的屬性應為單數。
元件的命名與類別的命名類似,只不過當它與其它元件名稱衝突時,你可以加上3個字元的前綴,用以識別公司、個人或其他實體。例如,一個時鐘元件可以這樣聲明:
TddgClock = class(TComponent)
注意,作為前綴的3 個字元要小寫。
元件實例的名稱應能描述其實際意義,這裡命名規則使用了一個變更的匈牙利前綴命名規範。使用前綴而不使用後綴的原因是在搜尋時,在物件檢視器和程式碼探索器中搜尋構件的名字比搜尋構件的類型更容易。在這個標準中,元件實例名包括兩個部分:前綴和性質識別名。
元件的前綴多是表現元件類型的字母縮寫。請參閱下面表中的元件前綴:
元件類別名稱 | 元件前綴 |
TActionList, TAction表示動作的清單項目 | act |
TButton, TSpeedButton, TBitBtn等所有的按鈕類 | btn |
TCheckBox, TDBCheckBox等所有的檢查框 | chk |
TRadioButton單選按鈕類 | rdo |
TToolBar工具條 | tb |
TMainMenu所有的主選單類 | mm |
TMainMenuItem所有的選單項目類 | mi |
TPopupMenu所有的彈出式選單類 | pm |
TPopupMenuItem所有的彈出式選單項目類 | pmi |
TLabel, TStaticText等所有用來顯示的標籤類 | lbl |
TPanel等所有的面板類 | pnl |
TPageControl等所有的頁式控制項類 | pgc |
TEdit, TMaskEdit等所有的單行編輯框類 | edt |
TMemo, TRichEdit等所有的多行編輯框類 | mmo |
TDrawGrid, TStringGrid等所有的網格類 | grd |
TAnimate等所有的動畫類 | ani |
TImageList等所有的圖片列表類 | il |
TImage等圖片類 | img |
TChart圖表類 | cht |
TComboBox, TDBComboBox等所有的下拉式列錶框類 | cbo |
TListBox, TDBList等所有的列錶框類 | lst |
TTreeView | tv |
TListView | lv |
THotKey | hk |
TSplitter等所有的分隔符號類 | spt |
TOpenDialog等所有的對話框元件類 | dlg |
TTable等所有的資料表類 | tbl |
TQuery等所有的SQL查詢類別元件 | qry |
TClientDataSet所有的客戶資料集元件 | cds |
TDataSource | ds |
TDatabase | db |
TSockConnection,TDCOMConnection等連接元件類 | con |
TQuickRep, TFastReport等所有的報表元件類 | rpt |
TDDEClientConv,TDDEClientItem等所有的DDE元件類 | dde |
TMonthCalendar等所有的日曆類 | cal |
TGroupBox等控制項類 | grp |
如上所示,元件類型前綴是從分析描述元件的類型性質而來的。通常情況下,下面的規則描述如何定義一個元件類型前綴:
注意:元件的前綴是為了表示出元件的類型,是按鈕,還是標籤等等,因此沒有必要為每個特別元件類別建立一個元件前綴,如: TMyButton的元件前綴仍為btn。
元件性質標識名是元件意圖的描述。例如,一個用於關閉窗體的TButton元件實例可命名為btnClose。一個編輯姓名的元件實例可命名為edName。
窗體或對話方塊類型的名稱應表達出窗體的用途,如果是窗體要加“Tfrm”前綴,如果是對話框要加“Tdlg”,後跟描述性名。例如,About窗體類型名稱為:
TfrmAbout = class(TForm)
主窗體的類型名稱為:
TfrmMain = class(TForm)
客戶登入窗體的類型名稱為:
TfrmCustomerEntry = class(TForm)
登陸對話框的類型名稱為:
TdlgLogin = class(TForm)
窗體實例的名稱與對應的型別名稱相同,但沒有前綴T 。例如,前面提到的窗體類型與實例的名稱為:
類型名稱 | 實例名 |
TfrmAbout | frmAbout |
TfrmMain | frmMain |
TfrmCustomerEntry | frmCustomerEntry |
TdlgLogin | dlgLogin |
除非特別原因,只有主窗體才自動產生。其他所有窗體必須從Project Options對話方塊的自動產生清單中刪除。更進一步資訊,請參閱後面幾節。
所有窗體單元都應含有實例化函數,用於建立、設定、模式顯示和釋放窗體。這個函數將會傳回由窗體傳回的模式結果。傳遞給這個函數的參數遵循參數傳遞的規則。之所以要這樣封裝,是為了方便程式碼的重用和維護。
窗體的變數應從單元中移走,改在窗體實例化函數中作為局部變數定義(注意,要求從Project Options對話框的自動生成列表中移走該窗體。請看前面的內容。
例如,下面的單元檔案示範了GetUserData的實例化函數。
Unit UserDataFrm;
Interface
Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
Type
TfrmUserData = 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
frmUserData: TfrmUserData;
begin
frmUserData := TfrmUserData.Create(Application);
frmUserData.Caption:='Getting User Data' ;
Result : = frmUserData.ShowModal;
if Result=mrOK then
begin
aUserName := frmUserData.edtUserName.Text;
aUserID := StrToInt(frmUserData.edtUserID.Text);
end;
finally
frmUserData.Free;
end;
end;
End.
如果一個窗體結構過於複雜,就必須將其分化成為一個主窗體框架以及嵌入到主窗體框架的若干子窗體框架。如:
TfrmMainFrame: TfrmInfoFrame,TfrmEditorFrame
使用窗體框架,主要是為了解決介面和程式碼重複使用問題,以及提高單元程式碼的內聚力(劃分後,每個窗體框架為一個獨立單元),從而提高軟體工程品質。你必須提煉出介面關聯程式碼(可重複使用的)和應用關聯程式碼(不能重複使用的)。
資料模組類型名稱應表達出它的用途,且要加前綴“Tdm”,後面跟著描述性名稱。例如,Customer資料模組的型別名稱為:
TdmCustomer = class(TDataModule)
Orders 資料模組的型別名稱為:
TdmOrder = class(TDataModule)
資料模組實例的名稱應與對應的型別名稱相同,但沒有前綴T 。例如,前面的資料模組類型、實例名稱如下:
類型名稱 | 實例名 |
TdmCustomer | dmCustomer |
TdmOrder | dmOrder |
建議在所有來源文件、專案文件和單元文件中使用結構化的文件頭資訊。一個文件頭至少應包含以下資訊:
{
Copyright @ Year by Authors
}
項目文件的名稱應具有描述意義。例如,「The Delphi 5 Developer's Guide Bug Manager 」的專案名稱為DDGBugs.dpr,一個系統資訊程式的名稱為SysInfo.dpr。
窗體檔案的名稱應表示出窗體的用途,且具有Frm後綴。例如,About窗體的檔案名叫AboutFrm.dfm,主視窗的檔案名叫MainFrm.dfm。
資料模組檔案的名稱應表達出資料模組的作用,且具有DM後綴。例如,Customers資料模組的檔案名叫CustomersDM.dfm。
遠端資料模組檔案的名稱應表達出遠端資料模組的用途。名稱後面要加RDM後綴。例如,Customers遠端資料模組的檔案叫做CustomersRDM.dfm。
單元的名稱應有描述性。例如,應用程式的主窗體單元叫做MaimFrm.pas。
Interface部分的Uses子句應只包含該部分所需的單元。不要包含可能由Delphi自動新增的單元名稱。 Implementation部分的Uses子句應只包含該部分所需的單元,不要有多餘的單元。
Interface部分應只包含需要被外部單元存取的類型、變數、流程與函數的宣告。而且,這些聲明應在Implementation部分之前。
Implementation部分包括本單元私有的型別、變數、過程與函數的實作。
不要在Initialization部分放置花費時間很多的程式碼。否則,將導致應用程式啟動時看起來很慢。
確保釋放所有在Initialization部分中分配的資源。
窗體單元檔案的名稱與對應的窗體名稱相同,只是要將前綴變成後綴。例如,About窗體的單元名稱叫做AboutFrm.pas。主窗體的單元檔名稱叫做MainFrm.pas。
資料模組單元檔案的名稱與對應的資料模組名稱相同。例如,資料模組單元的名稱叫CustomersDM.pas。
通用單元的名稱應表示出它的用途,名稱前要加上「u」前綴。例如,一個實用偵錯工具單元的名稱叫uDebugUtilities.pas,包含全域變數的單元名稱叫uCustomerGlobals.pas。
請注意,一個項目中單元名稱必須是唯一的。通用單元名不能重新命名。
元件單元應放在單獨的路徑中,以表示它們是定義元件的單元。它們一般與項目不放在同一路徑下。單元檔案名稱應表達出其內容。
注意,有關元件命名標準的更多信息,請參閱“元件類型的命名標準”。
元件單元只能含有一個主要元件,這是指出現在元件選項板上的元件。其他輔助性的元件或物件也可以包含在同一單元中。
元件的註冊過程應從元件單元中移走,放在一個單獨的單元中。這個註冊單元用於註冊所有元件、屬性編輯器、元件編輯器、精靈等。
元件註冊應在設計期包中進行。因此,註冊單元應包含在設計期包而非運行期包中。建議註冊單元這樣命名:
xxxReg.pas
其中,xxx字元前綴,以識別元件包名稱或公司、個人、其他實體。例如,註冊單元命名為xxxReg.pas。
運行期包中應只包含所需的單元。那些屬性編輯器和元件編輯器的單元應放在設計期包中。註冊單元也應放在設計期包中。
包的命名遵循下列模式:
dcliiiDescvvCn.pkg —設計期包
iiiDescvvCn.pkg —運行期包
其中,iii代表一個2-3字元的前綴,用於標識公司、個人或其他需要標識的事情,也可不要;Desc表示該控制項包的簡短描述;vv代表包的版本號,你可以根據需要取捨;前綴「dcl」表示設計期包,沒有該前綴表示運行期包;字母「Cn」表示編譯器類型與編譯器版本號,如:Delphi5=D5, Delphi4=D4, CBuilder3=C3...。
注意套件名稱中的lib或std分別表示這是設計期套件還是運行期套件。例如:
dclrbStdComPSD5.pkg —Delphi 5的設計期包
rbStdCompsD5.pkg —Delphi 5的運轉期包
儘管大多數的程式碼自動格式化工具能夠幫你重排原始程式格式,以及更新保留字和標示符的大小寫,但是這最好在使用版本控制之前進行,如果你已經使用了版本控制,建議你不要輕易使用程式碼自動格式化工具,即使多一個空格,版本控制工具也會認為該行已被修改,從而為程式管理帶來不變。