Pull to refresh

Comments 66

люблю, когда все просто и доступно объясняют. минимум заумных слов, куча примеров и результат в действии :)
А мне очень нравится как это сделано в django. Сейчас пытаюсь выдрать forms оттуда.
А как это реализовано? Поделитесь.
Строго говоря, модуль django.forms не только проверяет данные, он может ещё их вводом-выводом заниматься. Например, рендерить исходную форму (обратите внимание на widget=forms.Textarea), а также указывать на ошибки ввода, предлагая их исправить (с показом введённых ранее данных).

Вот простой и довольно реальный пример на Django:

from django import forms
import re

class ContactForm(forms.Form):
    "Пусть это некоторая форма обратной связи - для выяснения состояния заказа"

    name = forms.CharField(label=u"Ваше имя", max_length=100)
    # Поле может быть необязательным для заполнения (required=False)
    company = forms.CharField(label="Название вашей организации", required=False)
    text = forms.CharField(label=u"Текст вопроса", widget=forms.Textarea)
    email = forms.EmailField(label=u"E-mail для ответа")
    order = forms.CharField(label=u"Код заказа")
    
    def is_valid_order(self, order):
        # здесь проверяется код заказа и выдаётся True или False
    
    def clean_order(self):
        """Функция, дополнительно проверяющая поле "order". Она может
        также изменить значение поля - например, отформатировав его.
        В данном случае, из кода заказа исключаются начальные и конечные пробелы,
        а сам заказ проверяется методом is_valid_order, включенным прямо в форму.
        """
        order = self.cleaned_data['order']
        order = order.strip() # Немного форматируем
        if self.is_valid_order(order):
            raise forms.ValidationError(u"Вы ошиблись в коде заказа.")
        return order


Самые простые случаи (обязательное/необязательное поле, число символов, максимум и минимум, соответствие регулярному выражению) Django умеет обрабатывать сам — надо только добавить простые параметры — «min_value», «regex», «required» и пр. Для более сложных ситуаций, можно написать свой метод вида «clean_XYZ», и Django будет использовать его для проверки поля «XYZ».

А для случаев, когда поля как-то связаны между собой (например, надо заполнить хотя бы одно из полей) — можно просто написать метод «clean», и он будет вызван на последней стадии, после проверки всех полей в отдельности. Там уже можно оценить все поля совокупно, и «вынести вердикт».

В итоге, используется форма так:

def process_form(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            # Используем данные формы, например, просто выдадим их в косоль:
            for key,value in form.cleaned_data.items():
                print key, ":", value
            # Кстати, в form.cleaned_data лежат уже не просто строки, а реальные данные
            # соответствующих типов - int, datetime, unicode и т.п.
    else:
        form = ContactForm()
    ...


А зачем выдирать?
Лучше просто его используйте! Ведь джанго это не только формы, но еще куча полезных и удобных вещей.
Автор, исправляй ошибки. Это не скомпилируется, так как ожидается список, а не массив.

public List GetResultCodes {
get { return resultCodes.ToArray(); }
}

Также стоит переписать

public void AddError(ResultCode code) {
isSucceedResult = false;
resultCodes.Add(code);
}

как

public void AddError(ResultCode code) {
if (!resultCodes.Contains(code))
resultCodes.Add(code);
}

чтобы не добавлять одну и ту же ошибку дважды

А также перепишите свойство

public bool IsSucceed {
get { return isSucceedResult; }
}

как

public bool IsSucceed {
get { return resultCodes.Count == 0; }
}

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

public ValidationResult Validate(string userName, string userPassword)

имя метода должно быть ValidateUser.
> люди часто изобретают свои собственные велосипеды для реализации механизма валидации.
О да, Вы правы.
В ASP.NET (речь ведь о нём, раз серверная валидация?) есть встроенные валидаторы. И на клиенте работают, и на сервере. И есть CustomValidator, в котором можно реализовать что угодно. Велосипед, говорите?..
Это можно применять для winforms, wcf, asp.net.
Решение вполне имеет право на жизнь. И насколько я понял, синтаксис C# был взят исключительно для иллюстрации механизма и никакой речо про ASP.NET небыло…
> пришёл к выводу, что люди часто изобретают свои собственные велосипеды

поэтому я решил предложить свой? :)
Велосипеды тоже надо уметь изобретать :)
Из статьи я не понял, в чём собственно бонус от использования Composite. На мой взгляд, в приведённом примере будет проще и понятнее, если просто вызвать валидаторы по очереди, а не засовывать их в список (add) и делать Validate. Вы можете привести пример, когда предлагаемое Вами «заворачивание» действительно нужно/удобно?

