【大龙驹.共享贴】编程过程中需要注意的一些问题【初级】
老大叫写一些需要注意的地方,要求不要太高级,下午写了点,顺便拿来共享 ^_^。。
也顺便希望大家补充,建议,谢谢。<还在继续写>
-------------------------------------------
设计、编程规范
本规范粗略确定一些具体编程中的规范,我们编程首先看重的是能够运行起来,而且是安全的运行,然后是可读性、可维护性、可移植性,其次是高效率性,最后是可扩充性。我们在具体编程过程中,应该尽量考虑是否安全的运行,是否能很容易的让人理解,是否为高效的代码,绝不能只是简单的实现了当前的功能而已。
本规范列出了具体开发过程中可能出现的错误,请大家补充。
一. 安全性的编程
0. 我们自己书写的函数、过程,应当尽可能的被客户端安全的调用,即使客户端是错 误的调用,所以就要求对函数(过程)的参数进行足够的判断。
1. 防止数组越界,注意数组的下标以及上限,使用Low,High确定其下标及上限。
2. 使用指针,对象时最好先判断其是否为空,使用完后,请记得在必要的时候释放其所指向的空间,并重赋值为nil,使用方法为GetMem/FreeMem, New/Dispose配对。
3. 防止使用可能被优化的数据
比如 for I := 0 to 100 do
begin // 循环代码 end;
J := I; // 注意:此处的I的值不一定就是101,因为I可能被优化,所以这种风格 是不安全的,而万一出现问题,则是非常隐蔽的BUG,安全的形式是以另外一个变量 同时进行增加。
4. 定义变量,函数返回值请尽量先初始化,防止变量使用前没进行有意义的初始化,包括自定义的变量以及函数的返回值。
5. 字符串注意空格,保口是否保留空格,是否截空等处理,尤其是在字符串的比较的时候需要注意空格。
6. 字符NULL,包括不同数据库的具体意义,实现以及Delphi前台的意义,需要注意。
7. 浮点数的比较不一定可靠,因为浮点数的实现跟机器相关,不同的机器可能具有不同的精度。
8. 所有的循环操作注意结束条件,以及对控制变量的处理,比如在数据集的循环处理时,请记得写AdoQry.Next,以避免死循环。
9. 当类型转换时,需要考虑如果转换不成功时的处理,比如StrToInt(a); 如果转换不成功的话,那么就会出现错误,所以这种情况,请尽量用StrToIntDef,StrToFloatDef等函数进行转换。
10. 注意数据的溢出可能,比如数据库中的主键为12位整型,能表达的数范围为10^12-1,如果我们用 StrToInt的话,那么可能就会出现数据的截断,因为10^12大于Integer所能表示的范围2147483647, 那么就可能发生截断从而主键重复的错误(虽然这种可能性比较少,但是请尽量避免错误),所以考虑可以使用StrToInt64,否则会出现这种非常难以发现的BUG。
11. 进行事务处理时,请不要在自己的事务处理中直接提交当前别的事务,否则这是非常难以发现的BUG,而且可能导致严重的错误,因为这种行为是在背后进行的,且无法预料,所以坚决避免。
12. 使用动态数组之前请记得先SetLength,使用类似TList,TStringList之类的动态容器时,使用前,请用 .Create先构造分配空间,最后如果容器里面保存的是指针,还需要循环释放里面的指针所指向的空间,最后再.Free本容器,以免造成内存泄漏,注意我们一般使用的是Count属性,非Capacity属性。
13. 使用动态库之类的,请尽量用动态调用,避免出现如果客户端没有DLL就不能启动应用程序的情况,增强健壮性。
14. 可能出错的代码请尽量用try except end异常处理保护起来, 并考虑尽可能的记载下来,进行恢复,尤其当与数据库交互时,所有访问点请尽量用try, except, end保护起来,并对不同的错误代码进行不同的处理,比如与数据库的连接中断等,如果遇到这种情况,就一定要处理好。
15.
二. 可读性、可维护性、可移植性
1. 模块,单元应该有对整体的说明信息,重要函数、过程、变量尽量有有意义的注释信息,且注意区分大小写。
2. 事件响应代码,业务处理代码请尽量以函数,过程进行封装,且函数过程的代码不要过长,即:函数,过程的功能应该尽可能简单,以具有比较好的可维护性以及重用性。
3. 尽可能的声明为函数,且返回有意义的值。当然,如果函数体不可能出现某种异常的话,那么可以声明为过程即可。
4. Form,Unit,函数,过程间尽量以函数(过程)参数的形式进行传递,不能以Form的公有变量(数据)进行直接绑定,这样会增加模块间的耦合度,使得具有更差的可移植性,可维护性。
5. 变量,过程名,函数名,结构体等的命名尽量有意义,且符合规范。
6. 功能相似的代码请尽量抽象为过程、函数,提高代码的重用度以及维护性。
7. 数据,数据结构定义,业务逻辑处理请尽量抽象分开保存到另外单独的.pas单元中,即前台控制与业务处理尽量分开。
8. 可以考虑使用更有意义的状态代替某些初始状态,比如数据库中0代表男性,1代表女性,那么我们可能这样的方式进行代替:
type SexFlag = ( sMale = 0, sFemale = 1 ); 从而这样更具有意义,增强可读性,并 增强了独立性。
9. 尽量提供有意义、合适的提示信息。
10.
三. 高效率的编程
1. 尽可能的对信息进行封装,,尽可能少的定义public数据成员,尽可能避免直接使用其他Form的public成员进行访问,而应当考虑使用统一的接口进行访问,比如:
TForm1:
Pulic:CreateFlag: string;
调用:
Form1 := Tform1.Create(Self);
Form1.CreateFlag := ‘add’;
Form1.Left := 100;
// 以上使用方法虽然比较常见,缺是非常不好的设计方法,不符合OO思想,应该采取下面的方法:
TForm1:
private:
CreateFlag: string;
public:
constructor Create(AOwner: TComponent; crFlag:string; nLeft:Integer);
begin
inherited(AOwner);
CreateFlag := ‘add’;
Left := nLeft; // ….
end;
调用:
Form1 := TForm1.Create(Self, ‘add’, 100);
这样达到了信息封装的目的,而且使用统一的接口进行存取,使得耦合度降低, 更容易维护等。
2. 定义结构体时,请尽量用静态维数的数组,尽量少用string,因为string类型为动态增长的数据类型,会导致运行时时间成本增加。比如:
数据表: Person
P_ID: char[10]
P_Name: char[10];
我们定义相应的结构体时,应该这样定义: (1)
type PersonRec = record
pID: array[0..10] of char;
pName: array[0..10] of char;
end;
// 因为我们明确的知道字段的长度,那么就定义相应的静态数组,这就是所谓的以空间换时间的设计方法。
而如果这样定义时: (2)
type PersonRec = record
pID: string;
pName: string;
end;
// 虽然 这样也可以 但是存在一些缺陷:
A. 导致运行时时间成本的增加,由string的动态增长性质确定
B. 在使用指针类型转换时,采取这种形式可能会出错或者转换不正确。
3. 多个if else if ..else分支时,请尽量用case of进行代替,后者的效率更高。
4. 当需要对某个函数的返回值做多次分支判断、多次存取时,应该用局部变量储存其返回值,比如应该:
I := FuncA;
if I > 10 then
else if I > 5 then
else ….
而不是
if FuncA >10 then
else if FuncA > 5 then
…
这样会多次存取,使得效率低下,甚至可能出现错误。
5.
四. 可扩充性
1. 分支执行语句请尽量以 begin end括起来,比如
if a > 0 then
begin
end
else
begin
end;
哪怕只有一句执行代码,因为可能在将来的版本中会增加处理代码,这样会方便将来。
2. 分支执行语句请考虑条件状态的扩充 比如数据库某个字段的现有状态为’0’,’1’
为了考虑扩充性,下面的写法不妥:
if FieldByName(‘aaa’).AsString = ‘0’ then
//…
else
//…
因为可能将来会增加一个新状态’2’,那么到时候就很难知道但前的else指的是哪种状态,而这么写则比较合适:
if .. = ‘0’ then
//
else if .. = ‘1’ then
// ..
3.
五. 一些基本的API函数
1. ZeroMemory, FillChar
比如 rec为一个record类型的变量(非指针类型),初始化可以用
ZeroMemory(@rec, SizeOf(rec)) 或 FillChar(rec, SizeOf(rec), 0); 而不需要用单独为 其每个成员初始化的形式,比如 rec.a := 0;rec.b := ‘’;这样是效率低下的,如果还想有特殊的初始化 可以这样 ZeroMemory, FillChar之后 rec.a := 1;
2. StrCopy,
六.