从您的方法返回异常是不好的做法

时间:2009-06-10 01:19:15

标签: .net exception error-handling

我的同事编写的许多代码库方法都会执行自己的错误处理,通常是捕获,通知和记录。

在这些情况下,方法返回一个布尔值,表示成功或失败。

有时候,如果一个方法失败了,我希望调用代码知道原因,并且返回一个布尔值是不够的。

解决此问题的一种方法是在方法中保留错误处理,但使方法无效,并让方法抛出异常。

然而,最近我养成了以适当的方法返回异常的习惯,通常是行动方法,否则可能无效,但我想知道行动的状态。

返回异常而不是使用void方法抛出异常的优点是我认为它使其他程序员更容易使用你的库,而且,更重要的是,这意味着你可以安全地调用该方法不得不担心捕获异常

例如,如果方法只是无效,程序员可能不会立即明白成功或失败。

但是如果方法专门指定了Exception的返回类型,那么您知道如果需要可以检查成功或失败。但这也意味着如果你不想,你不必担心会发现错误。

这有意义吗?也许我没有使用最好的例子,但一般情况下,返回Exceptions是否可以,或者是否有更好的模式?

更新

哇,压倒性的结果是没办法。我是这么想的。我必须说,做到这一点(返回一个例外)有点解决了一个问题,但确实感觉不对。

那么,从你的一些答案中,这些特定场景的最佳解决方案(复杂的类,或具有一个或多个外部依赖关系的类(即Web服务))似乎是自定义结果类?

更新

我非常感谢所有的意见,我正在阅读所有内容,而且我正在仔细考虑所有的意见。

目前我赞成使用void方法,抛出异常,然后在外面捕获它们......那样更好吗?

16 个答案:

答案 0 :(得分:32)

如果你的意思是......

public Exception MyMethod( string foo )
{
   if( String.IsNullOrEmpty() )
   {
      return new ArgumentNullException( "foo" );
   }
}

......而不是......

public void MyMethod( string foo )
{
   if( String.IsNullOrEmpty() )
   {
      throw new ArgumentNullException( "foo" )
   }
}

然后绝对没有,这样做是不对的。您将完全重新发明异常的目的并将其用作结果。以下是一些标准best practices

另一个很好的理由不是标准代表将不再匹配您的方法签名。因此,例如,您无法再在MyMethod上使用Action<string>,而是需要使用Func<string,Exception>

@Andy,评论,回答太长时间评论:不是真的。不要那么关心来电者。无论如何,他应该在设计中保持防守。想想Exception的语义......“除非有人知道如何解决这个问题,否则这个应用程序的执行将会停在这里。”如果您可以解决此问题,则应解决该问题。如果你做不到,你必须记录它并将它扔到一个新的水平,因为他们可能知道该做什么。

你应该处理你可以处理的事情并抛弃你不能处理的东西。根据定义,调用堆栈中的人比你更了解世界。您的应用程序需要具备弹性,能够处理异常并继续运行。他们唯一的方法是采取防御性编码,并将问题推向更高级别的信息,以确定是否可以做任何事情。

在一天结束时,如果答案是“否”,那么将问题记录到它可以理解,是一个好的公民的盒子,优雅地终止应用程序,并在另一天生活。不要自私,并尝试为您的来电者隐藏错误。要防守,他也会这样做。 :)

查看Enterprise Library Exception handling block。它认为他们真正阐述了如何在整个架构中处理异常的伟大愿景。

答案 1 :(得分:13)

没有其他人这样说过:我的理解是在.NET中,在异常抛出之前,不会填充异常的堆栈跟踪。请参阅Best Practices for Handling Exceptions所在的位置:

  • 堆栈跟踪从抛出异常的语句开始,并以捕获异常的catch语句结束。在决定将抛出语句放在何处时,请注意这一事实。

  • 使用异常构建器方法。一个类在其实现中从不同的地方抛出相同的异常是很常见的。为避免过多的代码,请使用创建异常的辅助方法并将其返回。

