12

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

  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();
     }
 }

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

4

5 回答 5

6

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

于 2008-08-25T17:01:07.287 回答
1

我通常的做法是使用契约来处理由于“客户端”调用引起的任何类型的错误,即由于外部错误(即 ArgumentNullException)。

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

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

于 2008-08-25T17:00:45.273 回答
0

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

于 2008-08-25T16:59:43.280 回答
0

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

于 2008-08-25T18:13:44.443 回答
0

两种方法都是正确的。这意味着合同应该以这样一种方式编写,即为所有并非真正异常的情况指定不需要引发异常的行为。

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

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

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

// 如果失败,设置 ok false 并返回 default<TValue>。
TValue TryGetResult(ref bool ok, TParam param);
// 如果失败,请在 GetKeyErrorInfo 中指出具体问题
// 并返回默认值<TValue>。
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param);

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

  var myThingResult = myThing.TryGetSomeValue(ref ok, 不管);
  如果(好的){ do_whatever }

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

于 2011-09-22T15:43:02.030 回答