Вообще, почему именно Composite? Вы хотите сделать «древовидный валидатор»? А нужно ли это?

И ещё я не понял, как подразумевается обрабатывать ошибки из GetResultCodes? Ведь теряется соответствие между ошибкой и валидатором. Неужто это будет выглядеть вроде if (result[1])...? А что будет в «древовидном» случае?

Поправьте, пожалуйста, если я что-то не так понял.
При использовании фабрики валитаров мы получаем готовый объект, состоящий из необходимых в нашем случае валидаторов. С точки зрения разработчика, для валидации данных нужно будет вызвать всего один метод Validate. А вызывать валидаторы по очереди не всегда удобно. А если их 5, 10 20? Проще 1 раз написать фабричный метод, чем постоянно писать ненужные строки кода.

Например, нужно провалидировать анкету перед сохранением в базу, которую заполнил пользователь. У вас есть фабричный метод: GetFormValidator(параметры). Дёргаете этот метод и получаете результат валидации. Всё. Я считаю, это очень удобно.

Что касается обработки ошибок, то обычно этот процесс заключается в том, чтобы уведомить пользователя о проблемах ввода. Например, слишком длинное имя пользователя, неправильный e-mail. Провалидировав данные, вы получаете плоский список всех ошибок, которые небольшим вспомогательным классом можно поставить в соответствие текстовые сообщения для уведомления.
При грамотном использовании MVC, написать набор проверок через фабричные методы прийдется так же один раз — в контроллере, отвечающем за сохранение анкеты. Ведь если у вас анкета может сохраняться в десяти местах — неужели Вы считаете, что компоновщик избавит от ошибок?
БОлее того — компоновщик не для этого придуман…
Немного не понял вашего комментария.

«При грамотном использовании MVC, написать набор проверок через фабричные методы прийдется так же один раз — в контроллере, отвечающем за сохранение анкеты» — нужный фабричный метод будет написан 1 раз.

«Ведь если у вас анкета может сохраняться в десяти местах — неужели Вы считаете, что компоновщик избавит от ошибок?» — от ошибок валидации избавляют валидаторы, на которые написаны хорошие тесты.
Я же не говорил о том, чтобы «постоянно писать ненужные строки кода». Я говорил о конкретном примере, который приведён в статье. Конкретно в этом случае, я бы не стал делать так, как показано. Видимо, не очень удачный пример, про фабричные методы лучше бы получилось :)

«Небольшой вспомогательный класс» меня пугает, как представлю себе его реализацию, учитывая уже упомянутое мной отсутствие соответствия между ошибкой и валидатором. Например, в валидатор добавили ещё один валидатор, теперь нужно идти, образно говоря, править индексы в том вспомогательном классе?

