Когда бросить исключение? - PullRequest
400 голосов
/ 17 сентября 2008

У меня есть исключения, созданные для каждого условия, которое мое приложение не ожидает. UserNameNotValidException, PasswordNotCorrectException и т. Д.

Однако мне сказали, что я не должен создавать исключения для этих условий. В моем UML эти ARE являются исключениями для основного потока, так почему бы не быть исключением?

Любое руководство или лучшие практики для создания исключений?

Ответы [ 33 ]

576 голосов
/ 17 сентября 2008

Моя личная рекомендация такова: исключение выдается, когда фундаментальное предположение о текущем кодовом блоке оказывается ложным.

Пример 1: допустим, у меня есть функция, которая должна проверять произвольный класс и возвращать true, если этот класс наследует от List <>. Эта функция задает вопрос "Является ли этот объект потомком List?" Эта функция никогда не должна генерировать исключение, потому что в ее работе нет серых областей - каждый отдельный класс наследует или не наследует от List <>, поэтому ответ всегда «да» или «нет».

Пример 2: допустим, у меня есть другая функция, которая проверяет List <> и возвращает true, если его длина больше 50, и false, если длина меньше. Эта функция задает вопрос: «Есть ли в этом списке более 50 элементов?» Но этот вопрос делает предположение - он предполагает, что объект, который ему дан, является списком. Если я передаю ему значение NULL, то это предположение неверно. В этом случае, если функция возвращает либо true или false, то она нарушает свои собственные правила. Функция не может вернуть что-либо и утверждать, что она ответила на вопрос правильно. Так что он не возвращается - он выдает исключение.

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

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

279 голосов
/ 17 сентября 2008

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

60 голосов
/ 17 сентября 2008

На мои маленькие рекомендации сильно повлияла великая книга "Код завершен":

  • Используйте исключения для уведомления о вещах, которые не следует игнорировать.
  • Не используйте исключения, если ошибка может быть обработана локально
  • Убедитесь, что исключения находятся на том же уровне абстракции, что и остальная часть вашей рутины.
  • Исключения должны быть зарезервированы для того, что действительно исключительное .
34 голосов
/ 17 сентября 2008

Это НЕ исключение, если имя пользователя неверно или пароль не верен. Это то, что вы должны ожидать в нормальном режиме работы. Исключением являются вещи, которые не являются частью нормальной работы программы и встречаются довольно редко.

РЕДАКТИРОВАТЬ: мне не нравится использовать исключения, потому что вы не можете сказать, если метод выдает исключение, просто посмотрев на вызов. Вот почему исключения следует использовать только в том случае, если вы не можете справиться с ситуацией достойным образом (подумайте «не хватает памяти» или «компьютер горит»).

25 голосов
/ 17 сентября 2008

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

23 голосов
/ 17 сентября 2008

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

Я не считаю плохой логин частью нормального потока, это исключение. Обычно пользователь вводит правильный пароль, и файл существует. Исключительные случаи являются исключительными, и вполне допустимо использовать исключения для них. Усложнение вашего кода путем распространения возвращаемых значений через n уровней вверх по стеку является пустой тратой энергии и приведет к грязному коду. Сделайте самое простое, что может сработать. Не преждевременно оптимизируйте с помощью кодов ошибок, исключительные вещи по определению случаются редко, а исключения ничего не стоят, если вы их не выбросите.

16 голосов
/ 17 сентября 2008

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

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

14 голосов
/ 17 сентября 2008

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


Вы увидите множество советов, в том числе в ответах на этот вопрос, о том, что вы должны бросать исключения только в «исключительных» обстоятельствах. Это кажется поверхностно разумным, но это ошибочный совет, потому что он заменяет один вопрос («когда я должен выбросить исключение») другим субъективным вопросом («что является исключительным»). Вместо этого, следуйте совету Херба Саттера (для C ++, который доступен в статье Dr Dobbs Когда и как использовать исключения , а также в его книге с Андреем Александреску, C ++ Стандарты кодирования ): выбросить исключение, если и только если

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

