exception - собственные - стандартные исключения c++




Обработка исключений: Контракт против Исключительного подхода (4)

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

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

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



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

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

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


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


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

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

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





exception