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

时间:2009-01-14 21:01:19

标签: c# exception return-code

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

15 个答案:

答案 0 :(得分:8)

怎么样:

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

答案 1 :(得分:7)

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

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

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

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

答案 2 :(得分:7)

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

不能将例外用于程序流程。再读一遍,这很重要。

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

对于这样的错误,您需要错误代码。如果您使用异常就好像它们是“超级错误代码”那么您最终会编写如您所提到的代码 - 将每个方法调用包装在try / catch块中!您也可以返回一个enum,它的 lot 更快,更容易读取错误返回代码,而不是丢弃7行代码而不是1。(它也更可能是正确的代码 - 见erikkallen的回复)

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

答案 3 :(得分:5)

呀。 2件事。

将try-catch块置于有意义的位置。如果您对myCall的异常感兴趣(以及我的值),请使用

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

针对不同的错误抛出不同类的对象。如果您对财务处理中发生的逻辑错误感兴趣,请抛出finances :: logic_error,而不是std :: exception(“error msg”)或一些东西。这样你就可以抓住你需要的东西。

答案 4 :(得分: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);
      }
      ...

这可能不会 expilictly 记录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之前的内容。

答案 5 :(得分:4)

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

答案 6 :(得分:3)

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

从istream派生一个throwable对象,你可以使用&gt;&gt;将信息流入其中。 教会对象如何显示自己&lt;&lt;。

当您检测到错误情况时,在下面的级别或下面的N级别。使用良好的上下文信息填充对象,然后抛出它。当您捕获对象时,告诉它将其上下文信息显示到日志文件和/或屏幕和/或您希望它的位置。

答案 7 :(得分:2)

  

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

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

答案 8 :(得分:2)

首先,首先。如果你正在捕获异常,那你错了。您应该捕获您期望的特定异常。

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

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

答案 9 :(得分:2)

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

答案 10 :(得分: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);
}

答案 11 :(得分:2)

我们失去了轻松查看代码如何处理不同位置故障的可能性。 Raymond Chen撰写了一篇good article about it

答案 12 :(得分:1)

嘛!你可以这样做:

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

我认为例外比Java更酷。真正有趣的是让调试器出现在每个未处理的异常上,然后在发生故障时查看堆栈,以便您可以在不必更改代码行的情况下检查i。也就是说,我相信,应该有什么例外。

答案 13 :(得分:1)

许多异常处理工具(Delphi的MadExcept是一个)允许您在捕获异常时检索整个堆栈跟踪。所以,你知道完全它被抛出的位置。

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

答案 14 :(得分:-2)

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