Какие исключения следует выдавать для неверных или неожиданных параметров в .NET?

163

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

Следовать за:

Какое исключение вы бы использовали, если у вас есть функция, ожидающая целое число, соответствующее месяцу, и вы передали '42'? Будет ли это попадать в категорию «вне диапазона», даже если это не коллекция?

Даже Миен
источник

Ответы:

249

Я хотел бы использовать: ArgumentException, ArgumentNullException, и ArgumentOutOfRangeException.

  • ArgumentException - Что-то не так с аргументом.
  • ArgumentNullException - Аргумент нулевой.
  • ArgumentOutOfRangeException - Я не очень часто этим пользуюсь, но обычно используется индексация в коллекции и предоставление индекса, который слишком велик.

Есть и другие варианты, которые не фокусируются так сильно на самом аргументе, а скорее оценивают вызов в целом:

  • InvalidOperationException- Аргумент может быть в порядке, но не в текущем состоянии объекта. Кредит идет в STW (ранее Yoooder). Голосуйте также за его ответ .
  • NotSupportedException- Переданные аргументы действительны, но просто не поддерживаются в этой реализации. Представьте себе FTP-клиент, и вы передаете команду, которую клиент не поддерживает.

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

Мне нравится, когда сообщения об ошибках указывают на помощь, документацию или другие ресурсы. Например, Microsoft сделала хороший первый шаг со своими статьями базы знаний, например, «Почему я получаю сообщение об ошибке« Операция прервана »при посещении веб-страницы в Internet Explorer?» , Когда вы сталкиваетесь с ошибкой, они указывают на статью базы знаний в сообщении об ошибке. Что они не делают хорошо, так это то, что они не говорят вам, почему конкретно это не удалось.

Спасибо STW (бывший Yoooder) еще раз за комментарии.


В ответ на ваше продолжение я бы кинул ArgumentOutOfRangeException. Посмотрите, что MSDN говорит об этом исключении:

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

Таким образом, в этом случае вы передаете значение, но это недопустимое значение, поскольку ваш диапазон составляет 1–12. Однако то, как вы документируете, проясняет, что выдает ваш API. Потому что, хотя я мог бы сказать ArgumentOutOfRangeException, другой разработчик мог бы сказать ArgumentException. Сделать это легко и документировать поведение.

JoshBerke
источник
Psst! Я согласен, что вы правильно ответили на его конкретный вопрос, но посмотрите мой ответ ниже, чтобы помочь завершить защитное кодирование и проверку параметров ;-D
STW
+1, но документирование, какое исключение выдается и почему это важнее, чем выбор «правильного».
pipTheGeek
@pipTheGeek - я думаю, что это действительно спорный вопрос. В то время как документирование определенно важно, оно также ожидает, что потребляющий разработчик будет проактивен или защищен и фактически прочитает документацию подробно. Я бы предпочел дружественную / описательную ошибку над хорошей документацией, поскольку у конечного пользователя есть шанс увидеть одну из них, а не другую; есть большая вероятность того, что конечный пользователь сообщит описательную ошибку плохому программисту, чем плохому программисту, читающему полную документацию
STW
4
Обратите внимание: если вы перехватываете ArgumentException, он также перехватывает ArgumentOutOfRange.
Стивен Эверс
Как насчет FormatException: Исключение, которое выдается, когда формат аргумента недопустим или когда строка составного формата не правильно сформирована.
Энтони
44

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

System.InvalidOperationException должно быть выброшено, если аргумент допустим, но объект находится в состоянии, где аргумент не должен использоваться.

Обновление взято из MSDN:

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

Допустим, у вашего объекта есть метод PerformAction (действие enmSomeAction), допустимыми являются enmSomeActions Open и Close. Если вы вызываете PerformAction (enmSomeAction.Open) два раза подряд, то второй вызов должен вызвать исключение InvalidOperationException (поскольку arugment был действителен, но не для текущего состояния элемента управления)

