Исключения против кодов возврата: мы что-то теряем (получая что-то другое)? - PullRequest
8 голосов
/ 15 января 2009

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

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

Спасибо заранее! Sylvain.

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

Ответы [ 15 ]

8 голосов
/ 15 января 2009

как насчет:

for(int i = 0; i < n; i++)
{
  try
  {
    myCall();
  }
  catch(Exception e)
  {
    Log(String.Format("Problem with {0}", i));
  }
}
7 голосов
/ 15 января 2009

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

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

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

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

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

7 голосов
/ 15 января 2009

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

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

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

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

5 голосов
/ 15 января 2009

Да. 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") или что-то , Таким образом, вы можете поймать то, что вам нужно.

4 голосов
/ 15 января 2009

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

4 голосов
/ 15 января 2009

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

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

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

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

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

<code>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 непосредственно перед вызовом, вызвавшим исключение.
3 голосов
/ 15 января 2009

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

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

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

2 голосов
/ 15 января 2009

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

2 голосов
/ 15 января 2009

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

Вы можете передать '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);
}
2 голосов
/ 15 января 2009

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

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