代码之家  ›  专栏  ›  技术社区  ›  Sylvain Rodrigue

异常与返回代码:我们是否失去了一些东西(同时获得了其他东西)?

  •  8
  • Sylvain Rodrigue  · 技术社区  · 17 年前

    我的问题很模糊: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”(这就是我正在做的)。但我不喜欢它——我喜欢在使用变量的地方声明变量,而不是以前。

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

    提前感谢! 西尔万。

    ADDED:myCall()是一个模糊的API调用-我不知道它能抛出什么。此外,我当然可以在每次调用周围添加一个Try/Catch块,但我最好使用返回码?那么,我会在重要的代码行周围添加很多噪音吗?。

    14 回复  |  直到 17 年前
        1
  •  8
  •   scottm    17 年前

    怎么样:

    for(int i = 0; i < n; i++)
    {
      try
      {
        myCall();
      }
      catch(Exception e)
      {
        Log(String.Format("Problem with {0}", i));
      }
    }
    
        2
  •  8
  •   gbjbaanb    17 年前

    我认为你错了,这并不奇怪,就像许多其他人一样。

    例外情况不得用于程序流。再读一遍,这很重要。

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

    对于这样的错误,您需要错误代码。如果你把异常当作“超级错误代码”来使用,那么你最终会像你提到的那样编写代码——把每个方法调用都包装在try/catch块中!你也可以返回一个枚举,它是 许多 与用7行代码而不是1行代码来处理所有内容相比,更快、更容易阅读错误返回代码。(它也更有可能是正确的代码——见erikkallen的回复)

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

        3
  •  7
  •   Daniel Daranas    17 年前

    我不喜欢“现在,除了……”这样的表达。

    例外情况是 工具 如果你认为这是最好的选择,就使用它,否则就不要使用。

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

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

        4
  •  5
  •   Iraimbilanja Iraimbilanja    17 年前

    是啊。两件事。

    把试抓块放在有意义的地方。 如果你对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”)或其他类似的东西。这样你就可以抓住你需要的东西。

        5
  •  4
  •   Andrzej Doyle    17 年前

    在我看来,这里有两件事。

    期望异常本身包含有关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 就在引发异常的电话之前。
        6
  •  4
  •   pngaz    17 年前

    考虑Raymond Chen的观点以及微软的x64思维。
    (雷蒙德·陈除外)作为谷歌查询,足以让你看到他的经典文章“更干净、更优雅、更错误——仅仅因为你看不到错误路径,并不意味着它不存在。”以及澄清“更干净,更优雅,更难识别”。
    (x64异常模型))让您阅读MSDN文章“开始64位Windows系统编程所需的一切”,其中引用了“基于表的异常处理(相对于基于x86堆栈的模型)的缺点是,从代码地址查找函数表条目比遍历链表要花更多的时间。优点是函数没有在每次执行函数时设置try数据块的开销。”
    总结一下这句话,在x64中,设置一个从未使用过的“catch”是免费的或几乎免费的,但实际抛出catch异常比x86慢。

        7
  •  3
  •   EvilTeach    17 年前

    如果您抛出的对象可以包含上下文信息,从而告诉您错误的性质,那么它可以更优雅。

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

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

        8
  •  2
  •   Konrad Rudolph    17 年前

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

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

        9
  •  2
  •   Matthew Brubaker    17 年前

    第一件事,第一。如果你正在捕获异常,那么你错了。您应该捕获您所期望的特定异常。

    但除此之外,如果你的异常是由你自己的代码抛出的,你可以使用智能异常来包含你需要知道的关于该异常的所有数据。

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

        10
  •  2
  •   Jon B    17 年前

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

        11
  •  2
  •   arul    17 年前

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

    你可以将'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);
    }
    
        12
  •  2
  •   Sylvain Rodrigue    5 年前

    我们无法轻易看到代码将如何处理不同地方的故障。Raymond Chen写了一篇 good article about it

        13
  •  1
  •   nes1983    17 年前

    好!你可以这样做:

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

    我认为异常比Java向您展示的更酷。真正有趣的是,调试器在每个未处理的异常上都会出现,然后在失败时查看堆栈,这样你就可以在不更改一行代码的情况下检查i。我认为,这就是例外情况。

        14
  •  1
  •   Roddy    17 年前

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

    获取“i”的常见技术是捕获异常,并在重新生成异常之前向其添加额外数据(istream技术)。很少有必要这样做,但如果你坚持的话。..

    推荐文章