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

StackOverflow https://stackoverflow.com/questions/26383

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

Мой обычный подход заключается в использовании contract для обработки любого рода ошибок из-за вызова "client", то есть из-за внешней ошибки (т.е. 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 }

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top