Почему бы нам не выбросить эти исключения?

111

Я наткнулся на эту страницу MSDN, в которой говорится:

Не создавайте исключения Exception , SystemException , NullReferenceException или IndexOutOfRangeException намеренно из собственного исходного кода.

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

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

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

DonBoitnott
источник
22
Из msdn.microsoft.com/en-us/library/ms182338.aspx : если вы выбрасываете общий тип исключения, такой как Exception или SystemException в библиотеке или фреймворке, он заставляет потребителей перехватывать все исключения, включая неизвестные исключения, которые они делают. не умею обращаться.
Хенрик
3
Зачем вам бросать исключение NullReferenceException?
Rik
5
@Rik: похоже на то, NullArgumentExceptionчто некоторые люди могут спутать обоих.
Мусульманин Бен Дхау
2
Методы расширения @Rik, согласно моему ответу
Марк Грейвелл
3
Еще один, который вы не должны бросать,ApplicationException
Мэтью Уотсон

Ответы:

98

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

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

NullReferenceExceptionи IndexOutOfRangeExceptionбывают другого рода. Это очень специфические исключения, поэтому их можно выбросить . Однако вы все равно не захотите их бросать, поскольку они обычно означают, что в вашей логике есть некоторые реальные ошибки. Например, исключение нулевой ссылки означает, что вы пытаетесь получить доступ к члену объекта, который является null. Если это возможно в вашем коде, вам всегда следует явно проверять nullи генерировать более полезное исключение (например ArgumentNullException). Так же,IndexOutOfRangeException s возникают, когда вы обращаетесь к недопустимому индексу (для массивов, а не списков). Вы всегда должны сначала убедиться, что вы этого не делаете, и сначала проверять границы, например, массива.

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

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

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

совать
источник
3
Третья часть не имеет для меня особого смысла. Конечно, вам лучше избегать возникновения этих ошибок, но когда вы, например, пишете IListреализацию, это не в ваших силах повлиять на запрошенные индексы, это логическая ошибка вызывающего абонента, когда индекс недействителен, и вы можете только сообщить им этой логической ошибки, вызвав соответствующее исключение. Почему IndexOutOfRangeExceptionне подходит?
7
@delnan Если вы реализуете IList, то вы будете бросать, ArgumentOutOfRangeExceptionкак предлагает документация по интерфейсу . IndexOutOfRangeExceptionпредназначен для массивов, и, насколько я знаю, вы не можете повторно реализовать массивы.
poke
2
То, что также может быть несколько связано, NullReferenceExceptionобычно вызывается внутри как частный случай AccessViolationException(IIRC, тест выглядит примерно так cmp [addr], addr, то есть он пытается разыменовать указатель, и если он терпит неудачу с нарушением доступа, он обрабатывает разницу между NRE и AVE в полученном обработчике прерывания). Таким образом, помимо семантических причин, здесь также присутствует некоторый обман. Это также может помочь отговорить вас от проверки nullвручную, когда это бесполезно - если вы все равно собираетесь бросить NRE, почему бы не позволить .NET сделать это?
Luaan
Что касается вашего последнего заявления о пользовательских исключениях: я никогда не чувствовал необходимости делать это. Возможно, я что-то упускаю. При каких условиях потребуется создать собственный тип исключения вместо чего-либо из фреймворка?
DonBoitnott
1
Что ж, для небольших приложений в этом может не быть необходимости. Но как только вы создаете что-то более сложное, где отдельные части работают как независимые «компоненты», часто имеет смысл вводить пользовательские исключения для пользовательских ситуаций ошибок. Например, если у вас есть какой-то уровень управления доступом, и вы пытаетесь выполнить какую-то службу, хотя у вас нет на это полномочий, вы можете создать настраиваемое «исключение отказа в доступе» или что-то в этом роде. Или, если у вас есть синтаксический анализатор, который анализирует какой-либо файл, у вас могут быть свои собственные ошибки синтаксического анализатора, чтобы сообщить пользователю.
тыкает
36

