Почему исключения считаются настолько плохими для проверки входных данных? - PullRequest
46 голосов
/ 04 января 2009

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

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

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

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

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

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

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

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

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

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

Ответы [ 17 ]

1 голос
/ 19 сентября 2017

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

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

Конечно, вы могли бы сначала выполнить проверку и написать один и тот же шаблонный код для каждой отдельной «команды», но я думаю, что это кошмар обслуживания, чем перехват неверного пользовательского ввода в одном месте вверху, которое обрабатывается одинаково, независимо от , (Меньше кода!) Падение производительности произойдет только в том случае, если пользователь предоставит неверные данные, что не должно случаться так часто (или у вас плохой интерфейс). Любые и все правила на стороне клиента должны быть переписаны на сервере, так или иначе, так что вы можете просто написать их один раз, сделать AJAX-вызов, и задержка <500 мс сэкономит вам массу времени на кодирование (только 1 место для размещения всей вашей логики проверки). </p>

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

1 голос
/ 04 января 2009

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

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

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

1 голос
/ 15 мая 2013

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

например

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-исключение, даже необработанное, остановит выполнение и появится во время теста.

1 голос
/ 27 января 2009

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

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){}

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

1 голос
/ 04 января 2009

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

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

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

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

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

0 голосов
/ 04 января 2009

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

0 голосов
/ 04 января 2009

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

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