Які винятки слід викинути за недійсні або несподівані параметри в .NET?


163

Які типи винятків слід викинути за недійсні або несподівані параметри в .NET? Коли я б обрав один замість іншого?

Слідувати:

Який виняток ви б використали, якщо функція очікувала ціле число, що відповідає місяцю, і ви перейшли у "42"? Чи потрапить це в категорію "поза діапазоном", хоча це не колекція?

Відповіді:


249

Я хотів би використовувати: ArgumentException, ArgumentNullException, і ArgumentOutOfRangeException.

  • ArgumentException - Щось не так з аргументом.
  • ArgumentNullException - Аргумент недійсний.
  • ArgumentOutOfRangeException - Я не дуже використовую цей, але звичайне використання - це індексація до колекції та надання індексу, який є великим.

Є й інші варіанти, які не так зосереджуються на самому аргументі, а скоріше оцінюють виклик у цілому:

  • InvalidOperationException- Аргумент може бути в порядку, але не в поточному стані об'єкта. Кредит йде на STW (раніше Yoooder). Проголосуйте також за його відповідь .
  • NotSupportedException- Передані аргументи є дійсними, але просто не підтримуються в цій реалізації. Уявіть собі FTP-клієнта, і ви передаєте команду, у якій клієнт не підтримує.

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

Мені подобається, коли повідомлення про помилки вказують на допомогу, документацію чи інші ресурси. Наприклад, Microsoft зробила перший крок зі своїми статтями про КБ, наприклад, "Чому я отримую повідомлення про помилку" Операція перервана ", коли я відвідую веб-сторінку в Internet Explorer?" . Коли ви зіткнулися з помилкою, вони вказують на статтю KB у повідомленні про помилку. Те, що вони не роблять добре, це те, що вони не скажуть вам, чому конкретно це не вдалося.

Завдяки STW (колишній Yoooder) знову за коментарі.


У відповідь на ваш наступний результат я б кинув ArgumentOutOfRangeException. Подивіться, що MSDN говорить про цей виняток:

ArgumentOutOfRangeExceptionвикидається, коли викликається метод, і принаймні один з аргументів, переданих методу, не має нульової посилання ( Nothingу Visual Basic) і не містить дійсного значення.

Отже, у цьому випадку ви передаєте значення, але це не є дійсним значенням, оскільки ваш діапазон становить 1–12. Однак, як ви документуєте, це дає зрозуміти, що кидає ваш API. Тому що, хоча я можу сказати ArgumentOutOfRangeException, інший розробник може сказати ArgumentException. Зробіть це легко і задокументуйте поведінку.


Psst! Я погоджуюсь, що ви точно відповіли на його конкретне запитання, але дивіться мою відповідь нижче, щоб допомогти округлити захисні кодування та перевірити параметри ;-D
STW

+1, але документувати, який виняток викинуто, і чому важливіше, ніж вибрати "правильний".
pipTheGeek

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

4
Зауважте, якщо ви зловили ArgumentException, він також захопить ArgumentOutOfRange.
Стівен Еверс

Як щодо FormatException: Виняток, який видається, коли формат аргументу недійсний або коли складений рядок формату недостатньо сформований.
Ентоні

44

Я проголосував за відповідь Джоша , але хотів би додати ще один до списку:

System.InvalidOperationException слід кинути, якщо аргумент дійсний, але об'єкт знаходиться в стані, коли аргумент не слід використовувати.

Оновлення, взяті з MSDN:

InvalidOperationException застосовується у випадках, коли недосвідчення методу викликано причинами, відмінними від недійсних аргументів.

Скажімо, що ваш об’єкт має метод PerformAction (enmSomeAction action), дійсними enmSomeActions є Open and Close. Якщо ви викликаєте PerformAction (enmSomeAction.Open) двічі поспіль, то другий виклик повинен кинути InvalidOperationException (оскільки аругмент був дійсним, але не для поточного стану елемента управління)

Оскільки ви вже робите правильно, захищаючи програмування, у мене є ще одне виключення - ObjectDisposedException. Якщо ваш об'єкт реалізує IDisposable, у вас завжди повинна бути змінна класу, яка відстежує розміщений стан; якщо ваш об’єкт був розміщений і на нього викликається метод, ви повинні підняти ObjectDisposedException:

