Как стать автором
Обновить

Комментарии 37

Генераторы все-таки удобнее (хотя по сути паттерн-то тот же):

public IEnumerable<ValidationError> Validate() {
    if (Date.IsBefore(DateTime.Now))
        yield return new ValidationError(nameof(Date), "date cannot be before today");
    if (NumberOfSeats < 1)
        yield return new ValidationError(nameod(NumberOfSeats), "number of seats must be positive");
    //raise, rinse, repeat
  }
А ничего что IEnumerable обрабатывается лениво?
Если код вызывающий Validate не будет делать force (явно проходить по коллекции ошибок) то метод не будет выполняться даже если по логике он проходит всю валидацию?
Единственный способ не вызвать проход по коллекции — это проигнорировать возвращенный результат (аналогично невызову notification.hasErrors() из поста).

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

А вообще получается довольно гибкая вещь. Валидаторы можно склеивать тоже лениво, например так:
Validate1().Union(Validate2())
А затем, если мы вдруг решим аггрегировать не все ошибки, а только первую, или первые n то код валидаторов останется тот же. Достаточно будет добавить к результату .Take(1)
Так и есть. Просто в этом случае у нас возникает очень сложно отслеживаемая ошибка, особенно когда в validate есть побочные эффекты.

В Validate не должно быть побочных эффектов (что семантически правильно).
Согласен, что для чистых validate бессмысленно его вызывать, если не использовать результат.
Но возможны validate и с побочными эффектами. Например когда нам нужно проверить не существует ли пользователя с таким логином и нам нужны запросы к БД.
Тогда как мне кажется ленивость может сыграть с нами злую шутку.
Например когда нам нужно проверить не существует ли пользователя с таким логином и нам нужны запросы к БД. Тогда как мне кажется ленивость может сыграть с нами злую шутку.

И какую же? Мы не сделаем ненужного запроса к БД, если нас интересует, валидна ли форма регистрации, но не интересует список ошибок (хотя это и глупо достаточно)?
В статье четко написано, почему выбран класс, а не список или IEnumerable
Хотя простая идиома списка обеспечивает легковесную реализацию паттерна, я предпочитаю этим не ограничиваться и пишу простой класс.
Применяя реальный класс, я четче выражаю мое намерение — читателю кода не приходится ментально соотносить идиому и ее полное значение.

И это правильно, инкапсуляция рулит
В отличие от возвращаемого IEnumerable, простой список не дает такого радикального выигрыша в читаемости валидационного метода.

А еще в .net это просто уже существующая типовая реализация.
А разве нельзя в Java сделать исключение, которое будет коллекцией других исключений? Добавляем в коллекцию, как заполнили — бросаем ее?
Можно, но обработать в catch() вы сможете только одно исключение за раз — то, которое выбросите (то есть, контейнер). По сути, это то же самое, что описывается в статье.
Ну логично. Я получил контейнер, достал из него нужные мне исключения, обработал, убрал из контейнера и кинул его дальше.
Лет 5 назад использовал такой подход в веб-приложениях — вполне себя оправдывает, все ошибки показываются пользователю скопом, что в целом очень удобно.
Валидатор со списком ошибок крайне полезен в B2B — мало что бесит юзера больше, чем нажатие на кнопку «сохранить» после каждой исправленной ошибки. Особенно если форма на экран не влазит.
Уважаемые переводчики издательского дома «Питер»!
«date == null» это не «нулевая дата», это пустая дата. И уж тем более не «проверка на нуль».
Сколько кода из ничего. Составляем список лямбд. Каждая принимает значение и отдает либо ноль, либо положительный код ошибки. Прогоняем значение по лямбдам. Если хоть где-то результат не ноль, валидация не прошла. По кодам получам текст ошибок.
Зачем эти классы?
… чтобы не заниматься последующим сопоставлением кодов ошибок с текстами, например? Или не пытаться придумать, как дополнительную информацию (например, имя ошибочного свойства) в коды запихнуть? Или не пытаться каким-то чудом избежать дублирования кода внутри лямбд при одинаковых проверках?
Догадки у вас есть, а ответа, похоже, нет.
Эти «догадки» — и есть ответ. Заодно можете посмотреть на первый тред, там описан (идиоматичный для .net) вариант без дополнительного класса.
В статье наглядно описан процесс рефакторинга кода, начиная с описания проблемы и постановки задачи по существу. Подробно объясняются мотивации для каждого действия, локальные засады и решения. Класс!
как то громоздко
либо передавать нотификкатор параметром, либо создавать внутри и возвращать, либо специальный метод и
Теперь могу проверить уведомление и выдать исключение, если оно содержит ошибки.


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

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

