当我调用函数来获取值时,我通常会初始化变量,以防函数失败或者不返回任何内容而我想避免处理未初始化的变量。我对字符串,整数或任何其他类型都这样做。
整数变量的示例:
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
IF vPropValue > 0 Then
...
这是我最常用的方式。
我知道我可以使用:
If GetPropValue(vObject,'Height') > 0 Then
...
但是第一个例子我避免多次调用函数,如果我稍后需要在代码中再次得到结果。
相同的字符串(即使我知道本地字符串被初始化为空字符串,而整数不是可以保存任何值)
vName := '';
vName := GetObjectName(vObject,'ObjectName');
IF Trim(vPropStrValue) <> '' Then
...
我知道我可以采取措施避免重复的值赋值,比如确保函数在所有内容都失败时返回0。但是我有100多个功能而且我不能依赖我从来没有弄错过函数如何处理所有事情而且我确定有些人不会返回0,如果一切都失败了。
我试图理解为什么这不是理想的做法以及如何最好地避免它。
修改
以下是函数未返回正确值或0:
的示例function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
在这种情况下,函数返回的值是一些随机数。
在这种情况下,初始化似乎是有效的方法。 还是不?
编辑2:
正如大卫在答案中指出的那样,没错,有警告
[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined
但是,我无视它,没有理由,只是没有看到那里。因为它让我编译它,我认为它没关系。所以,我确实在寻找警告,并且我已经修好了#39;相当多的函数都有类似的问题,因为所有的IF结果可能都没有被定义。
编辑3&amp;结论
我希望它增加了问题和解释的范围:
也许我在大多数函数中使用的另一个扭曲的例子,也可以解释为什么我认为我需要对变量进行初始化,是因为我不确定我的函数是否会一直正常运行,尤其是在嵌套函数的情况下。很多人仍然这样设置:
function GetProperty(vType:integer):integer;
begin
Try
if vType = 99 then
Result:=GetDifferentProperty(vType)// <-- Call to another Function, that could return whatever...
else
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
except
end;
end;
现在我正在解决这些问题Try Except End;
,但有些功能已有10年历史,并且根据我当时的经验,期望它们100%工作,并不值得依赖。
作为这个项目的唯一开发人员,我认为我应该相信我的功能(以及他的其余代码),但我无法在多个开发人员环境中想象所有功能都已正确设置。
所以我的结论:因为我没有处理基础知识 - 设计合理的函数,我需要进行所有这些检查(变量初始化,Try Except
行。 )可能还有一些其他不必要的东西。
答案 0 :(得分:15)
假设vPropValue
是一个局部变量,那么这个代码
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
与
无法区分vPropValue := GetPropValue(vObject,'Height');
一个更简单的例子可能是这样的:
i := 0;
i := 1;
将0
分配给i
然后立即将1
分配给i
有什么意义?所以,你肯定永远不会写那个。你会写:
i := 1;
在您的代码中,在此答案的顶部,您为同一个变量分配两次。第一个分配中分配的值立即替换为第二个分配中指定的值。因此,第一项任务毫无意义,应予以删除。
第二个例子有点复杂。假设您的函数编写正确,并始终分配给它们的返回值,并且vName
是一个局部变量,那么
vName := '';
vName := GetObjectName(vObject,'ObjectName');
与
无法区分vName := GetObjectName(vObject,'ObjectName');
我添加额外条件的原因与下面讨论的函数返回值的实现有关。这种情况与上述情况之间的区别在于返回值类型。这是托管类型string
,而在第一个示例中,类型是简单的Integer
。
同样,考虑到函数总是分配给返回值的条件,第一个赋值是没有意义的,因为值立即被替换。删除第一个作业。
关于编辑中的功能,如果启用提示和警告,编译器将警告您错误的实现。编译器会告诉您并非所有代码路径都返回一个值。
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
如果两个条件都不满足,则不会为结果变量赋值。这个功能应该是:
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else
Result:=0;
end;
我不能强调你总是从函数中返回一个值是多么重要。事实上,Delphi甚至允许编译你的函数是一个可怕的弱点。
你的双重赋值有时对你有用的原因是由于在Delphi中实现函数返回值的怪癖。与几乎所有其他语言不同,某些更复杂类型的Delphi函数返回值实际上是var
参数。所以这个功能
function foo: string;
实际上,在语义上与此相同:
procedure foo(var result: string);
这是德尔福设计师做出的一个非常奇怪的决定。在大多数其他语言中,如C,C ++,C#,Java等,函数返回值就像是从被调用者传递给调用者的按值参数。
这意味着如果您希望反常,可以通过返回值将值传递给函数。例如,请考虑以下代码:
// Note: this code is an example of very bad practice, do not write code like this
function foo: string;
begin
Writeln(Result);
end;
procedure main;
var
s: string;
begin
s := 'bar';
s := foo;
end;
当您致电main
时,会输出bar
。这是一个相当奇怪的实现细节。你不应该依赖它。让我重复一遍。你不应该依赖它。不要养成在呼叫站点初始化返回值的习惯。这导致无法维护的代码。
而是遵循简单的规则,确保函数返回值始终由函数指定,并且在分配之前永远不会读取。
documentation提供了有关函数返回值实现的更多详细信息,重点是:
以下约定用于返回函数结果 值。
- 如果可能,在CPU寄存器中返回序数结果。 AL中返回字节,AX中返回单词,双字返回 在EAX中返回。
- 在浮点协处理器的栈顶寄存器(ST(0))中返回实际结果。对于Currency类型的函数结果, ST(0)中的值缩放10000.例如,Currency值 在ST(0)中将1.234作为12340返回。
- 对于字符串,动态数组,方法指针或变体结果,效果与将函数结果声明为 声明参数后面的附加var参数。其他 单词,调用者传递另一个指向a的32位指针 返回函数结果的变量。
- 在EDX:EAX中返回Int64。
- 在EAX中返回指针,类,类引用和过程指针结果。
- 对于静态数组,记录和设置结果,如果该值占用一个字节,则在AL中返回;如果该值占用两个字节 在AX中返回;如果值占用四个字节,则返回 EAX。否则,结果将在其他var参数中返回 在声明的参数之后传递给函数。
答案 1 :(得分:4)
以下代码(A)
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
与(B)无法区分
vPropValue := GetPropValue(vObject,'Height');
GetPropValue
是否“正确书写”的问题完全无关。
让我们考虑即使您错误地写GetPropValue
,会发生什么。
function GetPropValue(AObject: TObject; AStr: String): Integer;
begin
if AStr = 'Hello' then Result := 5;
end;
如您所知,当输入AStr
不是“Hello”时,函数的结果将是非常随机的。 (为了讨论起见,我们假设它将返回-42
。)
代码块(A)将执行以下操作:
vPropValue
设为0 vPropValue
设置为 - 42 代码块(B)会立即将vPropValue
设置为 - 42。
提示:编写浪费的代码行是没有意义的,因为你担心你可能在你调用的函数中犯了错误。
首先,正如David指出的那样,只需关注编译器提示和警告就可以避免许多错误。 其次,那种“偏执”编码只会导致更浪费的代码,因为现在你必须开始考虑无效值作为可能的结果。
当有一天您的“安全值”实际上是 有效值 时,这会变得更糟。例如。你怎么说出“默认0”和“正确返回0”之间的区别?
不要通过使用不必要的冗余膨胀代码来人为地编程编程。
旁注
在某些特殊情况下,代码的行为可能会有所不同。但是在任何情况下都应该避免导致这些情况的设计,因为它们会使维护代码变得更加困难。
我完全是为了完整而提到它们,上面的建议仍然存在。
1)如果将vPropValue
实现为属性,则setter可能会产生导致不同行为的副作用。虽然属性没有任何问题,但当他们做出意想不到的事情时,你的手上就会出现严重问题。
2)如果vPropValue
是类中的字段(或者更糟糕的是全局变量),那么(A)和(B)可能的行为方式不同 但是仅当 GetPropValue
引发异常时。这是因为异常会阻止分配结果。请注意,除了特殊情况之外,这是要避免的,因为它确实使得更难以推断出您的代码正在做什么。
事实上,这使得避免冗余初始化变得更加重要。您希望您的特殊案例代码在其余部分中脱颖而出。
答案 2 :(得分:2)
在Pastebin失败的情况下从顶级评论中删除我的建议
function GetValueUpdate3(vType:integer):integer;
begin
// with SOME types like Integer you can use Pascal case-block instead of error-prone many-ifs-ladder
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else Result := 12345; // initialization with safe default value for illegal input like vType=2
end; // case-block
end;
function GetValueUpdate3e(vType:integer):integer;
begin
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else raise EInvalidArgument.Create('prohibited value - GetValue( ' + IntToStr(vType) +' )' );
// runtime eror when vType = 2 or any other illegal input
end;
end;
function GetValueUpdate1(vType:integer):integer;
begin
Result := 12345; // initialization with safe default value;
if vType=1 then Exit(100); // special value for special case #1
if (vType>2) and (vType<=9) then Exit(200); // special value for special case #2
// exit with default value
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
// http://stackoverflow.com/questions/33927750
答案 3 :(得分:2)
您可以使用Assertions验证输入是否正确。 断言有助于在代码中查找逻辑错误。
使用断言会强制您考虑有效和无效的输入数据,并帮助编写更好的代码。 在编译时启用和禁用断言是通过\ $ C或\ $ ASSERTIONS编译器(全局开关)完成的
在您的示例中,函数GetValue可以使用如下断言:
function GetValue(vType:integer):integer;
begin
Assert((vType >= 1) and (vType <=9), 'Invalid input value in function GetValue');
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else Result := 0; // always catch the last else!
end;
此外,每个if语句都应该捕获最终的其他内容! (在我看来总是!)