Почему метод .NET StringValidator Validate выдает исключение, когда это не удается? - PullRequest
2 голосов
/ 26 октября 2010

Как видно из документации MSDN StringValidator , метод Validate возвращает void.
Если проверка не удалась, метод Validate выдает ArgumentException.
Я подумал, что «Вы генерируете исключение только тогда, когда происходит что-то исключительное».
Конечно, валидатор, который не прошел проверку, не является исключением ..
Почему бы не вернуть bool?Что мне здесь не хватает?
Является ли это «проблемой стиля» (т. Е. Если бы он возвращал bool, он все равно был бы правильным, но просто другой стиль)?

Примечание: Метод CanValidate может иметь какое-то отношение к этому, но я до сих пор не вижу причин для такого поведения.

Ответы [ 5 ]

2 голосов
/ 26 октября 2010

см. ArgumentException Class

Можете ли вы использовать bool для замены информации, которую может содержать ArgumentExeption?И на этой странице

ArgumentException: «Исключение, которое выдается, когда один из аргументов, предоставленных методу, недопустим.

Так что, если аргумент»Объект "в StringValidator.Validate(Object) недопустим, какой должен быть лучший выбор? Вернуть целый ряд объектов или просто выбросить ArgumentException?

1 голос
/ 27 октября 2010

Вы правы, когда говорите:

Примечание: метод CanValidate может иметь какое-то отношение к нему

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

Давайте рассмотрим метод static MySettings ReadSettings(string filePath). Этот метод получит какой-то значимый объект из содержимого файла. Давайте также представим, что мы делаем все это «близко к металлу», не завися от классов файловых операций в BCL; не потому, что это хорошая идея, а потому, что она переносит все проблемы обработки исключений прямо в области того, с чем нам приходится иметь дело (в действительности мы можем объединить несколько исключений, которые мы собираемся рассмотреть вместе, разрешив файл BCL- связанные методы либо возвращают нам пригодный для использования поток, либо выдают исключение для нескольких возможностей, но мы хотим рассмотреть эти возможности здесь).

Возможные вещи, которые могут пойти не так:

  1. filePath недействительно.
  2. Файл не существует или не может быть открыт по другой причине.
  3. Файл не содержит соответствующих данных.
  4. Мы пытаемся открыть файл, к которому ОС ограничивает доступ по соображениям безопасности.
  5. Мы пытаемся открыть файл, к которому ОС не ограничивает доступ (он не подпадает под общее правило безопасности), но который будет открывать для безопасности (например, он принадлежит другому «пользователю») по правилам нашего приложения, но не по правилам ОС).
  6. Файловые операции частично прерываются (например, ошибка диска).
  7. Произошла неожиданная ошибка, которую мы еще не рассмотрели.
  8. Произошла неожиданная ошибка безопасности, которую мы не можем перехватить, потому что мы ее не учли (по сути, в нашем коде есть дыра в безопасности).

Примечательно, что 7 и 8 не являются случаями, для которых мы явно кодируем; по своей природе они являются вещами, для которых мы и не думали кодировать.

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

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

  1. Списать потерю (употребление исключений, как правило, плохая идея, но в редких случаях это имеет смысл, поэтому мы включим это в наш список).
  2. Сообщите пользователю, возможно, обращаясь к руководству пользователя о том, что делать дальше.
  3. Повторите попытку другим способом.
  4. Отключите некоторые функции (например, если мы пытаемся загрузить плагины).
  5. Записать исключение.
  6. Завершите работу всего приложения.
  7. Целая куча других специфических для приложения способов, которыми можно захотеть справиться с ситуацией.
  8. Комбинация вышеперечисленного.

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

СначалаПодход к планированию этого, наш набор процедур:

  1. Получить дескриптор файла на основе пути (исключение в случае сбоя).
  2. Открыть файл (исключение в случае сбоя).
  3. Считывание данных (исключение при сбое).
  4. Анализ данных (исключение при сбое).
  5. Создание объекта MySettings (исключение при сбое).
  6. Проверка MySettings объект является допустимым (исключение при сбое).
  7. Возвращает объект MySettings.