Поскольку вы уже делаете правильные вещи, программируя в обороне, я должен упомянуть еще одно исключение - 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 день), вы бы не имели дело с самим диапазоном, и я бы выдал обобщенное исключение ArgumentException, в котором указаны действительные значения, и я также задокументировал бы их в комментариях метода.

STW
источник
1
Хорошая точка зрения. Это может объяснить разницу между неверным и неожиданным вводом. +1
Даниэль Брюкнер
Psst, я согласен с тобой, просто не собирался красть твой гром. Но так как вы указали это, я обновил свой ответ
JoshBerke
38

В зависимости от фактического значения и того, какое исключение подходит лучше всего:

Если это не достаточно точно, просто выведите свой собственный класс исключений из ArgumentException.

Ответ Yoooder просветил меня. Ввод недействителен, если он недействителен в любое время, в то время как ввод является неожиданным, если он недопустим для текущего состояния системы. Так что в последнем случае InvalidOperationExceptionразумный выбор.

Даниэль Брюкнер
источник
6
Взято со страницы MSDN на InvalidOperationException: «InvalidOperationException используется в случаях, когда сбой вызова метода вызван причинами, отличными от недопустимых аргументов».
STW
3

ArgumentException :

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

Несколько подклассов также существуют для определенных типов инвалидности. Ссылка содержит резюме подтипов и когда они должны применяться.

Бен С
источник
1

Краткий ответ:
ни

Более длинный ответ:
использование Argument * Exception (кроме библиотеки, в которой есть продукт, например, библиотека компонентов) - это запах. Исключением является обработка исключительных ситуаций, а не ошибок и нехваток пользователя (т. Е. Пользователя API).

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

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

Вот как выглядит обработка нулевого исключения (очевидно, с сарказмом):

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

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

Кстати: в данном конкретном случае вы могли бы использовать тип Month вместо int. C # терпит неудачу, когда дело доходит до безопасности типов (Aspect # rulez!), Но иногда вы можете предотвратить (или отловить во время компиляции) эти ошибки все вместе.

И да, MicroSoft ошибается в этом.

THX-1138
источник
6
ИМХО, исключения также должны создаваться, когда вызываемый метод не может разумно продолжаться. Это включает в себя случай, когда вызывающая сторона передала фиктивные аргументы. Что бы вы сделали вместо этого? Вернуть -1?
Джон Сондерс,
1
Если неверные аргументы приводят к сбою внутренней функции, каковы плюсы и минусы проверки аргументов на валидность по сравнению с перехватом InvalidArgumentException из внутренней функции и переносом его на более информативную? Последний подход, похоже, улучшает производительность в общем случае, но я не видел, чтобы он сделал много.
Суперкат
Быстрый поиск в Google по этому вопросу указывает на то, что генерация общего исключения является худшей практикой из всех. Что касается утверждения аргумента, я вижу преимущество в этом в небольших личных проектах, но не в корпоративных приложениях, где неверные аргументы наиболее вероятны из-за плохой конфигурации или плохого понимания приложения.
Макс
0

Существует стандартное ArgumentException, которое вы можете использовать, или вы можете создать подкласс и создать свой собственный. Существует несколько конкретных классов ArgumentException:

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

Какой из них работает лучше всего.

Скотт М.
источник
1
Я не согласен почти во всех случаях; предоставленные классы исключений .NET Argument * очень часто используются и дают вам возможность предоставить достаточно конкретной информации, чтобы уведомить потребителя о проблеме.
STW
Чтобы уточнить - я не согласен почти со всеми случаями наследования от Argument * Exception. Использование одного из исключений аргумента .NET, а также описательного и понятного сообщения предоставляет достаточно подробностей для более или менее любой ситуации, когда аргументы недопустимы.
STW
согласился, но я просто описывал доступные варианты. Я определенно должен был быть более ясным в отношении «предпочтительного» метода.
Скотт М.