8

我的问题很模糊:o) - 但这里有一个例子:

当我编写 C 代码时,我能够在失败时记录计数器的值:

   <...>
   for ( int i = 0 ; i < n ; i++ )
      if ( SUCCESS != myCall())
         Log( "Failure, i = %d", i );
   <...>

现在,使用异常,我得到了这个:

  try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }

当然,可以在 try/catch 语句之外声明“i”(这就是我正在做的事情)。但我不喜欢它——我喜欢在使用它们的地方声明变量,而不是以前。

但也许我在这里错过了什么。你有什么优雅的解决方案吗?

预先感谢 !西尔万。

添加:myCall() 是一个不起眼的 API 调用——我不知道它会抛出什么。另外,我当然可以在每个调用周围添加一个 Try/Catch 块,但是我会更好地使用返回码吗?然后我会在重要的代码行周围添加很多噪音吗?

4

14 回答 14

8

怎么样:

for(int i = 0; i < n; i++)
{
  try
  {
    myCall();
  }
  catch(Exception e)
  {
    Log(String.Format("Problem with {0}", i));
  }
}
于 2009-01-14T21:07:24.363 回答
8

我认为你弄错了,这并不像许多其他人一样令人惊讶。

异常不能用于程序流。再读一遍,很重要。

例外情况是您希望在运行时永远不会看到的“哇,这不应该发生”错误。显然,当您的第一个用户使用它时,您就会看到它们,这就是为什么您必须考虑它们可能发生的情况,但您仍然不应该尝试将代码放入捕获、处理和继续,就好像什么都没发生一样。

对于这样的错误,您需要错误代码。如果您使用异常,就好像它们是“超级错误代码”,那么您最终会像您提到的那样编写代码 - 将每个方法调用包装在一个 try/catch 块中!您还不如返回一个枚举,它比用 7 行代码而不是 1 行乱扔所有代码快得多,而且更容易阅读错误返回代码。(它也更有可能是正确的代码 - 请参阅 eri​​kkallen 的回复)

现在,在现实世界中,通常情况下,方法会在您不希望它们没有抛出异常的地方抛出异常(例如 EndOfFile),在这种情况下,您必须使用“try/catch wrapper”反模式,但是如果您要设计方法,请不要将异常用于日常错误处理 - 仅将它们用于特殊情况。(是的,我知道这种设计很难,但很多设计工作也是如此)

于 2009-01-14T22:58:22.563 回答
7

我不喜欢“现在,有例外……”的表达方式。

异常是您在编程中使用它的工具——如果您认为它是最佳选择,请使用它,否则不要。

我遵循个人规则,即在内部代码中不抛出任何我可以避免抛出的异常。对于公开可用的 DLL 的 API,应启用前置条件检查并在失败时触发异常,是的;但是对于内部逻辑,我很少(如果有的话)在我的设计中包含异常。相反,当我使用一些记录了如果发生某些不良情况时将抛出的函数时,我倾向于立即捕获异常——毕竟这是一个预期的异常。

如果您认为您的非特殊选择更好 - 坚持下去!

于 2009-01-14T21:20:19.017 回答
5

是的。2 件事。

将 try-catch 块放在有意义的地方。如果您对 myCall 的异常(以及 i 的值)感兴趣,请使用

for ( int i = 0 ; i < n ; i++ )
    try { myCall(); } catch ( Exception exception ) {
        Log( "Failure, i = %d", i );
    }

为不同的错误抛出不同类的对象。如果您对财务处理中发生的逻辑错误感兴趣,请抛出 finances::logic_error,而不是 std::exception("error msg") 或其他内容。这样你就可以捕捉到你需要的东西。

于 2009-01-14T21:09:20.723 回答
4

从我的角度来看,这里有两件事。

期望异常本身包含有关 i 的值的信息,或者不太具体地包含有关它被评估的上下文和出了什么问题的信息,这并不令人发指。举个简单的例子,我绝不会直接扔顺子InvalidArgumentException;相反,我会确保将准确的描述传递给构造函数,例如


   public void doStuff(int arg) {
      if (arg < 0) {
         throw new InvalidArgumentException("Index must be greater than or equal to zero, but was " + arg);
      }
      ...

这可能不会显式地记录 i 的值,但在大多数情况下,您将能够了解导致错误的输入的问题所在。这也是一个支持异常链接的论据——如果你在每个概念级别捕获、包装和重新抛出异常,那么每个包装都可以添加自己的相关变量,这些变量太高而无法被基本的低级错误看到或理解.

或者,如果事情真的太抽象以至于您的myCall函数无法知道发生了什么,那么我发现在进行调用之前以更高的详细程度记录日志效果很好,例如

try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         DebugLog("Trying myCall with i = " + i);
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }
这样,如果确实出现问题,您可以检查您的高详细调试日志并找到i引发异常的调用之前的内容。

