变量应该在调用函数之前初始化吗?

Should variable be initialized before call to function?

当我调用 Functions 获取值时,我通常会初始化变量,以防函数失败或 return 什么都没有,我想避免处理未初始化的变量。我对字符串、整数或任何其他类型执行相同的操作。

整数变量示例:

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
...

我知道我可以采取措施避免重复赋值,比如确保函数 returns 0 如果一切都失败了。但我有 100 多个函数,我不能相信我从来没有犯过函数如何处理所有事情的错误,我确信有些函数不会 return 0,如果一切都失败了。

我试图理解为什么这不是理想的做法以及如何最好地避免它。

编辑

这里是函数没有 return 正确值或 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;

在这种情况下,从函数中 return 得到的值是一些随机数。

在这种情况下,初始化似乎是有效的方法。 还是不?

编辑 2:

正如大卫在他的回答中指出的那样,正确,有一个警告

[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined

但是,我无缘无故地忽略了它,就是没有看那里。当它让我编译它时,我认为它可以。所以,我确实在寻找警告,并且我 'fixed' 相当多的函数都有类似的问题,因为所有的 IF 结果可能都没有被定义。

编辑 3 和结论:

我希望它能增加问题和解释的范围:

也许我在我的大部分函数中使用的另一个扭曲的例子也可以解释为什么我认为我需要初始化变量,因为我不确定我的函数是否会一直正确运行,尤其是在嵌套函数的情况下。他们的mots仍然是这样设置的:

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行..),可能还有其他一些不必要的东西。

假设vPropValue是一个局部变量那么这段代码

vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');

没有区别
vPropValue := GetPropValue(vObject,'Height');

一个更简单的例子可能是这样的:

i := 0;
i := 1;

0分配给i然后立即将1分配给i有什么意义?所以,你肯定永远不会写那个。你会写:

i := 1;

在您的代码中,在此答案的顶部,您将两次赋值给同一个变量。第一个赋值中赋值的值立即被第二个赋值中赋值的值替换。因此,第一个赋值是没有意义的,应该被删除。

第二个例子有点复杂。假设你的函数写得正确,并且总是赋值给它们的 return 值,并且 vName 是一个局部变量,那么

vName := '';
vName := GetObjectName(vObject,'ObjectName');

没有区别
vName := GetObjectName(vObject,'ObjectName');

我添加额外条件的原因与下面讨论的函数 return 值的实现的怪癖有关。这种情况与上述情况的区别在于 return 值类型。这是一个托管类型,string,而在第一个示例中,该类型是一个简单的 Integer.

同样,考虑到函数总是赋值给 return 值的附带条件,第一次赋值是没有意义的,因为该值会立即被替换。删除第一个分配。


关于您编辑的函数,如果您启用提示和警告,编译器将警告您它的错误实现。编译器会告诉你,并不是所有的代码路径都是return一个值。

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;

我无法强调始终 return 来自函数的值有多重要。事实上,Delphi 甚至允许编译您的函数,这是一个可怕的弱点。


您的双重赋值有时对您有用的原因是 Delphi 中函数 return 值的实现的一个怪癖。与几乎所有其他语言不同,某些更复杂类型的 Delphi 函数 return 值实际上是一个 var 参数。所以这个函数

function foo: string;

实际上在语义上与此相同:

procedure foo(var result: string);

Delphi 设计师做出了一个非常奇怪的决定。在大多数其他语言中,如 C、C++、C#、Java 等,函数 return 值就像从被调用方传递给调用方的按值参数。

这意味着,如果你想变态,你可以通过函数的 return 值将值传递给函数。例如,考虑以下代码:

// 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。这是一个相当奇怪的实现细节。你不应该依赖它。让我重复一遍。你不应该依赖它。不要养成在调用站点初始化 return 值的习惯。这会导致无法维护的代码。

而是遵循确保函数 return 值始终由函数分配的简单规则,并且在分配之前绝不读取。


函数 return 值的更多实现细节由 documentation 提供,我强调:

The following conventions are used for returning function result values.

  • Ordinal results are returned, when possible, in a CPU register. Bytes are returned in AL, words are returned in AX, and double-words are returned in EAX.
  • Real results are returned in the floating-point coprocessor's top-of-stack register (ST(0)). For function results of type Currency, the value in ST(0) is scaled by 10000. For example, the Currency value 1.234 is returned in ST(0) as 12340.
  • For a string, dynamic array, method pointer, or variant result, the effects are the same as if the function result were declared as an additional var parameter following the declared parameters. In other words, the caller passes an additional 32-bit pointer that points to a variable in which to return the function result.
  • Int64 is returned in EDX:EAX.
  • Pointer, class, class-reference, and procedure-pointer results are returned in EAX.
  • For static-array, record, and set results, if the value occupies one byte it is returned in AL; if the value occupies two bytes it is returned in AX; and if the value occupies four bytes it is returned in EAX. Otherwise, the result is returned in an additional var parameter that is passed to the function after the declared parameters.

下面的代码(一)

vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');

与(B)没有区别

vPropValue := GetPropValue(vObject,'Height');

The question of whether GetPropValue is "written correctly" is totally irrelevant.

让我们考虑一下即使您 GetPropValue 写错了会发生什么,例如

function GetPropValue(AObject: TObject; AStr: String): Integer;
begin
  if AStr = 'Hello' then Result := 5;
end;

如您所知,当输入 AStr 不是 "Hello" 时,函数的结果将非常随机。 (为了便于讨论,我们假设它将 return -42。)

代码块 (A) 将执行以下操作:

  • vPropValue 设置为 0
  • 然后将 vPropValue 设置为 - 42

代码块 (B) 将立即简单地将 vPropValue 设置为 - 42。

TIP: There is no point in writing a wasteful line of code just because you're worried you might have made a mistake in a function you call.
First, As David points out, you can avoid many mistakes simply by paying attention to your compiler hints and warnings. Second, that sort of "paranoid" coding simply leads to more wasteful code because now you have to start considering invalid values as possible results.
This becomes worse when one day your "safe-value" is actually a valid value. E.g. how would you tell the difference between "default 0" and "correctly returned 0"?

不要用不必要的冗余使代码膨胀,人为地使编程变得困难。


旁注

在一些特殊情况下,代码的行为可能会有所不同。但是无论如何您都应该避免导致这些情况的设计,因为它们会使维护代码变得更加困难。

我提到它们纯粹是为了完整起见,上面的建议仍然有效。

1) 如果 vPropValue 作为 属性 实现,setter 可能会产生导致不同行为的副作用。虽然属性没有错,但当它们做出意想不到的事情时,您的手上就会出现严重的问题。

2) 如果 vPropValue 是 class 上的字段(或更糟的是全局变量),则 (A) 和 (B) 可以 行为不同 但仅当 GetPropValue 引发异常时。这是因为异常会阻止分配结果。请注意,除非在特殊情况下,否则应避免这种情况,因为它确实会使您更难推断您的代码在做什么。

And in fact, this is something that makes it so much more important to avoid the redundant initialisations. You want your special case code to look stand out from the rest.

从顶级评论中获取我的建议以防 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;

// 

您可以使用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 语句都应该抓住最终的 else! (在我看来永远如此!)