Бизнес-объекты, валидация и исключения - PullRequest
37 голосов
/ 18 сентября 2008

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

Допустим, у меня есть бизнес-объект с геттерами / сеттерами для свойств объекта. Допустим, мне нужно проверить, что значение составляет от 10 до 20. Это бизнес-правило, поэтому оно принадлежит моему бизнес-объекту. Так что, мне кажется, это означает, что код проверки у меня в установщике. Теперь у меня есть пользовательский интерфейс, связанный со свойствами объекта данных. Пользователь вводит 5, поэтому правило должно завершиться сбоем, и пользователю не разрешается выходить из текстового поля. , Пользовательский интерфейс привязан к свойству, поэтому метод вызова будет вызван, правило проверено и не выполнено. Если бы я вызвал исключение из моего бизнес-объекта, чтобы сказать, что правило не выполнено, пользовательский интерфейс подхватит это. Но это, кажется, идет вразрез с предпочтительным использованием исключений. Учитывая, что это сеттер, у вас не будет «результата» для сеттера. Если я установлю другой флаг на объекте, это будет означать, что пользовательский интерфейс должен проверять этот флаг после каждого взаимодействия с пользовательским интерфейсом.

Так как же должна работать проверка?

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

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

Ответы [ 18 ]

18 голосов
/ 25 сентября 2008

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

Вот, по словам Пола, минусы к созданию исключений в установщиках (на основе примера, где свойство Name не должно быть пустым):

  • В некоторых случаях вам может понадобиться пустое имя. Например, в качестве значения по умолчанию для «Создать учетную запись» формы.
  • Если вы полагаетесь на это для проверки каких-либо данных перед сохранением, вы пропустите случаи, когда данные уже недействительны. Под этим я подразумеваю, что если вы загружаете учетную запись из базы данных с пустым именем и не меняете ее, вы никогда не узнаете, что она недействительна.
  • Если вы не используете привязку данных, вам нужно написать много кода с блоками try/catch, чтобы показать эти ошибки пользователю. Попытка показать ошибки в форме, когда пользователь заполняет ее, становится очень трудной.
  • Мне не нравится бросать исключения для неисключительных вещей. Пользователь, устанавливающий имя учетной записи на "Supercalafragilisticexpialadocious" , не исключение, это ошибка. Это, конечно, личная вещь.
  • Очень трудно получить список всех правил, которые были нарушены. Например, на некоторых веб-сайтах вы увидите сообщения проверки, такие как «Необходимо ввести имя. Адрес должен быть введен. Адрес электронной почты должен быть введен» . Чтобы отобразить это, вам понадобится много try/catch блоков.

А вот основные правила альтернативного решения:

  1. Нет ничего плохого в том, что у вас есть недопустимый бизнес-объект, если вы не пытаетесь его сохранить.
  2. Любые и все нарушенные правила должны быть извлечены из бизнес-объекта, чтобы привязка данных, а также ваш собственный код могли видеть наличие ошибок и обрабатывать их соответствующим образом.
8 голосов
/ 18 сентября 2008

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

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

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

8 голосов
/ 18 сентября 2008

Я всегда был поклонником подхода Роки Лхотки в рамках CSLA (как упомянуто Чарльзом). Как правило, независимо от того, управляется ли он сеттером или вызывает явный метод Validate, коллекция объектов BrokenRule поддерживается внутри бизнес-объекта. Пользовательский интерфейс просто должен проверить метод IsValid на объекте, который, в свою очередь, проверяет количество BrokenRules и обрабатывает его соответствующим образом. В качестве альтернативы, вы можете легко заставить метод Validate вызывать событие, которое может обработать пользовательский интерфейс (возможно, более чистый подход). Вы также можете использовать список BrokenRules для отображения сообщений об ошибках для использования либо в сводной форме, либо рядом с соответствующим полем. Хотя платформа CSLA написана на .NET, общий подход можно использовать на любом языке.

Я не думаю, что выбрасывание исключения - лучшая идея в этом случае. Я определенно придерживаюсь мысли, что «Исключения должны быть в исключительных обстоятельствах», а это не простая ошибка проверки. По моему мнению, создание события OnValidationFailed было бы более правильным выбором.

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

5 голосов
/ 18 сентября 2008

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

Этот подход используется в CSLA.Net.

4 голосов
/ 18 сентября 2008

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

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

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

3 голосов
/ 25 сентября 2008

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

Я согласен с последним ответом shabbyrobe о том, что бизнес-объекты должны создаваться так, чтобы их можно было использовать и правильно работать в нескольких средах, а не только в среде winforms, например, бизнес-объект можно использовать в веб-службе типа SOA, командной строке interface, asp.net и т. д. Объект должен вести себя правильно и защищать себя от недопустимых данных во всех этих случаях.

Аспект, который часто упускается из виду, это также то, что происходит при управлении взаимодействиями между объектами в отношениях 1-1, 1-n или nn, если они также принимают добавление недопустимых соавторов и просто поддерживают недопустимый флаг состояния, который должен быть проверен или должен активно отказаться от добавления недействительных коллабораций. Я должен признать, что на меня сильное влияние оказывает подход упрощенного моделирования объектов (SOM), предложенный Jill Nicola et al. Но что еще логично.

