Чому ми не кидаємо ці винятки?


111

Я натрапив на цю сторінку MSDN, де зазначено:

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

На жаль, пояснити, чому це не турбує, це не турбує. Я можу здогадатися про причини, але сподіваюся, що хтось більш авторитетний з цього приводу може запропонувати своє розуміння.

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

Далі, це єдині винятки, яких слід уникати? Якщо є інші, що вони такі і чому їх теж слід уникати?


22
Від msdn.microsoft.com/en-us/library/ms182338.aspx : Якщо ви викинете загальний тип винятку, наприклад Виняток або SystemException, у бібліотеку чи рамку, він змушує споживачів вловлювати всі винятки, включаючи невідомі винятки, які вони роблять не знаю, як впоратися.
Генрік

3
Чому б ви кинули NullReferenceException?
Рік

5
@Rik: схоже на те, NullArgumentExceptionщо деякі люди можуть плутати обидва.
Мусульманський Бен Дау

2
Методи розширення @Rik, відповідно до моєї відповіді
Marc Gravell

3
Ще одне, що не варто кидатиApplicationException
Меттью Уотсон

Відповіді:


98

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

Ця ж причина стосується і SystemException. Якщо ви подивитесь на перелік похідних типів, ви можете побачити величезну кількість інших винятків з дуже різною семантикою.

NullReferenceExceptionі IndexOutOfRangeExceptionмають різний вид. Зараз це дуже конкретні винятки, тому кидати їх можна було б чудово. Однак ви все одно не хочете їх кидати, оскільки вони зазвичай означають, що в вашій логіці є деякі фактичні помилки. Наприклад, виключення з нульовою посиланням означає, що ви намагаєтеся отримати доступ до члена об'єкта, який є null. Якщо така можливість є у вашому коді, то завжди слід чітко перевірити nullі замість цього викинути корисніше виключення (наприклад ArgumentNullException). Аналогічно, IndexOutOfRangeExceptions трапляються, коли ви отримуєте доступ до недійсного індексу (для масивів - не списків). Ви завжди повинні переконатися, що ви цього не робите в першу чергу і спочатку перевірте межі, наприклад, масив.

Є кілька інших винятків , наприклад , ті два, наприклад , InvalidCastExceptionабо DivideByZeroException, які викидаються для конкретних помилок в коді і , як правило , означають , що ви робите що - то неправильно або ви не перевіряючи для деяких неприпустимих значень першого. Закидаючи їх свідомо з вашого коду, ви просто ускладнюєте код, який викликає, щоб визначити, чи були вони кинуті через якусь несправність у коді, чи просто тому, що ви вирішили використати їх для чогось у вашій реалізації.

Звичайно, є деякі винятки (га) цих правил. Якщо ви будуєте щось, що може спричинити виняток, який точно відповідає існуючому, тоді сміливо використовуйте це, особливо якщо ви намагаєтесь відповідати деякій вбудованій поведінці. Просто переконайтеся, що тоді ви вибрали дуже конкретний тип виключення.

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


3
Третя частина має для мене мало сенсу. Звичайно, вам слід скоріше уникати таких помилок, але коли ви, наприклад, пишете IListреалізацію, впливати на запитувані індекси не в силах, це логічна помилка абонента, коли індекс недійсний, і ви можете лише повідомити їх цієї логічної помилки, кинувши відповідний виняток. Чому це IndexOutOfRangeExceptionне підходить?

7
@delnan Якщо ви реалізуєте IList, то ви будете кидати а, ArgumentOutOfRangeExceptionяк підказує документація на інтерфейс . IndexOutOfRangeExceptionпризначений для масивів, і наскільки я знаю, ви не можете повторно реалізувати масиви.
ткнути

2
Що також може бути дещо пов’язане, NullReferenceExceptionяк правило, внутрішньо кидається як особливий випадок AccessViolationException(тест IIRC є чимось на зразок cmp [addr], addr, тобто він намагається знеструмити покажчик, і якщо він не вдається з порушенням доступу, він обробляє різницю між NRE та AVE в отриманому оброблювачі переривань). Окрім смислових причин, тут є і деяка обман. Це також може допомогти відмовити вас від перевірки на nullручну перевірку, коли це не корисно - якщо ви все-таки кинете NRE, чому б не дозволити .NET зробити це?
Луаан

Що стосується вашого останнього твердження, щодо користувацьких винятків: я ніколи не відчував необхідності цього робити. Можливо, я щось пропускаю. За яких умов потрібно створити спеціальний тип виключення замість чогось із рамки?
DonBoitnott

