Как работать с исключениями

Мой технический руководитель настаивает на этом механизме исключения:

try
{
    DoSth();
}
catch (OurException)
{
    throw;
}
catch (Exception ex)
{
    Util.Log(ex.Message, "1242"); // 1242 is unique to this catch block
    throw new OurException(ex);
}

1242 - это идентификатор метода catch, которым мы обрабатываем исключение, отличное от OurException. Каждый блок catch в проекте должен иметь уникальный идентификатор, чтобы мы могли узнать, где произошло исключение, просмотрев журналы.

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

Это разумный подход? Если есть, то какие альтернативы лучше?

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

Edit2: Всем спасибо. Я использовал ваши ответы как часть своего аргумента против этого, но мне сказали, что вы недостаточно опытны и не знаете, как действовать в реальных жизненных ситуациях. Я должен идти этим путем.

Здравствуйте! Вы можете создать собственный класс Exception, чтобы ведение журнала происходило в конструкторе исключений и вместо идентификатора использовалась трассировка стека. Это .NET? Colby Africa

Colby Africa 23.12.2008 20:50

Да, это .net. OurException - наш единственный настраиваемый класс исключений.

Serhat Ozgel 23.12.2008 20:52

Как убедиться, что идентификаторы уникальны? Почему бы просто не записать трассировку стека?

matt b 23.12.2008 20:57

Я не понимаю, почему вы поймаете OurException, ничего не сделаете с ним, а затем повторно выбросите. Помоги мне...

Craig Shearer 23.12.2008 21:00

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

Serhat Ozgel 23.12.2008 21:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
568
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

Все выглядит правильно, кроме этого странного числа.

Трассировка стека будет содержать необходимую информацию.

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

Что касается пользовательского исключения, почему бы этого не сделать?

try
{
DoSth();
}
catch(Exception ex)
{
Util.Log(ex.Message, ex.StackTrace);
if (ex is OurException) throw ex;
else throw new OurException(ex); // use the original exception as the InnerException
}

Кроме того, я не уверен, почему вы хотите повторно генерировать исключение после того, как оно было обработано - вы можете объяснить причину этого?

@Ali A - Очень верный момент, поэтому позвольте мне перефразировать - зачем повторно генерировать исключение вместо того, чтобы закончить его обработку прямо здесь?

Обновлено:

Почему бы не сделать это вместо повторного заброса?

try
{
DoSth();
}
catch(Exception ex)
{
Util.HandleException(ex);
}

Util.HandleException:

public static void HandleException(ex)
{
Util.Log(ex); // Util.Log should log the message and stack trace of the passed exception as well as any InnerExceptions - remember, than can be several nested InnerExceptions.

// Any additional handling logic, such as exiting the application, or showing the user an error message (or redirecting in a web app)
}

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

На мой взгляд, это не было "обработано", это было зарегистрировано.

Ali Afshar 23.12.2008 21:01
Ответ принят как подходящий

Вы также можете посмотреть Блок приложения для обработки исключений.

Я использовал его в нескольких проектах, и он очень полезен. Особенно, если позже вы захотите изменить способ работы обработки исключений и какую информацию собирать.

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

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

edit: это правда, что stacktrace не дает всегда значимых результатов в режиме выпуска, но я думаю, что то же самое можно сказать об этой жестко запрограммированной схеме номеров! :-)

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

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

Что касается того, хорошая это идея или нет, при отсутствии какой-либо дополнительной информации я бы предпочел сказать нет. Если каждый вызов метода заключен в блок try / catch, это будет очень дорого. Как правило, исключения можно разделить на два лагеря: те, с которыми вы можете разумно ожидать и справиться, и те, которые действительно являются ошибками (или, по крайней мере, не ожидаются успешно). Мне кажется, что использование дробовика для перехвата всех исключений может сделать больше для сокрытия последних, чем для их разрешения. Это будет особенно верно, если на верхнем уровне вы перехватили все исключения, так что единственная запись, которая у вас есть, - это сообщение журнала.

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

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

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

supercat 14.12.2011 21:03

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

Недостатки:

  1. Это число не внушает особого доверия, трассировка стека намного лучше

  2. Зачем нужны 2 блока catch, если ваше настраиваемое исключение можно обработать на "catch (Exception ex)" "?

Потому что, если это как OurException, оно уже зарегистрировано, поэтому нам не нужно снова регистрировать и переносить его.

Serhat Ozgel 23.12.2008 23:20

Иметь OurException довольно странно. Обычно вам нужны специализированные блоки catch, а затем последний, тот, который улавливает общий Exception, - это то место, где вы ведете журнал:

try 
{
    DoSomething();
}
catch (DivideByZeroException)
{
    // handle it here, maybe rethrow it, whatever
}
// more catch blocks
catch (Exception)
{
    // oops, this is unexpected, so lets log it
}

Но то, что вы делаете, будет работать. Я действительно считаю, что 1242 должен уйти. Вот способ распечатать метод, имя файла и номер строки, которые вы могли бы использовать вместо этого. Сам не пробовал, но выглядит неплохо:

    [Conditional("DEBUG")]
    public static void DebugPrintTrace()
    {
        StackTrace st = new StackTrace(true);
        StackFrame sf = st.GetFrame(1); // this gets the caller's frame, not this one
        Console.WriteLine("Trace "
            + sf.GetMethod().Name + " "
            + sf.GetFileName() + ":"
            + sf.GetFileLineNumber() + "\n");
    } 

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

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

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

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

Крайний пример:

void a() {
  try {
    c();
  } catch(MyException1 ex) {
    throw;
  } catch(Exception ex) {
    log(ex);
    throw new MyException1(ex);
  }
}

void b() {
  try {
    a();
  } catch(MyException2 ex) {
    throw;
  } catch(Exception ex) {
    log(ex);
    throw new MyException2(ex);
  }
}

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

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

Лично я предпочитаю регистрировать исключение только в блоке catch, где я его обрабатываю. Где-нибудь еще может просто создать дублированное ведение журнала.

Поскольку мне разрешено вводить только одно настраиваемое исключение (MyException в вашем примере), мне нужно написать второй метод, используя MyException вместо MyException2. Это гарантирует, что исключения регистрируются один раз. Все остальное, что вы говорите, правильно, и да, именно этого они и хотят от меня.

Serhat Ozgel 25.12.2008 17:38

Мое приложение построено в режиме выпуска, и я использую блок приложения для обработки исключений, поэтому у меня никогда не было блоков кода catch ex, он перехватывается EHAB, он сам записывает в файл журнала всю необходимую информацию; трассировка стека и т. д., время и т. д.

единственная проблема с этим, если кто-то поставил

catch ex
{
    //do stuff
    throw ex;
}

так как это сотрет трассировку стека.

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