2008-08-25 2 views
11

Я знаю два подхода к обработке исключений, давайте посмотрим на них.Обработка исключений: Договор против Исключительный подход

  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, я чертовски вэй ожидать экземпляр автомобиля, а не какой-то паршивый указатель NULL, который может опустошить мой код бега десяток строк позже. Таким образом, я предпочитаю контракт с этим:

class CarController 
{ 

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

     return new Car(); 
    } 
} 

Какой стиль вы используете? Как вы думаете, лучший ли общий подход к Исключениям?

ответ

7

Я пользуюсь тем, что вы называете «договором». Возврат нулей или других специальных значений для указания ошибок не требуется на языке, который поддерживает исключения. Мне гораздо легче понять код, когда он не имеет кучу «if (result == NULL)» или «if (result == -1)», которые смешиваются с тем, что может быть очень простой, простой логикой.

+0

+1 для вашего примера возврата нулевого или -1 результата. Вместо того, чтобы сделать код более четким, нулевой или -1 возврат заставляет писателя вызывающего метода знать, как вызываемый метод решает сообщить нестандартный результат. Исключением является более чистый, контрактный подход, при котором invoker может обрабатывать вызываемый как (возможно) меняющийся черный ящик. Все invoker должно знать, что ему придется обрабатывать исключение, а не то, что сегодня это null, а Integer.MIN_VALUE - завтра. – rajah9 2013-10-01 17:22:49

0

Я считаю, что если вы строите класс, который будет использоваться внешней программой (или будет использоваться другими программами), вы должны использовать контрактный подход. Хорошим примером этого является API любого типа.

1

Мой обычный подход заключается в использовании контракта для обработки любых ошибок из-за «клиентского» вызова, то есть из-за внешней ошибки (например, ArgumentNullException).

Всякая ошибка в аргументах не обрабатывается. Исправлено исключение, и «клиент» отвечает за его обработку. С другой стороны, для внутренних ошибок всегда пытайтесь исправить их (как будто вы не можете получить соединение с базой данных по какой-то причине), и только если вы не можете справиться с этим, сделайте ререйз исключения.

Важно иметь в виду, что большинство необработанных исключений на таком уровне не будет обрабатываться клиентом в любом случае, поэтому они, вероятно, перейдут к самому общему обработчику исключений, поэтому, если такое исключение происходит, вы вероятно, FUBAR в любом случае.

0

Оба подхода являются правильными. Это означает, что контракт должен быть написан таким образом, чтобы указать для всех случаев, которые по-настоящему исключительны, поведение, которое не требует выброса исключения.

Обратите внимание, что некоторые ситуации могут быть или не быть исключительными в зависимости от того, что ожидает вызывающий код. Если вызывающий абонент ожидает, что словарь будет содержать определенный элемент, а отсутствие этого элемента указывает на серьезную проблему, то отказ найти элемент является исключительным условием и должен вызвать исключение. Если, однако, вызывающий абонент не знает, существует ли элемент и в равной степени подготовлен к работе с его присутствием или его отсутствием, то отсутствие элемента будет ожидаемым условием и не должно вызывать исключения. Лучший способ справиться с такими вариациями в ожидании вызова - заключить контракт в двух методах: методе 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 myThingResult = myThing.TryGetSomeValue(ref ok, whatever); 
    if (ok) { do_whatever } 

не совсем стандартный подход, но в некоторых случаях преимущества могут оправдать.