我知道两种异常处理方法,让我们来看看它们。
合同方法。
当一个方法没有按照它在方法头中所做的那样做时,它会抛出一个异常。因此,该方法“承诺”它将执行操作,如果由于某种原因失败,它将抛出异常。
特殊方法。
只有在发生真正奇怪的事情时才抛出异常。当您可以使用正常控制流(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();
}
}
你使用哪种款式?您认为对例外的最佳一般方法是什么?
答案 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 }
不完全是标准方法,但在某些情况下,优势可能是合理的。