Как я должен обрабатывать неправильный ввод пользователя?

12

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

Я склонен придерживаться очень оборонительного стиля программирования. Мой типичный блок или метод выглядит так:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

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

Чтобы выразить это более абстрактно, мой подход

if all input is OK --> compute result
else               --> do not compute result, notify problem

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

Мой ответ на это обычно: но что, если ошибка не обнаружена во время тестирования и появляется, когда продукт уже используется клиентом? Каков предпочтительный способ проявления ошибки? Должна ли это быть программа, которая не выполняет определенного действия, но все еще может продолжать работать, или программа, которая аварийно завершает работу и требует перезапуска?

Подведение итогов

Какой из двух подходов к обработке неправильного ввода вы бы посоветовали?

Inconsistent input --> no action + notification

или

Inconsistent input --> undefined behaviour or crash

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

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

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

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

Джорджио
источник
Всегда используйте защитный код. В конце концов, по соображениям производительности, вы можете включить переключатель, чтобы отключить некоторые тесты в режиме выпуска.
Deadalnix
Сегодня я исправил ошибку, связанную с отсутствующей проверкой NULL-указателя. Некоторый объект был создан во время выхода из приложения, и конструктор использовал getter для доступа к другому объекту, которого больше не было. Объект не должен был быть создан в этой точке. Он был создан из-за другой ошибки: некоторый таймер не был остановлен во время выхода из системы -> был отправлен сигнал -> получатель попытался создать объект -> запросил конструктор и использовал другой объект -> указатель NULL -> сбой ). Мне бы очень не хотелось, чтобы в такой неприятной ситуации вылетало мое приложение.
Джорджио
1
Правило ремонта: когда вы должны потерпеть неудачу, потерпите неудачу шумно и как можно скорее.
Deadalnix
«Правило восстановления: когда вы должны потерпеть неудачу, шумите и как можно скорее.»: Я полагаю, что все эти BSOD для Windows являются применением этого правила. :-)
Джорджио

Ответы:

8

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

Выше было высказано хорошее замечание: что делать, если входные данные неверны, но программа не падает? Затем вы получаете мусор в базе данных и ошибки по линии.

Когда спрашивают номер (например, цену в долларах или количество единиц), я люблю вводить «1e9» и посмотреть, что делает код. Это может случится.

Четыре десятилетия назад, получив степень бакалавра в области компьютерных наук от UCBerkeley, нам сказали, что хорошая программа - это 50% обработка ошибок. Будь параноиком.

Энди Кэнфилд
источник
Да, ИМХО, это одна из немногих ситуаций, в которых параноик - это особенность, а не проблема.
Джорджио
«Что делать, если входные данные недействительны, но программа не дает сбоя? Тогда вы получаете мусор в базе данных и ошибки на линии». Вместо сбоя программа может отказаться от выполнения операции и вернуть неопределенный результат. Undefined будет распространяться через вычисления, и никакой мусор не будет произведен. Но программа не должна падать, чтобы достигнуть этого.
Джорджио
Да, но - моя точка зрения заключается в том, что программа должна обнаружить неправильный ввод и справиться с ним. Если вход не проверен, он будет работать в системе, а неприятные вещи появятся позже. Даже сбой лучше, чем это!
Энди Кэнфилд
Я полностью согласен с вами: мой типичный метод или функция начинается с последовательности проверок, чтобы убедиться, что входные данные верны.
Джорджио
Сегодня у меня снова было подтверждение, что стратегия «все проверяй, ничего не доверяй» часто является хорошей идеей. У моего коллеги было исключение NULL-указатель из-за пропущенной проверки. Оказалось, что в этом контексте было правильно иметь указатель NULL, потому что некоторые данные не были загружены, и было правильно проверять указатель и просто ничего не делать, когда он равен NULL. :-)
Джорджио
7

У вас уже есть правильная идея

Какой из двух подходов к обработке неправильного ввода вы бы посоветовали?

Непоследовательный ввод -> никаких действий + уведомление

или лучше

Непоследовательный ввод -> правильно обработанное действие

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

Сдержанность догматизма с прагматизмом.

Стив Макконнелл сказал это лучше всего

Стив Макконнелл в значительной степени написал книгу ( Code Complete ) по защитному программированию, и это был один из методов, которые он посоветовал вам всегда проверять на правильность своих данных.

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

Джастин Шилд
источник
2
Вместо общедоступных, я бы предложил, все не приватные методы для защиты языков, которые защищают, совместно используют или не имеют концепции ограничения доступа (все открыто, неявно).
JustinC
3

Здесь нет «правильного» ответа, особенно без указания языка, типа кода и типа продукта, в который может войти код. Рассмотреть возможность:

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

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

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

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

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

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

Калеб
источник
+1 - Хороший ответ. Моя главная проблема заключается в том, что неправильный ввод может вызвать проблему, которая возникает в производственной системе (когда уже слишком поздно что-то делать с этим). Конечно, я думаю, что вы абсолютно правы, говоря, что это зависит от ущерба, который такая проблема может нанести пользователю.
Джорджио
Язык играет большую роль. В PHP половина кода вашего метода заканчивает тем, что проверяет тип переменной и предпринимает соответствующие действия. В Java, если метод принимает int, вы не можете передать ему что-либо еще, поэтому ваш метод в итоге становится более понятным.
глава
1

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

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

На более заметке, связанной с конечным пользователем, лучше вернуть что-то описательное, но простое, чтобы каждый мог понять это. Если ваш клиент звонит и говорит: «Сбой программы, исправьте ее», у вас есть много работы, чтобы отследить, что пошло не так и почему, и надеясь, что вы сможете воспроизвести проблему. Использование правильной обработки исключений может не только предотвратить сбой, но и предоставить ценную информацию. Вызов от клиента, говорящий: «Программа выдает мне ошибку. Она говорит:« XYZ не является допустимым вводом для метода M, потому что Z слишком большой », или что-то в этом роде, даже если они понятия не имеют, что это значит, вы точно знать, где искать. Кроме того, в зависимости от методов ведения бизнеса вашей / вашей компании, возможно, вы не решаете эти проблемы, поэтому лучше оставить им хорошую карту.

Итак, короткая версия моего ответа такова: ваш первый вариант - лучший.

Inconsistent input -> no action + notify caller
yoozer8
источник
1

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

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

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

Ричард
источник
0

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

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

Отредактируйте: просто чтобы добавить, что если вы посмотрите, например, на Oracle JDK, вы увидите, что они никогда не проверяют наличие «null» и позволяют сбою кода. Так как это все равно будет выдавать исключение NullPointerException, зачем беспокоиться о проверке на наличие нуля и создании явного исключения. Я думаю, в этом есть какой-то смысл.

Jalayn
источник
В Java вы получаете исключение нулевого указателя. В C ++ нулевой указатель вылетает из приложения. Возможно, есть другие примеры: деление на ноль, индекс вне диапазона и так далее.
Джорджио