.

.
Вы это серьезно? Статическая зависимость для передачи данных между двумя функциями? А параллельное выполнение как же?
это служебная функция а не бизнес логика.
И речь идет о взаимодействии с пользователем а не о каком то серверном процессе- при чем тут паралельность. Если это веб — хранить колекцию в сессии.


это служебная функция а не бизнес логика.

На нее поэтому не распространяются принципы программирования?

И речь идет о взаимодействии с пользователем а не о каком то серверном процессе- при чем тут паралельность. Если это веб — хранить колекцию в сессии.

… а в сессии параллельных процессов никогда не бывает, конечно же.

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

Индусский код — это не принцип програмирования.

а в сессии параллельных процессов никогда не бывает, конечно же.

у нормальных людей не бывает.
если это веб — какие паралельные процессы на одного юзера в обработке реквеста с браузера?

А еще, скажем, бывает вложенная валидация.

Речь о том чтобы вывести пользователю ошибку на введенные им данные а не что бывает в индусском мозгу.



Индусский код — это не принцип програмирования.

Я, вроде бы, про индусский код не спрашивал, я конкретный вопрос задал.

если это веб — какие паралельные процессы на одного юзера в обработке реквеста с браузера?

А у вас пользовательская сессия ровно на один реквест завязана?

Речь о том чтобы вывести пользователю ошибку на введенные им данные а не что бывает в индусском мозгу.

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

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

Разговоры о «принципах» не бывают конкретными.
У вас принцип максимально все усложнить чтобы обьять необьятное — 100% всевозможных ситуаций.
Мой принцип — бритва Оккама -не надо плодить сущьности сверх необходимого. Возможно и есть проекты и архитектуры где статика не работает но в 9 случаев из 10 не вижу проблем.


А у вас пользовательская сессия ровно на один реквест завязана?

Обычно да. Так работает веб.
Даже если на странице налеплено аякса все равно идет ответ на действие пользака. Иначе все равно все ошибки налезут в кучу. Даже при оттдельных валидаторах

Как вы предлагаете это делать в вашем подходе?

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

Впрочем я так понимаю вам лишь бы спорить. Считайте что ваше слово сталось последним и не будем забивать коменты бесполезной дискуссией.


Разговоры о «принципах» не бывают конкретными.

Почему же? Сами по себе принципы часто (не всегда) весьма конкретны. В частности, принцип использовать как можно меньше глобального состояния весьма конкретен.

Обычно да. Так работает веб.

Какой именно «веб» так работает? Если что, «веб» по задумке своей вообще не имеет состояния, в том числе и столь любимых вами сессий.

Есть бизнес функция где проверяется адрес, она же и пишет ошибку понимая контекст

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

ну вот это состояние и будет единственным на весь проект. Его будет максимально меньше хоть и ненулевое.
Иначе надо отменить статические классы и запретить паттерн синглетон.

Какой именно «веб» так работает? Если что, «веб» по задумке своей вообще не имеет состояния, в том числе и столь любимых вами сессий.

веб сервера имеют
А откуда она понимает контекст?

откуда что это функция валидирующая конкретное действие пользователя и соответственно понимающая контекст, в частности что именно вводит пользователь.
И это не зависит кстати от того какая конструкция валидатора.

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

ну вот это состояние и будет единственным на весь проект.

А зачем оно нужно, почему нельзя без него обойтись? И да, как вы гарантируете, что оно единственное, что мешает рядом аналогично сделать для других целей?

Иначе надо отменить статические классы и запретить паттерн синглетон.

Ну так паттерн синглтон и считается антипаттерном уже.

откуда что это функция валидирующая конкретное действие пользователя и соответственно понимающая контекст, в частности что именно вводит пользователь.

