Разделы портала

Онлайн-тренинги

.
Эссе о валидации данных
17.10.2009 15:15

В комментариях к заметке "Можно ли делить на 0,01 ?" я обещал написать отдельный пост, посвященный валидации данных, которые приложения получают извне -- от пользователя, от других программ, из файлов и т.д. Кроме того, не так давно эта тема вновь ненадолго возникла в обсуждении заметки "Проверка экранных форм". Так что, видимо, пришло время обсудить этот вопрос в деталях.

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

Зачем нужна валидация данных?

Казалось бы, "невалидные" данные, не удовлетворяющие определённым ограничениям, могут вызвать сбой в работе программы. Но что это означает? Предположим, в каком-то месте программы возникает исключение при попытке преобразовать строку в число, если строка имеет некорректный формат. Разумеется, если исключение не будет нигде перехвачено, это может привести к аварийному завершению программы. Но это маловероятный сценарий развития событий. Скорее всего в каком-то месте сработает перехватчик, который либо выдаст пользователю какое-то сообщение об ошибке в программе, либо сделает запись в журнал ошибок, после чего программа постарается восстановиться от сбоя и продолжить работу. То есть даже если валидацию не выполнять, вполне вероятно, что ничего страшного не случится.

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

  1. Невозможность восстановиться после сбоя. Не всегда программа способна "вернуть всё назад". Возможно, в процессе работы программа выполнила какие-то необратимые действия - удалила файл, отправила данные по сети, напечатала что-то на принтер, запустила резец станка и он частично произвёл обработку заготовки детали. Но даже если восстановление в принципе возможно, алгоритм восстановления может тоже содержать ошибки, и это иногда приводит к совсем печальным последствиям.
  2. Дополнительная нагрузка на систему. Восстановление после сбоя -- это лишняя работа. Вся работа, которая была выполнена до момента сбоя -- тоже лишняя. А это означает дополнительную нагрузку на систему, которой можно избежать, если заранее проверить данные. С другой стороны, валидация - это тоже дополнительная нагрузка, причём восстановление приходится делать лишь изредка, а проверку надо выполнять каждый раз, так что ещё неизвестно, что выгоднее.
  3. Инъекции не вызывают сбоев. Один из основных способов эксплуатации уязвимостей в программах заключается в том, чтобы "обмануть" валидаторы, то есть передать данные, которые валидатор признаёт корректными, но при этом они интерпретируются непредусмотренным образом, так что злоумышленник может получить несанкционированный доступ к данным или некоторым возможностям программы, либо способен разрушить данные или программу. Если валидации нет вообще, задача злоумышленника максимально упрощается.
  4. Сложность идентификации причины проблемы. Если исключение вылетело откуда-то из глубины программы, определить причины его возникновения не так-то просто. И даже если это возможно, может оказаться нелегко объяснить пользователю, что сбой вызван данными, которые он ввёл некоторое время назад в каком-то совершенно другом месте программы. А если проверка выполнена немедленно после ввода данных, никаких сложностей с идентификацией источника проблемы не возникает.

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

Где и когда выполнять валидацию данных?

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

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

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

Увы, здравый смысл иногда вынужден отступить перед натиском действительности. «Фейс-контроль» данных на входе иногда не просто нецелесообразен, но вообще невозможен. Ниже приведены некоторые причины этого.

  1. Для валидации требуется доступ к недоступной части состояния системы. Это особенно характерно для проверки данных, вводимых человеком через графический интерфейс пользователя. Современные приложения часто построены с использованием многоуровневой архитектуры, которая предполагает, что реализация пользовательского интерфейса выделена в презентационный слой, а для проверки требуется доступ к другим слоям, вплоть до слоя базы данных.

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

Как выполнять валидацию данных?

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

  1. Посимвольная проверка. Как правило такие проверки выполняются в пользовательском интерфейсе, по мере ввода данных. Но не только. Например, лексический анализатор компилятора тоже выявляет недопустимые символы непосредственно в процессе чтения компилируемого файла. Поэтому такие проверки можно условно назвать «лексическими».
  2. Проверка отдельных значений. Для пользовательского интерфейса это проверка значения в отдельном поле, причём выполняться она может как по мере ввода (проверяется то неполное значение, которое введено к настоящему моменту), так и после завершения ввода, когда поле теряет фокус. Для программного интерфейса (API) это проверка одного из параметров, переданных в вызываемую процедуру. Для данных, получаемых из файла, это проверка какого-то прочитанного фрагмента файла. Такие проверки, опять-таки по аналогии с компиляторной терминологией, можно назвать «синтаксическими».
  3. Совокупность входных значений. Можно предположить, что в программу сначала передаются какие-то данные, после чего подаётся некоторый сигнал, который инициирует их обработку. Например, пользователь ввёл данные в форму или в несколько форм (в так называемом «визарде») и наконец нажал кнопку «OK». В этот момент можно выполнить так называемые «семантические» проверки, нацеленные на валидацию не только отдельных значений, но и взаимосвязей между ними, взаимных ограничений.

    Да, вполне возможна ситуация, когда каждое отдельное значение «синтаксически» корректно, но вместе они образуют несогласованный набор. Для программного интерфейса эта разновидность валидации предполагает проверку набора входных параметров вызываемой процедуры, для случая получения данных из файла это проверка всех прочитанных данных.
  4. Проверка состояния системы после обработки данных. Наконец, есть последний способ, к которому можно прибегнуть, если валидацию непосредственно входных данных выполнить не удаётся -- можно попытаться их обработать, но оставить возможность вернуть всё к исходному состоянию. Такой механизм часто называется транзакционным.

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

Какой способ валидации следует применять на практике в том или ином случае? Чаще всего одним способом ограничиться не удаётся, да и не нужно. Валидацию данных можно и нужно выполнять в несколько этапов, усложняя проверки.

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

После того, как ввод завершён, можно проверить всё значение целиком. Для введённого числа могут быть какие-то ограничения, например, оно не должно превышать определённого максимального допустимого значения. Если наше числовое поле представляет собой возраст, оно должно находиться в пределах от 0 до, скажем, 120.

Когда заполнены все поля, можно проверить, согласованы ли введённые значения друг с другом. Например, если в форме кроме поля для указания возраста есть поле для ввода номера паспорта, приложение может проверить, что при заполнении номера паспорта возраст должен быть не менее 14 лет.

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

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

Тестирование валидаторов

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

Начнём с посимвольной проверки. Графический редактор Paint, диалог изменения размеров рисунка, ширина рисунка. В это поле допускается вводить только цифры, при попытке ввести другие символы выдаётся сообщение об ошибке:

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

Впрочем, это не приводит к негативным последствиям, потому что на следующем уровне стоит ещё одна проверка, которая срабатывает при нажатии кнопки OK:

Есть и другие ограничения для этого поля, которые тоже проверяются после нажатия кнопки OK:

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

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

Ну и, наконец, в заметке "Почему не хватает памяти, чтобы уменьшить размеры рисунка?" описана ошибка, связанная с тем, что в этом графическом редакторе отсутствует корректная обработка сбоев и откат транзакции при слишком сильном увеличении размера рисунка.

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

Заключение

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

Обсудить в форуме