Когда бросить исключение?
У меня есть исключения, созданные для каждого условия, которое мое приложение не ожидает. UserNameNotValidException
, PasswordNotCorrectException
и т.п.
Однако мне сказали, что я не должен создавать исключения для этих условий. В моем UML эти ARE являются исключениями для основного потока, так почему же это не должно быть исключением?
Любое руководство или лучшие практики для создания исключений?
34 ответа
Моя личная рекомендация такова: исключение выдается, когда фундаментальное предположение о текущем блоке кода оказывается ложным.
Пример 1: скажем, у меня есть функция, которая должна проверять произвольный класс и возвращать true, если этот класс наследует от List<>. Эта функция задает вопрос: "Является ли этот объект потомком List?" Эта функция никогда не должна генерировать исключение, потому что в ее работе нет серых областей - каждый отдельный класс наследует или не наследует от List<>, поэтому ответом всегда будет "да" или "нет".
Пример 2: скажем, у меня есть другая функция, которая проверяет List <> и возвращает true, если его длина больше 50, и false, если длина меньше. Эта функция задает вопрос: "Есть ли в этом списке более 50 элементов?" Но этот вопрос делает предположение - он предполагает, что объект, который ему дан, является списком. Если я передаю ему значение NULL, то это предположение неверно. В этом случае, если функция возвращает истину или ложь, она нарушает свои собственные правила. Функция не может ничего вернуть и утверждает, что ответила на вопрос правильно. Так что он не возвращается - он вызывает исключение.
Это сравнимо с логической ошибкой "нагруженного вопроса". Каждая функция задает вопрос. Если вводимые данные делают этот вопрос ошибочным, выведите исключение. Эту линию труднее нарисовать с помощью функций, которые возвращают void, но суть в следующем: если предположения функции относительно ее входных данных нарушаются, она должна генерировать исключение вместо нормального возврата.
Другая сторона этого уравнения: если вы обнаружите, что ваши функции часто генерируют исключения, вам, вероятно, нужно уточнить их допущения.
Потому что это вещи, которые будут происходить нормально. Исключения не являются механизмами управления потоком. Пользователи часто неправильно вводят пароли, это не исключительный случай. Исключения должны быть действительно редкой вещью, UserHasDiedAtKeyboard
Тип ситуации.
Мои маленькие рекомендации находятся под сильным влиянием великой книги "Код завершен":
- Используйте исключения для уведомления о вещах, которые не следует игнорировать.
- Не используйте исключения, если ошибка может быть обработана локально
- Убедитесь, что исключения находятся на том же уровне абстракции, что и остальная часть вашей рутины.
- Исключения должны быть зарезервированы для того, что действительно исключительное.
Это НЕ исключение, если имя пользователя неверно или пароль неверен. Это то, что вы должны ожидать в нормальном режиме работы. Исключения составляют вещи, которые не являются частью нормальной работы программы и встречаются довольно редко.
РЕДАКТИРОВАТЬ: я не люблю использовать исключения, потому что вы не можете сказать, если метод выдает исключение, просто посмотрев на вызов. Вот почему исключения следует использовать только в том случае, если вы не можете справиться с ситуацией достойным образом (подумайте "не хватает памяти" или "компьютер горит").
Одно из практических правил - использовать исключения в случае чего-то, что вы обычно не можете предсказать. Примерами являются подключение к базе данных, отсутствие файла на диске и т. Д. Для сценариев, которые вы можете предсказать, например, пользователи, пытающиеся войти в систему с неверным паролем, должны использовать функции, которые возвращают логические значения и знают, как корректно обрабатывать ситуацию. Вы не хотите внезапно завершать выполнение, вызывая исключение только потому, что кто-то набрал неверный пароль.
Другие предлагают, чтобы исключения не использовались, потому что неправильный вход в систему следует ожидать в нормальном потоке, если пользователь ошибается. Я не согласен, и я не понимаю причины. Сравните это с открытием файла... если файл не существует или недоступен по какой-либо причине, то исключительная ситуация будет выдана платформой. Использование вышеупомянутой логики было ошибкой Microsoft. Они должны были вернуть код ошибки. То же самое для разбора, веб-запросов и т. Д. И т. Д.
Я не считаю плохой логин частью нормального потока, это исключение. Обычно пользователь вводит правильный пароль, и файл существует. Исключительные случаи являются исключительными, и вполне допустимо использовать исключения для них. Усложнение вашего кода путем распространения возвращаемых значений через n уровней вверх по стеку является пустой тратой энергии и приведет к грязному коду. Сделайте самое простое, что может сработать. Не преждевременно оптимизируйте с помощью кодов ошибок, исключительные вещи по определению случаются редко, а исключения ничего не стоят, если вы их не выбросите.
Я думаю, что вы должны генерировать исключение только тогда, когда вы ничего не можете сделать, чтобы выйти из своего текущего состояния. Например, если вы выделяете память, а выделять ее не на что. В тех случаях, когда вы упоминаете, вы можете четко восстановиться после этих состояний и соответственно вернуть код ошибки своему абоненту.
Вы увидите множество советов, в том числе в ответах на этот вопрос, что вы должны бросать исключения только в "исключительных" обстоятельствах. Это кажется поверхностно разумным, но это ошибочный совет, потому что он заменяет один вопрос ("когда я должен выбросить исключение") другим субъективным вопросом ("что является исключительным"). Вместо этого следуйте совету Херба Саттера (для C++, который доступен в статье доктора Доббса " Когда и как использовать исключения", а также в его книге с Андреем Александреску, " Стандарты кодирования C++"): выведите исключение, если и только если
- предварительное условие не выполняется (что обычно делает невозможным одно из следующих действий) или
- альтернатива не будет соответствовать постусловию или
- альтернатива не сможет поддерживать инвариант.
Почему это лучше? Разве это не заменяет вопрос несколькими вопросами о предварительных условиях, постусловиях и инвариантах? Это лучше по нескольким связанным причинам.
- Предварительные условия, постусловия и инварианты являются конструктивными характеристиками нашей программы (ее внутреннего API), тогда как решение о
throw
это деталь реализации. Это заставляет нас помнить о том, что мы должны рассматривать проект и его реализацию отдельно, и наша задача при реализации метода заключается в создании чего-то, что удовлетворяет проектным ограничениям. - Это заставляет нас думать с точки зрения предусловий, постусловий и инвариантов, которые являются единственными допущениями, которые должны делать вызывающие нашего метода, и выражаются точно, обеспечивая слабую связь между компонентами нашей программы.
- Это слабое связывание позволяет нам при необходимости реорганизовать реализацию.
- Постусловия и инварианты тестируемы; это приводит к коду, который может быть легко протестирован модулем, потому что постусловия являются предикатами, которые наш код модульного теста может проверить (утвердить).
- Мышление в терминах постусловий естественным образом создает дизайн, который имеет успех как постусловие, что является естественным стилем использования исключений. Нормальный ("счастливый") путь выполнения вашей программы расположен линейно, а весь код обработки ошибок перемещен в
catch
статьи.
Исключения являются довольно дорогостоящим эффектом, если, например, если у вас есть пользователь, предоставивший неверный пароль, обычно лучше передать обратно флаг сбоя или какой-либо другой индикатор того, что он недействителен.
Это связано с тем, что исключения обрабатываются, действительно неверный ввод и уникальные критические элементы останова должны быть исключениями, а не ошибочной информацией для входа.
Я бы сказал, что нет жестких и быстрых правил, когда следует использовать исключения. Однако есть веские причины использовать или не использовать их:
Причины использования исключений:
- Поток кода для общего случая понятнее
- Может возвращать информацию о сложной ошибке как объект (хотя это также может быть достигнуто с помощью параметра "out" ошибки, переданного по ссылке)
- Языки, как правило, предоставляют некоторые средства для управления аккуратной очисткой в случае исключения (try / finally в Java, использование в C#, RAII в C++)
- Если исключение не выдается, выполнение может иногда выполняться быстрее, чем проверка кодов возврата.
- В Java проверенные исключения должны быть объявлены или перехвачены (хотя это может быть причиной против)
Причины не использовать исключения:
- Иногда бывает слишком сложно, если обработка ошибок проста
- Если исключения не задокументированы или не объявлены, они могут быть отключены с помощью вызывающего кода, что может быть хуже, чем если бы вызывающий код просто игнорировал код возврата (выход из приложения против сбоя без вывода сообщений - что еще хуже, может зависеть от сценария)
- В C++ код, использующий исключения, должен быть безопасным для исключений (даже если вы их не выбрасываете и не перехватываете, а косвенно вызываете функцию выбрасывания)
- В C++ трудно сказать, когда функция может выдать, поэтому вы должны быть параноиком по поводу безопасности исключений, если вы используете их
- Создание и отлов исключений, как правило, значительно дороже по сравнению с проверкой флага возврата.
В целом, я был бы более склонен использовать исключения в Java, чем в C++ или C#, потому что я придерживаюсь мнения, что исключение, объявленное или нет, является фундаментальной частью формального интерфейса функции, поскольку изменение гарантии исключения может сломать вызывающий код. Самым большим преимуществом их использования в Java IMO является то, что вы знаете, что вызывающая программа ДОЛЖНА обрабатывать исключение, и это повышает вероятность правильного поведения.
Из-за этого на любом языке я всегда получаю все исключения в слое кода или API из общего класса, так что вызывающий код всегда может гарантировать перехват всех исключений. Также я бы посчитал плохим выбрасывать классы исключений, специфичные для реализации, при написании API или библиотеки (т. Е. Переносить исключения из нижних уровней так, чтобы исключение, которое получает ваш вызывающий объект, было понятно в контексте вашего интерфейса).
Обратите внимание, что Java делает различие между общими и исключениями времени выполнения в том смысле, что последние не нужно объявлять. Я бы использовал классы исключений времени выполнения только тогда, когда вы знаете, что ошибка является результатом ошибки в программе.
Если это код, выполняющийся внутри цикла, который, вероятно, будет вызывать исключение снова и снова, то генерирование исключений не очень хорошая вещь, потому что они довольно медленные для больших N. Но нет ничего плохого в том, чтобы генерировать пользовательские исключения, если производительность не проблема. Просто убедитесь, что у вас есть базовое исключение, которое они все наследуют, называемое BaseException или что-то подобное. BaseException наследует System.Exception, но все ваши исключения наследуют BaseException. Вы можете даже иметь дерево типов исключений для группировки похожих типов, но это может быть или не быть чрезмерным.
Итак, краткий ответ таков: если это не приводит к значительному снижению производительности (чего не должно быть, если вы не выбросите много исключений), тогда продолжайте.
Классы исключений похожи на "нормальные" классы. Вы создаете новый класс, когда он "является" объектом другого типа, с разными полями и разными операциями.
Как правило, вы должны попытаться найти баланс между количеством исключений и степенью детализации исключений. Если ваш метод генерирует более 4-5 различных исключений, вы, вероятно, можете объединить некоторые из них в более "общие" исключения (например, в вашем случае "AuthenticationFailedException") и использовать сообщение об исключении, чтобы подробно описать, что пошло не так. Если ваш код не обрабатывает каждый из них по-разному, вам не нужно создавать много классов исключений. И если это произойдет, пусть вы просто вернете перечисление с ошибкой, которая произошла. Так немного чище.
Практическое правило создания исключений довольно простое. Вы делаете это, когда ваш код перешел в НЕИЗВЕСТНОЕ НЕВЕРНОЕ состояние. если данные скомпрометированы или вы не можете свернуть обработку, которая произошла до этой точки, вы должны прекратить ее. действительно, что еще вы можете сделать? ваша логика обработки в конечном итоге потерпит неудачу в другом месте. если вы можете как-то восстановиться, то делайте это и не бросайте исключения.
в вашем конкретном случае, если вы были вынуждены сделать что-то глупое, например принять снятие денег, и только после этого проверять пользователя / пароль, вы должны прекратить процесс, выдав исключение, чтобы уведомить о том, что произошло что-то плохое, и предотвратить дальнейший ущерб.
Я бы сказал, что, как правило, каждый фундаментализм ведет в ад.
Вы, конечно, не захотите закончить потоком, управляемым исключениями, но вообще избегать исключений - тоже плохая идея. Вы должны найти баланс между обоими подходами. Что бы я не делал, это создавал тип исключения для каждой исключительной ситуации. Это не продуктивно.
Обычно я предпочитаю создавать два основных типа исключений, которые используются во всей системе: LogicalException и TechnicalException. Их можно дополнительно различать по подтипам, если это необходимо, но обычно это не является необходимым.
Техническое исключение означает действительно неожиданное исключение, такое как отключение сервера базы данных, соединение с веб-службой, вызвало IOException и так далее.
С другой стороны, логические исключения используются для распространения менее серьезной ошибочной ситуации на верхние уровни (как правило, некоторый результат проверки).
Обратите внимание, что даже логическое исключение не предназначено для регулярного использования для управления потоком программы, а скорее для освещения ситуации, когда поток действительно должен завершиться. При использовании в Java оба типа исключений являются подклассами RuntimeException, а обработка ошибок в значительной степени ориентирована на аспекты.
Поэтому в примере входа в систему может быть целесообразно создать что-то вроде AuthenticationException и различать конкретные ситуации по значениям перечисления, таким как UsernameNotExisting, PasswordMismatch и т. Д. Тогда у вас не будет огромной иерархии исключений, и вы сможете поддерживать блоки catch на поддерживаемом уровне, Вы также можете легко использовать некоторый общий механизм обработки исключений, так как вы классифицировали исключения и хорошо знаете, что распространять до пользователя и как.
Мы обычно используем исключение LogicalException во время вызова веб-службы, когда ввод пользователя был неверным. Исключение передается в детали SOAPFault, а затем снова отправляется в исключение на клиенте, что приводит к отображению ошибки проверки в одном поле ввода определенной веб-страницы, поскольку исключение имеет правильное сопоставление с этим полем.
Это, конечно, не единственная ситуация: вам не нужно нажимать веб-сервис, чтобы вызвать исключение. Вы можете сделать это в любой исключительной ситуации (например, в случае необходимости быстрого отказа) - все это на ваше усмотрение.
Я согласен с Джаплоком там, где это возможно - бросайте акцепт, если вы не уверены в результате операции. Вызовы API, доступ к файловым системам, вызовы базы данных и т. Д. Каждый раз, когда вы пересекаете "границы" ваших языков программирования.
Я хотел бы добавить, не стесняйтесь бросить стандартное исключение. Если вы не собираетесь делать что-то "другое" (игнорировать, отправлять по электронной почте, регистрировать, показывать, что твиттер изображает китов и т. Д.), То не беспокойтесь о пользовательских исключениях.
Во-первых, если пользователи вашего API не заинтересованы в конкретных мелкозернистых ошибках, то наличие особых исключений для них не имеет никакого значения.
Поскольку зачастую невозможно узнать, что может быть полезно для ваших пользователей, лучшим подходом является наличие определенных исключений, но обеспечение их наследования от общего класса (например, std::exception или его производных в C++). Это позволяет вашему клиенту ловить определенные исключения, если они захотят, или более общее исключение, если им все равно.
Как правило, вы хотите создать исключение для всего, что может произойти в вашем приложении, которое является "Исключительным".
В вашем примере оба этих исключения выглядят так, как будто вы вызываете их с помощью проверки пароля / имени пользователя. В этом случае можно утверждать, что на самом деле не исключено, что кто-то введет неверное имя пользователя / пароль.
Они являются "исключениями" для основного потока вашего UML, но являются более "ветвями" в обработке.
Если вы попытались получить доступ к файлу или базе данных passwd и не смогли, это было бы исключительным случаем и потребовало бы исключения.
Простой ответ: всякий раз, когда операция невозможна (либо из-за приложения, либо из-за нарушения бизнес-логики). Если метод вызывается и невозможно сделать то, для чего был написан метод, выдается исключение. Хорошим примером является то, что конструкторы всегда выдают ArgumentExceptions, если экземпляр не может быть создан с использованием предоставленных параметров. Другой пример - InvalidOperationException, который генерируется, когда операция не может быть выполнена из-за состояния другого члена или членов класса.
В вашем случае, если вызывается такой метод, как Login (имя пользователя, пароль), если имя пользователя недопустимо, действительно правильно генерировать исключение UserNameNotValidException или PasswordNotCorrectException, если пароль неверен. Пользователь не может войти в систему, используя предоставленные параметры (т. Е. Это невозможно, потому что это нарушит аутентификацию), поэтому выведите исключение. Хотя, возможно, ваши два исключения унаследованы от ArgumentException.
Сказав это, если вы не хотите генерировать исключение, потому что сбой входа в систему может быть очень распространенным, одна стратегия вместо этого создать метод, который возвращает типы, которые представляют различные сбои. Вот пример:
{ // class
...
public LoginResult Login(string user, string password)
{
if (IsInvalidUser(user))
{
return new UserInvalidLoginResult(user);
}
else if (IsInvalidPassword(user, password))
{
return new PasswordInvalidLoginResult(user, password);
}
else
{
return new SuccessfulLoginResult();
}
}
...
}
public abstract class LoginResult
{
public readonly string Message;
protected LoginResult(string message)
{
this.Message = message;
}
}
public class SuccessfulLoginResult : LoginResult
{
public SucccessfulLogin(string user)
: base(string.Format("Login for user '{0}' was successful.", user))
{ }
}
public class UserInvalidLoginResult : LoginResult
{
public UserInvalidLoginResult(string user)
: base(string.Format("The username '{0}' is invalid.", user))
{ }
}
public class PasswordInvalidLoginResult : LoginResult
{
public PasswordInvalidLoginResult(string password, string user)
: base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
{ }
}
Большинство разработчиков учат избегать исключений из-за накладных расходов, вызванных их выбросом. Здорово быть внимательным к ресурсам, но обычно не в ущерб дизайну вашего приложения. Это, вероятно, причина, по которой вам сказали не бросать ваши два исключения. Использовать ли исключения или нет обычно сводится к тому, как часто будет происходить исключение. Если это довольно распространенный или вполне ожидаемый результат, то это когда большинство разработчиков избегают исключений и вместо этого создают другой метод для указания сбоя из-за предполагаемого потребления ресурсов.
Вот пример того, как избежать использования Исключений в сценарии, как только что описано, с помощью шаблона Try():
public class ValidatedLogin
{
public readonly string User;
public readonly string Password;
public ValidatedLogin(string user, string password)
{
if (IsInvalidUser(user))
{
throw new UserInvalidException(user);
}
else if (IsInvalidPassword(user, password))
{
throw new PasswordInvalidException(password);
}
this.User = user;
this.Password = password;
}
public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
{
if (IsInvalidUser(user) ||
IsInvalidPassword(user, password))
{
return false;
}
validatedLogin = new ValidatedLogin(user, password);
return true;
}
}
Исключения предназначены для событий, которые представляют собой ненормальное поведение, ошибки, сбои и тому подобное. Вместо этого функциональное поведение, пользовательские ошибки и т. Д. Должны обрабатываться логикой программы. Поскольку неверная учетная запись или пароль являются ожидаемой частью логического потока в процедуре входа в систему, она должна иметь возможность обрабатывать эти ситуации без исключений.
Для меня должно быть выброшено исключение в случае сбоя требуемого технического или бизнес-правила. например, если сущность автомобиля связана с массивом из 4 шин... если одна или несколько шин имеют нулевое значение... должно быть сгенерировано исключение "NotEnoughTiresException", поскольку оно может быть перехвачено на другом уровне системы и иметь значительный смысл через логирование. кроме того, если мы просто попытаемся контролировать поток с нулевым значением и предотвратить возникновение машины. мы никогда не сможем найти источник проблемы, потому что шина не должна быть нулевой.
У меня философские проблемы с использованием исключений. По сути, вы ожидаете, что произойдет конкретный сценарий, но вместо того, чтобы обращаться с ним явно, вы отталкиваете проблему от решения "в другом месте". И где это "где-то еще", можно догадаться.
Безопасность связана с вашим примером: вы не должны сообщать злоумышленнику, что имя пользователя существует, но пароль неправильный. Это дополнительная информация, которой вам не нужно делиться. Просто скажите "имя пользователя или пароль неверны".
"PasswordNotCorrectException" не является хорошим примером для использования исключений. Следует ожидать, что пользователи неправильно вводят свои пароли, поэтому вряд ли это исключение. Вы, вероятно, даже восстанавливаетесь после этого, показывая хорошее сообщение об ошибке, так что это просто проверка достоверности.
Необработанные исключения в конечном итоге остановят выполнение - и это хорошо. Если вы возвращаете ложные, нулевые коды или коды ошибок, вам придется самостоятельно решать состояние программы. Если вы забудете где-нибудь проверить условия, ваша программа может продолжать работать с неверными данными, и вам может быть трудно выяснить, что произошло и где.
Конечно, вы можете вызвать ту же проблему с пустыми операторами catch, но, по крайней мере, определить их проще и не требует от вас понимания логики.
Итак, как правило:
Используйте их там, где вы не хотите или просто не можете восстановиться после ошибки.
Создание исключений приводит к разматыванию стека, что оказывает некоторое влияние на производительность (допустим, современные управляемые среды улучшили это). Все еще неоднократно бросать и ловить исключения во вложенной ситуации было бы плохой идеей.
Возможно, более важно то, что исключения предназначены для исключительных условий. Они не должны использоваться для обычного потока управления, потому что это ухудшит читабельность вашего кода.
Основная причина, по которой следует избегать выбрасывания исключения, заключается в том, что при создании исключения возникает много накладных расходов.
В статье ниже утверждается, что исключение - исключительные условия и ошибки.
Неправильное имя пользователя - это не обязательно ошибка программы, а ошибка пользователя...
Вот достойная отправная точка для исключений в.NET: http://msdn.microsoft.com/en-us/library/ms229030(VS.80).aspx
На мой взгляд, фундаментальный вопрос должен заключаться в том, следует ли ожидать, что вызывающая сторона захочет продолжить нормальный поток программ, если возникает условие. Если вы не знаете, либо у вас есть отдельные методы doSomething и trySomething, где первый возвращает ошибку, а второй нет, либо есть подпрограмма, которая принимает параметр, указывающий, следует ли генерировать исключение в случае сбоя). Рассмотрим класс для отправки команд в удаленную систему и отчета об ответах. Определенные команды (например, перезапуск) заставят удаленную систему отправить ответ, но затем не будут отвечать на запросы в течение определенного промежутка времени. Таким образом, полезно иметь возможность отправить команду "ping" и выяснить, отвечает ли удаленная система в течение разумного промежутка времени без необходимости генерировать исключение, если это не так (вызывающая сторона, вероятно, ожидает, что первые несколько " попытки пинга не увенчались успехом, но со временем все бы сработало). С другой стороны, если есть последовательность команд, например:
exchange_command ("открыть временный файл"); exchange_command("записать данные временного файла {что угодно}"); exchange_command("записать данные временного файла {что угодно}"); exchange_command("записать данные временного файла {что угодно}"); exchange_command("записать данные временного файла {что угодно}"); exchange_command("закрыть временный файл"); exchange_command("скопировать временный файл в реальный файл");
Хотелось бы, чтобы сбой любой операции прервал всю последовательность. Хотя можно проверить каждую операцию, чтобы убедиться в ее успешности, более полезно, чтобы процедура exchange_command() выдавала исключение в случае сбоя команды.
На самом деле, в приведенном выше сценарии может быть полезно иметь параметр для выбора ряда режимов обработки ошибок: никогда не генерировать исключения, генерировать исключения только для ошибок связи или генерировать исключения в любых случаях, когда команда не возвращает "успех" Индикация
У меня есть три типа условий, которые я ловлю.
Плохой или отсутствующий ввод не должен быть исключением. Используйте js на стороне клиента и регулярное выражение на стороне сервера, чтобы обнаруживать, устанавливать атрибуты и пересылать сообщения обратно на одну и ту же страницу.
AppException. Обычно это исключение, которое вы обнаруживаете и добавляете в свой код. Другими словами, это те, которые вы ожидаете (файл не существует). Зарегистрируйте его, установите сообщение и вернитесь на страницу с общей ошибкой. На этой странице обычно есть немного информации о том, что произошло.
Неожиданное исключение. Это те, о которых вы не знаете. Зарегистрируйте это с деталями и перешлите их на страницу с общей ошибкой.
Надеюсь это поможет
В конечном счете, решение сводится к тому, будет ли более полезным иметь дело с ошибками уровня приложения, такими как эта, с помощью обработки исключений или с помощью собственного собственного механизма, такого как возврат кодов состояния. Я не думаю, что есть жесткое правило о том, что лучше, но я бы подумал:
- Кто звонит на ваш код? Это какой-то общедоступный API или внутренняя библиотека?
- Какой язык вы используете? Если это, например, Java, то генерирование (проверенного) исключения накладывает явное бремя на вашего вызывающего, чтобы каким-то образом обрабатывать это состояние ошибки, а не возвращать статус, который можно игнорировать. Это может быть хорошо или плохо.
- Как обрабатываются другие ошибки в том же приложении? Вызывающие абоненты не захотят иметь дело с модулем, который обрабатывает ошибки уникальным способом, в отличие от чего-либо еще в системе.
- Сколько вещей может пойти не так с рассматриваемой процедурой, и как они будут обрабатываться по-другому? Рассмотрим разницу между серией блоков перехвата, которые обрабатывают различные ошибки, и переключением на код ошибки.
- У вас есть структурированная информация об ошибке, которую нужно вернуть? Создание исключения дает вам лучшее место для размещения этой информации, чем просто возвращение статуса.
Вы можете использовать несколько общих исключений для этих условий. Например, ArgumentException предназначен для использования, когда что-то не так с параметрами метода (за исключением ArgumentNullException). Обычно вам не нужны исключения, такие как LessThanZeroException, NotPrimeNumberException и т. Д. Подумайте о пользователе вашего метода. Количество условий, которые она хочет обработать специально, равно количеству типов исключений, которые должен выдавать ваш метод. Таким образом, вы можете определить, насколько подробными будут ваши исключения.
Кстати, всегда старайтесь предоставить пользователям ваших библиотек несколько способов избежать исключений. TryParse - хороший пример, он существует, так что вам не нужно использовать int.Parse и перехватывать исключение. В вашем случае вы можете захотеть предоставить некоторые методы для проверки правильности имени пользователя или пароля, чтобы вашим пользователям (или вам) не приходилось много обрабатывать исключения. Надеемся, что это приведет к более читабельному коду и лучшей производительности.
Некоторые полезные вещи, которые следует учитывать при принятии решения о целесообразности исключения:
какой уровень кода вы хотите запустить после появления кандидата на исключение, то есть сколько слоев стека вызовов должно быть разобрано. Как правило, вы хотите обработать исключение как можно ближе к месту его возникновения. Для проверки имени пользователя и пароля вы обычно обрабатываете сбои в одном и том же блоке кода, а не позволяете появиться исключению. Так что исключение, вероятно, не подходит. (OTOH, после трех неудачных попыток входа в систему поток управления может сместиться в другое место, и здесь может быть уместно исключение.)
Это событие вы хотели бы видеть в журнале ошибок? Не каждое исключение записывается в журнал ошибок, но полезно спросить, будет ли полезна эта запись в журнале ошибок - то есть вы попытаетесь что-то с этим сделать или будете мусором, который вы игнорируете.
Исключения против возвращаемого аргумента кода ошибки должны относиться к управлению потоком, а не к философии (насколько "исключительной" является ошибка):
void f1() throws ExceptionType1, ExceptionType2 {}
void catchFunction() {
try{
while(someCondition){
try{
f1();
}catch(ExceptionType2 e2){
//do something, don't break the loop
}
}
}catch(ExceptionType1 e1){
//break the loop, do something else
}
}