Может ли следующий код рассматриваться как хорошая практика? Если нет, то почему?
try
{
// code that can cause various exceptions...
}
catch (Exception e)
{
throw new MyCustomException("Custom error message", e);
}
Может ли следующий код рассматриваться как хорошая практика? Если нет, то почему?
try
{
// code that can cause various exceptions...
}
catch (Exception e)
{
throw new MyCustomException("Custom error message", e);
}
Короткий ответ: не делайте этого, если нет причин, по которым вы должны. Вместо этого поймайте определенные исключения, с которыми вы можете справиться в той точке, с которой вы можете справиться, и разрешите всем остальным исключениям пузырьки в стеке.
TL; DR ответ: это зависит от того, что вы пишете, что будет вызывать ваш код и почему вы считаете, что вам нужно ввести особый тип исключения.
Самый важный вопрос, который я задаю, - это , если я поймаю, какая польза от моего звонящего получает?
Представьте, что вы обрабатываете низкоуровневый источник данных; возможно, вы делаете некоторую кодировку или десериализацию. Наша система приятная и модульная, поэтому у вас мало информации о том, что такое низкоуровневый источник данных или как вызывается этот код. Возможно, ваша библиотека развернута на сервере, слушая очередь сообщений и записывая данные на диск. Возможно, это на рабочем столе, и данные поступают из сети и отображаются на экране.
Существует множество различных исключений (DbException, IOException, RemoteException и т.д.), и у вас нет полезных способов их обработки.
В этой ситуации, в С#, общепринятая вещь, чтобы сделать, это исключение. Ваш вызывающий абонент знает, что делать: клиент рабочего стола может предупредить пользователя о проверке своего сетевого подключения, служба может записать в журнал и разрешить новые сообщения в очереди.
Если вы закроете исключение в своем собственном исключении MyAwesomeLibraryException, вы сделали работу с вызывающим абонентом более сложной. Теперь вашему абоненту необходимо: -
Убедитесь, что дополнительные усилия стоят времени. Не делайте это без причины!
Если основное оправдание вашего настраиваемого типа исключений заключается в том, что вы можете предоставить пользователю дружественные сообщения об ошибках, вы должны быть осторожны с чрезмерно неспецифичными в тех исключениях, которые вы уловили. Я потерял счетчик количества раз - как пользователя, так и инженера - что чрезмерное выражение о вылове скрыло истинную причину проблемы: -
try
{
GetDataFromNetwork("htt[://www.foo.com"); // FormatException?
GetDataFromNetwork(uriArray[0]); // ArrayIndexOutOfBounds?
GetDataFromNetwork(null); // ArgumentNull?
}
catch(Exception e)
{
throw new WeClearlyKnowBetterException(
"Hey, there something wrong with your network!", e);
}
или, еще один пример: -
try
{
ImportDataFromDisk("C:\ThisFileDoesNotExist.bar"); // FileNotFound?
ImportDataFromDisk("C:\BobsPrivateFiles\Foo.bar"); // UnauthorizedAccess?
ImportDataFromDisk("C:\NotInYourFormat.baz"); // InvalidOperation?
ImportDataFromDisk("C:\EncryptedWithWrongKey.bar"); // CryptographicException?
}
catch(Exception e)
{
throw new NotHelpfulException(
"Couldn't load data!", e); // So how do I *fix* it?
}
Теперь наш вызывающий пользователь должен развернуть наше обычное исключение, чтобы сообщить пользователю, что на самом деле случилось не так. В этих случаях мы попросили нашего абонента выполнить дополнительную работу без какой-либо выгоды. Нецелесообразно вводить специальный тип исключений, обертывающий все исключения. В общем, я: -
Поймать самое конкретное исключение, которое я могу
В этот момент я могу что-то с этим сделать
В противном случае я просто позволю исключению пузырей
Помните, что скрытие деталей того, что пошло не так, не всегда полезно
Это не значит, что вы никогда не должны этого делать!
Иногда Exception
- это наиболее конкретное исключение, которое вы можете уловить, потому что вы хотите обрабатывать все исключения одинаковым образом - например. Log(e); Environment.FailFast();
Иногда у вас есть контекст для обработки исключения прямо в том месте, где он забрасывается - например. вы просто пытались подключиться к сетевому ресурсу и хотите повторить попытку
Иногда характер вашего вызывающего абонента означает, что вы не можете позволить исключению пузыриться - например. вы пишете код регистрации, и вы не хотите, чтобы сбой ведения журнала "заменил" исходное исключение, которое вы пытаетесь зарегистрировать!
Иногда появляется полезная дополнительная информация, которую вы можете дать своему вызывающему абоненту в точке, где выбрано исключение, которое не будет доступно выше стека вызовов - например, в моем втором примере выше мы могли бы catch (InvalidOperationException e)
и включить путь к файлу, с которым мы работали.
Иногда то, что пошло не так, не так важно, как там, где оно пошло не так. Возможно, было бы полезно отличить a FooModuleException
от a BarModuleException
, независимо от того, что представляет собой настоящая проблема. если происходит какое-то асинхронное соединение, которое в противном случае могло бы помочь вам допросить трассировку стека.
Хотя это вопрос с С#, стоит отметить, что на некоторых других языках (особенно Java) вы можете принудительно переносить, потому что проверенные исключения составляют часть контракта метода - например, если вы реализуете интерфейс, а интерфейс не указывает, что метод может вызывать IOException.
Все в порядке. Вам не нужно улавливать каждый тип исключения отдельно. Вы можете поймать определенный тип исключения, если хотите обработать его определенным образом. Если вы хотите обрабатывать все исключения таким же образом - поймите базу Exception
и обработайте ее так же, как и вы. В противном случае вы будете иметь дублированный код в каждом блоке catch.
Нет, как правило, вы не должны этого делать: это может замаскировать реальное исключение, что может указывать на проблемы программирования в вашем коде. Например, если код внутри try
/catch
имеет плохую строку, которая приводит к тому, что индекс массива выходит из связанной ошибки, ваш код тоже поймает это, и выкинет для него настраиваемое исключение. Пользовательское исключение теперь бессмысленно, потому что оно сообщает о проблеме с кодированием, поэтому никто не поймает его вне вашего кода, он сможет сделать с ним что-либо значимое.
С другой стороны, если код внутри try
/catch
выдает исключения, которые вы ожидаете, ловить и обернуть их в пользовательское исключение - хорошая идея. Например, если ваш код читает из специального файла, приватного для вашего компонента, а чтение вызывает исключение ввода-вывода, то лучше поймать это исключение и сообщить о нем, поскольку это помогает скрыть операцию файла с вызывающего.
Там отличная запись в блоге Эрика Липперта, "Vexing exceptions" . Эрик отвечает на ваш вопрос некоторыми универсальными рекомендациями. Вот цитата из части "sum up":
- Не допускайте фатальных исключений; вы все равно ничего не можете с ними поделать, и, как правило, это делает хуже.
- Исправьте свой код, чтобы он никогда не запускал исключение, основанное на головном мозге. Исключение исключение "вне диапазона" никогда не должно происходить в производственном коде.
- Избегайте досадных исключений, когда это возможно, вызывая "Try" версии этих досадных методов, которые вызывают не исключительные обстоятельства. Если вы не можете избежать вызова метода досады, поймайте его досадные исключения.
- Всегда обрабатывайте исключения, которые указывают на неожиданные экзогенные условия; обычно не стоит или практично предвидеть каждый возможный сбой. Просто попробуйте операцию и будьте готовы к обрабатывать исключение.
Это то, что я обычно говорю об обработке исключений:
Итак, я бы сказал, что ваш пример кода может быть в порядке. Это действительно зависит от "Пользовательского сообщения об ошибке" (и, возможно, исключительных пользовательских свойств - убедитесь, что они сериализуемы). Он должен добавить значение или значение, чтобы помочь диагностировать проблему. Например, это выглядит вполне нормально для меня (может быть улучшено):
string filePath = ... ;
try
{
CreateTheFile(filePath);
DoThisToTheFile(filePath);
DoThatToTheFile(filePath);
...
}
catch (Exception e)
{
throw new FileProcessException("I wasn't able to complete operation XYZ with the file at '" + filePath + "'.", e);
}
Это не означает:
string filePath = ... ;
try
{
CreateTheFile(filePath);
DoThisToTheFile(filePath);
DoThatToTheFile(filePath);
}
catch (Exception e)
{
throw new Exception("I wasn't able to do what I needed to do.", e);
}
Предисловие: Я считаю, что этот шаблон можно использовать только в определенных условиях и с полным пониманием его хороших и плохих сторон.
Заявление 2: Иногда мы хотим от члена библиотеки: a) получить результат или b) сказать нам это невозможно, и мы не заботимся обо всех деталях, мы просто пересылаем детали разработчикам библиотеки.
Заключение из St.1, St.2: При внедрении метода библиотеки мы можем обернуть общее исключение и вынести пользовательский, в том числе исходный как его InnerException. В этом случае разработчик, использующий этот элемент, должен будет поймать только одно исключение, и мы все равно получим информацию об отладке.
<ч/" > Случай 1:. Вы реализуете библиотечный метод и не хотите раскрывать его внутренности, или вы намерены/предполагаете изменить его внутренности в будущем.
public string GetConfig()
{
try
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "MyCompany.MyProduct.MyFile.cfg";
// ArgumentNullException
// ArgumentException
// FileLoadException
// FileNotFoundException
// BadImageFormatException
// NotImplementedException
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
// ArgumentException
// ArgumentNullException
using (StreamReader reader = new StreamReader(stream))
{
// OutOfMemoryException
// IOException
string result = reader.ReadToEnd();
}
return result;
// TODO: Read config parameter from DB 'Configuration'
}
catch (Exception ex)
{
throw new ConfigException("Unable to get configuration", ex);
}
}
Это довольно солидный код, и вы уверены, что он никогда не будет генерировать исключение. Потому что это не так. Ты уверен? Или вы на всякий случай обернули бы его в try-catch? Или вы заставите разработчика сделать эту работу? Что, если ему все равно, удастся ли этот метод, может быть, у него есть план резервного копирования? Может быть, он закроет вызов этого метода для try-catch (Exception e) вместо вас? Я так не думаю.
Плюсы:
InnerException
.Минусы:
<ч/" > Случай 2: Вы хотите добавить информацию в исключение. Это не напрямую код, который вы написали в вопросе, но он все еще ловит общий Exception
, и я думаю, что это важная недооцененная часть средства обработки исключений.
catch (Exception ex)
{
ex.Data.Add(paramName);
throw;
}
Дополнение: Я бы отредактировал ваш шаблон следующим образом:
try
{
// code that can cause various exceptions...
}
catch (Exception e)
{
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
{
throw;
}
throw new MyCustomException("Custom error message", e);
}
<ч/" > Резюме: Этот шаблон можно использовать при разработке общедоступного метода библиотеки, чтобы скрыть детали реализации и упростить его использование разработчиками.
Общий принцип с исключениями состоит в том, чтобы поймать их и обрабатывать их локально, если это применимо, и в противном случае разрешить им пузыриться до контейнера верхнего уровня.
Все зависит от контекста, в котором работает ваше приложение. Например, если приложение является веб-приложением ASP.Net, то некоторые исключения могут обрабатываться сервером приложений ASP в IIS, но ожидаемые конкретные ошибки приложения (те, которые определенные в ваших собственных интерфейсах) должны быть пойманы и представлены конечному пользователю, если это необходимо.
Если вы имеете дело с I/O, тогда есть много факторов, которые не поддаются контролю (доступность сети, аппаратное обеспечение диска и т.д.), поэтому, если здесь есть сбой, тогда это хорошая идея справиться с этим сразу, поймав исключение и представив сообщение пользователю и сообщение об ошибке.
Самое главное не терпит неудачу, поэтому не делайте исключения в своем собственном исключении, а затем игнорируйте их. Лучше разрешить им пузыриться и находить их в журнале веб-сервера, например.
После прочтения вопроса, ответов и комментариев, опубликованных, я думаю, что тщательный ответ Iain Galloway мог бы объяснить большую часть этого в более широком смысле.
Мой короткий и сладкий момент, чтобы объяснить это,
В некоторых других комментариях, которые вы упомянули, как насчет системных критических исключений, таких как OutOfMemoryException
, вы могли бы только поймать это исключение, если вы явно добавили раздел [HandleProcessCorruptedStateExceptions]
по функции и получить подробный ответ в каком сценарии вы должен обработать его прочитать этот пост SO
Главное, от чего зависит, где вы ловите исключение. В общем, библиотеки должны быть более консервативными с исключениями catching, тогда как на верхнем уровне вашей программы (например, в вашем основном методе или в верхней части метода действия в контроллере и т.д.) Вы можете быть более либеральным с тем, что вы поймаете.
Причиной этого является то, что, например, вы не хотите перехватывать все исключения в библиотеке, потому что вы можете маскировать проблемы, которые не имеют ничего общего с вашей библиотекой, например "OutOfMemoryException", которые вы действительно предпочли бы пузыри, чтобы пользователь мог получать уведомления и т.д. С другой стороны если вы говорите об улавливании исключений внутри вашего метода main(), который ловит исключение, отображает его, а затем выходит... ну, вероятно, безопасно поймать практически любое исключение здесь.
Самое важное правило об улавливании всех исключений состоит в том, что вы никогда не должны просто проглатывать все исключения молчаливо... например. что-то вроде этого в Java:
try {
something();
}
catch (Exception ex) {}
или это в Python:
try:
something()
except:
pass
Потому что это могут быть некоторые из самых сложных проблем для отслеживания.
Хорошее эмпирическое правило состоит в том, что вы должны ловить только исключения, которые вы можете правильно решать сами. Если вы не можете полностью справиться с этим исключением, вы должны позволить ему подпрыгнуть к кому-то, кто может
В зависимости от того, что вы должны делать, когда возникает исключение. Если вы хотите глобально обрабатывать все исключения, это ОК, чтобы повторно передать исключение методу вызывающего и разрешить ему обрабатывать исключения. Но существует множество сценариев, в которых исключения должны обрабатываться локально, в таких случаях предпочтительным является исключение catching с известными типами, а последний catch должен ловить Exception ex
(чтобы поймать неожиданное исключение) и перебросить исключение в вызывающий.
try
{
// code that can cause various exceptions...
}
catch (ArithmeticException e)
{
//handle arithmetic exception
}
catch (IntegerOverflowException e)
{
//handle overflow exception
}
catch (CustomException e)
{
//handle your custom exception if thrown from try{} on meeting certain conditions.
}
catch (Exception e)
{
throw new Exception(e); //handle this in the caller method
}
Это может быть хорошая практика или, возможно, не зависит от сценария. Хотя я не эксперт здесь, насколько мне известно, я считаю, что делать это хорошо, когда вы не уверены в исключении. Я вижу, что вы рассматриваете код в try
, который может вызывать несколько исключений. Если вы не уверены в том, какое исключение может быть брошено, всегда лучше делать это так, как вы это делали. В любом случае выполнение не позволит вам выполнить несколько блоков catch
. Всякий раз, когда вы нажимаете первый блокирующий блок, управление будет идти в конце или в любом месте, куда вы хотите, но определенно не будет другим блоком catch.
Первое универсальное правило - Никогда не проглатывайте исключения! Когда вы пишете фрагмент кода, вы должны иметь возможность прогнозировать большинство случаев, когда может быть исключение, и требует уведомления, такие случаи должны обрабатываться в коде. У вас должен быть общий модуль ведения журнала, который регистрирует любое исключение, пойманное приложением, но не может быть действительно полезным для пользователя.
Покажите ошибки, которые имеют отношение к пользователю, например, пользователь системы может не понимать исключение IndexOutOfRange, но нам может быть интересно изучить, почему это произошло.
Нам нужно всегда знать, что пошло не так, и когда мы могли проанализировать первопричину и предотвратить ее повторение, потому что если это может произойти один раз. Это повторится, и может быть, это может привести к катастрофе. Единственный способ найти, что не так, - это войти в то, что когда-либо ошибка, с которой сталкивается приложение.
Вы также можете использовать эллипсы.
catch (...)
{
// catches all exceptions, not already catches by a catch block before
// can be used to catch exception of unknown or irrelevant type
}
Кроме того, что вы можете сделать, это вложенный try-catch
try
{
//some code which is not for database related
try
{
//database related code with connection open
}
catch(//database related exception)
{
//statement to terminate
}
**finally()
{
//close connection,destroy object
}**
}
catch(//general exception)
{
//statement to terminate
}
По моему мнению, это поможет вам получить более сжатое представление о вашем типе ошибок.