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

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

выдает a NullReferenceException, когда obj - null.

Ответ 3

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

В отличие от этого, однако, в статье throw (ссылка на С#) 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, могут быть случаи, когда этот тип исключения может быть брошен программистом и считается приемлемой практикой.

Ответ 4

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

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

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