0

我一直(多年来)想知道实现以下最有意义的方式(这对我来说有点自相矛盾):

想象一个函数:

DoSomethingWith(value)
{
    if (value == null) { // Robust: Check parameter(s) first
        throw new ArgumentNullException(value);
    }

    // Some code ...
}

它被称为:

SomeFunction()
{
    if (value == null) { // Fail early
        InformUser();

        return;
    }

    DoSomethingWith(value);
}

但是,要捕获 ArgumentNullException,我应该这样做:

SomeFunction()
{
    if (value == null) { // Fail early
        InformUser();

        return;
    }

    try { // If throwing an Exception, why not *not* check for it (even if you checked already)?
        DoSomethingWith(value);
    } catch (ArgumentNullException) {
        InformUser();

        return;
    }
}

要不就:

SomeFunction()
{
    try { // No fail early anymore IMHO, because you could fail before calling DoSomethingWith(value)
        DoSomethingWith(value);
    } catch (ArgumentNullException) {
        InformUser();

        return;
    }
}

?

4

1 回答 1

0

这是一个非常普遍的问题,正确的解决方案取决于具体的代码和架构。

一般关于错误处理

主要关注点应该是在您可以处理的级别上捕获异常。

在正确的位置处理异常使代码健壮,因此异常不会使应用程序失败,并且可以相应地处理异常。

尽早失败会使应用程序健壮,因为这有助于避免不一致的状态。

这也意味着在执行的根部应该有一个更通用的try catch块来捕获任何无法处理的非致命应用程序错误。这通常意味着您作为程序员没有想到这个错误来源。稍后你可以扩展你的代码来处理这个错误。但是执行根不应该是您考虑异常处理的唯一地方。

你的例子

在您关于ArgumentNullException的示例中:

  • 是的,你应该尽早失败。每当使用无效的 null 参数调用您的方法时,您都应该抛出此异常。
  • 但是你永远不应该捕获这个异常,因为它应该可以避免它。请参阅与该主题相关的这篇文章:如果捕获空指针异常不是一个好习惯,那么捕获异常是一个好习惯吗?
  • 如果您正在处理用户输入或来自其他系统的输入,那么您应该验证输入。例如,您可以在检查空值后在 UI 上显示验证错误,而不会引发异常。如何向用户显示问题始终是错误处理的关键部分,因此请为您的应用程序定义适当的策略。您应该尽量避免在预期的程序执行流程中抛出异常。请参阅这篇文章:https ://msdn.microsoft.com/en-us/library/ms173163.aspx

请参阅下面有关异常处理的一般想法:

处理方法中的异常

如果在 DoSomethingWith 方法中抛出异常并且您可以处理它并继续执行流程而没有任何问题,那么您当然应该这样做。

这是重试数据库操作的伪代码示例:

void DoSomethingAndRetry(value)
{
   try
   {
       SaveToDatabase(value);
   }
   catch(DeadlockException ex)
   {
       //deadlock happened, we are retrying the SQL statement
       SaveToDatabase(value);
   }
}

让异常冒泡

假设您的方法是公开的。如果发生意味着方法失败并且您无法继续执行的异常,那么异常应该冒泡,以便调用代码可以相应地处理它。这取决于调用代码如何对异常做出反应的用例之一。

在让异常冒泡之前,您可以将其包装到另一个特定于应用程序的异常中作为内部异常,以添加额外的上下文信息。您也可以以某种方式处理异常,例如记录它,或者将日志记录留给调用代码,具体取决于您的日志记录体系结构。

public bool SaveSomething(value)
{
   try 
   {
       SaveToFile(value);
   }
   catch(FileNotFoundException ex)
   {
       //process exception if needed, E.g. log it
       ProcessException(ex);
       //you may want to wrap this exception into another one to add context info
       throw WrapIntoNewExceptionWithSomeDetails(ex);
   }
}

记录可能的异常

在 .NET 中,定义您的方法正在抛出哪些异常以及它可能抛出它的原因也很有帮助。这样调用代码就可以考虑到这一点。请参阅https://msdn.microsoft.com/en-us/library/w1htk11d.aspx

例子:

/// <exception cref="System.Exception">Thrown when something happens..</exception>
DoSomethingWith(value)
{
   ...
}

忽略异常

对于您可以接受失败方法并且不想一直在其周围添加 try catch 块的方法,您可以创建一个具有类似签名的方法:

public bool TryDoSomethingWith(value)
{
   try 
   {
       DoSomethingWith(value);
       return true;
   }
   catch(Exception ex)
   {
        //process exception if needed, e.g. log it
        ProcessException(ex);
        return false;
   }
}
于 2015-11-30T14:37:43.300 回答