Почему Исключения считаются настолько плохими для входной проверки?

Я понимаю, что "Исключения для исключительных случаев" [a], но кроме того, что они повторяются over и over, я никогда не нашел реальной причины для этого факта.

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

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

Вход не является ожидаемым и, следовательно, является исключительным. Выброс исключения позволяет мне точно определить, что было не так, как StringValueTooLong или IntegerValueTooLow или InvalidDateValue или что-то еще. Почему это считается неправильным?

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

Кроме того, я нашел эту статью Мартина Фаулера относительно того, как обращаться с такими вещами - шаблон уведомления. Я не уверен, как я вижу это как нечто иное, кроме Исключения, которые не останавливают выполнение.

a: Всюду я читал что-нибудь об Исключениях.

--- Редактировать ---

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

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

Ответ 1

Читая эти ответы, я считаю бесполезным сказать: "Исключения должны использоваться только для исключительных условий". Это задает весь вопрос о том, что такое "исключительное условие". Это субъективный термин, лучшим определением которого является "любое условие, с которым нормальный логический поток не имеет отношения". Другими словами, исключительным условием является любое условие, с которым вы имеете дело с использованием исключений.

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

Если вы собираетесь спорить об исключениях в определенном случае, вы должны объяснить, как разделить вселенную условий на "исключительные" и "не исключительные".

В некотором смысле это похоже на ответ на вопрос: "Где границы между процедурами?" Ответ: "Где бы вы ни положили начало и конец", а затем мы можем поговорить о эмпирических правилах и разных стилях для определения, где их разместить. Нет жестких и быстрых правил.

Ответ 2

Пользователь, вводящий "плохой" вход, не является исключением: его можно ожидать.

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

В прошлом многие авторы говорили, что Исключения изначально дороги. Джон Скит писал в блоге вопреки этому (и упоминал несколько раз в ответах здесь, на SO), заявив, что они не так дороги, как сообщалось (хотя я бы не стал использовать их в трудном цикле!)

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

Ответ 3

Есть одна важная причина, кроме тех, которые уже упоминались:

Если вы используете исключения только для исключительных случаев, вы можете запустить в своем отладчике с настройкой отладчика "stop when throw is throw". Это очень удобно, потому что вы попадаете в отладчик на точной строке, которая вызывает проблему. Использование этой функции экономит ваше количество времени каждый день.

В С# это возможно (и я рекомендую его полностью), особенно после того, как они добавили методы TryParse ко всем числовым классам. В общем, ни одна из стандартных библиотек не требует или использует "плохую" обработку исключений. Когда я подхожу к кодовой базе С#, которая не была написана на этом стандарте, я всегда заканчиваю преобразование ее в исключения из-за обычных случаев, потому что stop-om-throw настолько ценен.

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

Когда я программирую Java, это на самом деле невозможно, потому что многие вещи используют исключения для не исключительных случаев, включая множество стандартных java-библиотек. Таким образом, эта функция экономии времени недоступна для использования в java. Я считаю, что это связано с проверенными исключениями, но я не буду начинать разглагольствовать о том, как они злы.

Ответ 4

Ошибки и исключения - что, когда и где?

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

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

Существует три типа ошибок:

  • условие, которое не позволяет функции выполнить предварительное условие (например, ограничение параметра) другой функции, которая должна быть вызвана;
  • условие, которое препятствует функции устанавливать одно из своих собственных постусловий (например, создание допустимого возвращаемого значения является постусловием); и
  • условие, которое не позволяет функции переустанавливать инвариант, который отвечает за поддержание. Это особый постусловие, которое особенно относится к функциям члена. Существенным постусловием каждой нечастной функции-члена является то, что он должен восстановить инварианты своих классов.

Любое другое условие не является ошибкой и не должно сообщаться как ошибка.

Почему Исключения считаются настолько плохими для входной проверки?

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

Ответ 5

Я думаю, что разница зависит от контракта конкретного класса, т.е.

Для кода, предназначенного для работы с пользовательскими вводами, и для защиты программы (т.е. для его очистки) было бы неправильно выбрасывать исключение для недопустимого ввода - ожидается.

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

