我该如何找出一个Delphi函数可能抛出哪些异常?

13

有没有好的方法可以找到 Delphi 程序/函数可能抛出的异常(包括其调用程序/函数)?

在 Java 中,您总是需要声明可能抛出的异常,但在 Delphi 中并非如此,这可能会导致未处理的异常。

是否有任何代码分析工具可以检测到未处理的异常?


1
也许在解决问题之前,你需要加上为什么需要解决这个问题。 - Jim McKeeth
Jim是正确的。由于我们不知道您想要实现什么,因此很难提供正确的答案。 - Gabriel
如果有人想要记录这些内容,可以使用某种方法注释来实现。 - George Birbilis
10个回答

12

(编辑:现在显然问题只涉及设计时检查。)

新答案:

我无法确定是否有工具可以为您进行此项检查。例如,Pascal分析器就没有这个功能。

但是,我可以告诉你,在大多数Delphi应用程序中,即使有工具可以为您进行此项检查,您也不会得到任何结果。

为什么?因为TApplication.Run()中的主消息循环将所有HandleMessage()调用都包装在一个异常处理块中,它捕获了所有异常类型。因此,在大多数应用程序中,您将隐含/默认地处理99.999%的代码。在大多数应用程序中,这种异常处理将占用您自己代码的100% - 0.001%未被包裹在异常处理中的代码将是自动生成的代码。

如果有可用的工具可以为您进行此项检查,您需要重写Application.run(),使其不包括异常处理。

(以前的答案:Application.OnException事件处理程序可以被分配来捕获所有没有被其他异常处理程序处理的异常。虽然这是运行时,因此可能不完全是您想要的(听起来像是您想在设计时识别它们),但它确实允许您捕获任何其他地方未处理的异常。结合像Jedi Code Library中的JCLDebug等工具,您可以记录堆栈跟踪以查明异常发生的位置和原因,这将允许进一步调查并在有罪代码周围添加特定的异常处理或预防措施...


11

我猜你想让Delphi的行为像Java一样,这不是一个好的方法。我建议不要过于担心未处理的异常。最坏的情况下,它们会冒泡到通用的VCL异常处理程序,并导致Windows消息对话框。在正常的应用程序中,它们不会停止应用程序。

良好编写的代码将记录可能引发的不同异常,以便您可以有意义地处理它们。不推荐使用全捕捉处理程序,因为如果您不知道为什么引发异常,实际上没有办法知道该做什么。我还强烈推荐madExcept。


我也推荐使用madExcept。 - Gabriel

5
简短的回答是没有工具可以做到你说的那样,即使扫描raise关键字也无法实现。 EAccessViolationEOutOfMemory是可能在任何地方引发的多个异常之一。
Delphi的一个基本特点是异常是分层的:所有定义的语言异常都是从Exception派生而来,尽管值得注意的是,实际上可以引发任何TObject后代。
如果您想捕获在特定过程中引发的每个异常,请将其包装在try / except块中,但正如提到的那样这不是推荐的做法
// Other code . . . 
try
  SomeProcedure()
except  // BAD IDEA!
  ShowMessage('I caught them all!');
end;

这将捕获所有内容,甚至包括引发的TObject实例。虽然我认为这并不是最好的做法。通常情况下,您应该使用try / finally块,然后允许全局异常处理程序(或一个最终的try / except块)来处理异常。


5
除了“raise”关键字的扫描之外,在Delphi中没有语言结构告诉普通读者可以从方法中期望哪些异常。
在运行时,可以在每个方法中添加一个catch-all异常处理程序,但这并不可取,因为它会减慢执行速度。(而且做起来也很麻烦)。
向方法添加异常处理块将为其添加一些汇编指令(即使未触发异常),当非常频繁地调用该方法时,这将导致可测量的减速。
确实存在一些库可以帮助您分析运行时异常,例如madExceptJclDebugEurekaLog。这些工具可以记录有关异常的各种详细信息,强烈建议使用其中之一!

一个try...except语句块是否会减慢程序的执行速度(如果没有异常发生)? - mjn
@mjn:是的,但只有在经常调用try..except的函数和/或紧密循环中才会变得明显。 - PatrickvL
我会推荐jclDebug或madExcept,但不会推荐EurekaLog,因为它太容易出现错误了。 - Gabriel

4
任何未明确或通常在特定级别处理的异常将向上游传递到调用栈。Delphi运行时库(RTL)将生成一组不同的异常类 -(数学错误,访问错误,特定于类的错误等)。您可以选择在不同的try except块中特定或通常处理它们。
除非需要使用异常传播特定的功能上下文,否则您实际上不需要声明任何新的异常类。
正如之前的评论者所写,您还可以添加一个所有未处理的异常处理程序,例如MadExcept或EurekaLog。
编辑:这是对未处理异常的全面保险。
try
  ThisFunctionMayFail;
except
  // but it sure won't crash the application
  on e:exception
  do begin
    // something sensible to handle the error 
    // or perhaps log and/or display the the generic e.description message
  end
end;

4
我将推荐(或者说是第三次推荐)MadExcept。我已经在多个商业应用程序中成功使用它,没有任何问题。MadExcept的好处是它会为您生成一个报告,包含完整的堆栈跟踪,通常会指向错误方向,并且还可以包括屏幕截图,只需轻松点击一下鼠标即可从客户机器自动向您发送电子邮件。
但是,您不希望将其用于所有异常,而只是捕获您错过的异常。例如,如果您打开数据库并且登录失败,则最好自己捕获和处理此异常,而不是在应用程序中给用户显示MadExcept默认错误消息。

3

我们已经在使用 madExcept,但我正在寻找一种在设计时查找未捕获异常的方法。我一直在寻找某种能够覆盖此功能的代码分析工具,但没有找到任何一种合适的。 - ajob

2

如果需要运行时异常处理,可以尝试使用Eurekalog工具。我不知道是否有设计时的工具。当你使用第三方代码且没有源代码时,可能会遇到更多困难。在Delphi中,捕获异常是不必要的,因此你不需要像Java一样声明它们。

我的意思是,Delphi不要求必须处理异常,它只会终止程序。EurekaLog提供了记录已处理和未处理异常的方法,并在异常发生时提供了大量有关程序状态的信息,包括发生异常的代码行和调用堆栈。


如果某些东西引发了异常,我会说有必要捕获它,否则它将在运行时作为未处理的异常浮现。问题是如何确保所有引发的异常都得到了真正的处理。 - ajob
madExcept是一个更好的替代品。jedi jclDebug是另一个替代品(免费)。 - Gabriel

1
正如Jim McKeeth所指出的那样,您无法得到一个明确的答案,但是通过静态分析,您可以部分回答这个问题:给定一个特定的函数/过程,请构建一个调用图。检查该调用图中的每个函数是否有raise语句。例如,这将告诉您TIdTcpClient.ReadString可能会引发EIdNotConnected(等等)。
聪明的分析器还可以注意到一些代码使用/运算符,并将EDivByZero作为一种可能性,或者某些过程访问数组并包括ERangeError。
这个答案比简单地搜索“raise”要紧凑一些。

0

单元的终结部分也可能会引发异常。我认为这些异常可能会被忽略...并且也有一些问题。

我认为Delphi IDE有一个内置的“堆栈跟踪”或“堆树”之类的东西。

这个问题让我想起了Skybuck的TRussianRoulette游戏...搜索一下,它的代码和答案可能会有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接