Здравствуйте, yenik, Вы писали:
Y>Остаётся вопрос, можем ли мы обобщить это так, что любые ошибки приложения (кроме отказов инфраструктуры) должны возвращать 4хх?
Нет. Я же приводил цитату из стандарта:
The 4xx class of status code is intended for cases in which the client seems to have erred.
Y>>Остаётся вопрос, можем ли мы обобщить это так, что любые ошибки приложения (кроме отказов инфраструктуры) должны возвращать 4хх?
НС>Нет. Я же приводил цитату из стандарта: НС>
НС>The 4xx class of status code is intended for cases in which the client seems to have erred.
400 означает, что неверны не вообще любые аргументы чего угодно, а конкретно аргументы, переданные клиентом.
То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше. Исключения за пределами первичной валидации — это уже 500.
В самом начале речь шла об ArgumentException. Такое исключение может прийти и из глубин бизнес-логики, много позже первичной валидации, и оно может быть связано с ошибкой клиента.
Ну и описанная выше ситуация с отсутствием денег на счету не отлавливается первичной валидацией. Однако мы установили, что она должна порождать код 400, а не 500.
Y>Ну и описанная выше ситуация с отсутствием денег на счету не отлавливается первичной валидацией. Однако мы установили, что она должна порождать код 400, а не 500.
отсутствие денег на счету это не 400. с самим реквестом в данном случае всё ок, счет указан верно, ошибок в данных реквеста нет.
это 403 — операция запрещена. она классифицируется как ошибка клиента — т.к. при таких данных реквеста (они валидны), операция не может быть выполнена, по штатным причинам. повторять клиенту реквест смысла нет.
500 — это когда — мало памяти/веб-сервер перегружен и пока не может/ошибка при связи с сервером БД/сервер БД перегружен/ещё какая-та "неведомая херня", в итоге — сервер по техническим причинам не может обработать реквест.
Y>400 означает, что неверны не вообще любые аргументы чего угодно, а конкретно аргументы, переданные клиентом.
Y>То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше.
Y>Исключения за пределами первичной валидации — это уже 500.
Там речь идёт именно об ArgumentException.
Y>В самом начале речь шла об ArgumentException. Такое исключение может прийти и из глубин бизнес-логики, много позже первичной валидации, и оно может быть связано с ошибкой клиента.
А может быть и не связано. Всё, что мы знаем, поймав ArgumentException — то, что какому-то методу не понравился аргумент. Например, у нас поломалась конфигурация, и куда-то вместо сертификата приехал null, вызвавший ArgumentNullException.
Вот для того, чтобы не гадать, что за аргумент и кем был забракован, и нужно выделять проверку аргументов метода контроллера в отдельную фазу.
Либо сразу же обучать те методы, которые будут бросать исключения на более поздних фазах, выбрасывать не ArgumentException, а подходящий потомок HttpException.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, MadHuman,
если вы боитесь, что внутри вашей функции какие-то другие функции выкинут какой-то ArgumentException,
который не будет значить, что пользовательские данные некорректны, а будет иметь отношение к реализации тела вашей функции, то тогда хорошим способом будет
написать внутренний блок try-catch для вашей функции GetThing и полученный эксепшн обернуть во что-то другое.
типа
public object GetThing(UserInput input) {
input = input ?? throw new ArgumentNullException("UserInput can not be empty.");
try {
return GetThingInternal(input.SubscriptionId, input.PromoCode, input.Card);
}
catch(Exception ex) {
throw new InvalidOperationException("Subscription can not be renewed.", ex);
}
}
Если этот путь не подходит, можно создать валидатор для данных и использовать этот валидатор перед тем как
отправлять ваши обьекты в модель. Сам валидатор прикрутить через middleware, так как речь идет о веб приложении.
Для таких валидаторов есть библиотечки типа fluentvalidation и куча примеров как их использовать.
Здравствуйте, -n1l-, Вы писали:
N>Здравствуйте, MadHuman, N>если вы боитесь, что внутри вашей функции какие-то другие функции выкинут какой-то ArgumentException, N>который не будет значить, что пользовательские данные некорректны, а будет иметь отношение к реализации тела вашей функции, то тогда хорошим способом будет N>написать внутренний блок try-catch для вашей функции GetThing и полученный эксепшн обернуть во что-то другое.
N>типа
N>
N>public object GetThing(UserInput input) {
N> input = input ?? throw new ArgumentNullException("UserInput can not be empty.");
N> try {
N> return GetThingInternal(input.SubscriptionId, input.PromoCode, input.Card);
N> }
N> catch(Exception ex) {
N> throw new InvalidOperationException("Sabscription can not be renewed.", ex);
N> }
N>}
N>
N>Если этот путь не подходит, можно создать валидатор для данных и использовать этот валидатор перед тем как N>отправлять ваши обьекты в модель. Сам валидатор прикрутить через middleware, так как речь идет о веб приложении. N>Для таких валидаторов есть библиотечки типа fluentvalidation и куча примеров как их использовать.
А почему бы UserInput не валидировать данные в конструкторе?
Здравствуйте, vaa, Вы писали:
vaa>А почему бы UserInput не валидировать данные в конструкторе?
Я предполагаю что конструктор там так просто не напишешь, так как данные приходят ввиде какого-то json документа,
который десериализуется System.Text.Json или Json.net и уже по факту создания класса записывает данные.
Ну и такая валидация может различаться в зависимости от конкретной функциональности системы, это некая бизнес валидация, а не просто валидация параметров,
так что я бы использовал fluent validator или делал для этого отдельный класс как минимум.
Ничего подобного, за годы тема подробнейшим образом изучена но по прежнему дисскуссионнная.
Если единственная реакция — это запись в лог и аборт процесса — тогда исключения великолепны, но в этой же модели — коды возвратов чувствуют себя не хуже.
Во всех остальных случаях это — осознанное снижение качества кода, а неосознанное использование исключений (например как весь System.IO из .net 1) ведет к тяжелым проблемам сопровождения или дизайна (проблемы не в том смысле, что оно плохо, а в смысле — что это всегда компромисс, который лежит на ваших плечах). Есть области, на подобии как тут обсуждается (веб-сервер, процессинг) где такая модель во благо, но плюньте в сторону и это уже совсем не так.
Кроме того, в основном — все сводится к языковой поддержке или концепции ошибок или функций с эффектами и механизмами заражения кода эффектами (итерация с колбэком который может бросить исключение и который не может этого сделать — это совершенно разный низкоуровневый код, и т.п.). {Т.е. камень преткновения часто перфоманс и неготовность порождать эффективный код, адекватный локальной ситуации.}
В добавок, вы упускаете из вида, что правильная программа всегда делает валидные вызовы (в идеальном мире ессно — в реальном мире для этого надо очень постараться), вне зависимости от поданных значений на её вход. Т.е. в рамках дисскуссии — номер счета который совсем не похож на него, — это часть БЛ. Это штатная ситуация и места исключениям в подобных вопросах в общем случае нет.
Но, я не хочу быть не правильно понятым: исключения — отличная концепция, в добавок присутствует почти во всех языках, хоть компилируемых, хоть скриптовых. Просто их нужно крайне аккуратно использовать и быть готовым мириться с гетерогенной природой некоторых исключений. У меня искаженное их восприятие, потому, что, я регулярно вижу отвратительные способы их использования (не тут), и в основной массе работаю с кодом без исключений (C++). Но по моим наблюдениям, средний код ничего не потеряет если не будет их использовать даже на C#. Ну а с точки зрения перфоманса, то т.н. "zero-cost exceptions" — они зеро только по отношению к setjmp, а так, они всегда хуже кода на кодах ошибок. Протаскивание кодов на верх — это такой миф. Ну и конечно же просто рекорды от души пороть в C# (не струкуры) — это хуже чем исключения, надо бить по рукам ногами за такое.
PS: Мне можно не отвечать. Это просто мое мнение. Я отвечать не буду. Я даже читать не буду. Я за 2 месяца зашел сюда впервые.
N>> input = input ?? throw new ArgumentNullException("UserInput can not be empty."); N>> throw new InvalidOperationException("Sabscription can not be renewed.", ex);
Не могу пройти мимо и не протянуть руку пoмoщи!
И да будет милостив модератор.