Ну и в конце концов я так и не понял, зачем всё же нужен «древовидный» Composite.
По поводу примера в статье — опять же, самое простое, что пришло в голову. Для данного случая фабрика не нужна. Если имеем сложную систему, то уже надо об этом хорошо подумать.
Что касается добавления валидаторов:
допустим, у вас в анкете появилось новое поле, которое нужно проверять. Вы создаёте валидатор, добавляете новую ошибку в класс ResultCode и добавляете его в коллекцию композитного валидатора анкеты. Всё. Никаких индексов не нужно больше править.
Согласен с предыдущим оратором.
Основное предназночение Composite — создание древовидных структур с возможностью единообразного перехода по всему дереву. Автор использует этот подход, только для того, чтобы представить flat structure, и никакого намека на то, что когда-нибудь валидаторы захотят стать многоуровневыми не имеется.
Как пофиксить: используйте Chain of Responsibility для организации структуры валидаторов. Вы все так же будете использовать Factory для их создания\инициализации, будет один метод validate() для дерганья, но структура объектов будет более грамотная и использоваться по назначению, не вводя в заблуждение опытных разработчиков, которые все же знают, что такое композит.
интересно, но слова «дешего» нет в русском языке, поправьте пожалуйста
UFO just landed and posted this here
Мне казалось, что в любом нормальном фреймворке эта проблема уже решена. Например, в Django при создании форм можно сразу задать ограничения на поля:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

То есть банальной валидацией: типо пустое поле, слишком длинное/короткое и тп. разработчик даже не должен заморачиваться.
Если нужно, что-то чуть сложнее, то добавляйте свой метод. Например, вот так можно проверить, что поле subject содержит подстроку «msg#»:

    def clean_subject(self):
        data = self.cleaned_data['subject ']
        if 'msg#' not in data:
             raise ValidationError, 'Поле subject  должно содержать номер сообщения' 

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

Зачем все это «велосипедить» руками? Не может быть, чтобы в явоввских фреймворках не было аналогичного механизма.
Предложенный мной метод может применятся не только для валидации данных в клиент-серверных приложениях и не толко для валидации пользовательских данных. Подобный подход, например, можно использовать для проверки внутренних объектов на соответствие правилам бизнес логики.
Я ориентировался на заголовок статьи: "Серверная валидация пользовательских данных" :)
А так само собой, что исключения можно применять где угодно :-)
Но не все же пользуются фреймворками.
Да. Просто валидация пользовательских данных — один из столпов нормальной разработки, поэтому для этого уже порядком наработано хороших решений. Если их не использовать, то придется делать руками. Потеря времени.
Пользуются почти все, только часто сами об этом не подозревают :) Даже набор стандартных PHP-шных функций можно назвать фреймворком «из коробки». Сам PHP — это набор готовых инструментов для разработки. С остальными языками ситуация ещё более ясная. Если делается не простой CGI-скрипт, то сразу встаёт потребность в наборе из кучи специализированных функций — эскейпить HTML, работать с HTTP-заголовками, загружать файлы… Всё это уже можно назвать фреймворком. Включая то, что вы дописали сами.
1) Приведенный способ валидации мог быть с успехом воплощен использую коллекцию (паттерн Comopsite представляет собой иерархию, что вряд-ли применимо в данном контексте)

2) Ошибка валидации бизнесс данных не есть ошибка приложения, НО исключение тоже не обязательно является ошибкой приложения. Более детальное обьяснение приводит Дж. Рихтер CLR via C# (Глава «Ислючения» «Что-же такое исключение») совертую прочитать, почень познавательно.
Кроме того. Зачастую забытый catch и в итоге падение приложение, НАМНОГО лучше забытой проверки результата возвращенного ф-цией валидации (в вашем случае проверка содержимого коллекции).
Представтье к чему может привести такая забывчивость в обоих случаях. Например при валидаци данных для валютных транзакций.

p.s. Понятие красивости, и главное, правильности в области программирования — очень тонкая материя, я бы не торопился давать общих советов, что правильно а что нет :)
используются исключения (exceptions) на этапе бизнес валидации. Важно помнить, что ошибки валидации данных != ошибкам работы приложения;

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

По-существу: вы хотите сказать, что приведенный ниже велосипед получился красивее, нежели оригинал? Сомневаюсь, начали с Composite, в итоге просто грубо говоря вынесли интерфейс (Validate) и сделали обертку над коллекций валидаторов. В итоге Composite не получился, а получилась адска обертка сочетающая в себе и поведенческую логику и элементы интерфейса :)
Из приведённого вами материала, я бы сделал другую вещь. Сама по себе валидация несет несколько целей:
— Проверить можно ли продолжать, к примеру регестрировать пользователя в системе.
— Если возникли ошибки, вывести какое-то уведомление.