Не, вы не поняли. У меня есть бизнес-объект «Адрес», который в системе используется в десяти разных местах. Во всех этих местах у него есть обязательная валидация, которая одинаковая. Она, естественно, вынесена в отдельный юнит (если это ООП — то метод класса, если ФП — то функцию). (1) откуда этот юнит узнает про контекст (2) теперь этот юнит обязан знать про ваш статический класс (3) что будет, когда у меня юнит-тесты по этой валидации запустятся десятком в параллель?
А зачем оно нужно, почему нельзя без него обойтись?

можно. Но такое решение упрощает проект во многих местах.
Ну так паттерн синглтон и считается антипаттерном уже.

Вы слишком много внимание уделяете теоретическим конструкциям и терминам вместо практического програмирования. Есть люди, зарабатывюще на жизнь книгами по теории програмированияю Они придумывают разные теоретические конструкции. Сначала паттерны, потом типа сиквела — антипаттерны чтобы написать еще одну книгу потом еще чего нибудь понапридумывают.
И есть люди (не будем тыкать пальцами) которые готовы днями спорить является ли Active Record подвидом ORM или нет хотя практической пользы правильная классификация согласно неким теоретическим положнякам, аж никакой.

.

откуда этот юнит узнает про контекст

если он не знает то как поможет приведеное в статье решение?

теперь этот юнит обязан знать про ваш статический класс

ну куда то ж он по любому складирует ошибку. Почему не может быть статический метод.

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

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

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

Но такое решение упрощает проект во многих местах.

В каких? Чем оно легче простого возврата объекта с ошибками?

Вы слишком много внимание уделяете теоретическим конструкциям и терминам вместо практического програмирования.

Не вам это решать.

если он не знает то как поможет приведеное в статье решение?

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

ну куда то ж он по любому складирует ошибку. Почему не может быть статический метод.

Потому что статический метод — это глобальная зависимость. А если это, скажем, сессия, то это еще и зависимость, привязанная к веб-фреймворку, чего в домене надо избегать.

а зачем запускать впараллель юнит тесты

Чтобы быстрее было, очевидно.

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

Я эти «искусственные или крайне редко используемые конструкции» вижу каждый день более пяти лет.
Статический класс со статическими методами — это, по сути, кучка глобальных переменных со своими методами. Фу. К тому же, вы предлагаете ещё и вставить палку в колесо IDE. Например, в Intellij IDEA нажатие Alt+F7 на идентификаторе или Ctrl+Mouse1 (в объявлении) покажет вам все места, где используется некий объект. Если вы создаёте объект нотификации по мере надобности и передаёте аргументами в методы валидации, вы увидите в коде некий отдельный контекст валидации — например, форму регистрации. Если же вы используете статический класс, то при поиске его использования вы получите все контексты разом, и вам придётся определять, какой вызов к какому контексту относится.
вообще то переменная всего одна (причем инкапсулирована в статическпй клас) — на то она и статическая.
Что касается IDE, выберите подходящую. Или по вашему код надо писать так чтобы он был к IDE привязан?
вообще то переменная всего одна (причем инкапсулирована в статическпй клас) — на то она и статическая.
Вам разве не очевидно, что я говорил об общем случае, который включает в себя и ваш конкретный? Или это придирка к несущественным деталям с целью возразить из принципа? ОК, я разжую за вас: ключевой момент — глобальное состояние.

Что касается IDE, выберите подходящую.
Где вы видели IDE, которая понимает не только синтаксис, но и _назначение_ вашего кода?

Или по вашему код надо писать так чтобы он был к IDE привязан?
Я нигде не говорил про привязку. Разжую ещё раз: если можно писать код так, чтобы IDE облегчала работу с ним, и это не отражается отрицательно на его читабельности — стоит писать именно так.
Вам разве не очевидно, что я говорил об общем случае,

зачем обсуждать некие общие случаи если речь идет о конкретном прикладном решении.

Где вы видели IDE, которая понимает не только синтаксис, но и _назначение_ вашего кода?

я может вообще в блокноте пишу. При чем тут вообще iDE.

если можно писать код так, чтобы IDE облегчала работу с ним, и это не отражается отрицательно на его читабельности — стоит писать именно так

это если вы пишете один. А если с этим кодом должен работать кто то еще у которых другая IDE?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий