如何替换空块除外?

How to replace empty except block?

浏览一些遗留代码时,我遇到了一些 "empty" except 块。它们都是出于类似的原因而实现的,即处理从 TEdit 中的文本到数值的转换。由于TEdit可能是空的,这种情况应该不会有错误信息:

procedure TmyForm.EditExit(Sender: TObject);
begin
  ...  
  try
    _value := StrToFloat(Edit.Text);
  except
  end; 
  ...
end;

这很有效,但我想这不是一个好的做法。有没有更好的方法来获得相同的行为?

你应该使用 TryStrToFloat:

if TryStrToFloat(Edit1.Text, _value) then
  // do something with _value

这是一个函数,returns 一个表示转换成功的布尔值。转换成功后的值将在输出参数中返回。

TLDR 不要为异常吞噬者寻求 'one size fits all' 一揽子替代方案。而是确定您在每种情况下尝试解决的具体问题,并确保您的代码明确传达其意图。


您可能已经知道这一点,但我想强调所提供代码中的一些问题。了解这些问题会在可能出错的地方产生不同的可能性。这有助于思考您在每种情况下想要的行为,并且您应该确保您的代码明确反映您的决定,使其更易于阅读和维护。

begin
  ...  
  try
    _value := StrToFloat(Edit.Text);
  except
  end; 
  ...
end;

吞异常原因解释为:

As the TEdit might be empty, there should be no error message in such a situation.

  1. 参考引用的评论:异常吞噬者 隐藏的 比声明的意图更多。无效字符或“不适合”且无法存储的值也会引发异常,这些异常会被立即吞下。这可能是可以接受的,但我的猜测是这些可能性更有可能被忽视。尽管可能性很小,但即使是与转换本身完全无关的异常也会被吞噬:例如 'out of memory' 或 'stack overflow'.
  2. 如果在 StrToFloat 中出现异常,则跳过对 _value 的赋值。所以它保留了它以前的值。
    • 这不一定是坏的如果它之前的值是可预测的其先前的值是可接受的
    • 但该值可能无法预测。特别是如果它是一个未初始化的局部变量:它会拥有调用时堆栈上发生的任何事情。但是您可能还会发现,如果它是一个对象的成员,它具有可预测但很难辨别以前的值 - 使代码难以阅读。
    • 即使看起来可以预测,该值也可能不是 "fit for purpose"。考虑一个表单在单击按钮时将编辑控件值读取到属性中。第一次单击时,该值是有效的 属性 集。但是在第二次点击时,值已更改并且 属性 未更新 - 但它保留了第一次点击时显然不再有效的值。
  3. 最后,最重要的问题是,如果出现问题,吞噬异常的方法无法正确执行其任务。调用该方法的其他代码(但可能依赖于它的正确行为)很幸运地没有意识到这个问题。通常,这只会导致延迟错误(更难修复)。根本问题是隐藏的,所以后来出了问题。例如。
    • 另一种调用上面期望 _value 的方法将 正确地 分配给某些计算。
    • 根本错误被隐藏,所以_value不正确。
    • 以后的计算可能会产生 EDivByZero 或者只是一个不正确的结果。
    • 如果错误结果一直存在,问题可能会隐藏多年。

如前所述,如何修复异常吞噬器取决于您在每种情况下的意图(是的,这种方式更有效,但如果您要修复某些东西,它有助于修复它 "properly")。

选项 1

你真的需要隐瞒 "user made a mistake" 的事实吗?

假设代码中没有其他错误处理错误:

  • 如果用户键入 "Hello" 将引发异常,中止当前操作。用户将看到一条错误消息,指出 'Hello' is not a valid floating point value..
  • 同样,不输入任何值将导致:'' is not a valid floating point value.

因此,值得认真考虑的一个选项当然是:简单地删除吞咽者。

begin
  ...  
  _value := StrToFloat(Edit.Text);
  ...
end;

选项 2

由于您特别关心在 TEdit 时引发错误,因此您可以将此视为特殊情况。

  • 当用户未提供值时,假设 0 是合适的默认值可能是有意义的。 注意!仅当 确实是 合适的默认值时才这样做。 (见下一点)
  • 该值可能是可选的。在这种情况下,如果未提供可选内容,您不想报告错误。而是设置一个与指示未提供的值关联的标志。 (注意:不要重载 valid 值来指示 'value not provided';否则您将无法区分两者。 )

在任何一种情况下,显式处理特殊情况并允许默认错误报告启动都是值得的。

begin
  ...  
  if (Trim(Edit.Text) = '') then
    _value := 0
    //or _valueIsNull := True;
  else
    _value := StrToFloat(Edit.Text);
  ...
end;

注意:您当然也可以在用户更新控件之前在控件值中设置默认值 0。这使用户清楚默认值。如果用户 选择 删除 默认值,则返回选项 1 会通知用户这是不允许的。

选项 3

默认的错误处理可以做得更多user-friendly。例如。您可能需要更多信息,确保焦点设置到 'error control',或设置提示消息。

在这种情况下,您需要根据 David 的回答使用 TryStrToFloat()据我所知,这是在 Delphi 7 中引入的。(在旧版本中,您可以使用 TextToFloat()。检查 StrToFloat() 的实现作为示例.)

以下只是一个可能的示例,用于演示如何编写一些简单的实用程序函数来封装您的特定需求并使您对某些情况的特殊处理更加明确。

type
  TDefaultMode = (dmNone, dmDefaultEmptyValue, dmDefaultInvalidValue);

function ReadFloatFromEditControl(AEdit: TCustomEdit; ADefaultMode: TDefaultMode = dmNone; ADefaultValue: Double = 0.0): Double;
begin
  if not TryStrToFloat(AEdit.Text, Result) then
  begin
    if (ADefaultMode = dmDefaultEmptyValue) and (Trim(AEdit.Text) = '') then
      Result := ADefaultValue
    else if (ADefaultMode = dmDefaultInvalidValue) then
      Result := ADefaultValue
    else
    begin
      AEdit.SetFocus;
      raise EConvertError.Create('['+AEdit.Text+'] is an invalid floating point value.');
    end;
    {If default is applied, replace value in edit control}
    AEdit.Text := FloatToStr(Result);
  end;
end;

{Use as follows:}
v1 := ReadFloatFromEditControl(Edit1);
v2 := ReadFloatFromEditControl(Edit2, dmDefaultInvalidValue);
v3 := ReadFloatFromEditControl(Edit3, dmDefaultEmptyValue, 1.0);