Почему это лучше? Разве это не заменяет вопрос на несколько вопросов о предусловиях, постусловиях и инвариантах? Это лучше по нескольким связанным причинам.

  • Предварительные условия, постусловия и инварианты - это дизайн характеристик нашей программы (ее внутренний API), тогда как решение throw является деталью реализации. Это заставляет нас помнить, что мы должны рассматривать проект и его реализацию по отдельности, и наша задача при реализации метода заключается в создании чего-то, что удовлетворяет проектным ограничениям.
  • Это заставляет нас думать с точки зрения предусловий, постусловий и инвариантов, которые являются только допущениями, которые должны делать вызывающие стороны нашего метода, и выражаются точно, обеспечивая слабую связь между компонентами нашей программы .
  • Это слабое связывание позволяет нам при необходимости реорганизовать реализацию.
  • Постусловия и инварианты тестируемы; это приводит к коду, который может быть легко протестирован модулем, потому что постусловия являются предикатами, которые наш код модульного теста может проверить (утвердить).
  • Мышление в терминах постусловий естественным образом создает дизайн, который имеет успех в качестве постусловия , что является естественным стилем использования исключений. Обычный («счастливый») путь выполнения вашей программы выложен линейно, а весь код обработки ошибок перемещен в пункты catch.
10 голосов
/ 17 сентября 2008

Я бы сказал, что нет жестких и быстрых правил, когда следует использовать исключения. Однако есть веские причины использовать или не использовать их:

Причины использования исключений:

  • Поток кода для общего случая яснее
  • Может возвращать информацию о сложных ошибках как объект (хотя это также может быть достигнуто с помощью параметра ошибки "out", переданного по ссылке)
  • Языки, как правило, предоставляют некоторые средства для управления аккуратной очисткой в ​​случае исключения (попробуйте / наконец в Java, используя в C #, RAII в C ++)
  • Если исключение не выдается, выполнение иногда может быть быстрее проверки кодов возврата
  • В Java проверенные исключения должны быть объявлены или перехвачены (хотя это может быть причиной против)

Причины не использовать исключения:

  • Иногда слишком просто, если обработка ошибок проста
  • Если исключения не задокументированы или не объявлены, они могут быть отключены с помощью вызывающего кода, что может быть хуже, чем если бы вызывающий код просто игнорировал код возврата (выход из приложения против сбоя без вывода сообщений - что хуже, может зависеть от сценария)
  • В C ++ код, использующий исключения, должен быть безопасным для исключений (даже если вы не генерируете и не перехватываете их, а вызываете функцию выброса косвенно)
  • В C ++ трудно сказать, когда функция может выдать, поэтому вы должны быть параноиком по поводу безопасности исключений, если вы используете их
  • Создание и отлов исключений, как правило, значительно дороже по сравнению с проверкой флага возврата

В целом, я был бы более склонен использовать исключения в Java, чем в C ++ или C #, потому что я придерживаюсь мнения, что исключение, объявленное или нет, является фундаментальной частью формального интерфейса функции, поскольку изменение вашего Гарантия исключения может нарушить вызывающий код. Самым большим преимуществом их использования в Java IMO является то, что вы знаете, что вызывающая сторона ДОЛЖНА обработать исключение, и это повышает вероятность правильного поведения.

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

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

5 голосов
/ 17 сентября 2008

Если это код, выполняющийся внутри цикла, который, вероятно, будет вызывать исключение снова и снова, то генерировать исключения нехорошо, потому что они довольно медленны для больших N. Но нет ничего плохого в том, чтобы создавать пользовательские исключения, если производительность не проблема. Просто убедитесь, что у вас есть базовое исключение, которое они все наследуют, называемое BaseException или что-то подобное. BaseException наследует System.Exception, но все ваши исключения наследуют BaseException. Вы даже можете иметь дерево типов исключений для группировки похожих типов, но это может быть или не быть чрезмерным.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...