Следующее, как работать с окнами форм. Я смотрю на создание оболочки UI для бизнес-объектов для этих сценариев.

3 голосов
/ 18 сентября 2008

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

Рассмотрим многоуровневое приложение и требования / средства проверки каждого уровня. Средний слой, Object, является тем, который, кажется, должен обсуждаться здесь.

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

  • Object

  • ASP.NET / Windows Forms
    защищает состояние формы (не объекта) с помощью подпрограмм валидатора и / или элементов управления без с использованием исключений (winforms не поставляется с валидаторами, но в msdn есть отличная серия, описывающая, как их реализовать )

Допустим, у вас есть таблица со списком гостиничных номеров, и в каждом ряду есть столбец для количества кроватей, называемых «кроватями». Наиболее разумным типом данных для этого столбца является малое целое число без знака *. У вас также есть простой объект ole со свойством Int16 * под названием «Кровати». Проблема в том, что вы можете вставить -4555 в Int16, но когда вы собираетесь сохранить данные в базе данных, вы получите исключение. Что хорошо - моя база данных не должна позволять утверждать, что в гостиничном номере меньше 0 кроватей, потому что в гостиничном номере не может быть меньше, чем 0 кроватей.

* Если ваша база данных может представлять ее, но давайте предположим, что она может
* Я знаю, что вы можете просто использовать ushort в C #, но для целей этого примера давайте предположим, что вы не можете

Существует некоторая путаница относительно того, должны ли объекты представлять вашу бизнес-единицу или они должны представлять состояние вашей формы. Конечно, в ASP.NET и Windows Forms форма отлично способна обрабатывать и проверять свое собственное состояние. Если у вас есть текстовое поле в форме ASP.NET, которое будет использоваться для заполнения того же поля Int16, вы, вероятно, разместили на своей странице элемент управления RangeValidator, который проверяет ввод, прежде чем он будет присвоен вашему объекту. Он не позволяет вам ввести значение меньше нуля и, вероятно, не дает вам ввести значение, превышающее, скажем, 30, что, как мы надеемся, будет достаточным для обслуживания худшего хоста, зараженного блохами, который вы можете себе представить. При обратной передаче вы, вероятно, будете проверять свойство IsValid на странице перед построением вашего объекта, тем самым предотвращая когда-либо представление вашего объекта менее чем в ноль и предотвращая вызов вашего установщика со значением, которое он не должен ' t hold.

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

Зачем вам вообще быть в этом сценарии? Это должно быть довольно исключительное стечение обстоятельств! Поэтому вашему установщику необходимо выдать исключение при получении неверных данных. Это никогда не должно быть брошено, но это может быть. Вы можете написать форму Windows для управления объектом, чтобы заменить форму ASP.NET, и забыть проверить диапазон перед заполнением объекта. Вы можете использовать объект в запланированной задаче, в которой вообще отсутствует взаимодействие с пользователем, и которая сохраняет другую, но связанную область базы данных, а не таблицу, в которую отображается объект. В последнем сценарии ваш объект может войти в состояние, в котором он недопустим, но вы не будете знать, пока недопустимое значение не повлияет на результаты других операций. Если вы проверяете их и генерируете исключения, то есть.

3 голосов
/ 18 сентября 2008

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

Каждый открытый метод должен проверять свои входные данные и выбрасывать «ArgumentException», если они неверны. (И частные методы должны проверять свои входные данные с помощью «Debug.Assert ()», чтобы упростить разработку, но это уже другая история.) Это правило о проверке входных данных для открытых методов (и свойств, конечно) верно для каждого уровня приложения. ,

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

Хотя приведенные выше правила почти никогда не следует нарушать, иногда проверка бизнес-объектов может быть очень сложной, и эту сложность не следует навязывать пользовательскому интерфейсу. В этом случае для интерфейса BO полезно предусмотреть некоторую свободу в том, что он принимает, а затем предоставить явный предикат Validate (out string []), чтобы проверить свойства и дать отзыв о том, что необходимо изменить. Но обратите внимание, что в этом случае все еще существуют четко определенные требования к интерфейсу и не нужно создавать никаких исключений (при условии, что вызывающий код следует правилам).

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

3 голосов
/ 18 сентября 2008

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

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

Microsoft выдает исключения из бизнес-объектов при сбое проверки. По крайней мере, так работает блок приложения проверки библиотеки предприятия.

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
public class Customer
{
  [StringLengthValidator(0, 20)]
  public string CustomerName;

  public Customer(string customerName)
  {
    this.CustomerName = customerName;
  }
}
3 голосов
/ 18 сентября 2008

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

Один из подходов, которые я использовал, заключался в применении настраиваемых атрибутов к свойствам бизнес-объектов, в которых описаны правила проверки. e.g.:

[MinValue(10), MaxValue(20)]
public int Value { get; set; }

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

...