Ответ 6

  • Поддержание работоспособности - Исключения создают нечетные коды кода, в отличие от GOTO.
  • Простота использования (для других классов) - Другие классы могут доверять тому, что исключения, полученные от вашего пользователя класс ввода - это фактические ошибки.
  • Производительность. На большинстве языков исключение несет использование памяти.
  • Семантика. Значение слов имеет значение. Плохой ввод не "Исключительный".

Ответ 7

Возможно ли, что некоторые разногласия связаны с отсутствием консенсуса относительно того, что означает "пользовательский ввод"? И действительно, на каком уровне вы кодируете.

Если вы кодируете пользовательский интерфейс GUI или обработчик веб-формы, вы можете ожидать недопустимый ввод, поскольку он поступает непосредственно от печатных пальцев человека.

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

Если вы кодируете сервер на уровне протокола, вы можете разумно ожидать, что клиент будет проверять ввод пользователя. Опять же, неверный ввод здесь действительно будет исключением. Это совсем не так, как доверять клиенту 100% (это было бы очень глупо) - но, в отличие от прямого ввода пользователем, вы прогнозируете, что большинство входных данных будут в порядке. Линии размыты здесь несколько. Чем вероятнее, что что-то происходит, тем меньше вы хотите использовать исключения для его обработки.

Ответ 8

Это лингвистический pov (точка зрения) по этому вопросу.

Почему Исключения считаются настолько плохими для входной проверки?

заключение:

  • Исключения не определены достаточно четко, поэтому существуют разные мнения.
  • Неверный ввод рассматривается как нормальная вещь, а не как исключение.

мысли?

Вероятно, это связано с ожиданиями о создании кода.

  • Клиенту нельзя доверять
    • проверка должна выполняться на стороне сервера. сильнее: каждая проверка выполняется на стороне сервера.
    • поскольку проверка выполняется на стороне сервера, ожидается, что она будет выполнена там, и ожидаемое не является исключением, так как ожидается.

Однако

  • нельзя <доверять << →
  • можно доверять клиенту подтверждение ввода
    • Если доверенность проверена, можно ожидать, что она выдаст допустимый ввод
    • теперь ожидается, что каждый вход будет действительным.
    • неверный ввод теперь неожиданным, исключение

.

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

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

Ответ 9

При использовании исключений код обработки ошибок отделен от кода, вызывающего ошибку. Это намерение обработки исключений - это исключительное условие, ошибка не может обрабатываться локально, поэтому исключение передается в какую-то более высокую (и неизвестную) область. Если не обрабатывается, приложение выйдет, прежде чем будет выполнено более сложное.

Если вы когда-либо когда-либо бросаете исключение, когда выполняете простые логические операции, например, проверяете ввод пользователя, вы делаете что-то очень, очень очень, неправильно.

Ввод - это не то, что ожидалось, и следовательно, является исключительным.

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

Бросание исключения позволяет мне точно определить, что было не так, как StringValueTooLong или или IntegerValueTooLow или InvalidDateValue или что-то еще. Почему это считается не так?

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

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

Ответ 10

Еще одно голосование против обработки исключений для вещей, которые не являются исключениями!

Ответ 11

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

Есть проблемы с производительностью с исключениями, но в коде GUI вам обычно не придется беспокоиться о них. Итак, что, если проверка требует дополнительных 100 мс для запуска? Пользователь не замечает этого.

В некотором смысле это сложный вызов. С одной стороны, вы можете не захотеть свернуть все ваше приложение, потому что пользователь ввел дополнительную цифру в текстовое поле почтового индекса, и вы забыли обработать исключение. С другой стороны, подход "сбой на раннем этапе, неудачный" гарантирует, что ошибки будут обнаружены и исправлены быстро и сохранят вашу драгоценную базу данных. В целом я думаю, что большинство фреймворков рекомендуют не использовать обработку исключений для проверки ошибок пользовательского интерфейса, а некоторые, например .NET Windows Forms, предоставляют хорошие способы сделать это (события ErrorProviders и проверки) без исключений.

