Можно ли перехватывать все типы исключений, если вы перебрасываете их в другое исключение? - PullRequest
15 голосов
/ 25 февраля 2010

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

try
{
  //code that can throw an exception
}
catch
{
   //what? I don't see no
}

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

try
{
//code that can throw an exception
}
catch(TypeAException)
{
   //TypeA specific code
}

catch(TypeBException)
{
   //TypeB specific code
}

Но нормально ли перехватывать все типы исключений, если вы заключаете их в другое исключение? Рассмотрим этот Save() метод ниже, который я пишу как часть класса Catalog. Что-то не так со мной, перехватывая все типы исключений и возвращая единственное пользовательское исключение CatalogIOException с исходным исключением как внутреннее исключение?

По сути, я не хочу, чтобы какой-либо вызывающий код знал что-либо обо всех конкретных исключениях, которые могут быть выброшены внутри метода Save (). Им нужно только знать, пытались ли они сохранить каталог только для чтения (CatalogReadOnlyException), не удалось сериализовать каталог (CatalogSerializationException) или возникла какая-то проблема с записью в файл (CatalogIOException).

Это хороший или плохой способ обработки исключений?

/// <summary>
/// Saves the catalog
/// </summary>
/// <exception cref="CatalogReadOnlyException"></exception>
/// <exception cref="CatalogIOException"></exception>
/// <exception cref="CatalogSerializingExeption"></exception>
public void Save()
{
    if (!this.ReadOnly)
    {
        try
        {
            System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
            this._catfileStream.SetLength(0); //clears the file stream
            serializer.Serialize(this._catfileStream, this);
        }
        catch (InvalidOperationException exp)
        {
            throw new CatalogSerializationException("There was a problem serializing the catalog", exp);
        }
        catch (Exception exp)
        {
            throw new CatalogIOException("There was a problem accessing the catalog file", exp);
        }
    }
    else
    {
        throw new CatalogReadOnlyException();
    }
}

Обновление 1

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

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

    private void saveCatalogToolStripMenuItem_Click(object sender, EventArgs e)
    {
        //Check if the catalog is read only
        if (this.Catalog.ReadOnly)
        {
            MessageBox.Show("The currently opened catalog is readonly and can not be saved");
            return;
        }

        //attempts to save
        try
        {
            //Save method doesn't catch anything it can't deal with directly
            this.Catalog.Save(); 
        }
        catch (System.IO.FileNotFoundException)
        {
            MessageBox.Show("The catalog file could not be found");
        }
        catch (InvalidOperationException exp)
        {
            MessageBox.Show("There was a problem serializing the catalog for saving: " + exp.Message);
        }
        catch (System.IO.IOException exp)
        {
            MessageBox.Show("There was a problem accessing the catalog file: " + exp.Message);
        }
        catch (Exception exp)
        {
            MessageBox.Show("There was a problem saving the catalog:" + exp.Message);
        }
    }

Обновление 2

Еще одна вещь. Изменился бы ответ вообще, если бы метод Save () был частью открытого API против внутреннего кода? Например, если бы он был частью общедоступного API, мне пришлось бы выяснить и задокументировать все возможные исключения, которые может выдавать Save (). Это было бы намного проще, если бы знал, что Save () может выдать только одно из моих трех пользовательских исключений.

Кроме того, если Save () является частью общедоступного API, не будет ли проблема безопасности? Возможно, я бы хотел, чтобы потребитель API знал, что сохранение не было успешным, но я не хочу раскрывать что-либо о том, как работает Save (), позволяя им получить исключения, которые могли быть сгенерированы.

Ответы [ 12 ]

7 голосов
/ 25 февраля 2010

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

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

Обновленный ответ

В ответ на обновление вашего вопроса (особенно о том, как вы хотите обработать сохранение), мой вопрос к вам будет такой: почему вы используете исключения как средство определения пути, по которому движется ваша программа? Например, давайте возьмем «FileNotFoundException». Очевидно, что это может случиться время от времени. Но вместо того, чтобы разрешить проблему и уведомить пользователя об этом, перед сохранением (или чем-либо еще) с файлом, почему бы не проверить сначала, что файл может быть найден. Вы все еще получаете тот же эффект, но вы не используете исключения для управления потоком программы.

Надеюсь, все это имеет смысл. Дайте мне знать, если у вас есть дополнительные вопросы.

5 голосов
/ 25 февраля 2010

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