在.NET中,如果您只是实例化一个Exception并返回它,那么您将不会在该Exception中拥有任何堆栈信息。当您使用此功能创建“异常”构建器方法时,这是一个好处,当您按照要求的方式使用“异常”时会丢失。相比之下,在Java中,在实例化异常时添加堆栈信息,无论是否抛出它。

因此,在.NET中,当您返回一个从未抛出的异常时,您会放弃堆栈跟踪,告诉您问题发生的位置。在这种情况下,也许你不在乎,但我认为值得指出。

在任何现代JVM和任何最新的.NET版本中,抛出Exception的性能开销都相当小。返回异常而不是抛出它 - 出于性能原因 - 是不合适的优化... 除非你已经分析了你的程序并且发现特定的抛出确实是一个瓶颈,并且返回异常会产生有意义的差异。 (这会让我感到惊讶,但一切皆有可能。)

注意:假设你创建一个方法,否则void会在出错时返回异常。这意味着如果您要检查错误,则必须检查null以查看是否存在错误。作为一般规则,在出错时返回null的方法会导致脆弱的代码。例如,返回List的方法应该返回一个空List而不是null。

另请注意,在重新抛出异常时,在.NET中,由于堆栈是在throw子句中填充的,因此通常会丢失原始堆栈跟踪。要避免这种情况,请使用裸throw而不指定Exception实例。如果你throw ex那么你将失去原始的堆栈跟踪。

答案 2 :(得分:9)

你永远不应该返回异常。相反,如果您的函数足够复杂,请创建一个类,该类是您的函数的结果。例如,GetPatientList方法返回GetPatientListResult。它可能有一个成功的成员。例外是非常罕见的东西,例如文件在操作中消失,为函数设置无效或空参数,数据库关闭。此外,它打破了整个抽象模型 - Dog.GoForAWalk应该返回DogWalkReport,而不是PossiblyNullDogWalkException。

答案 3 :(得分:6)

没有

例外应该是仅在特殊情况下存在的事物(因此名称)。我唯一能看到让方法返回异常的方法是某种类型的工厂方法,其目的是专门创建异常 - 但即便如此,它仍在延伸。

如果需要返回比布尔值更多的信息,请创建一个包含布尔值+其他信息的自定义类(不是从异常派生的)。

答案 4 :(得分:6)

绝对是非正统的例外使用。使异常变得强大的特性是抛出的异常会在调用堆栈中冒出任意数量的层。如果您只是将其退回,则会失去该属性(但也会失去与之相关的负面性能影响)。

我真的不明白这与简单地返回包含有关可能错误的信息的类的实例有何不同。唯一的区别是返回从Exception类继承的对象可能会引起其他开发人员的混淆,后者将在以后读取您的代码。

因此,虽然返回包含有关错误的信息的对象没有任何问题,但为了避免混淆,我建议使用不从Exception继承的类,以避免混淆。

答案 5 :(得分:5)

在您描述的方案中返回异常是一个坏主意,因为异常不是为此目的而设计的。

最好创建一个包含所需信息的结构或类,然后返回它。

public enum MyErrorType
{
    Abc,
    Xyz,
    Efg
};

public struct Result
{
    public bool Success;
    public MyErrorType ErrorType;
};

然后方法:

private Result MyMethod()
{
   // code
}

答案 6 :(得分:4)

我必须同意,一般,这是一个非常糟糕的主意。

然而,我可以想到一两个可能有意义的场景。

例如,如果您正在开发一个类库项目,您可能希望抛出自己的自定义异常,并且您可能希望在抛出其中一个异常时执行一些额外的工作(如果用户允许,则为home home) 。

要强制执行此操作,您可能希望使用工厂模式,这样只有您可以创建它们并强制创建通过一个地方(您的“电话之家”代码也存在)。

在这种情况下,您不希望工厂自己抛出异常,因为这会使您的堆栈跟踪关闭。相反,它会将它返回到原始方法以抛出。

但是像这样的情况是**咳嗽**例外,而不是规则。 在任何情况下我都不希望看到跨类库边界传递的异常,在任何情况下都不会创建异常而不确定它是否会被抛出。

答案 7 :(得分:2)

不,它完全混淆了语言(技术,因为你只是指定.NET)。

