Исключения против кодов возврата: теряем ли мы что-то (приобретая что-то еще)?

мой вопрос довольно расплывчатый: o) - но вот пример:

когда я писал код C, я смог зарегистрировать значение счетчика, когда что-то не удалось :

   <...>
   for ( int i = 0 ; i < n ; i++ )
      if ( SUCCESS != myCall())
         Log( "Failure, i = %d", i );
   <...>

теперь, используя исключения, я получаю это :

  try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }

конечно, можно объявить " i " вне оператора try/catch (и это то, что я делаю). Но мне это не нравится - мне нравится объявлять переменные там, где они используются, а не раньше.

но, может быть, я что-то упускаю здесь. Делать у вас есть элегантное решение ?

спасибо заранее ! Сильвэн.

ADDED: myCall () - это неясный вызов API-я понятия не имею, что он может бросить. Кроме того, я могу, конечно, добавить блок Try/Catch вокруг каждого вызова, но мне было бы лучше использовать коды возврата ? Добавлю ли я тогда много шума вокруг важных строк кода ?.

15 ответов


Как насчет:

for(int i = 0; i < n; i++)
{
  try
  {
    myCall();
  }
  catch(Exception e)
  {
    Log(String.Format("Problem with {0}", i));
  }
}

Мне не нравится "сейчас, за исключением..." выражение.

исключения инструмент что у вас есть для использования его в вашем программировании - если вы считаете, что это лучший вариант, используйте его, иначе не делайте.

Я следую личному правилу не бросать никаких исключений, которые я могу избежать броска, во внутреннем коде. Для API общедоступной библиотеки DLL проверки предварительных условий должны быть оставлены включенными и вызывать исключения, если они терпят неудачу, да; но для внутренних логика, я редко (если вообще) включаю исключения в свой дизайн. И наоборот, когда я использую какую - то функцию, которая документирует, что она будет бросать, если произойдет какая-то плохая ситуация, я, как правило, фиксирую исключение inmediately-это ожидаемое исключение, в конце концов.

Если вы думаете, что ваша не исключительная альтернатива лучше-придерживайтесь ее!


Я думаю, что вы ошибаетесь, и это не удивительно, как и многие другие люди.

исключения не должны использоваться для потока программы. Прочтите это еще раз, это важно.

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

для таких ошибок вам нужны коды ошибок. Если вы используете исключения, как если бы они были "супер кодами ошибок", то вы в конечном итоге пишете код, как вы упомянули - обертывание каждого вызова метода в блоке try/catch! Вы также можете вернуть перечисление вместо этого, его много быстрее и значительно проще читать код возврата ошибок, чем засорять все 7 строками кода вместо 1. (его также, скорее всего, будет правильный код слишком-см. ответ эриккаллена)

теперь, в реальном мире, часто бывает так, что методы бросают исключения, где вы бы предпочли, чтобы они не делали (например, EndOfFile), в этом случае вы должны использовать анти-шаблон "try/catch wrapper", но если вы получите, чтобы разработать свои методы, не используйте исключения для повседневной обработки ошибок-используйте их только для исключительных обстоятельств. (да, я знаю, что трудно получить такой дизайн правильно, но так много проектных работ)


да. 2 вещи.

поместите блоки try-catch, где они имеют смысл. если вас интересуют исключения из myCall (а также значение i), используйте

for ( int i = 0 ; i < n ; i++ )
    try { myCall(); } catch ( Exception exception ) {
        Log( "Failure, i = %d", i );
    }

бросать объекты разных классов для разных ошибок. если вас интересуют логические ошибки, возникающие в финансовой обработке, бросьте finances::logic_error, а не std:: exception("error msg") или что-то еще. Таким образом, вы можете поймать то, что вам нужно.


две вещи здесь, с моей точки зрения.

не возмутительно ожидать, что само исключение будет содержать информацию о значении i или, менее конкретно, о контексте, в котором оно было оценено, и что пошло не так. Для тривиального примера я бы никогда просто не бросил straight InvalidArgumentException; скорее, я бы гарантировал, что передал точное описание в конструктор, например


   public void doStuff(int arg) {
      if (arg < 0) {
         throw new InvalidArgumentException("Index must be greater than or equal to zero, but was " + arg);
      }
      ...

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

альтернативно, если вещи действительно слишком абстрактны для вашего myCall функция для того чтобы знать что продолжая, я нахожу, что ведение журнала на более высоком уровне многословия перед вызовом работает хорошо, например

try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         DebugLog("Trying myCall with i = " + i);
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }
Таким образом, если что-то пойдет не так, вы можете проверить свой журнал отладки высокой детализации и найти, что i просто перед вызовом, который вызвал исключение.

