Рекомендации по производительности для исключения исключений

Я встречался со следующим типом кода много раз, и мне интересно, является ли это хорошей практикой (с точки зрения производительности) или нет:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

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

Как это отличается от Performance из следующих двух:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

или

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

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

Ответ 1

@Brad Tutterow

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

Я также второй комментарий Брэда, что вы не должны беспокоиться о производительности. Такая микро-оптимизация - УЖАСНАЯ идея. Если вы не говорите о том, чтобы бросать исключение на каждой итерации цикла for, который работает в течение длительного времени, вы, скорее всего, не столкнетесь с проблемами производительности, как ваш использование исключений.

Всегда оптимизируйте производительность, когда у вас есть показатели, которые указывают, что вам НЕОБХОДИМО оптимизировать производительность, а затем ударить по пятнам, которые, как доказано, являются виновниками.

Гораздо лучше иметь читаемый код с легкими возможностями отладки (IE не скрывает трассировку стека), а не делать что-то быстрее на наносекунду.

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

Ответ 2

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

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

Edit: Удалены вещи, которые были просто неправильно, и Майк был достаточно любезен, чтобы указать.

Ответ 3

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

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

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

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

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

В качестве примечания:

Мне лично не нравится использование исключений (иерархии классов, полученных из класса Exception) для реализации логики. Как и в случае:

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

Ответ 4

Не выполнять:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

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

Вместо этого выполните:

try
{
    // some code
}
catch (Exception ex) { throw; }

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

Ответ 5

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

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

Он может не читаться с диска (FileIOException), пользователь может попытаться получить к нему доступ откуда-то, где они не разрешены (SecurityException), файл может быть поврежден (XmlParseException), данные могут быть в неправильном формате ( DeserialisationException).

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

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

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

Ответ 6

В вашем первом примере есть накладные расходы на создание нового объекта CustomException.

Повторный бросок во втором примере вызовет исключение типа Exception.

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

Таким образом, второй и третий примеры используют меньше ресурсов.

Ответ 7

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

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

Ответ 8

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

Я видел только требования к производительности в отношении успеха, но никогда в отношении отказа.