我也不同意它会让你的API更容易被其他人使用,任何已经学习并且使用包含异常的模型对编程有基本了解的人都会发现使用你的代码更加困难。

如果将来您的方法需要返回值,会发生什么?如果有多个错误情况怎么办?如果您真的想要了解该方法的更多信息,那么您需要创建一个封装结果和所需的任何其他状态信息的类。

答案 8 :(得分:2)

我不确定返回异常的危害,因为我可以将异常消息,类型和相关信息报告到sql server上的异常日志中。

某些'例外'如sql server连接失败,因为报告了其他异常,我不需要冒泡到另一个级别;我可以尝试将它们写入网络驱动器而不是崩溃。

其他类型的异常我会尽快检查调用该函数的方法是否可以处理该特定问题而不会实际抛出它的性能

除了这些方法之外,我还包含一个布尔参数,允许调用者决定它是应该抛出异常还是只返回它。

答案 9 :(得分:1)

我从未见过一个返回异常的方法。通常,该方法返回一个值,该方法可能抛出异常。但是我从未见过返回新的System.InvalidOperationException(“Logfile不能只读”)的代码;例如......

所以回答你的问题,我会说是的,这很糟糕......

答案 10 :(得分:1)

不,我不认为这是一个好习惯。

例外是有名的:它们应该是特殊情况,而不是常态。以这种方式编写函数是不可能的,因为如果你返回异常,就不能返回计算的成果。

除非你真的有一个值得实施的恢复策略,否则你不应该在你的方法中处理异常。我不认为记录和重新投掷是一种恢复策略。

您可以随时通过抛出未经检查的异常来避免您的客户编写try / catch块。

答案 11 :(得分:1)

如果您担心其他程序员没有捕获它们,您可以使用代码分析工具来确保捕获所有异常。甚至可以编写自定义属性来实现Java中的已检查异常。

话虽如此,遵循既定的惯例并抛出异常并不容易吗?如果你把它放在一个功能评论中,它就足够了。如果您考虑一些特定的程序员没有捕获异常,那么您需要处理PKBAC

答案 12 :(得分:1)

例外情况仅适用于特殊情况。将它留给造成例外情况的人(来电者)来解决问题。假如你有一个方法可以将两个数字分开,如

int Div(int x, int y)
{
return x/y;
}

如何为该方法实现正确的错误处理?在下面的案例中你会回到这个电话有什么价值?

Div(10,0);
or(int.MinValue,-1); //result of int.MinValue == int.MaxValue + 1;

在两种情况下,没有'正确'的动作取决于方法的用法。 通常,在出现意外情况时中断代码执行是个好主意。

错误处理超过try{] catch{}。无论进入catch块,需要根据错误做出明智的决定。简单地捕获异常并返回异常不是错误处理;它的症状隐藏起来,并且会使调试充其量非常困难并且经常是噩梦。

答案 13 :(得分:0)

在某些情况下,例外可能会很好,但请记住,它们可能会产生巨大的性能损失。

答案 14 :(得分:0)

我经常使用这种模式返回异常,以避免多个try {} catch块混淆代码,所以我想说,在某些情况下返回异常是一个好主意。

public void Method(string text)
{
   //throw exception if needed
   throw new Exception("exception"); 
}

public bool TryMethod(string text, out Exception ex)
{
   try
   {
      Method("test")
      ex = null;
      return true;
   }
   catch(Exception e)
   {
      ex = e;
      return false;
   }
}

答案 15 :(得分:0)

我同意,在几乎所有情况下,返回而不是抛出异常是一种不好的做法,除了一个,其中函数的工作是构建异常。例如,如果您使用的Com库只返回结果而不是引发异常,那么您可能会创建一个将结果转换为异常的函数:

static public ErrorException ReturnErrAsExcep()
{
    int errcd = 0;
    string errmsg = "";

    CrummyComLib.GetLastError(out errcd, out errmsg);
    ErrorException ex = new SAPErrorException(errmsg, errcd);
    return ex;
} 

我喜欢使用

int retval = CrummyComObject.DoStuff();

if (retval != 0)
    throw globals.ReturnErrAsExcep();