рассмотрим мнения Раймонда Чена, а также x64-мышление Microsoft.
((исключения Рэймонда Чена)) как Google-запроса достаточно, чтобы получить вас к его классическим эссе "чище, элегантнее и неправильно-только потому, что вы не видите путь ошибки не означает, что он не существует. и уточнение "чище, элегантнее и труднее распознать".
((x64 модель исключения)) возвращает вас к статье MSDN "все, что вам нужно знать, чтобы начать Программирование 64-разрядной Windows Системы", который содержит цитату "недостатком обработки исключений на основе таблиц (относительно модели на основе стека x86) является то, что поиск записей таблицы функций из адресов кода занимает больше времени, чем просто прогулка по связанному списку. Преимущество заключается в том, что функции не имеют накладных расходов на настройку блока данных try каждый раз, когда функция выполняется."
Подводя итог этой цитате, в x64 бесплатно или почти бесплатно настроить "catch", который никогда не используется, но на самом деле бросить-поймать исключение медленнее, чем в x86.


Это может быть более элегантно, если объект, который вы бросаете, может содержать контекстную информацию, которая говорит вам что-то о природе ошибки.

выведите объект throwable из istream, и вы можете использовать >> для потоковой передачи информации в него. научите объект, как отображать себя

при обнаружении условия ошибки, на уровне ниже, или N уровней ниже. Заполните объект хорошей контекстной информацией и выбросьте его. Когда вы поймаете объект, скажите он отображает контекстную информацию в файл журнала и/или на экране и / или там, где вы хотите.


конечно, можно объявить " i " вне оператора try/catch (и это то, что я делаю).

Ну ... если вам действительно нужно знать значение i тогда это похоже на обработку исключений с регистрацией транспортных средств, вероятно,не лучший способ. Если вы хотите обработать исключение условно (т. е. только при отладке), поставить try блок внутри петлю. Поскольку это может повредить производительность (в зависимости от вашего environment), только сделайте это в режиме отладки.


первые вещи сначала. Если вы ловите исключение, вы ошибаетесь. Вы должны ловить конкретное исключение, которое вы ожидаете.

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

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


вы можете получить более конкретную информацию двумя способами. Во-первых, не ловите исключение, ловите конкретное исключение. Во-вторых, используйте несколько операторов try/catch в функции, где вам нужно быть уверенным, какой вызов функции вызвал исключение.


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

вы можете передать " i " в качестве параметра функции myCall(); и если произойдет какая-либо ошибка, будет создано специальное исключение. Например:

public class SomeException : Exception
{
     public int Iteration;

     public SomeException(int iteration) { Iteration = iteration; }
}

цикл блок:

try
{
    for(int i = 0; i < n; ++i)
    {
        myCall(i);
    }
}catch(SomeException se)
{
    Log("Something terrible happened during the %ith iteration.", se.Iteration);
}

и, наконец, функция myCall ():

void myCall(int iteration)
{
    // do something very useful here

    // failed.
    throw new SomeException(iteration);
}

мы теряем возможность легко увидеть, как код будет обрабатывать сбои в разных местах. Раймонд Чен написал хорошая статья об этом


хорошо! вы могли бы сделать это:

   try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         try {
            myCall();
         } catch(Exception e) {
            println("bloody hell! " + i);
         }
      <...>
   }

Я думаю, что исключения круче, чем показывает вам Java. Что действительно интересно, так это то, что отладчик придумывает каждое исключение, которое не обрабатывается, а затем смотрит на стек в момент сбоя, чтобы вы могли изучить i без необходимости изменять строку кода. Вот для чего, я полагаю, должны быть исключения.


многие инструменты обработки исключений (MadExcept для Delphi один) позволяет получить всю трассировку стека, когда вы ловите исключение. Итак, вы знаете ровно, где он был брошен.

общий метод получения " i " - поймать исключение и добавить к нему дополнительные данные (метод istream) перед его переосмыслением. Это редко бывает необходимо, но если вы настаиваете...


вы можете наследовать от RuntimeException и создать свое собственное исключение, которое mycall() бросает. Тогда вы можете поймать это с помощью try / catch.