Я подозреваю, что намерение последних двух состоит в том, чтобы предотвратить путаницу со встроенными исключениями, которые имеют ожидаемое значение. Однако я считаю, что если вы сохраняете точное намерение исключения : оно правильное throw. Например, если вы пишете собственную коллекцию, кажется вполне разумным использовать IndexOutOfRangeException- более ясный и конкретный, IMO, чем ArgumentOutOfRangeException. И хотя вы List<T>можете выбрать последнее, в BCL есть не менее 41 места (благодаря отражателю) (не считая массивов), которые создают индивидуальные настройки IndexOutOfRangeException- ни одно из них не является «низким уровнем», чтобы заслуживать особого исключения. Так что да, я думаю, вы можете справедливо возразить, что это правило глупо. Точно так же,NullReferenceException полезен в методах расширения - если вы хотите сохранить семантику, которая:

obj.SomeMethod(); // this is actually an extension method

выкидывает NullReferenceExceptionкогда objесть null.

Марк Гравелл
источник
2
"если вы сохраняете точное намерение исключения" - конечно, если бы это было так, исключение было бы сгенерировано без необходимости в первую очередь проверять его? А если вы уже тестировали, то не исключение?
PugFugly
5
@PugFugly потратьте 2 секунды, чтобы посмотреть на пример метода расширения: нет, он не будет запущен, если вам не нужно его проверять. Если SomeMethod()нет необходимости выполнять доступ к члену, принудительно его делать неправильно. Точно так же: возьмите эту точку зрения с 41 местом в BCL, которые создают обычай IndexOutOfRangeException, и 16 местами, которые создают обычайNullReferenceException
Марк Гравелл
16
Я бы сказал, что метод расширения по-прежнему должен выдавать ArgumentNullExceptionвместо NullReferenceException. Даже если синтаксический сахар из методов расширения допускает тот же синтаксис, что и обычный доступ к членам, он все равно работает по-другому. И получить NRE MyStaticHelpers.SomeMethod(obj)было бы неправильно.
тык
1
@PugFugly BCL - это «библиотека базовых классов», в основном это ядро ​​.NET.
poke
1
@PugFugly: существует множество сценариев, в которых отказ от упреждающего обнаружения условия приведет к возникновению исключения в «неудобное» время. Если операция не увенчалась успехом, лучше выбросить исключение на раннем этапе, чем начать операцию, пройти ее на полпути и затем очистить полученный частично обработанный беспорядок.
supercat
5

Как вы указываете, в статье Создание и выброс исключений (Руководство по программированию на C #) в разделе « Чего следует избегать приSystem.IndexOutOfRangeException создании исключений» Microsoft действительно указывает тип исключения, который не следует намеренно создавать из вашего собственного исходного кода.

Однако в отличие от статьи throw (Справочник по C #) Microsoft, похоже, нарушает свои собственные правила. Вот метод, который Microsoft включила в свой пример:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Таким образом, сама Microsoft не является последовательной, поскольку IndexOutOfRangeExceptionв своей документации для throw!

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

DavidRR
источник
1

Когда я прочитал ваш вопрос, я спросил себя, при каких условиях нужно выбросить типы исключений NullReferenceException, InvalidCastExceptionили ArgumentOutOfRangeException.

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

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

Bellash
источник
Хорошие моменты. Однако я думаю, что было бы правильнее сказать, что программист должен позволить среде выполнения .NET Framework генерировать эти типы исключений (и что программист должен обрабатывать их соответствующим образом).
DavidRR
0

Отложив обсуждение NullReferenceExceptionи IndexOutOfBoundsExceptionотложим в сторону:

Насчет ловли и бросания System.Exception. Я часто создавал исключения этого типа в своем коде, и меня это никогда не подводило. Точно так же очень часто я ловлю неспецифический Exceptionтип, и у меня это тоже неплохо сработало. Итак, почему это так?

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

Итак, что касается метания, Exceptionя не вижу причин запрещать это во всех случаях.

РЕДАКТИРОВАТЬ: также со страницы MSDN:

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

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

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