我知道两种异常处理方法,让我们看一下。

  1. 契约法。

    当一个方法没有执行方法头中规定的操作时,它将抛出异常。因此,该方法“承诺”它将执行该操作,如果由于某种原因失败,它将抛出异常。

  2. 特殊的方法。

    仅当发生真正奇怪的事情时才抛出异常。当可以使用正常控制流(If 语句)解决问题时,不应使用异常。您不会像在契约方法中那样使用异常来控制流。

让我们在不同的情况下使用这两种方法:

我们有一个 Customer 类,它有一个名为 OrderProduct 的方法。

合同方式:

class Customer
{
     public void OrderProduct(Product product)
     {
           if((m_credit - product.Price) < 0)
                  throw new NoCreditException("Not enough credit!");
           // do stuff 
     }
}

特殊方法:

class Customer
{
     public bool OrderProduct(Product product)
     {
          if((m_credit - product.Price) < 0)
                   return false;
          // do stuff
          return true;
     }
}

if !(customer.OrderProduct(product))
            Console.WriteLine("Not enough credit!");
else
   // go on with your life

在这里,我更喜欢例外的方法,因为假设客户没有赢得彩票而没有钱,这并不是真正的例外。

但这里有一种情况,我在合同风格上犯了错误。

卓越:

class CarController
{
     // returns null if car creation failed.
     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         return null;
     }
 }

当我调用一个名为 CreateCar 的方法时,我非常希望得到一个 Car 实例,而不是一些糟糕的空指针,这可能会在十几行后破坏我正在运行的代码。因此,我更喜欢合同而不是这个:

class CarController
{

     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         throw new CarModelNotKnownException("Model unkown");

         return new Car();
     }
 }

您使用哪种风格?您认为处理异常的最佳通用方法是什么?

有帮助吗?

解决方案

我赞成你所说的“合同”方法。在支持异常的语言中,不需要返回空值或其他特殊值来指示错误。我发现,当代码中没有一堆“if (result == NULL)”或“if (result == -1)”子句与非常简单、直接的逻辑混合在一起时,理解代码会容易得多。

其他提示

我通常的方法是使用合约来处理由于“客户端”调用而导致的任何类型的错误,即由于外部错误(即 ArgumentNullException)。

参数上的每个错误都不会被处理。出现异常,由“客户”负责处理。另一方面,对于内部错误,始终尝试纠正它们(就好像由于某种原因无法获得数据库连接),并且只有在无法处理它时才会重新引发异常。

重要的是要记住,这种级别的大多数未处理的异常无论如何都无法由客户端处理,因此它们可能只会进入最通用的异常处理程序,所以如果发生这样的异常,无论如何你都可能很沮丧。

我相信,如果您正在构建一个将由外部程序使用(或将由其他程序重用)的类,那么您应该使用契约方法。任何类型的 API 都是一个很好的例子。

如果您确实对异常感兴趣并想考虑如何使用它们构建健壮的系统,请考虑阅读 在存在软件错误的情况下打造可靠的分布式系统.

两种做法都是对的。这意味着契约的编写方式应该为所有并非真正异常的情况指定不需要抛出异常的行为。

请注意,根据代码调用者的期望,某些情况可能会或可能不会异常。如果调用者期望字典将包含某个项目,并且缺少该项目将表明存在严重问题,则无法找到该项目是一种异常情况,并且应该导致抛出异常。然而,如果调用者并不真正知道某个项目是否存在,并且同样准备好处理它的存在或不存在,那么该项目的不存在将是预期的情况,并且不应导致异常。处理调用者期望的这种变化的最佳方法是让合约指定两种方法:DoSomething 方法和 TryDoSomething 方法,例如

TValue GetValue(TKey Key);
bool TryGetValue(TKey Key, ref TValue value);

请注意,虽然标准的“尝试”模式如上所示,但如果设计一个生成项目的接口,一些替代方案也可能会有所帮助:

 // In case of failure, set ok false and return default<TValue>.
TValue TryGetResult(ref bool ok, TParam param);
// In case of failure, indicate particular problem in GetKeyErrorInfo
// and return default<TValue>.
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param);

请注意,在接口中使用类似于普通 TryGetResult 模式的内容将使接口相对于结果类型保持不变;使用上述模式之一将允许接口相对于结果类型是协变的。此外,它还允许在“var”声明中使用结果:

  var myThingResult = myThing.TryGetSomeValue(ref ok, whatever);
  if (ok) { do_whatever }

不完全是标准方法,但在某些情况下,其优点可能证明它是合理的。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top