【问题标题】:Delphi Exception Handling - How to clean up properly?Delphi 异常处理 - 如何正确清理?
【发布时间】:2011-01-29 05:40:40
【问题描述】:

我正在查看我们的应用程序中的一些代码,并遇到了一些与我通常所做的有点奇怪的事情。通过异常处理和清理,我们(以及许多其他程序员,我敢肯定)使用嵌入了 Try/Except 块的 Try/Finally 块。现在我已经习惯了 Try/Finally 里面的 Try/Except,就像这样:

Try
  Try
    CouldCauseError(X);
  Except
    HandleError;
  end;
Finally
  FreeAndNil(x);
end;

但是这个其他代码块是这样颠倒的:

Try
  Try
    CouldCauseError(X);
  Finally
    FreeAndNil(x);
  end;
Except
  HandleError;
end;

环顾网络,我看到人们这样做是双向的,但没有解释原因。我的问题是,哪个获得外部区块和哪个获得内部区块有关系吗?或者,无论其结构如何,都会处理 except 和 finally 部分吗?谢谢。

【问题讨论】:

    标签: delphi exception-handling


    【解决方案1】:

    一个区别是 try..finally..except 可能容易受到异常屏蔽情况的影响。

    假设 CouldCauseError() 中发生了异常。然后想象在 finally 中尝试 FreeAndNIL(X) 会导致进一步的异常。原始异常(很可能导致导致 FreeAndNIL() 异常的不稳定)丢失。 except 处理程序现在正在处理原始异常之后发生的“下游”异常。

    try..except..finally当然会避免这种情况,因此应该首选(处理尽可能接近其来源的异常)。

    处理诸如此类的简单情况(被清理的单个对象)的另一种方法是将清理包括在正常流程中的异常处理程序中:

    try
      CouldCauseError(X);
      FreeAndNil(x);
    except
      HandleError;
      FreeAndNil(x);
    end;
    

    起初看起来有点吓人(“我需要确定 FreeAndNIL(X) 被调用,所以我必须有一个FINALLY!!”)但是第一个 FreeAndNIL() 可能不会被调用的唯一方法是,如果有一个异常并且如果有 is 一个异常,那么无论如何你也是 FreeAndNIL()ing,它使清理的顺序在异常情况下更清晰一些(在某种意义上去除必须“过滤”掉以了解正在发生的事情的噪音)。

    但是,我个人不喜欢它 - 如果您在异常处理程序或正常流程中更改代码,则可能会破坏清理行为,但这取决于此类块周围的代码以及块本身的大小,为了简化起见,在某些情况下可以认为减少“噪音”是合理的。

    但是,这依赖于 FreeAndNIL() 实际上是“NILthenFree()”这一事实... X 是 NIL'd在它被 Free 之前,所以如果在正常流程中的 FreeAndNIL(X) 中发生异常,那么当异常处理程序捕获 X.Free 引发的异常时,X 将是 NIL ,因此它不会尝试“双重释放”X。

    无论您做出什么决定,我希望对您有所帮助。

    【讨论】:

      【解决方案2】:

      finally 和 except 都会触发,顺序由你决定。这取决于您想在 finally 或 except 块中做什么。你想释放在 except 块中使用的东西吗? finally 放置在 except 块周围。

      【讨论】:

        【解决方案3】:

        这完全取决于您的 finally 块中的代码本身是否会引发异常(然后它需要由上层 try except 保护),或者您是否需要在异常处理中进行某些处理应该稍后释放(然后需要在上层finally块中释放)。

        这意味着有时您甚至可以编写一些代码,例如:

          try
            try
              try
                CouldCauseError(X);
              except
                HandleErrorWith(X);
              end;
            finally
              FreeAndNil(X); // and/or any resource cleanup
            end;
          except
            CatchAllError;
          end;
        

        【讨论】:

          【解决方案4】:

          起初您的代码看起来有点奇怪。我怀念X的创造。

          X := CreateAnX
          try
            DoSomeThing(X);
          finally
            FreeAndNil(x);
          end;
          

          这很重要。因为如果你有这样的代码

          // don't do something like this
          try
            X := CreateAnX
            DoSomeThing(X);
          finally
            FreeAndNil(x);
          end;
          

          你可以是幸运,它的工作原理。但是,如果构造失败,您可能是“幸运”并获得访问冲突,或者您运气不佳并在一段时间后获得访问冲突在完全不同的代码位置。

          另一种可能是

          X := nil;
          try
            X := CreateAnX
            DoSomeThing(X);
          finally
            FreeAndNil(x);
          end;
          

          在哪里使用 except 取决于您的意图。当您想捕获每个异常并知道所有调用代码都清除其问题时(最后使用 try),那么外部异常块就是要走的路

          try
            X := CreateAnX
            try
              DoSomeThing(X);
            finally
              FreeAndNil(x);
            end;
          except
            on e: Exception do
              LogException(e)
          end;      
          

          但是,当您想要捕获所有错误时,请始终考虑它。举个例子(我经常认为它是错误的)不要以这种方式在 Indy OnExecute-handler 中执行此操作。在那里你必须使用这样的东西

          try
            X := CreateAnX
            try
              DoSomeThing(X);
            finally
              FreeAndNil(x);
            end;
          except
            on EIdException do
              raise;
            on e: Exception do
              LogException(e)
          end;      
          

          如果您因为抛出异常或(例如)对话可能失败而预期异常,请查找最内部的位置以捕获错误:

          X := CreateAnX
          try
            DoSomeThing(X);
            try
              i := StrToInt(X.Text);
            except
              on EConvertError do
                i := 0;
            end;       
          finally
            FreeAndNil(x);
          end;
          

          别这样

          X := CreateAnX
          try
            try
              DoSomeThing(X);
              i := StrToInt(X.Text);
            except
              on EConvertError do
                i := 0;
            end;       
          finally
            FreeAndNil(x);
          end;
          

          仅当您预计 DoSomeThing 中也会出现 EConvertError 时才执行此操作。如果 DoSomeThing 抛出 EConvertError 并且您没有预料到它,那么您的代码存在需要更正的严重问题。在这种情况下,请确保用户可以保存他的工作(可能作为副本,因为他的工作可能会被损坏)并确保您获得有关问题的信息。

          至少尝试 except 是为了处理异常而不是隐藏它们。

          【讨论】:

          • 删除 crete 语句是故意的,因为我认为(是的,不好的举动)它与 begin 和 end 语句一样,会被认为与问题无关。是的,我们总是在第一个 try 语句之前创建 X。
          猜你喜欢
          • 1970-01-01
          • 2021-05-22
          • 1970-01-01
          • 2016-12-30
          • 2012-06-01
          • 2016-08-21
          • 2023-03-07
          • 2011-02-03
          • 2013-11-19
          相关资源
          最近更新 更多