Delphi编码规范
作者:Tulipsys 更新日期:2003年12月16日
目录
1. 一般的惯例(命名 - 缩进和空格 - 边距 - 大小写 - 注释)
2. 语句(begin…end语句-if语句-case语句-for语句-while语句-repeat语句-with语句-异常处理语句)
3. 过程和函数(命名与格式-形参-变量-类型-自定义类型)
4. 面向对象相关(类的命名与格式-字段-方法-属性-方法的实现)
制定编码规范的目的是为了使一组程序员生成同样风格的代码,使一个团队形成并保持一定的风格。如果这个目标能够实现,那么整个项目的文件看上去就像是一个程序员写的。好性很好玩,但这样的好处是每个程序员的代码都易于为他人所理解,从而会在很大程度上提高代码的可维护性,也因此会降低维护费用。对于任何团队来说,这均是一个十分理想的境界。对于个人,选择或自我生成一种编码规范,并坚持这个规范,同样会产生良好的效果。顺便提一下这是一个十分诱人的目标,不过并不太难实现。
每种程序设计语言都有属于自己的编码规范,编码规范可以说是经验的总结,当然也要借鉴其他的程序设计语言的规范。所以,向别人学习是十分重要的。其次,编码规范的使用是为了简化程序员的工作,“简化”的含义不是减少代码量(相反,很多时候遵从规范会带来更多的代码),而是减少程序员在维护代码时的劳动量。程序设计是一种非常复杂的工作,处理各种各样的关系是令人生畏的,而且各种关系之间还有着千丝万缕的联系。程序员应将大部分精力用来处理关系,而避免在过于细节的问题上浪费心机。如果他一眼就能够明白程序的思路和结构,那么对维护方案就会很快形成。而且,编码规范应该是一个非常人性化的规范,你可以参考,也可以修改,但是要保证易于使用。但是在一个小组中要保证大家使用同样的规范。程序设计是非常灵活的工作,只有灵活的思考,灵活的应用,才可能得到好的结果。另外,使用规范在很大程度上是为了减少程序员的记忆负担。人的思维能力是极其优秀的,而记忆则十分可怜,我们整天面对电脑,她要帮我们做得很重要的事情应该是记忆。所以尽可能发挥程序员的思维优势是我们的目标之一。
最后,程序设计工具对编码规范有很大的影响,这个影响来源于开发商的程序设计风格。同样基于C++,在Microsoft Visual C++和Borland C++ Builder中我们不会使用完全相同的编码规范。Microsoft和Borland有着各自不同的而且十分鲜明的风格。作为用户,我们可以在此基础上有所改变,但是这是有限度的。其实,在做出对供应商和开发工具的选择时,我们同时确定了我们未来的风格。
1. 一般的惯例
1.1 命名
命名的基本原则是名称要能够明确表示数据的功能。
Object Pascal支持长文件名。名称应该使用动词、名词或二者的组合。绝对不可以使用Delphi中定义的保留字和关键字,而且尽量不要使用其他语言中定义的保留字和关键字。尽量使用完整的词语而避免使用缩写、前缀和后缀、下划线或其它符号,不推荐使用匈牙利命名法。
命名规范是为了确保名称的可读性。以匈牙利命名法为代表的命名规范制定了许多前缀和后缀以表示数据的类型、作用域或其它各种属性。在Delphi中,你当然可用这种方法,但是这不是推荐的方法。有个原因是这类命名规范带来个太多额外的记忆任务,另外一个原因是由Delphi自身的特点决定的。Delphi的强制类型检查会自动监测所有的变量使用状况,所以只需要我们稍加留心(注意单词的大小写)而不必费劲的添加五花八门的前缀。另外,对数据的考虑要基于含义而不是类型或作用域,注意力应该放在放在程序结构、逻辑关系和设计思路上。所以在Delphi中只需要使用完整的词语组合来命名,不要考虑其它,当然应该尽可能的保持简洁。
在一些语句(比如for循环)中我们要用到若干个整形数作为计数变量。在此可以简单的使用i、j、k这三个字母作为变量名称。这是在Fortran语言中形成并被保留下来的习惯,事实证明这非常好用并且易于理解。当然,我们使用更有意义的名称会产生更好的效果,比如:MyCounter。一般来说i、j、k三个字母已完全够用了,否则应该划分出更多的过程或函数。
下面是几个例子:
1. SongsList //表明这是一个歌曲的列表,song使用复数表明歌曲不止一首
2. SetCarColor //表明这是一个设置汽车颜色的函数,若定义了一个TCar类,那么在类中就使用SetColor作为设置汽车颜色的函数成员。
另外要注意对布尔变量的命名。布尔变量的名称要能够明确的表示出True和False的含义。比如说记录某文件是否存在的变量,使用IsFileExisted,就比使用FileExisted好。
最后,永远不要将一个全局变量命名为:Temp或Tmp,但是在过程或函数中如此命名还是允许的。其实对于这条规则存在一点争议,在有的编码规范中更为严格,如此命名是绝对禁止的,即使是在过程或函数中。但是,很多时候这样命名的确很方便,尤其对于过程或函数。如果作为全局变量,很可能会出现类型不匹配的赋值语句,虽然此时编译器会给你很大的帮助,但是难以避免细小错误的发生。总之,遵守此规则会产生较好的效果,但是在必要的情况下没有什么是要严格遵守的。
1.2 缩进和空格
每级之间要缩进两个空格,这样会使程序层次分明,错落有致。千万不要使用制表符,因为制表符的宽度随不同的设置和应用程序的不同而难以保持一致,可不要指望你的程序只在Delphi中察看。另外要注意编辑器的使用,如果你只选择了Delphi,那么没有什么问题;如果你同时还使用了Word等文本处理器,请注意要使用适当的字体,以确保每个字母、符号的宽度相同。用Word等文本处理器打印时,同样要注意字体的选择。
空格的使用同样是为了保持程序的整洁,是程序员能够快速明白程序结构。下面是一些规范和相应的例子:
1. 每个单词之间要留有一个空格。例如:for TMyClass = class(TObject)
2. 在“=”、“<>”、“>=”、“<=”左右要留有一个空格;在“:=”和“:”右边要留有一个空格,而左边不留。例如:if a = b then a:= b;a: integer;
3. 保留字和关键字与左边的符号间要留有一个空格,与右边的符号间不留。例如:procedure ShowMessage; overload;
4. 括号的使用:在过程和函数的定义和调用中,括号与外部的单词和符号之间不留空格;与内部的单词之间不留空格。在if语句的条件判断中,与and、or等保留字之间要使用空格。例如:function Exchange(a: integer; b: integer); if (a = b) and ((a = c) or (a = d)) then … end;
1.3 边距
Delphi编辑器在右边大约第81个字符处留有一条暗线,实际上在Delphi的默认界面下,当分辨率在800*600时,最大化窗口将显示到该暗线左边4个字母处。因此,不要将源代码写到暗线之外,也就是说每行包括前面和中间的空格不要多于80个字符。如果语句过长,那么换行完成,换行后要缩进两个字符。这样也易于打印,在Delphi中超过暗线的部分不会被打印。如果使用Word等文字处理软件打印Delphi程序,超出的部分会调到下一行的首部,这样打印出的程序将难以阅读。所以,尽量在编写代码的时候做好一切调整,不要把这种工作留到打印的时候进行。
换行时要注意保持程序的可读性,尽量保持完整的部分。作为例子,如果函数过长,那么再换行时要将某个完整的参数说明换行,而不要只将数据类型声明换行。下面的前两种写法是正确的,后面的几种写法都是错误的:
function AdditonFiveInputNumber(a: integer; b: integer; c: integer; d: ineger;
e: integer): integer; //正确
function AdditonFiveInputNumber(
a: integer;
b: integer;
c: integer;
d: ineger;
e: integer): integer; //正确
function AdditonFiveInputNumber(a: integer; b: integer; c: integer; d:
ineger; e: integer): integer; //错误
function AdditonFiveInputNumber(a: integer; b: integer; c: integer; d: ineger;
e: integer): integer; //错误
function AdditonFiveInputNumber(a: integer; b: integer; c: integer; d: ineger;
e: integer): integer; //错误
1.4 大小写
所有的自定义名称中的每个单词的首字母要使用大写,其它字母使用小写。Delphi保留字和关键字要全部使用小写。Delphi预定义函数的写法与自定义名称写法相同。Delphi中的基本数据类型要使用小写,扩展的类类型要的前两个字母要大写(类类型的首字母是“T”)。下面是一些例子:
1. 自定义名称:MyFavouriteSong, CarList;
2. 保留字:if (a = b) and ((a = c) or (a = d)) then … end;
3. Delphi预定义函数:ShowMessage('Everything all right');
4. Delphi扩展类类型:MyStrings = TStrings.Create;
1.5 注释
Delphi支持两种注释:块注释({})和单行注释(//)。注释的作用是为了解释程序的设计思路,帮助程序员尽快明白两年前甚至昨天写的程序的思路。这实际上是为了解决记忆问题,大脑不该被过分作为存储器使用,在程序设计中永远不要过分依赖大脑,而要尽可能借助文字。所以说,注释在程序设计语言中是十分重要的方面,尽管很多人(尤其是初学者,也包括相当数量的程序员)对此毫不介意,他们很少写注释。注释的另外一个应用是在程序调试阶段,比如说有两个语句,事先并不知道哪一个更好,于是需要测试:将一条语句前放置"//"(也就是说将这条语句改为注释),运行另一条语句,然后再做相反的工作,我们就可以轻松做出选择。如果是一组语句,那就用块注释,但一定注意要将"{"和"}"放在显眼的位置,比如说放在单独的上下两行。下面是一些使用原则:
1. 多数情况下,在自定义变量、类型的前面放置注释是有必要的。
2. 多数情况下,在单元文件顶部放置注释是必要的。在此,注释中要包含:文件名、创建日期、修改日期、作者、修改作者以及必要的描述。
3. 注释要有意义,不要使用没用的注释。比如:
while i < 8 do
begin
…
i:= i + 1; //Add one to i
end;
注释“//Add one to i”在此是毫无意义的,当然并不是说简单的语句(类似于:i : = i + 1)就不需要注释。因为简单的语句往往会起到十分重要的作用,所以,如果这条语句会使人产生疑问或者让人难以理解,那么就要将此语句的作用标记下来。
4. 不要试图在注释中创建组合图案,除非你认为这十分必要。因为在保持图案完整美观的情况下修改注释是非常困难的。。
5. 要区分临时注释和永久注释,你可以用你的方法在注释中放置特殊符号来区分。这样的好处是易于查找。
6. 对语句的更改要映射到相应的注释中。
7. 注释与代码间要留有明显的间隔,要一眼就能够分清楚哪是语句哪是注释。可以将注释放在代码行的前一行、后一行或留有至少两个空格紧跟在代码的后面,但是在代码与注释在同一行时不要使代码跟在注释的后面,另外,不要将在注释放在代码中间。
2. 语句
2.1 begin…end语句
begin和end语句要独占一行,begin要与上一层的第一个字母对齐,也就是说在换行之后不要留任何空格而直接写begin,end要与所对应的begin对齐。本规则可以用在任何地方。下面是正确的写法:
for i:= 1 to 10 do
begin
…
end;
但在if语句中有个特例,如下:
if Condition then
begin
…
end
else begin
…
end;
这样写的原则是要保证程序足够紧凑。语句分层的好处是使我们面对一个清晰的程序段,但是过分的、不尽合理的分层会使程序过于松散,这同样是需要避免的。
2.2 if语句
将最有可能执行的情况放在then语句中,不太可能的情况放在else子句中。这样对维护来说不会带来多少帮助,但会使程序效率更高。
多级if语句的可读性不强,所以应经尽能避免出现多级if语句。当多于5级时,就该考虑使用case语句代替if语句。
不要在if语句中使用不必要的括号。在源代码中,括号除了语法作用外在就是在必要时对条件分段,以增加程序的可读性。所以在if语句中,如果条件明了而且在语法上不需要括号,那么就不要使用括号;如果条件过于复杂,那么就使用几个括号。总之,一切为了清晰。
如果在if语句中有多个条件要测试,应按照计算的复杂度进行排列。比如有三个条件:Condition1、Condition2和Condition3。按照这个顺序,复杂度依次上升,与就是说Condition1比Condition2快,Condition2比Condition3快,则if语句就这么写:
if Condition1 and Condition2 and Condition3 then
begin
…
end;
2.3 case语句
case语句中每种情况的常量应当按照字母或数字的顺序排列。
每种情况的处理与语句不要行数太多,因为这样会使整个case语句过长。应该尽量使用过程或函数以减少case语句的复杂度。
case语句的else子句只用于处理默认情况或进行错误检测。
2.4 for语句
循环语句共有三种:for语句、while语句和repeat语句。如果循环次数确定,那么就使用for语句;如果在第一次循环之前要实现执行一次以获得初始比较数值,那么就使用repeat语句;其他情况基本上都可以使用while语句。实际上这三条语句可以覆盖所有的循环需要,而且可以互相替代。
在Delphi中要注意,for语句中的循环变量不可以在循环体中被赋值。这是与Turbo Pascal不同的地方。
另外,在循环语句,尤其是多个循环嵌套使用的代码中,要小心使用
break和exit关键字。
2.5 while语句
建议不要使用exit来退出while循环。退出循环的最好方法是通过循环条件。如果在循环体中要考虑到意外处理,那么就要搞清楚处理的方式和对相关变量的影响。
2.6 repeat语句
repeat语句类似于while语句,且遵循同样的规则。
2.7 with语句
with语句是一类非常容易出错的语句,使用with语句可以有效避免重复的输入工作。但是这样会使程序难以检查。所以,不要随便使用with语句,而且绝对不要使用带有两个或多于两个对象或记录的with语句。顺便提一下,尽量不要随便使用with语句的原因不是因为易于出错,而是因为难以排错。考虑下面的例子:
with Label1, Label2 do
begin
…
Caption := “Delphi”;
…
end;
这样会出现什么问题呢?因为这条语句夹在众多语句之中,所以当程序出现问题时很难搞明白是怎么回事。
3. 过程和函数
3.1 命名与格式
(1) 过程和函数的名称应当由一个或多个完整的单词组成。名称的首字母应当大写,其后每个单词的首字母也应当大写,其它字母则小写。如果名称过长,可以考虑使用单词的缩写,但是要使用常用的缩写,比如:Tmp、Str。对于使用英文简写的单词或词组要全部使用大写字母,比如:ID、UTS。下面是几个正确的写法:GetUserID、InputPassword、FindUserListFile、ImportUserNameFromStr。
(2) 过程或函数的名称应当能够简单的表示该过程或函数的行为。使用动词和名词为过程和函数命名,动词表示行为,而名词表示行为的对象或目的。根据惯例,设置输入参数值的过程名要以Set为前缀,读取数值的函数名要以Get为前缀。下面是几个正确的命名:SaveToFile、ReadFromFile、SetUserID、GetUsersNumber。
3.2 形参
(1) 格式:形参的排列要按照日常惯用的顺序,比如:姓名、性别、年龄。而且在此基础上要尽可能地把同一类型的形参归并在一起。例如:
procedure SetUserInformation(Name, ID: string; Age: integer);
(2) 命名:所有的形参的命名要能够表达出该形参的用途。在合适的情况下,形参的名称最好以大写字母A为前缀。
(3) 读写规则:Delphi中有const和var形参,分别表示了对形参的读写控制。在形参列表中最好能够明确表示。
3.3 变量
(1) 使用规则:在程序的开始阶段要对所有的变量赋予明确的数值。普通类型变量要选择合适的数值;类的实例如果此时不需要创建,那么就赋值为NIL。所以,在主程序、过程或函数中要明确程序的初始化部分和程序的功能部分。
(2) 全局变量:在程序设计中,应该尽可能的避免使用全局变量。因为在程序的很多地方有可能修改全局变量的值,如果缺乏良好的管理机制,这将会使程序变得十分脆弱。而实际上很难在一个成百上千行的程序中对全局变量进行有效的监控。如果要使用全局变量,那么就尽可能在单元文件(.pas)的实现部分声明该变量。这样该变量就只在此文件中有效,而不会被其它文件访问。将全局变量放在单元文件的接口部分是很危险的。
(3) 局部变量:局部变量用于例程内部,遵循其它变量的命名规则。而且应该在例程的入口处立即初始化变量。
3.4 类型
大小写规则:因为类型标志符是保留字,所以应该全部小写。对于引用的其他公司、组织或个人的数据类型则尽可能地保留原样。
3.5 自定义类型
(1) 命名:对于自定义数据类型要以大写字母T为前缀,这样可以分清自定义数据类型和变量。名称的其它部分的命名遵守命名的一般惯例。
(2) 枚举类型:枚举类型的名称应该能够代表该枚举类型的用途。枚举类型的标志符列表的前缀应该包含两个或三个小写字母,以彼此关联,这些字母依次是组成枚举类型名称的单词的首字母。例如:
TAlign = (alNone, alTop, alBottom, alLeft, alRight, alClient, alCustom);
4. 面向对象相关
4.1 类的命名与格式
首先,类的名称应当能够清晰表达类的用途;其次,类的名称之前要以大写字母T开头,以表示这是一个自定义数据类型。在Delphi中,所有的类的祖先类都是TObject,在定义类中要明确表示出类的祖先类。也就是说,即使该类的祖先类是TObject,也要表明(在Delphi中如果不写明类的祖先类,那么就默认其祖先类为TObject)。
例如:
type
TCar = class(TObject)
private
…
protected
…
public
…
end
类的实例的名称可以与类名相同,如果只有一个实例时就使用没有前缀T的类名;如果有多个实例,那么就另外在前面添加其他合适的单词。
在一个单元文件中只可以定义一个类,而且单元文件的名称要与去掉前缀T的类名相同。如果在一个单元文件中定义了多个类,那么一个类就可以直接访问另一个类的私有字段或受保护字段,而不管该字段是否定义为只读。单元文件使用类的名称的目的是我们可以通过文件名直接察觉到类的大概,这样便于使用。
4.2 字段
字段的命名与变量的命名遵从同样的规则,但是要加上大写字母F为前缀,以表示这是一个字段。另外,字段名称应该为名词,而且要注意单词复数的使用(数组字段应当是复数,表示集合含义的字段也应该使用复数名称)。
所有的字段必须为私有,这样就可以通过属性来决定该字段在类的作用域之外的访问属性。这样组的目的是保证类的封装性。
字段名称的排列要参照字段的含义。参考名片管理系统,对一张名片我们首先关心的是姓名,然后是性别,然后是年龄,然后是各种联系方式,如果更细致一点就要考虑生日、家庭成员等。于是我们可以简单的定义类:
type
TBusinessCard = class(TObject)
private
FName: string;
FSex: string;
FAge: integer;
FEmail: string;
…
public
…
end;
4.3 方法
方法的命名与过程和函数的命名遵从同样的规则。对于读写某个字段的方法要使用Get或Set前缀加去掉前缀F的字段名作为名称,前缀Get表示读,Set表示写。如果希望某个字段具有只读属性,那么仅仅为其定义Get为前缀的方法并在属性中将其关联。
如果不希望一个方法被派生类覆盖,就使用静态方法;如果希望一个方法被派生类覆盖就使用虚拟方法;如果类的方法要被多个派生类直接或间接的使用,就使用动态方法;如果一个类需要创建实例,那么就不要使用抽象方法。
要注意方法的访问属性。类的外部接口通过方法实现,应当尽可能的使必要的方法作为接口,其它的方法要定义为私有方法。比如定义方法ExportNameList,其中有使用了一个子方法ExportName,如果不需要外部使用方法ExportName,那么就将其定义为私有方法,将ExportNameList定义为公有方法。
方法的参数尽量以字母A为前缀。如果某个方法使用参数:AName,那么此方法很可能要用到字段FName或属性Name,这样在实现代码中就不会将它们混淆。
在方法的定义中要将同样用途或为了实现同一目标的方法分为一组,在一组的第一个方法的前面和最后一个方法后面各留上一个空行即可。
4.4 属性
属性作为私有字段的访问器,应该使用相应字段的名称,但要去掉前缀F。
4.5 方法的实现
在方法的实现代码中要注意字段的访问方法。可以直接使用字段来访问,也可以使用属性。如果要对字段进行写操作,那么就直接使用字段;如果仅仅进行读操作,那么就使用属性。其实,在任何条件下都使用字段也很值得考虑。
如果要在程序发表后要修改某个方法,那么尽量不要改变现有的接口而通过修改实现代码,或者提供新的接口方法来实现。
首先要实现类的constructor和destructor方法,然后按照方法的定义顺序依次实现。