Я здесь вижу совершенно другой подход: обернуть логику проверки правильно веденных данных в валидатор. У валидатора сделать ивент, и на этот ивент подписывать заинтересованные классы. Например какой-нибудь Класс WrongUsernameReaction, который будет выводить сообщение об ошибке, и т. д. Т. е. максимум обсервер, никаких Composite.
Интересный вариант, спасибо.
>if (/*проверяем имя пользователя*/)
> throw new InvalidUsernameException();

Может лучше вызывать исключение внутри метода проверки. Ну, а дальше пошла какая-то ерунда, уродская замена стандартному механизму исключений, впрочем если нравятся извращения…
А в моём примере как получается?
Идёт проверка, если она непроходит, кидается exception. В примерах, которые погуглил, очень часто так делается.
Да, исключение кидается, исключение перехватывается. Майкрософт создала определённые правила по которым всё это записывается, это как бы общая рекомендация по написанию кода и данный образец ей не следует.

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

>— базовый класс Validator можно заменить на интерфейс, кому как нравится;

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

Может быть вы не правильно поняли ту идею, что я хотел донести, либо я её некорректно донёс. Базовый класс либо интерфейс нужен для того, чтобы у всех дочерних объектов была одинаковая сигнатура для метода — запуска валидации. Что касается наследования, то давайте посмотрим на .net framework, где все производные классы и интерфейсы наследуются от Object.

Что значит «получается такое увеличение на каждый валидатор», поясните.

«О чём и речь, это не просто решение нравится не нравится, это совершенно разные подходы к программированию. На интерфейсах валидатор сольётся с классом, который он проверяет, используя же наследование придётся включить объект валидатора в класс, и именно он будет торчать в списке валидаторов, а это уже совсем другая история.»
Признаться, я не совсем понимаю данную фразу, поясните пожалуйста.
>Что касается наследования, то давайте посмотрим на .net framework, где все производные классы и интерфейсы наследуются от Object.

Вот смотрю .NET Framework x.x и не вижу ни одного интерфейса, который бы прямо или косвенно наследовался от класса Object. Интерфейсы могут наследоваться от интерфейсов, но в итоге мы приходим к базовому интерфейсу, который не имеет базового класса Object.

>Базовый класс либо интерфейс нужен для того, чтобы у всех дочерних объектов была одинаковая сигнатура для метода — запуска валидации.

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

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

Таким образом если встаёт вопрос использовать ли включение (containment) (в UML — агрегирование) или наследование (inheritance) (в UML — генерализация), наследование используется лишь когда исходная сущность является развитием базовой сущности, а включение, когда она содержит в себе некую сущность.

Здесь у нас полиморфизм, но выводы те же самые, а именно ни в коем случае не использовать в данном случае полиморфизм через наследование, так как наследование увеличивает сложность связей.

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

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

Спасибо.
Мда… как у вас точка-нетников все сложно… Пользователи php-, ruby- и даже python- фреймворков уже давно решили эту проблему, а вы там то фабрики, то исключения придумываете…
Я думаю врядли решения перечисленных вами фреймворков позволяют полностью забыть о кастомной реализации валидации.
Есть более красивые решения, осуществляемые средствами Аспектно-Ориентированного программирования ( ru.wikipedia.org/wiki/AOP ).

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

Преимущества:
— абсолютная прозрачность кода, программист видит только то что он хочет: бизнес-логика, валидация, логгирование, кэширование;
— огромная гибкость;
— возможность расширять уже существующий код, вообще не трогая его исходники.
В этом подходе главное опыт. На моём первом проекте применили подобный подход к разработке, потом очень долго доводили до ума. Команда была большая и неопытная.
Ну нас вроде все довольно органично сделано. Собсна получилась отличная реализация кэширования и логгирования — стэк-трэйсы методов со значениями входных параметров помоему мечта любого программиста.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Не пройдёт на клиенте, или на сервере?
UFO just landed and posted this here
Sign up to leave a comment.

Articles