1
Що ж, для менших застосувань це може не виникнути. Але як тільки ви створюєте щось більш складне, де окремі частини працюють як незалежні «компоненти», часто є сенс вводити спеціальні винятки для користувацьких ситуацій з помилками. Наприклад, якщо у вас є якийсь рівень контролю доступу, і ви намагаєтеся виконати якусь послугу, хоча ви не маєте права цього робити, ви можете викинути спеціальний "доступ у відхиленні доступу" чи щось. Або якщо у вас є аналізатор, який аналізує якийсь файл, у вас можуть бути власні помилки аналізатора, щоб повідомити про це користувачеві.
ткнути

36

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

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

кидає a NullReferenceExceptionколи objє null.


2
"якщо ви зберігаєте точний намір виключення" - напевно, якби це було так, виняток буде кинутий, без того, щоб вам потрібно було тестувати його на перше місце? І якщо ви вже протестували на це, то це насправді не виняток?
PugFugly

5
@PugFugly знайдіть 2 секунди, щоб переглянути приклад методу розширення: ні, це не буде кинуто без того, щоб перевірити його. Якщо SomeMethod()не потрібно здійснювати доступ до учасників, примушувати його невірно. Аналогічно: візьміть цю точку вгору з 41 місцем у БКЛ, яке створює звичай IndexOutOfRangeException, та 16 місцями, які створюють звичайNullReferenceException
Марк Гравелл

16
Я б стверджував, що метод розширення все-таки повинен викидати ArgumentNullExceptionзамість а NullReferenceException. Навіть якщо синтаксичний цукор від методів розширення дозволяє той же синтаксис, що і звичайний доступ до членів, він все одно працює дуже інакше. І отримати НРЕ MyStaticHelpers.SomeMethod(obj)було б просто неправильно.
ткнути

1
@PugFugly BCL - це "бібліотека базового класу", в основному основний матеріал у .NET.
ткнути

1
@PugFugly: Є багато сценаріїв, коли невдача попереднього виявлення стану може призвести до того, що виняток буде кинуто в "незручний" час. Якщо операція не вдасться, кидати виняток на ранніх стадіях краще, ніж розпочати операцію, пройти на півдорозі, а потім прибрати очищення отриманого частково обробленого безладу.
supercat

5

Як ви зазначаєте, у статті Створення та викидання винятків (Посібник з програмування C #) під темою Що слід уникати, коли викидаєте винятки , Microsoft дійсно перераховуєSystem.IndexOutOfRangeException як тип виключення, який не слід навмисно викидати з вашого власного вихідного коду.

На відміну від цього, у статті, що стосується статті (C # Reference) , 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, можливо, трапляються випадки, коли цей виняток може бути кинутий програмістом і вважатися прийнятною практикою.


1

Коли я читав ваше запитання, я запитав себе, за яких умов ви хочете викинути типи винятків NullReferenceException, InvalidCastExceptionабо ArgumentOutOfRangeException.

На мою думку, стикаючись з одним із таких типів винятків, я (розробник) відчуваю стурбованість попередженням у тому сенсі, що компілятор розмовляє зі мною. Отже, дозволити вам (розробнику) кидати такі типи винятків еквівалентно (компілятору), який продає відповідальність. Наприклад, це говорить про те, що компілятор повинен тепер дозволяти розробнику вирішувати, чи є об'єкт null. Але прийняття такого визначення справді має бути завданням укладача.

PS: Починаючи з 2003 року, я розробляю власні винятки, тому можу їх кидати, як бажаю. Я думаю, що це вважається найкращою практикою для цього.


Хороші бали. Однак я вважаю, що було б точніше сказати, що програміст повинен дозволити виконуваному циклу .NET Framework кидати такі винятки (і що програміст повинен обробляти їх відповідним чином).
DavidRR

0

Відкладаючи дискусію про NullReferenceExceptionта IndexOutOfBoundsExceptionвідмовляючись:

А як щодо лову та метання System.Exception. Я дуже багато кинув у свій код цей виняток, і мене ніколи не накрутили. Так само дуже часто я ловлю неспецифічний Exceptionтип, і це також спрацювало досить добре для мене. Отже, чому це?

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

Отже, щодо кидання Exceptionя не бачу причини забороняти це у всіх випадках.

EDIT: також зі сторінки MSDN:

Винятки не слід використовувати для зміни потоку програми як частини звичайного виконання. Винятки слід використовувати лише для повідомлення та обробки умов помилки.

Не є найкращою практикою також передозування застережень щодо вилову з індивідуальною логікою для різних типів виключень.


Поки ще існує повідомлення про виняток, щоб повідомити, що сталося. Я не можу уявити, що ви створюєте новий тип винятків для кожної різної помилки, на всякий випадок, якщо споживач захоче програмно розрізнити ці помилки.
uebe

Що сталося з моїм попереднім коментарем?
DonBoitnott
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.