Иногда я буду делать то, что вы предлагаете, но я всегда регистрирую оригиналсначала исключение для сохранения трассировки стека.

3 голосов
/ 26 февраля 2010

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

Ваш update #1 является примером того, как вызывающему коду нужно слишком много знать о деталях реализации Save(). В ответ на ваше второе обновление я согласен 100%

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

Примеры сценариев, когда это особенно полезно: вы упаковываете стороннюю библиотеку, вы еще не знаете всех базовых исключений, которые могут быть сгенерированы, у вас нет исходного кода или какой-либо документации и т. Д. ,

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

2 голосов
/ 15 декабря 2010

Я предпочитаю упаковывать исключения с точки зрения того, что пользовательская иерархия исключений может разделить исключения на гораздо более полезные классификации, чем иерархия по умолчанию. Предположим, что кто-то пытается открыть документ и получает ArgumentException или InvalidOperationException. Действительно ли тип исключения содержит какую-либо полезную информацию? Предположим, однако, что вместо этого один получил CodecNotFoundException, PrerenderFingFailureException или FontLoadFailureException. Можно представить себе, что система перехватывает некоторые из этих исключений и пытается что-то с этим сделать (например, разрешить пользователю искать кодек, повторить рендеринг с настройками более низкого разрешения или разрешить замену шрифтов после предупреждения пользователя). Гораздо полезнее, чем исключения по умолчанию, многие из которых ничего не говорят о том, что на самом деле не так или что с этим можно сделать.

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

Хотя, вероятно, следует избегать отлова некоторых действительно плохих исключений (StackOverflowException, OutOfMemoryException, ThreadAbortException), я не уверен, что это действительно имеет значение. Если система выйдет из строя и сработает, она сделает это независимо от того, перехватит ли это исключение или нет. На vb.net может быть целесообразно «поймать Ex As Exception When IsNotHorribleException (Ex)», но C # не имеет такой конструкции и даже способа исключения определенных исключений из перехвата.

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

1 голос
/ 27 февраля 2010

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

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

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

Итак, в вашем примере я бы сделал что-то вроде этого:

try
{
    System.Xml.Serialization.XmlSerializer serializer = 
        new XmlSerializer(typeof(Catalog));
    this._catfileStream.SetLength(0); //clears the file stream
    serializer.Serialize(this._catfileStream, this);
}
// catch (InvalidOperationException exp)
// Don't catch this because I have nothing specific to add that 
// I wouldn't also say for all exceptions.
catch (Exception exp)
{
    throw new CatalogIOException(
        string.Format("There was a problem accessing catalog file '{0}'. ({1})",
            _catfileStream.Name, exp.Message), exp);
}

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

1 голос
/ 25 февраля 2010

Для получения дополнительной информации о том, почему catch (исключение) является плохим, ознакомьтесь с этой статьей: http://blogs.msdn.com/clrteam/archive/2009/02/19/why-catch-exception-empty-catch-is-bad.aspx

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

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

1 голос
/ 25 февраля 2010

Не думаю, что это хорошая идея.

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

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

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

0 голосов
/ 27 февраля 2010

Чтобы ответить на этот вопрос, вам нужно понять , почему перехватывает System.Exception - плохая идея, и понять ваши собственные мотивы для выполнения того, что вы предлагаете.

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

Итак, вопрос: эквивалентно ли то, что вы предлагаете, перехвату System.Exception? Дает ли пользователь вашего API больше знаний, чтобы вынести правильное решение? Разве это просто побуждает их перехватывать YourWrappedException, который является моральным эквивалентом перехвата System.Exception, за исключением того, что он не вызывает предупреждения FXCop?

В большинстве случаев, если вы знаете, что существует правило, запрещающее что-либо делать, и хотите узнать, что-то похожее «хорошо», вам следует начать с понимания обоснования исходного правила.

0 голосов
/ 26 февраля 2010

Какую выгоду получает пользователь от сообщения "При сериализации каталога произошла ошибка"? Я предполагаю, что ваш проблемный домен может быть необычным случаем, но каждая группа пользователей, для которых я когда-либо программировал, отвечала бы одинаково при чтении этого сообщения: «Программа взорвалась. Что-то о каталоге».

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

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

0 голосов
/ 26 февраля 2010

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

http://it.toolbox.com/blogs/paytonbyrd/improve-exception-handling-with-reflection-and-generics-8718

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