Подтвердить что ты не робот

Должен ли я поймать и обернуть общее исключение?

Может ли следующий код рассматриваться как хорошая практика? Если нет, то почему?

try
{
    // code that can cause various exceptions...
}
catch (Exception e)
{
    throw new MyCustomException("Custom error message", e);
}
4b9b3361

Ответ 1

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


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.

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Там отличная запись в блоге Эрика Липперта, "Vexing exceptions" . Эрик отвечает на ваш вопрос некоторыми универсальными рекомендациями. Вот цитата из части "sum up":

  • Не допускайте фатальных исключений; вы все равно ничего не можете с ними поделать, и, как правило, это делает хуже.
  • Исправьте свой код, чтобы он никогда не запускал исключение, основанное на головном мозге. Исключение исключение "вне диапазона" никогда не должно происходить в производственном коде.
  • Избегайте досадных исключений, когда это возможно, вызывая "Try" версии этих досадных методов, которые вызывают не исключительные обстоятельства. Если вы не можете избежать вызова метода досады, поймайте его досадные исключения.
  • Всегда обрабатывайте исключения, которые указывают на неожиданные экзогенные условия; обычно не стоит или практично предвидеть каждый возможный сбой. Просто попробуйте операцию и будьте готовы к обрабатывать исключение.

Ответ 5

Это то, что я обычно говорю об обработке исключений:

  • Первое, что нужно сделать с исключениями - это... ничего. Там будет высокоуровневый захват всех обработчиков (например, желтая страница с ошибкой ASP.NET), которые вам помогут, и у вас будет полный стек стека (обратите внимание, что это не так в других средах, отличных от .NET). И если у вас есть соответствующие PDB, у вас также будут номера строк исходного кода.
  • Теперь, если вы хотите добавить некоторую информацию к некоторым исключениям, то, конечно, вы можете это сделать, в тщательно выбранных местах (возможно, места, где действительно происходят реальные исключения, и вы хотите улучшить свой код для будущих ошибок), но убедитесь, что вы действительно добавляете значение в оригинальное (и убедитесь, что вы также вставили оригинал как внутреннее исключение, как в своем примере).

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

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);
}

Ответ 6

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


Заявление 1: Исключением является то, что член не выполняет задачу, которую она должна выполнять, как указано ее именем. (Джеффри Рихтер, CLR через четвертое издание С#)

Заявление 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);
}

<ч/" > Резюме: Этот шаблон можно использовать при разработке общедоступного метода библиотеки, чтобы скрыть детали реализации и упростить его использование разработчиками.

Ответ 7

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

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

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

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

Ответ 8

После прочтения вопроса, ответов и комментариев, опубликованных, я думаю, что тщательный ответ Iain Galloway мог бы объяснить большую часть этого в более широком смысле.

Мой короткий и сладкий момент, чтобы объяснить это,

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

В некоторых других комментариях, которые вы упомянули, как насчет системных критических исключений, таких как OutOfMemoryException, вы могли бы только поймать это исключение, если вы явно добавили раздел [HandleProcessCorruptedStateExceptions] по функции и получить подробный ответ в каком сценарии вы должен обработать его прочитать этот пост SO

Ответ 9

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

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

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

try { 
something(); 
} 

catch (Exception ex) {}

или это в Python:

try:
something()
except:
pass

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

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

Ответ 10

В зависимости от того, что вы должны делать, когда возникает исключение. Если вы хотите глобально обрабатывать все исключения, это ОК, чтобы повторно передать исключение методу вызывающего и разрешить ему обрабатывать исключения. Но существует множество сценариев, в которых исключения должны обрабатываться локально, в таких случаях предпочтительным является исключение 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
}

Ответ 11

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

Ответ 12

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

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

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

Ответ 13

Вы также можете использовать эллипсы.

   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
}

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