Обработка исключений: контракт против исключительного подхода - PullRequest
12 голосов
/ 25 августа 2008

Я знаю два подхода к обработке исключений, давайте посмотрим на них.

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

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

Ответы [ 5 ]

6 голосов
/ 25 августа 2008

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

1 голос
/ 25 августа 2008

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

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

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

0 голосов
/ 22 сентября 2011

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

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

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

0 голосов
/ 25 августа 2008

Если вы действительно заинтересованы в исключениях и хотите подумать о том, как использовать их для создания надежных систем, рассмотрите вопрос Создание надежных распределенных систем при наличии ошибок программного обеспечения .

0 голосов
/ 25 августа 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...