Можно ли поймать все типы исключений, если вы перевернули их, завербовали другое исключение?
Я знаю, что вы не должны писать код, который кэширует все типы исключений, подобные этому.
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(), позволяя им получить исключения, которые могут быть сгенерированы.
Ответы
Ответ 1
Выполнение общего catch-all и rethrowing как новый тип исключения на самом деле не решает вашу проблему и ничего не дает.
То, что вам действительно нужно сделать, - это поймать исключения, которые вы можете обрабатывать, а затем обрабатывать их (на соответствующем уровне - это то, где может быть полезно переработать). Все остальные исключения должны быть зарегистрированы, чтобы вы могли отлаживать, почему они произошли, или не должны происходить в первую очередь (например, убедитесь, что вы проверяете ввод пользователя и т.д.). Если вы поймаете все исключения, вы никогда не узнаете, почему вы получаете исключения, которые вы получаете, и, таким образом, не можете их исправить.
Обновленный ответ
В ответ на обновление вашего вопроса (особенно в том, как вы хотите обрабатывать случай сохранения), мой вопрос вам будет: почему вы используете исключения в качестве средства определения пути, которое ваша программа занимает? Например, возьмите "FileNotFoundException". Очевидно, что это может случиться время от времени. Но вместо того, чтобы разрешить проблему и сообщить пользователю об этом, прежде чем сохранять (или делать что-либо) с файлом, почему бы не проверить сначала, что файл можно найти. Вы по-прежнему получаете тот же эффект, но вы не используете исключения для управления потоком программы.
Я надеюсь, что все это имеет смысл. Дайте мне знать, если у вас есть дополнительные вопросы.
Ответ 2
Когда вы перебрасываете исходное исключение как внутреннее исключение, вы теряете исходную трассировку стека, которая является важной информацией для отладки.
Я иногда буду делать то, что вы предлагаете, но я всегда регистрирую первоначальное исключение, чтобы сохранить трассировку стека.
Ответ 3
Я не вижу проблемы с тем, что вы делаете. Причина обертывания исключений в настраиваемых типах исключений заключается в создании абстракции между слоями кода - для перевода ошибок нижнего уровня в контекст более высокого уровня. Выполнение этого освобождает вызывающий код от необходимости знать слишком много деталей реализации того, что делает Save
.
Ваш update #1
- пример кода вызова, который слишком много знает о деталях реализации Save()
. В ответ на ваше второе обновление я соглашаюсь на 100%
PS
Я не говорю, чтобы делать это в каждом сценарии, где вы встречаете исключения. Только тогда, когда преимущество перевешивает стоимость (обычно на границах модуля).
Примеры сценариев, когда это особенно полезно: вы обертываете стороннюю библиотеку, вы еще не знаете все основные исключения, которые могут быть выброшены, у вас нет исходного кода или какой-либо документации и т.д.,
Кроме того, он обертывает базовое исключение и не теряется информация. Исключение может быть зарегистрировано надлежащим образом (хотя вам потребуется рекурсия через InnerException
s).
Ответ 4
Я не думаю, что это хорошая идея.
Вы должны добавить только свой тип исключения, если вам есть что добавить.
И, кроме того, вы должны поймать только те исключения, которые вы ожидаете, и что вы можете справиться - все другие исключения должны быть разрешены.
Как разработчик, я должен сказать, я злюсь, если вы попытаетесь "скрыть" исключения от меня, проглотив или обернув их.
Ответ 5
Для получения дополнительной информации о том, почему catch (exception) является плохим, просмотрите эту статью: http://blogs.msdn.com/clrteam/archive/2009/02/19/why-catch-exception-empty-catch-is-bad.aspx
По сути, "Исключение" походит на то, что "если что-то пойдет не так, я не забочусь о продолжении" и ловле "Исключения", и обертывание это похоже на высказывание "если что-то пойдет не так, обращайтесь с ними так, как будто все пошло не так для того же самого причина".
Это не может быть правильным, если вы справитесь с этим, потому что вы его ожидали, или вы полностью не думаете, что это когда-либо произойдет КОГДА-ЛИБО (или не знали, что это будет). В этом случае вы хотите, чтобы какой-то журнал на уровне приложений указывал на проблему, которую вы никогда не ожидали, - не только один размер подходит для всего решения.
Ответ 6
Мое собственное правило состоит в том, чтобы ловить и обернуть 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()
в файл журнала где-нибудь.
Ответ 7
Я предпочитаю исключение для обертывания, с той точки зрения, что настраиваемая иерархия исключений может делить исключения на гораздо более полезные классификации, чем иерархия по умолчанию. Предположим, что один пытается открыть документ и получает исключение ArgumentException или исключение InvalidOperationException. Действительно ли тип исключения содержит какую-либо полезную информацию? Предположим, однако, что вместо этого было получено исключение CodecNotFoundException, исключение PrerenderFingFailureException или исключение FontLoadFailureException. Можно представить себе систему, которая улавливает некоторые из этих исключений и пытается что-то сделать с ней (например, разрешить пользователю искать CODEC, повторить рендеринг с настройками с низким разрешением или разрешить замену шрифтов после предупреждения пользователя). Гораздо более полезны, чем исключения по умолчанию, многие из которых не говорят ничего о том, что действительно неправильно или что можно сделать с этим.
С иерархической точки зрения, что действительно необходимо, это средство различения исключений, которые указывают на то, что метод, бросающий исключение, не смог выполнить свою задачу, но состояние системы похоже на то, что было до начала метода, и те которые указывают на то, что состояние системы повреждено по-разному, что подразумевается в результате отказа метода. Обычная иерархия исключений совершенно бесполезна для этого; если обернуть исключения, можно немного улучшить ситуацию (хотя и не так, как если бы иерархия была лучше разработана для начала). Исключением, которое заставляет транзакцию разматывать, не так плохо, как тот, который возникает при совершении или размотке транзакции. В первом случае известно состояние системы; в последнем случае это не так.
В то время как, вероятно, следует избегать попадания определенных действительно плохих исключений (StackOverflowException, OutOfMemoryException, ThreadAbortException). Я не уверен, что это действительно важно. Если система собирается сбой и сжигание, это будет сделано, если вы поймаете исключение или нет. В vb.net может оказаться целесообразным "Catch Ex As Exception When IsNotHorribleException (Ex)", но С# не имеет такой конструкции и даже не позволяет исключить определенные исключения из-за того, что они были пойманы.
Замечание о разделении: в некоторых случаях одна операция может генерировать несколько исключений, которые заслуживают регистрации. Только завершение исключений в пользовательском исключении, которое содержит список других исключений, действительно может быть выполнено.
Ответ 8
В этом конкретном случае исключения должны быть достаточно редкими, чтобы их упаковка не была полезной вещью и, скорее всего, просто мешала обработке ошибок по линии. Существует множество примеров в рамках .Net, где конкретное исключение, с которым я могу справиться, завернуто в более общий, и для меня намного сложнее (хотя и не невозможно) обработать конкретный случай.
Ответ 9
Я уже писал статью по этой теме. В нем я повторяю важность сбора как можно большего количества данных об исключении. Здесь URL-адрес статьи:
http://it.toolbox.com/blogs/paytonbyrd/improve-exception-handling-with-reflection-and-generics-8718
Ответ 10
Какую пользу получает пользователь от того, что "была проблема с сериализацией каталога"? Я полагаю, что ваша проблемная область может быть необычным случаем, но каждая группа пользователей, которую я когда-либо программировал, отвечала бы так же, когда читала это сообщение: "Программа взорвалась. Что-то в каталоге".
Я не хочу быть снисходительным к моим пользователям; Я не. Это просто, что, как правило, мои пользователи имеют лучшее отношение к их вниманию, чем разбазаривание, которое создает тонкую ментальную модель того, что происходит внутри моего программного обеспечения. Были времена, когда моими пользователями приходилось создавать такое понимание, чтобы использовать написанную мной программу, и я могу сказать вам, что этот опыт не был полезен для них или для меня.
Я думаю, что ваше время было бы намного лучше потрачено на выяснение того, как надежно регистрировать исключения и соответствующее состояние приложения в форме, доступ к которой вы можете получить, когда ваши пользователи скажут вам, что что-то сломалось, чем при разработке сложной структуры для создания ошибки сообщения, которые люди вряд ли поймут.
Ответ 11
Чтобы ответить на этот вопрос, вам нужно понять, почему ловить System.Exception - это плохая идея и понять свои собственные побуждения для выполнения ваших предложений.
Это плохая идея, потому что она делает утверждение, что все, что могло пойти не так, хорошо, и что приложение находится в хорошем состоянии, чтобы продолжать работать потом. Это очень смелое выражение.
Итак, возникает вопрос: что вы предлагаете, эквивалентно ловлю System.Exception? Предоставляет ли он потребителю вашего API больше знаний, чтобы лучше судить? Это просто побуждает их ловить YourWrappedException, что является моральным эквивалентом ловли System.Exception, за исключением того, что не вызывают предупреждения FXCop?
В большинстве случаев, если вы знаете, что существует правило против чего-то, и хотите знать, что что-то подобное "хорошо", вы должны начать с понимания обоснования исходного правила.
Ответ 12
Это стандартная практика в .NET и то, как следует обрабатывать исключения, особенно для исключений, которые можно извлечь из.
Изменить: Я действительно не понимаю, почему меня задерживают, возможно, я больше читаю авторов, чем всех остальных. Но так, как я читаю код, факт, что он включает эти исключения в свое пользовательское исключение, подразумевает, что потребитель этого метода оснащен этими исключениями и что потребитель несет ответственность за обработку ошибок.
Ни один человек, который на самом деле не оставил никаких реальных споров. Я согласен с тем, что это вполне приемлемо, потому что потребитель должен знать о возможных исключениях, которые могут быть брошены и оборудованы для их обработки, что показано явной оболочкой исключения. Это также сохраняет исходное исключение, поэтому трассировка стека доступна и доступно основное исключение.