于 2009-01-14T23:16:28.130 回答
4

考虑 Raymond Chen 的意见以及 Microsoft 的 x64 思维。
(( raymond chen exceptions )) 作为谷歌查询足以让你看到他的经典文章“更干净、更优雅、更错误——仅仅因为你看不到错误路径并不意味着它不存在。” 以及“更干净、更优雅、更难识别”的说明。
((x64 异常模型))将您带到 MSDN 文章“开始编程 64 位 Windows 系统您需要知道的一切”,其中包含引用“基于表的异常处理的缺点(相对于基于 x86 堆栈的模型)是从代码地址查找函数表条目比仅仅遍历链表需要更多时间。好处是函数不会
总结一下这句话,在 x64 中,设置一个从未使用过的“catch”是免费的或几乎是免费的,但实际上 throw-catch 异常比 x86 慢。

于 2009-01-15T06:25:21.570 回答
3

如果您抛出的对象可以保存告诉您有关错误性质的某些内容的上下文信息,则它可以更优雅。

从 istream 派生一个可抛出对象,您可以使用 >> 将信息流式传输到其中。教对象如何显示自己<<。

当您检测到错误情况时,在以下级别或以下 N 级别。用好的上下文信息填充你的对象,然后扔掉它。当您捕获对象时,告诉它在日志文件和/或屏幕和/或您想要的任何地方显示其上下文信息。

于 2009-01-14T21:06:51.603 回答
2

当然,可以在 try/catch 语句之外声明“i”(这就是我正在做的事情)。

好吧……如果你真的需要知道它的价值,i那么这似乎是一个日志工具——结构化异常处理可能不是最好的方法。如果您想有条件地处理异常(即仅在调试时),请将try放入循环中。由于这可能会损害性能(取决于您的环境),因此只能在调试模式下执行此操作。

于 2009-01-14T21:07:24.363 回答
2

第一件事,第一件事。如果您捕获异常,您就错了。您应该捕获您期望的特定异常。

但除此之外,如果您的异常是由您自己的代码引发的,您可以使用智能异常来包含您需要了解的有关该异常的所有数据。

例如,ArrayIndexOutOfBoundsException 将包含寻址的索引。

于 2009-01-14T21:08:09.103 回答
2

您可以通过两种方式获得更具体的信息。首先,不要捕捉异常,捕捉特定的异常。其次,在一个函数中使用多个 try/catch 语句,您需要确定哪个函数调用引发了异常。

于 2009-01-14T21:09:56.433 回答
2

在我看来,在这种情况下不应该使用异常,但如果你真的需要它们,我会采用以下方式:

您可以将“i”作为参数传递给 myCall(); 函数,如果发生任何错误,将抛出一些特殊异常。像:

public class SomeException : Exception
{
     public int Iteration;

     public SomeException(int iteration) { Iteration = iteration; }
}

循环块:

try
{
    for(int i = 0; i < n; ++i)
    {
        myCall(i);
    }
}catch(SomeException se)
{
    Log("Something terrible happened during the %ith iteration.", se.Iteration);
}

最后是 myCall() 函数:

void myCall(int iteration)
{
    // do something very useful here

    // failed.
    throw new SomeException(iteration);
}
于 2009-01-14T21:13:26.303 回答
2

我们失去了轻松查看代码如何处理不同位置的故障的可能性。Raymond Chen 写了一篇关于它的好文章

于 2009-01-14T22:28:39.147 回答
1

好!你可以这样做:

   try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         try {
            myCall();
         } catch(Exception e) {
            println("bloody hell! " + i);
         }
      <...>
   }

我认为异常比 Java 向您展示的要酷。真正有趣的是让调试器出现在每个未处理的异常上,然后在失败的那一刻查看堆栈,这样您就可以检查 i 而无需更改一行代码。也就是说,我相信例外应该是什么。

于 2009-01-14T21:08:55.437 回答
1

许多异常处理工具( Delphi 的MadExcept就是其中之一)允许您在捕获异常时检索整个堆栈跟踪。所以,你会确切地知道它被扔到了哪里。

获取“i”的常用技术是捕获异常,并在重新抛出异常之前向其添加额外数据(istream 技术)。这很少见,但如果你坚持...

于 2009-01-14T21:13:14.507 回答