Здесь есть проблемы.Во-первых, наша попытка получить дескриптор файла зависит от неудачи, если дескриптор недействителен.Это работает, но может стоить дороже, чем сначала тестирование (в зависимости от того, сколько работы ввода / вывода было выполнено до сбоя), и, что гораздо важнее, может привести к тому, что мы будем слепо делать что-то глупое на уровне ввода / вывода, что может привести кпроблема безопасности (что за проблема? Я не знаю, и если я сначала займусь проверкой, мне даже не нужно знать).Следовательно, мы вводим шаг 0, который проверяет filePath.Мы могли бы сделать эту проверку с последующим явным выбросом исключения в нашем ReadSettings, но те же правила проверки, вероятно, существуют в другом месте (например, WriteSettings), чтобы избежать дублирования усилий и (что более важно для корректности) централизациилогика проверки, у нас может быть метод void CheckValidSettingsPath(string filePath), который выдает ArgumentException, если путь неверен.

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

У нас также может быть несколько инвариантов в классе MySettings, поэтому мы можем снять часть ответственности за проверку их корректности.Теперь у нас есть:

  1. Проверка пути (исключение при сбое).
  2. Считывание данных (исключение при сбое - BCL сделает это за нас).
  3. ПостроитьMySettings объект (исключение в случае сбоя - MySettings делает, если для нас хотя бы некоторое время).
  4. (Необязательно) проверяет MySettings помимо тех, которые присущи всем MySettings объектам (исключение, еслиошибка).
  5. Вернуть объект MySettings.

Мы уменьшили сложность.Остается вопрос, должны ли мы просто позволить этим исключениям пройти, съесть их и выбросить свои собственные, или обернуть их новыми.Как правило, я бы позволил большинству проходить через него, но в публичных методах публичных классов их оборачивают в новый (так что, с одной стороны, пользователь кода видит ReadSettings терпящий неудачу, а не личный CheckValidSettingsPath, которого она никогда не слышалаиз, но с другой стороны, детали того, что пошло не так на нижнем уровне, все еще доступны [иногда могут быть причины безопасности для сокрытия этого]);еда и выбрасывание «свежих» исключений своих собственных (нет innerException) хорошо работает только тогда, когда есть возможность полностью объяснить, что пошло не так.

Итак, этот случай сопоставим с StringValidator.Validate.Действительно, его вполне можно использовать как часть нашего CheckValidSettingsPath метода.

Давайте теперь рассмотрим случай, когда мы пишем код, который вызывает ReadSettings.Очевидно, у нас есть строка, которая содержит filePath:

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

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

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

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

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

Обратите внимание, что все еще существует вероятность того, что после того, как мы проверим, путь является действительным, ReadSettings все еще идет не так, как надо, поэтому мы объединим подход «явная проверка с логическим значением» и подход «неявная проверка во время операции», который вызывает исключение.

Именно для случаев, подобных двум последним, StringValidator.CanValidate существует, и снова его вполне можно использовать как часть метода IsValidSettingsPath.

1 голос
/ 26 октября 2010

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

0 голосов
/ 26 октября 2010

StringValidator вместе с любым типом, производным от ConfigurationValidatorBase, предназначены для использования с системой конфигурации .NET (см. Статью Джона Риста для подробного обзора ее возможностей). Простой вариант использования выглядит следующим образом:

public class MyConfigurationSection : ConfigurationSection
{
    [ConfigurationProperty("ConfigurationText", IsRequired=true)]
    [StringValidator(0, 10)]
    public string Text { get; set; }  // implementation removed for brevity
}

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

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

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

0 голосов
/ 26 октября 2010

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

Я бы сказал, что эти случаи могут быть необычными, но ...

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