我见过太多的Delphi程序员编写相当大的程序,却从未激活范围、溢出和断言检查。当然,如果你愿意,你可以这样做,但你的代码会更加容易出错。
因此,如果你允许我插入一个类比(虽然仍然与错误有关)并回答你的问题,希望能说服更多的程序员立即启用这三个检查。最后还包括一些警告。
溢出检查
这将检查某些整数算术运算(+,-,*,Abs,Sqr,Succ,Pred,Inc和Dec)是否会发生溢出。例如,在执行加法操作后,编译器将插入额外的二进制代码来验证操作结果是否在支持的范围内。
“整数溢出”是指对整数变量进行的操作产生的结果超出了该变量的范围。例如,如果将整数变量声明为16位有符号整数,则其值的范围可以从-32768到32767。如果对此变量进行的操作产生的结果大于32767或小于-32768,则发生了整数溢出。
当发生整数溢出时,操作的结果是未定义的,并且可能导致程序中的未定义行为:
• 包装
结果可能会导致包装的值。这意味着数字32768实际上将存储为1,因为它比我们可以存储的最高值(32767)高1个单位。
• 截断
结果可能会被截断或以其他方式修改,以适应整数类型的范围。例如,数字32768实际上将存储为32767,因为那是我们可以存储的最高值。
未定义的程序行为是最糟糕的错误之一,因为它不容易重现。因此,很难跟踪和修复。
如果您激活此功能,则需要付出一点代价:程序的速度会略微降低。
IO检查
检查I/O操作的结果。如果I/O操作失败,则会引发异常。如果关闭此开关,则必须手动检查I/O错误。
如果您激活此功能,则需要付出一点代价:程序的速度会降低,但由于此检查引入的几微秒与I/O操作本身所需的毫秒级时间相比微不足道(硬盘速度较慢),因此影响不大。
范围检查
Delphi Geek将其称为“最重要的Delphi设置”,我完全同意。它检查所有数组和字符串索引表达式是否在定义的范围内。它还检查所有标量和子范围变量的赋值是否在范围内。
以下是一个示例代码,如果没有范围检查,则会破坏我们的生活:
Type
Pasword= array [1..10] of byte; // we define an array of 10 elements
…
x:= Pasword[20]; // Range Checking will prevent the program from accessing element 20 (ERangecheckError exception is raised). Security breach avoided. Error log automatically sent to the programmer. Bruce Willis saves everyone.
启用运行时错误检查
要激活运行时错误检查,请转到项目选项并勾选以下三个框:
在“项目选项”中启用运行时错误检查
断言
一个好的程序员必须在其代码中使用断言来提高程序的质量和稳定性。认真点!你真的需要使用它们。
断言用于检查程序在某个特定点上应始终为真的条件,并在未满足条件时引发异常。通常使用在 SysUtils 单元中定义的 Assert 过程来执行断言。
您可以将断言视为穷人版的单元测试。我强烈建议您深入了解断言。它们非常有用,而且不需要像单元测试那样多的工作。
典型示例:
SysUtils.Assert(Input <> Nil, ‘The input should not be nil!’);
但是,为了让程序检查我们的断言,我们需要在项目设置->编译器选项中激活此功能,否则它们将被忽略,就像它们不在我们的代码中一样。确保您理解我刚才说的含义!例如,如果我们在Assert中调用具有副作用的例程,那么我们会犯严重错误。在下面的示例中,在调试时启用断言时,Test()函数将被执行,并且'This was executed'将出现在Memo中。然而,在发布模式下,该文本将不会出现在备忘录中,因为现在Assert被简单地忽略了。恭喜,我们刚刚使程序在调试/发布模式下表现不同 ☹。
function TMainForm.Test: Boolean;
begin
Result:= FALSE;
mmo.Lines.Add('This was executed');
end;
procedure TMainForm.Start;
VAR x: Integer;
begin
x:= 0;
if x= 0
then Assert(Test(), 'nope');
end;
以下是一些使用示例:
1. 检查输入参数是否在0..100范围内:
procedure DoSomething(value: Integer);
begin
Assert((value >= 0) and (value <= 100), 'Value out of range');
…
end;
2 在使用指针之前,检查它是否为非空:
Var p: Pointer;
Begin
p := GetPointer;
Assert(Assigned(p), 'Pointer is nil');
…
End;
在继续之前检查变量是否具有特定值的方法:
var i: Integer;
begin
i := GetValue;
Assert(i = 42, 'Incorrect response to “What is the answer to life”!');
…
end;
通过定义项目选项中的NDEBUG符号或使用{$D-}编译器指令,我们也可以禁用断言。
在某些情况下,Assert也可以作为处理错误和异常的更优雅的方式,因为它更易读,并且还包括自定义消息,这有助于开发人员了解出了什么问题。
个人而言,我经常在例程顶部使用它来检查参数是否有效。
激活此功能会使您的程序变得更慢,因为…额外增加了一行代码。
没有免费的午餐
所有美好的东西都是有代价的(幸运的是,在我们的情况下只是一个小代价):启用运行时错误检查和断言会减慢我们的程序并使其稍微变大。
今天的计算机具有大量RAM,因此大小的轻微增加无关紧要,因此,让我们将其放在一边。但是让我们看看速度,因为这不是我们可以轻易忽略的事情:
Type Disabled Enabled
Range checking 73ms 120ms
Overflow checking 580ms 680ms
I/O checking Not tested Not tested
正如我们所看到的,程序的速度受这些运行时检查的影响非常大。如果我们有一个速度至关重要的程序,在调试期间最好激活“运行时错误检查”。我通常会在第一次发布时保持其处于激活状态,并等待几周。如果没有报告错误,则发布一个关闭“运行时错误检查”的更新。
个人而言,我总是保持“IO检查”处于激活状态。由于此检查的性能损失微不足道。
重要警告:
如果您有一个已经写得不太好的现有项目,并且激活下面任何一项运行时错误检查,则您的程序可能会比通常更经常崩溃。
不,运行时错误检查例程没有破坏您的程序。它一直是有问题的-您只是不知道而已。运行时检查例程现在正在找到那些代码可疑、低劣和发臭的地方。运行时检查的唯一目的是为了找出程序中的错误。