Comments 34
Бросать Exceptionы при валидации входящих данных в .NET это последнее что бы я делал на проекте, в котором много валидации данных. Еще лет 5 назад для этого атрибуты использовались и единый подход к валидации любых полей. И писать "private void ValidateDate(..." для каждого поля в каждой модели это не многим лучше throw Exception.
Несмотря на то, что обсуждали, отпишусь в новой статье. Я проведу аналогию с Java, что возможно немногим отличается.
Этот спор возвращает нужны ли checked и non-checked exception. Повсеместно бытует мнение, что checked exception лучше не использовать, и это так, если писать библиотеку, то checked exception заметно ухудшают жизнь пользователю этой библиотеки, так как он зачастую он использует библиотеку и не ждет данных ошибок, так как они предусмотрены другими ошибками.
Однако, существует случай, когда checked exception помогают, а именно при ожидаемых ошибках, например, неправильно заполненная форма или проверка. В этом случае желательно использовать checked exception и он, конечно же, специфичен для предметной области. Например, NotAvailableSeatsException extends GenericBookingException. Это даст необходимую информацию разработчику UI, что этот exception надо словить и показать, так как он является User case exception, в отличие от NullPointerException, IllegalArgumentException и других стандартных, которые должны быть non-checked и по-хорошему не должны появляется вообще.
Что касается notification, то этот механизм имеет свои плюсы и минусы по сравнению с checked business exception. Exception уже есть и их не надо придумывать, они интегрированы с IDE, представляют изначально объектно-ориентированную модель и расширяемы, они реализуют естественный pattern fail first and further checks rely on a predictable state. Главный минус exception, что для сложных валидаций, когда надо проверить все поля сразу или структурировать или когда проверок слишком много, код написанный в таком стиле уже будет неестественным.
Этот спор возвращает нужны ли checked и non-checked exception. Повсеместно бытует мнение, что checked exception лучше не использовать, и это так, если писать библиотеку, то checked exception заметно ухудшают жизнь пользователю этой библиотеки, так как он зачастую он использует библиотеку и не ждет данных ошибок, так как они предусмотрены другими ошибками.
Однако, существует случай, когда checked exception помогают, а именно при ожидаемых ошибках, например, неправильно заполненная форма или проверка. В этом случае желательно использовать checked exception и он, конечно же, специфичен для предметной области. Например, NotAvailableSeatsException extends GenericBookingException. Это даст необходимую информацию разработчику UI, что этот exception надо словить и показать, так как он является User case exception, в отличие от NullPointerException, IllegalArgumentException и других стандартных, которые должны быть non-checked и по-хорошему не должны появляется вообще.
Что касается notification, то этот механизм имеет свои плюсы и минусы по сравнению с checked business exception. Exception уже есть и их не надо придумывать, они интегрированы с IDE, представляют изначально объектно-ориентированную модель и расширяемы, они реализуют естественный pattern fail first and further checks rely on a predictable state. Главный минус exception, что для сложных валидаций, когда надо проверить все поля сразу или структурировать или когда проверок слишком много, код написанный в таком стиле уже будет неестественным.
Нет, нет и ещё раз. Не для того разработчики .NET включали исключения в CLR и мучались с их поддержкой в куче разных фич вроде async, чтобы мы опять использовали WinAPI-style с возвратом ошибок.
А вас не смущает, что те же самые разработчики .net предосмотрели целый интерфейс
IValidatableObject
с его поддержкой в куче фреймворков?Только предусмотрели они его прежде всего для стандартных механизмов валидации (а ля класс Validator) которые в итоге все равно кидают исключения.
Валидация в asp.net MVC, скажем, не бросает (не бросала) исключения. И в любом другом месте, где мне надо встроиться, я могу взять валидатор, получить из него ошибки и использовать их.
В целом стандартные .net механизмы по сути тоже через уведомления ошибки собирают, фактически в статье велосипед. Я к тому что сбор ошибок через уведомления в итоге не противоречит использованию исключений для выдачи суммарного результата проверки наружу, а подход 'использовать исключения только если в программе что то пошло не так как задумано', т.е. практически отказываться от них вообще, мне кажется несколько ограничен и приводит к большому количеству велосипедов.
Допустим есть некий wcf-сервис, и в нем метод на вход принимает объект с параметрами, на выход выдает объект с данными. Вполне нормально будет после валидации бросить исключение со списком ошибок, не городить же быдлокод из ненужного класса-обертки с самим объектом и коллекцией ошибок валидации, вполне нормально будет кинуть исключение, а на клиенте уже вытаскивать список ошибок из этого исключения в обработчике.
Допустим есть некий wcf-сервис, и в нем метод на вход принимает объект с параметрами, на выход выдает объект с данными. Вполне нормально будет после валидации бросить исключение со списком ошибок, не городить же быдлокод из ненужного класса-обертки с самим объектом и коллекцией ошибок валидации, вполне нормально будет кинуть исключение, а на клиенте уже вытаскивать список ошибок из этого исключения в обработчике.
В wcf нужно кидать FaultException (потому что он преобразуется в описанный стандартом SoapFault).
фактически в статье велосипед
Фактически, в статье описан подход, на котором построен и стандартный механизм .net, и некоторые механизмы в Java.
Я к тому что сбор ошибок через уведомления в итоге не противоречит использованию исключений для выдачи суммарного результата проверки наружу
Но зачем?
'использовать исключения только если в программе что то пошло не так как задумано', т.е. практически отказываться от них вообще
Почему вы приравниваете исключительные ситуации к "отказаться вообще"? У вас так мало исключительных ситуаций в программах?
приводит к большому количеству велосипедов.
Если использовать стандартные механизмы, то никаких особых велосипедов не будет.
Вполне нормально будет после валидации бросить исключение со списком ошибок, не городить же быдлокод из ненужного класса-обертки с самим объектом и коллекцией ошибок валидации, вполне нормально будет кинуть исключение, а на клиенте уже вытаскивать список ошибок из этого исключения в обработчике.
Как мило, вы вот так взяли и навязали майкрософтовский велосипед в виде исключений всем сторонним клиентам оптом. Хотя они знать не хотят про ваше исключение — у них есть
soap:fault
, и он их устраивает. Поэтому бросать надо не абстрактное исключение со списком ошибок, а FaultException
, типизованный вашим списком ошибок, и прописывать этот же список в FaultContract
— что, в итоге, и свелось к тому же самому списку ошибок, с которого начинали.И это все только потому, что в WCF нет удобного способа писать фильтры и возвращать произвольные типы данных: в MVC (или в WebAPI) можно было бы достигнуть того же эффекта вообще не используя исключения — там есть
ModelState
, который прекрасно валидируется во время биндинга, и на основе которого можно вернуть клиенту отлуп, который тот сможет корректно распарсить. Причем этот код можно написать один раз, запихнуть в фильтр, и забыть.Во-первых я никому ничего не навязывал. Во-вторых не каждая система подразумевает наличие сторонних клиентов. В-третьих я не конкретизировал какое именно исключение должен бросать wcf сервис, или вы считаете что я разглагольствую об исключениях в wcf-сервисах не зная о существовании FaultException? В-четвертых даже любое абстрактное исключение при желании можно обернуть в FaultException, это лишь вопрос конкретной реализации и сути не меняет поскольку речь о том что в итоге бросается именно исключение и именно при ожидаемом поведении и это допустимо и адекватно.
Такое впечатление что вы пишете просто чтобы потроллить. Я просто привел пример где бросок исключения для той же валидации вполне уместен и корректен, а своими знаниями по mvc трясите пожалуйста перед кем нибудь другим, я пока приводил в пример только wcf, если с чем то конкретно несогласны пишите по существу.
Такое впечатление что вы пишете просто чтобы потроллить. Я просто привел пример где бросок исключения для той же валидации вполне уместен и корректен, а своими знаниями по mvc трясите пожалуйста перед кем нибудь другим, я пока приводил в пример только wcf, если с чем то конкретно несогласны пишите по существу.
Во-вторых не каждая система подразумевает наличие сторонних клиентов
И поэтому давайте мы будем в одних системах кидать исключения, а в других — нет?
В-четвертых даже любое абстрактное исключение при желании можно обернуть в FaultException
Знаете, как это мило разбирается на клиенте?
Я просто привел пример где бросок исключения для той же валидации вполне уместен и корректен
Нет, не уместен и не корректен: он там вынужден, потому что в WCF нет другого простого способа вернуть fault. Но если работать на уровне, отличном от типизованного WCF-сервиса, т.е. иметь прямой доступ к сообщению, работать с фолтами становится проще, чем с исключениями.
Но самое главное, что по факту, если не использовать нормальные исключения (т.е., использовать
FaultException
), то выясняется, что в WCF паттерн, описанный в статье, прекрасно реализуется без какого-либо велосипеда.Но самое главное, что по факту, если не использовать нормальные исключения (т.е., использовать FaultException), то выясняется, что в WCF паттерн, описанный в статье, прекрасно реализуется без какого-либо велосипеда.
Вот да. Проще всего собрать все валидационные сообщения, положить их в коллекцию и только после этого бросить FaultException типизованный этой коллекцией. А на клиенте спокойно разобрать эту коллекцию валидационных сообщений как нужно.
Не так давно столкнулся с тем, что надо было валидировать данные на уровне бизнес логики, а не на уровне представления в ASP.NET Core (3-х уровневая архитектура). После долгих раздумий решил посмотреть как же делают другие. И понравилось решение реализации валидаторов в Identity (https://github.com/aspnet/Identity), UserValidator например c IdentityResult.
Фаулер не сделал еще три простых и очевидных шага. Возможно язык, которым он оперирует, не позволил ему это сделать?
Как итог мы имеем реализацию паттерна Notification на стандартных и привычных исключениях, получая все их преимущества.
Написать что-ли Фаулеру? Мол так и так, мистер, я тут кажется придумал еще один паттерн для вас )))
- Вводим конструкцию, которая является коллекцией исключений и сама по себе исключением одновременно. Это и есть его объект Notification, осталось только добавить к нему реализацию Throwable
- Позволяем нашему низкоуровневому коду (например — валидаторам конкретных полей) делать не throw new Exception, а yield new Exception. Таким образом валидатор либо молчит (всё ОК), либо генерирует серию исключений
- Код, который на уровень выше кода из пункта 2. собирает все исключения, сгенерированные кодом из пункта 2. в контейнер-коллекцию из пункта 1. Который уже выбрасывается.
Как итог мы имеем реализацию паттерна Notification на стандартных и привычных исключениях, получая все их преимущества.
Написать что-ли Фаулеру? Мол так и так, мистер, я тут кажется придумал еще один паттерн для вас )))
получая все их преимущества.
… и все недостатки.
Код, который на уровень выше кода из пункта 2. собирает все исключения, сгенерированные кодом из пункта 2. в контейнер-коллекцию из пункта 1. Который уже выбрасывается.
А зачем его выбрасывать?
Чтобы поймать. Выбрасывать нужно, чтобы прокинуть сквозь уровни кода. И да — чтобы поймать. На нужном нам уровне. Исключения же?
Выбрасывать нужно, чтобы прокинуть сквозь уровни кода.
А точно нужно прокидывать через все уровни кода? А зачем?
чтобы поймать. На нужном нам уровне.
Теперь у вас все уровни выше знают про исключения, кидаемые ниже. Это точно правильно?
А точно нужно прокидывать через все уровни кода? А зачем?
В одних случаях нужно, в других — нет. Вы архитектор, вам решать.
И да, почему через ВСЕ? Вы приписываете мне слова, которые я не говорил. Прокинуть через уровни. Всплыть вверх. Но не до конца, иначе будет Uncaught Exception. Следовательно не через все уровни кода.
Теперь у вас все уровни выше знают про исключения, кидаемые ниже.
С чего вы это взяли? Уровни ни о чем не знают, они просто в нужном месте потенциально опасный код окружают try {… } И это — правильно. Я не вижу причин, почему try {… } catch {… } с указанием конкретных ожидаемых исключений может быть "неправильно".
Метафору "уровни знают про исключения" я не понимаю, извините.
В одних случаях нужно, в других — нет. Вы архитектор, вам решать.
А если я не хочу прокидывать вообще? Если у меня валидация — это нормальный flow, зачем мне тогда дополнительные сложности в объекте и накладные расходы на обработку исключений?
Я не вижу причин, почему try {… } catch {… } с указанием конкретных ожидаемых исключений может быть «неправильно».
Потому что чтобы указать исключения, надо про них знать — а это неизбежное увеличение связности уровней.
Вы странно рассуждаете. Как будто связность уровней своего же собственного кода — это нечто плохое. Внимание: не связность модулей, а уровней!
Это интерфейс же. Исключения, бросаемые внутри try — это интерфейс кода, который там внутри. Что плохого в явном указании интерфейса?
try { user = new User; user.fill(data); user.save(); } catch (DbException e) { // чё-то не то в сохранении в БД } catch (ValidateErrors e) { // валидаторы сработали }
Это интерфейс же. Исключения, бросаемые внутри try — это интерфейс кода, который там внутри. Что плохого в явном указании интерфейса?
Вы странно рассуждаете. Как будто связность уровней своего же собственного кода — это нечто плохое.
Да, это плохое. Оно может быть компенсировано хорошим, и хорошее может перевешивать.
Что плохого в явном указании интерфейса?
Ничего. Но чем многословнее интерфейс, тем дальше мы от принципа сокрытия информации.
А если я не хочу прокидывать вообще?
Ну пишите без исключений. Пока еще это законодательно не запрещено. Просто с исключениями удобнее, знаете ли. Попробуйте.
Теперь у вас все уровни выше знают про исключения, кидаемые ниже. Это точно правильно?
int value = sum(2,2);
теперь у вас все уровни знают, что функция sum возвращает int! это точно правильно?
Когда я задумался над вопросом (C++11): код ошибки или исключение? Решил, что нужно дать возможность пользователю API выбирать. В результате появился подход с опциональным возвращаемым кодом ошибки (реализацию подглядел в Boost.System), т.е. сигнатура метода стала выглядеть:
а в теле:
Реализацию можно посмотреть тут. Ограничение (меня оно не смущает) — конструктор исключения должен уметь принимать
А вообще идея такая: если пользователь хочет обработать какую-то ошибочную ситуацию — пусть обрабатывает (здесь и сейчас), но если забил — будет брошено исключение.
void someMethod(int arg0, const std::string& arg1, std::error_code &ec = throws());
а в теле:
clear_if(ec); // очистит код ошибки если не throws(). У меня размещаются в начале метода
...
throws_if<SomeExceptionClass>(ec, errcode, errcathegory); // заполнит код ошибки или выбросит SomeExceptionClass
return ...;
Реализацию можно посмотреть тут. Ограничение (меня оно не смущает) — конструктор исключения должен уметь принимать
std::error_code
.А вообще идея такая: если пользователь хочет обработать какую-то ошибочную ситуацию — пусть обрабатывает (здесь и сейчас), но если забил — будет брошено исключение.
Sign up to leave a comment.
Замена выброса исключений уведомлениями