异常处理:合同与特殊方法

时间:2008-08-25 16:52:23

标签: exception

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

  1. 合同方法。

    当一个方法没有按照它在方法头中所做的那样做时,它会抛出一个异常。因此,该方法“承诺”它将执行操作,如果由于某种原因失败,它将抛出异常。

  2. 特殊方法。

    只有在发生真正奇怪的事情时才抛出异常。当您可以使用正常控制流(If语句)解决情况时,不应使用异常。您不能像在合同方法中那样使用例外来控制流程。

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

    我们有一个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();
         }
     }
    

    你使用哪种款式?您认为对例外的最佳一般方法是什么?

5 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

如果您确实对异常感兴趣并想考虑如何使用它们来构建健壮的系统,请考虑阅读Making reliable distributed systems in the presence of software errors

答案 4 :(得分:0)

两种方法都是正确的。这意味着合同应该以这样的方式编写,即为所有不是真正例外的案件指定不需要抛出异常的行为。

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

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

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

 // 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 }

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