Ответ 12

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

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

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

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

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

Ответ 13

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

public bool isValidDate(string date)
{
    bool retVal = true;
    //check for 4 digit year
    throw new FourDigitYearRequiredException();
    retVal = false;

    //check for leap years
    throw new NoFeb29InANonLeapYearException();
    retVal = false;
    return retVal;
}

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

public bool isValidDate(string date)
{
    bool retVal = false;
    retVal = doesDateContainAFourDigitYear(date);
    retVal = isDateInALeapYear(date);
    return retVal;
}

public bool isDateInALeapYear(string date){}

public bool doesDateContainAFourDigitYear(string date){}

Как уже упоминалось, возвращение ошибки struct/object, содержащей информацию об ошибке, является отличной идеей. Наиболее очевидным преимуществом является то, что вы можете собирать их и отображать все сообщения об ошибках пользователю одновременно, вместо того чтобы заставить их играть с Whack-A-Mole с проверкой.

Ответ 14

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

например

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

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

if not Validate() then
  DoNotContinue();

но он может забыть и только вызвать Validate() (я знаю, что он не должен, но, возможно, может).

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

Ответ 15

8 лет спустя, и я столкнулся с той же дилеммой, пытающейся применить шаблон CQS. Я на стороне, что проверка ввода может вызвать исключение, но с добавленным ограничением. Если какой-либо вход выходит из строя, вам нужно выбросить исключение типа ONE: ValidationException, BrokenRuleException и т.д. Не бросайте кучу разных типов, так как с ними невозможно справиться. Таким образом, вы получаете список всех нарушенных правил в одном месте. Вы создаете один класс, который отвечает за выполнение проверки (SRP), и генерирует исключение, если нарушено хотя бы одно правило. Таким образом, вы справляетесь с одной ситуацией с одним уловом, и вы знаете, что вы хороши. Вы можете обрабатывать этот сценарий независимо от того, какой код вызывается. Это оставляет весь код ниже по течению намного чище, поскольку вы знаете, что он находится в правильном состоянии или он не получил бы там.

Для меня получение недопустимых данных от пользователя - это не то, что вы обычно ожидаете. (Если каждый пользователь отправляет вам недействительные данные в первый раз, я бы посмотрел на ваш пользовательский интерфейс.) Любые данные, которые мешают вам обработать истинное намерение, является ли оно пользователем или источником ресурсов в другом месте, должны прервать обработку. Как это иначе, чем бросать ArgumentNullException из одной части данных, если она была введена пользователем, а это поле в классе, которое говорит, что это необходимо.

Конечно, сначала вы можете выполнить валидацию и написать тот же код шаблона для каждой отдельной команды, но я думаю, что это кошмар для обслуживания, чем ловушка неверного ввода пользователя в одном месте вверху, который обрабатывается одинаково независимо от того, (Меньше кода!) Удар по производительности будет появляться только в том случае, если пользователь выдаст неверные данные, что не так часто бывает (или у вас плохой интерфейс). В любом случае все правила на стороне клиента должны быть переписаны на сервере, поэтому вы можете просто написать их один раз, выполнить вызов AJAX, а < Задержка 500 мс сэкономит вам тонну времени кодирования (всего 1 место, чтобы поместить всю вашу логику проверки).

Кроме того, хотя вы можете сделать некоторую аккуратную проверку с помощью ASP.NET из коробки, если вы хотите повторно использовать логику проверки в других пользовательских интерфейсах, вы не сможете с тех пор, как она запекла в ASP.NET. Вам было бы лучше создать что-то ниже и обработать его выше, независимо от используемого пользовательского интерфейса. (Мои 2 цента, по крайней мере.)

Ответ 16

Я согласен с Митчем в том, что "Исключения не должны использоваться для нормального потока управления". Я просто хочу добавить, что из того, что я помню из своих классов компьютерных наук, ловить исключения дорого. Я никогда не пытался делать тесты, но было бы интересно сравнить производительность между словами: if/else vs try/catch.

Ответ 17

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