public void SomeMethod()
{
    If (m_Disposed) {
          throw new ObjectDisposedException("Object has been disposed")
     }
    // ... Normal execution code
}

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

Я особисто схиляюся до ArgumentOutOfRangeException і надаю повідомлення, яке вказує, що дійсні значення - 1-12. Моє міркування полягає в тому, що якщо ви говорите про місяці, якщо вважати, що всі цілі подання місяців є дійсними, то ви очікуєте значення в діапазоні 1-12. Якби тільки певні місяці (як, наприклад, місяці, що мали 31 день) були дійсними, то ти б не мав справу з діапазоном per se, і я б кинув загальний ArgumentException, який вказав дійсні значення, і я також задокументував би їх у коментарях методу.


1
Гарна думка. Це може пояснити різницю між недійсним та непередбаченим введенням. +1
Даніель Брюкнер

Psst, я згоден з тобою, що ти просто не збирався вкрасти твій грім. Але оскільки ви це вказали, я оновив свою відповідь
JoshBerke

38

Залежно від фактичного значення та того, який виняток найбільше підходить:

Якщо це недостатньо точно, вийдіть із власного класу виключень ArgumentException.

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


6
Зібрано зі сторінки MSDN на сайті InvalidOperationException: "InvalidOperationException використовується у випадках, коли недосвідчення методу викликано причинами, відмінними від недійсних аргументів."
STW


3

АргументException :

ArgumentException викидається, коли викликається метод, і принаймні один із переданих аргументів не відповідає специфікації параметра викликаного методу. Усі екземпляри ArgumentException повинні містити змістовне повідомлення про помилку, що описує недійсний аргумент, а також очікуваний діапазон значень аргументу.

Також існує кілька підкласів для конкретних типів інвалідності. Посилання містить підсумки підтипів і коли вони повинні застосовуватися.


1

Коротка відповідь:
Ні

Більш довга відповідь:
використання Argument * Виняток (за винятком бібліотеки, яка ввімкнений продукт, наприклад, бібліотека компонентів) - це запах. Винятки - це вирішення виняткової ситуації, а не помилки та недолік користувачів (тобто споживача API).

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

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

Ось як виглядає обробка нульового виключення (очевидно, саркастичний):

try {
    library.Method(null);
}
catch (ArgumentNullException e) {
    // retry with real argument this time
    library.Method(realArgument);
}

Винятки повинні використовуватися, коли ситуація очікується, але виняткова (трапляються речі, що не є під контролем споживача, наприклад, вихід з ладу). Аргумент * Виняток - це вказівка ​​на помилку, і він (на мою думку) обробляється тестами та допомагає з Debug.Assert

BTW: У цьому конкретному випадку ви могли використовувати тип місяця замість int. C # не вистачає, коли справа стосується безпеки типу (Aspect # rulez!), Але іноді ви можете запобігти (або зловити під час компіляції) ці помилки разом.

І так, MicroSoft в цьому помиляється.


6
ІМХО, винятки також слід кидати, коли викликаний метод не може розумно діяти. Це включає випадок, коли абонент передав хибні аргументи. Що б ти зробив замість цього? Повернення -1?
Джон Сондерс

1
Якщо недійсні аргументи можуть призвести до виходу з ладу внутрішньої функції, які плюси і мінуси тестування аргументів на валідність, порівняно з вилученням InvalidArgumentException з внутрішньої функції та оберненням його більш інформативним? Останній підхід, здавалося б, покращує ефективність у загальній справі, але я не бачив, щоб це було зроблено багато.
supercat

Швидкий пошук Google навколо цього питання вказує на те, що кидання загального винятку - найгірша практика з усіх. Що стосується твердження аргументу, я бачу це в малих особистих проектах, але не в корпоративних додатках, де неправдиві аргументи, швидше за все, пов’язані з поганою конфігурацією або поганим розумінням програми.
Макс

0

Існує стандартний ArgumentException, який ви можете використовувати, або ви можете підклас і зробити свій власний. Існує кілька специфічних класів ArgumentException:

http://msdn.microsoft.com/en-us/library/system.argumentexception(VS.71).aspx

Хто б не працював найкраще.


1
Я не згоден майже в усіх випадках; наданий аргумент .NET * Класи виключень дуже часто використовуються і надають вам можливість надати достатньо конкретної інформації, щоб повідомити споживача про проблему.
STW

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

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