繁体   English   中英

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

[英]Exceptions vs return codes : do we lose something (while gaining something else)?

我的问题很模糊 :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 块,但是我最好使用返回代码? 然后我会在重要的代码行周围添加很多噪音吗?

怎么样:

for(int i = 0; i < n; i++)
{
  try
  {
    myCall();
  }
  catch(Exception e)
  {
    Log(String.Format("Problem with {0}", i));
  }
}

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

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

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

对于这样的错误,您需要错误代码。 如果您将异常用作“超级错误代码”,那么您最终会编写您提到的代码 - 将每个方法调用包装在 try/catch 块中! 你还不如返回一个枚举,而不是,它的速度快了很多,并显著更易于阅读比垃圾一切错误返回码7行代码,而不是1(其也更可能是正确的代码太-看到erikkallen的答复)

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

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

例外是您在编程中使用它的一种工具- 如果您认为它是最好的选择,请使用它,否则,请不要使用。

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

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

是的。 2件事。

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

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

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

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

期望异常本身包含关于 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的状态。

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

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

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

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

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

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

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

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

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

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

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

您可以将“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);
}

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

嗯! 你可以这样做:

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

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

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

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

您可以从RuntimeException派生并创建自己的mycall()抛出的异常。 然后你可以通过try / catch捕获它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM