Всем привет!
хватит про политику, давайте про полезное)
кейс. есть функция GetThing, внутри проверяется её аргумент на валидность. если что не так — генерится ArgumentException (AE). пока всё ок.
теперь её надо заюзать например внутри хандлера рест-апи ендпойнта. хочется чтоб если извне пришедший аргумент для неё кривой — вернуть 400.
можно сделать кэтч для AE, но это может оказаться AE из более глубоких недр (то есть быть реальной ошибкой и не про наш аргумент который извне).
вводить спец класс ексепшина? это решит проблему — можно кэтчить именно его, но кажется как-то не айс на каждый подобный кейс вводить спец класс ексепшина... или норм?
ещё вариант — в кэтче как-то понимать что AE именно изнутри самой GetThing а не глубже... как-то такое можно?...
и ещё вариант — у AE можно задать paramName и тогда в кэтче ловим и если имя параметра наше, то так и опознаём. уже лучше, но всё равно не 100%, остается слабая вероятность что из недр и имя параметра совпало.
Здравствуйте, MadHuman, Вы писали: MH>кейс. есть функция GetThing, внутри проверяется её аргумент на валидность. если что не так — генерится ArgumentException (AE). пока всё ок. MH>теперь её надо заюзать например внутри хандлера рест-апи ендпойнта. хочется чтоб если извне пришедший аргумент для неё кривой — вернуть 400.
Так делать не надо.
400 означает, что неверны не вообще любые аргументы чего угодно, а конкретно аргументы, переданные клиентом.
То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше.
Исключения за пределами первичной валидации — это уже 500.
Покажите ваш код контроллера, который хэндлит рест апи. Какое место там занимает GetThing?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, MadHuman, Вы писали: MH>>кейс. есть функция GetThing, внутри проверяется её аргумент на валидность. если что не так — генерится ArgumentException (AE). пока всё ок. MH>>теперь её надо заюзать например внутри хандлера рест-апи ендпойнта. хочется чтоб если извне пришедший аргумент для неё кривой — вернуть 400. S>Так делать не надо. S>400 означает, что неверны не вообще любые аргументы чего угодно, а конкретно аргументы, переданные клиентом.
ну да. кейс как раз и такой. клиент передал неверное значение аргумента.
S>То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше.
необязательно. задачу по проверке аргумента можно делегировать GetThing (т.к. она там внутри лучше знает что и как проверять).
вопрос в том как это лучше обработать. дублировать в хэндлере логику проверки плохо, вынести код проверки аргумента из GetThing в отдельную функцайку
и в хэндлере её дернуть — можно, но ... нехотелось бы.
S>Покажите ваш код контроллера, который хэндлит рест апи. Какое место там занимает GetThing?
кода нет, я думаю как его написать. примерно так
//рабочая функция. размещена в другом месте от хэндлераpublic static object GetThing(string arg1){
if ( some condition for arg1) {
throw new ArgumentException("arg1 invalid");
}
//do work
}
//хэндлер для апи ендпойнтаpublic static object ProcApiRequest(string arg1, string arg2){
try {
var result1 = GetThing(arg1);
var result2 = DoSomeOtherWork(result1);
return MakeOutPut(result2);
} catch (ArgumentException ae){
return new HttpError(400, ae.Message);
}
}
Здравствуйте, MadHuman, Вы писали:
MH>ну да. кейс как раз и такой. клиент передал неверное значение аргумента.
Тогда нужно отфутболить запрос, не доходя даже до контроллера
В современном asp.net для этого очень много средств, смотри https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-6.0
Если не хватает атрибутов модели, то используй IValidatableObject
S>>То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше. MH>необязательно. задачу по проверке аргумента можно делегировать GetThing (т.к. она там внутри лучше знает что и как проверять).
Обязательно. То что ты предлагаешь это "проектирование снизу вверх", что почти всегда является антипаттерном.
MH>вопрос в том как это лучше обработать. дублировать в хэндлере логику проверки плохо, вынести код проверки аргумента из GetThing в отдельную функцайку MH>и в хэндлере её дернуть — можно
И нужно, а по возможности вынести в атрибут валидации или в реализацию IValidatableObject.Validate
MH>но ... нехотелось бы.
Ты сам создал ограничения и с ними борешься. Ради чего?
MH>примерно так
MH>
MH>//рабочая функция. размещена в другом месте от хэндлера
MH>public static object GetThing(string arg1){
MH> if ( some condition for arg1) {
MH> throw new ArgumentException("arg1 invalid");
MH> }
MH> //do work
MH>}
MH>//хэндлер для апи ендпойнта
MH>public static object ProcApiRequest(string arg1, string arg2){
MH> try {
MH> var result1 = GetThing(arg1);
MH> var result2 = DoSomeOtherWork(result1);
MH> return MakeOutPut(result2);
MH> } catch (ArgumentException ae){
MH> return new HttpError(400, ae.Message);
MH> }
MH>}
MH>
А чем это принципиально лучше, чем:
public static object GetThing(string arg1){
//do work
}
//хэндлер для апи ендпойнтаpublic static object ProcApiRequest(string arg1, string arg2){
if ( some condition for arg1) {
this.ModelState.AddModelError(nameof(arg1),"Condition failed");
}
if ( some other condition for arg2) {
this.ModelState.AddModelError(nameof(arg2),"Condition failed");
}
if(!this.ModelState.IsValid) {
return BadRequest(this.ModelState);
}
var result1 = GetThing(arg1);
var result2 = DoSomeOtherWork(result1);
return MakeOutPut(result2);
}
Вообще о валидацию много копий сломано,
но общая идея: парсить, а не валидировать
или еще как принцип "Making illegal states unrepresentable"
т.е. для arg1 и arg2 если они логически связаны создать valueobject и в конструкторе проверить валидность, а дальше уже передать нормальный объект
или в нем сделать статик метод подобный тому что выше и сделать конструктор приватным, чтобы не выкидывать исключений из конструктора.
G>public static object GetThing(string arg1){
G> //do work
G>}
G>//хэндлер для апи ендпойнта
G>public static object ProcApiRequest(string arg1, string arg2){
G> if ( some condition for arg1) {
G> this.ModelState.AddModelError(nameof(arg1),"Condition failed");
G> }
G> if ( some other condition for arg2) {
G> this.ModelState.AddModelError(nameof(arg2),"Condition failed");
G> }
G> if(!this.ModelState.IsValid) {
G> return BadRequest(this.ModelState);
G> }
G> var result1 = GetThing(arg1);
G> var result2 = DoSomeOtherWork(result1);
G> return MakeOutPut(result2);
G>}
G>
G>?
тем что, логика проверки аргумента arg1 довольно сложна (это не одна строчка для if как в упрощённом примере) и специфична именно для GetThing, и там уже реализована, тк функция используется не только для хандлера рест-апи ендпойнта.
в предлагаемом вами варианте логика проверки перенесена из GetThing в ProcApiRequest. это было бы ок, если она совсем простая, но она не совсем простая.
проверка внутри GetThing органична и реюзает часть внутренней логики. не так просто её взять и перенести.
и минус — в GetThing проверка всё равно нужно, т.к. GetThing не только для ProcApiRequest.
можно вынести логику валидации в отдельную функцию и реюзать и в GetThing и в ProcApiRequest, так будет уже лучше.
но было ощущение что кэтчем эксепшина из GetThing тоже норм.
не всегда хорошо чтоб каждое место использование функции вынуждало её менять/рефакторить.
Здравствуйте, MadHuman, Вы писали:
MH>
MH>тем что, логика проверки аргумента arg1 довольно сложна (это не одна строчка для if как в упрощённом примере) и специфична именно для GetThing, и там уже реализована, тк функция используется не только для хандлера рест-апи ендпойнта.
Что мешает просто в отдельный метод вынести? Или даже в отдельный класс?
Я так понимаю, что основной это "уже там реализована",но это аргумент слабый, так как с современными средствами рефакторинга перемещение кода довольно простое, а других положительных эффектов нет
MH>в предлагаемом вами варианте логика проверки перенесена из GetThing в ProcApiRequest. это было бы ок, если она совсем простая, но она не совсем простая.
Это неважно, рефакторинг extract method выполняется легко.
MH>проверка внутри GetThing органична и реюзает часть внутренней логики. не так просто её взять и перенести.
Это ограничение, которое ты сам себе создал. Кроме того непонятно почему такая нетривиальная логика кидает в итоге ArgumentException, который по сути usage, а не runtime exception.
MH>и минус — в GetThing проверка всё равно нужно, т.к. GetThing не только для ProcApiRequest.
Вовсе не факт. У тебя в каждом методе АПИ своя валидация.
Чтобы руками не дублировать во пользуйся model binding и встроенной валидацией
MH>можно вынести логику валидации в отдельную функцию и реюзать и в GetThing и в ProcApiRequest, так будет уже лучше. MH>но было ощущение что кэтчем эксепшина из GetThing тоже норм. MH>не всегда хорошо чтоб каждое место использование функции вынуждало её менять/рефакторить.
А какая разница где код править, в котроллере или GetThing?
Если сценарий использования меняется, тоти сама функция меняется. Это называется "проектирование сверху вниз"
Здравствуйте, vaa, Вы писали:
vaa>Вообще о валидацию много копий сломано, vaa>но общая идея: парсить, а не валидировать vaa>или еще как принцип "Making illegal states unrepresentable" vaa>т.е. для arg1 и arg2 если они логически связаны создать valueobject и в конструкторе проверить валидность, а дальше уже передать нормальный объект vaa>или в нем сделать статик метод подобный тому что выше и сделать конструктор приватным, чтобы не выкидывать исключений из конструктора.
Я тут задумался о реальном примере
Предположим веб-сервис перевода между счетами в банке
Принимаем на вход
record Transaction(string From, string To, decimal Amount)
1) Хотим чтобы номера счетов соответствовали формату, а сумма была положительная, это легко делается атрибутами:
record Transaction([RegularExpression(...)]string From, [RegularExpression(...)]string To, [RangeAttribute(...)]decimal Amount)
2) Хотим чтобы номера счетов были разными
record Transaction([RegularExpression(...)]string From, [RegularExpression(...)]string To, [RangeAttribute(...)]decimal Amount): IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (From == To)
{
yield return new ValidationResult("From and To are same", new[] { nameof(From), nameof(To) });
}
}
}
3) Хотим чтобы счета при конструировании были существующими.
Это атрибутами уже не сделаешь, да IValidatableObject в данном случае так себе решении, так как надо в базу сходить.
Более того проверка счетов уже часть БЛ, и полученные атрибуты счета будут использоваться в дальнейшем для логики перевода.
То есть примерно тот случай, про который пишет топикстартер.
Стоит ли в данном случае изнутри БЛ кидать ArgumentException? Нужно ли в таком случае выдавать 400 клиенту? Или это уже 500 ошибка?
G>3) Хотим чтобы счета при конструировании были существующими. G>Стоит ли в данном случае изнутри БЛ кидать ArgumentException? Нужно ли в таком случае выдавать 400 клиенту? Или это уже 500 ошибка?
Если ошибка в бухгалтерии, то это уже не ArgumentException, а лучше бросать специализированное исключение. И тогда это уже 400, а не 500.
ArgumentException подходит только для самых простых проверок, типа должен прийти емейл, а неожиданно пришёл какой-то мусор. Такие неожиданные исключения можно трактовать наравне с аналогичными исключениями из третьесторонних библиотек и выдавать пользователю 500.
Здравствуйте, MadHuman, Вы писали:
MH>можно сделать кэтч для AE, но это может оказаться AE из более глубоких недр (то есть быть реальной ошибкой и не про наш аргумент который извне). MH>вводить спец класс ексепшина? это решит проблему — можно кэтчить именно его, но кажется как-то не айс на каждый подобный кейс вводить спец класс ексепшина... или норм?
Норм. Если есть куча легаси — можно оба подхода использовать, и спецкласс, и набор well-known исключений.
Здравствуйте, Sinclair, Вы писали:
S>Так делать не надо. S>400 означает, что неверны не вообще любые аргументы чего угодно, а конкретно аргументы, переданные клиентом. S>То есть хэндлер рест-апи должен проверить аргументы, и только если они в порядке, ехать дальше. S>Исключения за пределами первичной валидации — это уже 500.
Гладко было на бумаге. Валидация, зараза, может быть тоже размазана по нескольким слоям. Часть валидации, к примеру, отрабатывается стандартным биндером на основании DataAnnotation, часть в коде контроллера, а часть — в бизнес-логике (к примеру, потому что провалидировать можно только выполнив половину работы).
Здравствуйте, yenik, Вы писали:
Y>ArgumentException подходит только для самых простых проверок, типа должен прийти емейл, а неожиданно пришёл какой-то мусор. Такие неожиданные исключения можно трактовать наравне с аналогичными
Почему 500? 400 означает, что проблема с переданными данными, 500 что это внутренняя проблема сервиса, на которую клиент повлиять не в состоянии. В описанном кейсе проблема именно в переданных данных.
Здравствуйте, gandjustas, Вы писали:
G>Что мешает просто в отдельный метод вынести? Или даже в отдельный класс?
К примеру, для проверки могут потребоваться промежуточные данные, которые затем используются для собственно вычислений. Если выдрать в отдельный класс, то либо эти внутренние данные вылезут аж в контроллер, что резко увеличит объем бойлерплейта, либо тебе придется делать одну и ту же работу дважды.
Ну и отдельный эротичный кейс это когда есть два сервиса, один из которых зовет другой и возвращает его результат наружу с минимальными изменениями (к примеру, просто обогащая данными). Чего и куда будем выносит в этом случае?
Y>>ArgumentException подходит только для самых простых проверок, типа должен прийти емейл, а неожиданно пришёл какой-то мусор. Такие неожиданные исключения можно трактовать наравне с аналогичными
НС>Почему 500? 400 означает, что проблема с переданными данными, 500 что это внутренняя проблема сервиса, на которую клиент повлиять не в состоянии. В описанном кейсе проблема именно в переданных данных.
Согласен. Лично я вообще против выдачи клиенту 500. Если исключение в UI или BL, то лучше его логировать и выдавать 400. Так разработчик хотя бы может предполагать, что исключение в его коде. А 500 — это может быть веб-сервер гавкнулся, или прокси-сервер гавкнулся, или приложение вообще не стартануло нормально.
G>Стоит ли в данном случае изнутри БЛ кидать ArgumentException? Нужно ли в таком случае выдавать 400 клиенту? Или это уже 500 ошибка?
Это общая проблема, если ловим конкретное исключение, то можно 400, если другое 500.
в корке можно возвращать record Result () { record Ok(r):Result(); record Error(exn)}
Здравствуйте, vaa, Вы писали:
vaa>Здравствуйте, gandjustas, Вы писали:
G>>Стоит ли в данном случае изнутри БЛ кидать ArgumentException? Нужно ли в таком случае выдавать 400 клиенту? Или это уже 500 ошибка?
vaa>Это общая проблема, если ловим конкретное исключение, то можно 400, если другое 500.
Про 400 и 500 это был провокационный вопрос.
Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
точно! по моему лучший вариант. вместо исключения возвращать Result, аля ФП F# подход с Result<'ok, 'err> , я ведь даже его знал.. но не сразу вспомнил
и волки сыты и овцы целы!
и ещё немаловажный бонус — в сценриаях когда ошибка ожидаемое поведение (а это как раз для рест-апи), возврат кода ошибки по перфомансу более выигрышно чем исключение и его кэтч.
type ErrorKind = InvalidArg of string * string
type Result = Ok | Error of ErrorKind
let getThing arg1 =
//bla blaif not (some condition) then
Error("arg1", "arg1 invalid")
else//do ok work
Ok
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, MadHuman, Вы писали:
MH>>и волки сыты и овцы целы!
НС>Ага, только куча логики по прокидыванию этого резалта через весь коллстек
в исходном примере, речь про один уровень.
НС>, и возвращаемые типы все в стиле maybe монады.
да, невижу в этом неудобств или недостатков.
ну и если быть точным не maybe (она про Some | None), а про Result ( Ok | Error ), где Error может включать доп информацию, а None из maybe нет
Здравствуйте, MadHuman, Вы писали:
НС>>Ага, только куча логики по прокидыванию этого резалта через весь коллстек MH>в исходном примере, речь про один уровень.
Исключения как раз придумали чтобы уйти от лапши, которую порождают коды возврата. А тут какой то back to the future.
НС>>, и возвращаемые типы все в стиле maybe монады. MH>да, невижу в этом неудобств или недостатков.
Это в шарпе то?
MH>ну и если быть точным не maybe (она про Some | None), а про Result ( Ok | Error ), где Error может включать доп информацию, а None из maybe нет
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, gandjustas, Вы писали:
G>>Что мешает просто в отдельный метод вынести? Или даже в отдельный класс?
НС>К примеру, для проверки могут потребоваться промежуточные данные, которые затем используются для собственно вычислений.
сразу виден обширный практический опыт) согласен с аргументацией
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Исключения как раз придумали чтобы уйти от лапши, которую порождают коды возврата. А тут какой то back to the future.
Исключения не предназначены, чтобы на них строить логику. Если приложение слоистое, то лучше использовать алгебраические типы.
НС>Это в шарпе то?
Есть либы для этого. Гораздо удобнее и нагляднее, в отличие от стандартных исключений.
Здравствуйте, IncremenTop, Вы писали:
НС>>Исключения как раз придумали чтобы уйти от лапши, которую порождают коды возврата. А тут какой то back to the future. IT>Исключения не предназначены, чтобы на них строить логику.
А кто предлагает на них строить логику? Речь про возврат ошибки. Это именно то для чего придуманы исключения.
IT> Если приложение слоистое, то лучше использовать алгебраические типы.
В шарпе нет алгебраических типов.
НС>>Это в шарпе то? IT>Есть либы для этого. Гораздо удобнее и нагляднее, в отличие от стандартных исключений.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здесь должен быть пример.
Result result = new Result.Ok("OK");
WriteLine(result switch
{
Result.Ok x => "200 " + x.Result,
Result.Error e => "400 " + e.Err
});
public record Result()
{
public record Ok(string Result) : Result();
public record Error(string Err) : Result();
}
vaa>Result result = new Result.Ok("OK");
vaa>WriteLine(result switch
vaa>{
vaa> Result.Ok x => "200 " + x.Result,
vaa> Result.Error e => "400 " + e.Err
vaa>});
vaa>public record Result()
vaa>{
vaa> public record Ok(string Result) : Result();
vaa> public record Error(string Err) : Result();
vaa>}
vaa>
ЧТД. Ровно 100% этого кода при использовании исключений можно выкинуть.
vaa>>Result result = new Result.Ok("OK");
vaa>>WriteLine(result switch
vaa>>{
vaa>> Result.Ok x => "200 " + x.Result,
vaa>> Result.Error e => "400 " + e.Err
vaa>>});
vaa>>public record Result()
vaa>>{
vaa>> public record Ok(string Result) : Result();
vaa>> public record Error(string Err) : Result();
vaa>>}
vaa>>
НС>ЧТД. Ровно 100% этого кода при использовании исключений можно выкинуть.
не совсем. Result — может быть библиотечным/инфраструктурным.
вместо свитча — будет try/catch. я бы сказал примерно одно и тоже.
в общем случае исключения конечно удобнее (когда не надо каждый вызов функции хэндлить и обрабатывать ошибку), за счет меньшего шума и доп бойлерплейта.
паттерн Result лучше, когда ошибочная ситуация ожидаема и часть штатного поведения, в этом случае лучше и перф (иногда это важно).
и мы точно знаем все ожидаемые исходы из самого типа результата.
решается и исходная проблема топика — неоднозначность места исключения (возникло ли оно внутри самой функции и то что мы и хотим словить либо из более глубоких недр).
ввод нового класса исключения по бойлерплейту будет примерно тоже что ввод нового класса Result для конкретного случая.
Здравствуйте, MadHuman, Вы писали:
MH>вместо свитча — будет try/catch. я бы сказал примерно одно и тоже.
Не будет там никакого try..catch, в этом суть. Просто вешается единожды написанная простенькая мидлвера. А вот твой Result придется явно протаскивать по всему коллстеку, загаживая код.
Еще раз — единственная фича исключений, ради которой их придумали — возможность абсолютно независимо от любого коллстека прервать выполнение и вернуть ошибку. И все это происходит абсолютно прозрачно для любого кода между источником и конечным обработчиком. А вот вариант с возвращаемыми кодами ошибок откатывает нас во времена до исключений, когда любой код приходилось специально под обработку ошибок затачивать. Особенно эротично это было в случае наличия колбеков. Вот есть некий библиотечный код, не твой. Ему на вход идет твой собственный колбек, в котором, внезапно, возникает ошибка. В случае с исключениями все работает, а вот что делать с кодами ошибок?
Здравствуйте, gandjustas, Вы писали:
G>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Все равно это должна быть какая-то ошибка 4xx, а не 5xx
MH>>вместо свитча — будет try/catch. я бы сказал примерно одно и тоже. НС>Не будет там никакого try..catch, в этом суть. Просто вешается единожды написанная простенькая мидлвера. А вот твой Result придется явно протаскивать по всему коллстеку, загаживая код.
тогда мы вернёмся к исходной проблеме. вот мидлвара словила исключение (даже наше спец исключение типа WrongAccountException), если под мидлварей вызов функции вида Transfer (из которой напрямую это исключение и возвращается),
то да — всё хорошо, возвращаем 400, т.к. номер счета передан снаружи неверно. но исключение может подняться по колстэку и из глубин бизнес-логики, и на вызывающей стороне (хандлере под мидлаварей) и не было речи ни о каком счете.
а когда у нас в частном случае появится новое исключение — снова идти править общую мидлвару?
НС>Еще раз — единственная фича исключений, ради которой их придумали — возможность абсолютно независимо от любого коллстека прервать выполнение и вернуть ошибку. И все это происходит абсолютно прозрачно для любого кода между источником и конечным обработчиком.
да, всё так.
НС>А вот вариант с возвращаемыми кодами ошибок откатывает нас во времена до исключений, когда любой код приходилось специально под обработку ошибок затачивать.
этот вариант надо использовать когда в конкретном случае он имеет преимущества перед кэтчем исключения. если его всегда использовать/злоупотреблять, то конечно мы получим как в Go и всё то о чем ты говориш (не надо так).
Здравствуйте, MadHuman, Вы писали:
MH>то да — всё хорошо, возвращаем 400, т.к. номер счета передан снаружи неверно. но исключение может подняться по колстэку и из глубин бизнес-логики, и на вызывающей стороне (хандлере под мидлаварей) и не было речи ни о каком счете.
Вот поэтому и вводится специальный класс бизнес-исключений.
НС>>А вот вариант с возвращаемыми кодами ошибок откатывает нас во времена до исключений, когда любой код приходилось специально под обработку ошибок затачивать. MH>этот вариант надо использовать когда в конкретном случае он имеет преимущества перед кэтчем исключения.
И что это за преимущества?
MH> если его всегда использовать/злоупотреблять, то конечно мы получим как в Go
В Go ситуация существенно лучше, потому что там весь код под такое заточен. А в дотнете это полный ахтунг.
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, gandjustas, Вы писали:
G>>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Б>Все равно это должна быть какая-то ошибка 4xx, а не 5xx
G>Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами. G>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Здесь возврат 400 для запроса, некорректного с точки зрения бизнес-логики. Запрос, который некорректен сегодня, может стать корректным завтра. И тогда его можно смело повторить и он вернёт 200.
Здравствуйте, yenik, Вы писали:
Y>Здесь возврат 400 для запроса, некорректного с точки зрения бизнес-логики. Запрос, который некорректен сегодня, может стать корректным завтра. И тогда его можно смело повторить и он вернёт 200.
Падажжите. Там речь идёт о некорректном токене, переданном с клиента. 4хх здесь — то, что доктор прописал.
5хх означает "да, вы всё правильно попросили, я должен был сделать то, что вы просите, но не смог".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, yenik, Вы писали:
Y>>В стандарте одно, а в жизни другое.
НС>В стандарте все хорошо, там нет ничего ни про какое время. НС>
НС>The 4xx class of status code is intended for cases in which the client seems to have erred.
НС>RFC2616.
Это смотря что считать ошибкой клиента. Передача номера счета, которого нет — ошибка клиента и надо вернуть 400 или все-таки 500?
Здравствуйте, gandjustas, Вы писали:
G>Это смотря что считать ошибкой клиента. Передача номера счета, которого нет — ошибка клиента и надо вернуть 400 или все-таки 500?
Здравствуйте, MadHuman, Вы писали:
MH>было подобное? какое решение находили?..
Все определяется архитектурой фрэймворка.
в данном случае есть стандартные NotFound(), OkObject(..) и т.д.
выбрасывать исключение, которого ожидали неоправданно.
Y>>Здесь возврат 400 для запроса, некорректного с точки зрения бизнес-логики. Запрос, который некорректен сегодня, может стать корректным завтра. И тогда его можно смело повторить и он вернёт 200. S>Падажжите. Там речь идёт о некорректном токене, переданном с клиента. 4хх здесь — то, что доктор прописал. S>5хх означает "да, вы всё правильно попросили, я должен был сделать то, что вы просите, но не смог".
Может быть, я всё неправильно понимаю. Просветите меня.
The request could not be understood by the server due to malformed
syntax. The client SHOULD NOT repeat the request without
modifications.
Однако, в моём примере запрос имеет безупречный синтаксис и прекрасно понимается. Просто была запрошена сущность, которая пока/уже не существует.
Если открыть админскую панель и завести таковую сущность, то тот же самый запрос можно смело повторить и получить код 200.
Y> The request could not be understood by the server due to malformed
Y> syntax. The client SHOULD NOT repeat the request without
Y> modifications.
Y>Однако, в моём примере запрос имеет безупречный синтаксис и прекрасно понимается. Просто была запрошена сущность, которая пока/уже не существует.
Фейл по вине клиента — это 4xx. Не обязательно прямо 400.
Если сущности ещё не существует — 404 Not Found
Если уже не существует — 410 Gone Y>Если открыть админскую панель и завести таковую сущность, то тот же самый запрос можно смело повторить и получить код 200.
Но если не заводить, то никаких 200 вы не получите.
500 означает, что запрос возможно сработает когда-то ещё, даже если в админскую панель не заходить и ничего не крутить.
Для сравнения — 403 Access Denied не означает, что доступ закрыт навсегда. Если пойти в админскую панель и выдать разрешение, то можно смело повторить запрос и получить 200.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something
that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)
так что, норм 400 если синтаксис ок, но сами данные невалидны с точки зрения сервера.
причем ещё для такого кейса вполне ок и 422, но с одной стороны он вроде как бы был специфичен для WebDAV
и я не понял, есть ли однозначный консенсус не нему на текущий момент.
но 400 точно ок.
S>Фейл по вине клиента — это 4xx. Не обязательно прямо 400. S>Если сущности ещё не существует — 404 Not Found S>Если уже не существует — 410 Gone Y>>Если открыть админскую панель и завести таковую сущность, то тот же самый запрос можно смело повторить и получить код 200. S>Но если не заводить, то никаких 200 вы не получите. S>500 означает, что запрос возможно сработает когда-то ещё, даже если в админскую панель не заходить и ничего не крутить. S>Для сравнения — 403 Access Denied не означает, что доступ закрыт навсегда. Если пойти в админскую панель и выдать разрешение, то можно смело повторить запрос и получить 200.
Про 400 и 500 это был провокационный вопрос.
Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем.
The request could not be understood by the server due to malformed
syntax. The client SHOULD NOT repeat the request without
modifications.
Однако в нашем случае запрос был понят сервером и он синтаксически правильный. И его вполне можно повторять без изменения, пока не заработает.
Или что тогда означает malformed syntax?
The 400 (Bad Request) status code indicates that the server cannot or
will not process the request due to something that is perceived to be
a client error (e.g., malformed request syntax, invalid request
message framing, or deceptive request routing).
Здесь уже не сказано, что клиенту не следует повторять запрос. И даётся примерный список причин для 400: malformed request syntax, invalid request message framing, or deceptive request routing.
Но это всё технические проблемы, а не бизнес-процесс. Однако лично я согласен, что ограничения бизнес-процесса тоже годятся.
Здравствуйте, yenik, Вы писали:
Y>Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем.
Нюанс тут, что альтернативой 400 предлагается 500
G>>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Б>Все равно это должна быть какая-то ошибка 4xx, а не 5xx
G>Почему? И какая?
Конкретно 400 может и не лучший вариант (есть и 404, и 409 и много чего еще, что может со временем поменяться), но 500 это точно ни в какие ворота.
Y>Тут коллега сделал хорошее замечание. Y>http://rsdn.org/forum/dotnet/8255491.1
Она просто стала адекватной де-факто использованию для API. Потому что набор стандартных 4хх далеко не исчерпывающий, и если ни один из них не подходит — используют 400.
НС>Нюанс тут, что альтернативой 400 предлагается 500 НС>
G>>>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Б>>Все равно это должна быть какая-то ошибка 4xx, а не 5xx
G>>Почему? И какая?
НС>Конкретно 400 может и не лучший вариант (есть и 404, и 409 и много чего еще, что может со временем поменяться), но 500 это точно ни в какие ворота.
Я бы сказал, что общепринятые коды ошибок вообще несколько отстали от жизни. 404 — вообще двусмысленная вещь.
Y>>Тут коллега сделал хорошее замечание. Y>>http://rsdn.org/forum/dotnet/8255491.1
НС>Она просто стала адекватной де-факто использованию для API. Потому что набор стандартных 4хх далеко не исчерпывающий, и если ни один из них не подходит — используют 400.
Y>Про 400 и 500 это был провокационный вопрос.
Y>Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Y>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Y>Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем.
Да, в том сообщении была приведена слишком жёсткая формулировка. Имеется в виду — самопроизвольно меняться со временем.
Y>Однако в нашем случае запрос был понят сервером и он синтаксически правильный. И его вполне можно повторять без изменения, пока не заработает. Y>Или что тогда означает malformed syntax?
Вы напрасно пытаетесь найти сакральный смысл в дословном переводе примеров ошибок клиента. 400 — это любая ошибка клиента, для которой нет более специфичного 4xx-кода.
Y>Здесь уже не сказано, что клиенту не следует повторять запрос. И даётся примерный список причин для 400: malformed request syntax, invalid request message framing, or deceptive request routing. Y>Но это всё технические проблемы, а не бизнес-процесс. Однако лично я согласен, что ограничения бизнес-процесса тоже годятся.
Дело не столько в ограничениях бизнес-процесса. Вопрос в том, кто именно допустил ошибку — клиент или сервер.
К примеру, если вы делаете сервис перевода денег со счёта на счёт, то способов сделать ошибку — много.
Самое простое — напороть в формате. Допустим, вы передали какой-то мусор вместо номера счёта или суммы для перевода. Это совершенно точно должно быть ошибкой 400, а не 5xx из-за исключения, вылетевшего из Decimal.Parse().
Затем — более интересные штуки. Например, номер счёта по формату валиден, но счёта такого нет. Опять — мы не перехватываем NullReferenceException с отдачей 500, а возвращаем 404.
Важное решение — как обрабатывать безопасность. Допустим, есть простое правило — счёт-источник должен принадлежать текущему пользователю. Мы могли бы возвращать 403 при нарушении этого правила. Но, быть может, мы захотим отдавать 404, чтобы не давать способа проверки существования счёта посторонним.
Вернёмся к ошибкам. Можно ли указывать один и тот же счёт в качестве источника и приёмника? Если нельзя — то это тоже 400.
То же самое — если на счету не хватает средств.
И так далее. 5хх означает, что сервер собирался выполнить запрос, но не смог.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, yenik, Вы писали:
Y>Я бы сказал, что общепринятые коды ошибок вообще несколько отстали от жизни.
А я бы не сказал. Есть некоторый бардак в использовании кодов от WebDAV, но это мелочи.
Тут надо понимать, что кодов возврата, любых, все равно недостаточно. И нужен все равно некий идентификатор ошибки, например что то типа type в RFC 7807. И на него должна полагаться клиентская логика. А именно коды нужны не столько для клиентской логики, сколько для инфраструктуры — браузера с кешами и окошками ввода паролей, интеллектуальных проксей и роутеров и т.п. И в этом плане существующих кодов вполне достаточно, пока не появится какой то новый с точки зрения инфраструктуры сценарий.
Для примера — в текущем проекте анализа именно статуса можно пересчитать по пальцам одной руки. Самый частый — это флажок notFoundAsDefault в базовом клиенте. Если он установлен в true, то возврат 404 приводит к возврату default из метода, а если false — к выбросу исключения.
Y>Про 400 и 500 это был провокационный вопрос.
Y>Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Y>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Y>Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем.
Конечно речь об изменениях, которые происходят независмо от запросов клиента.
Иначе можно дойти до того, что надо 500 кидать вместо 404 при отсутствии ресурса, ведь через секунду кто-то может сделать PUT на этот адрес и создать ресурс.
Здравствуйте, gandjustas, Вы писали:
Y>>Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем. G>Конечно речь об изменениях, которые происходят независмо от запросов клиента.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, yenik, Вы писали:
Y>>Я бы сказал, что общепринятые коды ошибок вообще несколько отстали от жизни.
НС>А я бы не сказал. Есть некоторый бардак в использовании кодов от WebDAV, но это мелочи. НС>Тут надо понимать, что кодов возврата, любых, все равно недостаточно. И нужен все равно некий идентификатор ошибки, например что то типа type в RFC 7807. И на него должна полагаться клиентская логика. А именно коды нужны не столько для клиентской логики, сколько для инфраструктуры — браузера с кешами и окошками ввода паролей, интеллектуальных проксей и роутеров и т.п. И в этом плане существующих кодов вполне достаточно, пока не появится какой то новый с точки зрения инфраструктуры сценарий.
О том и речь. Для клиентских приложений коды неадекватны. Хотя задумывались они в те времена, когда интеллектуальные прокси были ещё не в ходу, и коды предназначались для клиентов.
Y>>Про 400 и 500 это был провокационный вопрос.
Y>>Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Y>>Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Y>>Однако мы говорим, что 400 можно и нужно кидать для ситуаций, которые могут меняться со временем. S>Да, в том сообщении была приведена слишком жёсткая формулировка.
Просто неправильная — "нельзя" вместо "можно и нужно". И "для любых rntime эксепшенов" — тоже неверно.
S>Имеется в виду — самопроизвольно меняться со временем.
Самопроизвольно — это как спонтанное деление атомов?
Y>>Однако в нашем случае запрос был понят сервером и он синтаксически правильный. И его вполне можно повторять без изменения, пока не заработает. Y>>Или что тогда означает malformed syntax? S>Вы напрасно пытаетесь найти сакральный смысл в дословном переводе примеров ошибок клиента. 400 — это любая ошибка клиента, для которой нет более специфичного 4xx-кода.
Вы напрасно приписываете мне поиск чего-то сакрального. Как раз наоборот, я указываю на несакральность RFC.
S>Вернёмся к ошибкам. Можно ли указывать один и тот же счёт в качестве источника и приёмника? Если нельзя — то это тоже 400. S>То же самое — если на счету не хватает средств.
S>И так далее. 5хх означает, что сервер собирался выполнить запрос, но не смог.
Если на счету не хватает средств — это как раз и означает, что сервер собирался, но не смог.
Здравствуйте, yenik, Вы писали:
Y>О том и речь. Для клиентских приложений коды неадекватны. Хотя задумывались они в те времена, когда интеллектуальные прокси были ещё не в ходу, и коды предназначались для клиентов.
Ты зря так думаешь. К примеру, логика кеширования задумывалась на очень ранних этапах развития HTTP, и она уже по полной опирается на статускоды.
Здравствуйте, yenik, Вы писали:
Y>Просто неправильная — "нельзя" вместо "можно и нужно". И "для любых runtime эксепшенов" — тоже неверно.
Нет.
S>>Имеется в виду — самопроизвольно меняться со временем. Y>Самопроизвольно — это как спонтанное деление атомов?
Это как без изменения состояния приложения. То есть, к примеру, кончилось место на диске — и сервер выдаёт 500. Потом приходит админ, чистит логи, и сервер начинает работать.
Состояние приложения — не изменилось. Изменилось состояние сервера.
Или, к примеру, кто-то шаловливыми ручками полазил в сервере, с одного из фолдеров слетели пермиссии. Пока админ не вставит пермиссии на место, само ничего не заработает — но эти пермиссии не являются частью состояния приложения, поэтому возвращаем 500.
А вот если, скажем, у пользователя отобраны права на какой-то объект приложения, например на какой-то форум, то мы отдаём 403, а не 500. Потому что для того, чтобы ситуация изменилась, недостаточно перезапустить инсталлятор, или восстановиться из бэкапа, или почистить диск.
Y>Вы напрасно приписываете мне поиск чего-то сакрального. Как раз наоборот, я указываю на несакральность RFC.
Тогда зачем вы сосредотачиваетесь на трактовке malformed syntax?
S>>И так далее. 5хх означает, что сервер собирался выполнить запрос, но не смог. Y>Если на счету не хватает средств — это как раз и означает, что сервер собирался, но не смог.
Нет. Даже и не собирался. Это не сервер не смог, это состояние приложения не позволяет продолжать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Y>>Просто неправильная — "нельзя" вместо "можно и нужно". И "для любых runtime эксепшенов" — тоже неверно. S>Нет.
Y>>Вы напрасно приписываете мне поиск чего-то сакрального. Как раз наоборот, я указываю на несакральность RFC. S>Тогда зачем вы сосредотачиваетесь на трактовке malformed syntax?
Уже ответил.
S>>>И так далее. 5хх означает, что сервер собирался выполнить запрос, но не смог. Y>>Если на счету не хватает средств — это как раз и означает, что сервер собирался, но не смог. S>Нет. Даже и не собирался. Это не сервер не смог, это состояние приложения не позволяет продолжать.
Ах вот о чём речь, сервер отдельно, а приложение отдельно. Да, причин для отказа сервера может быть бесчисленное множество, но мы не будем их рассматривать, речь не об этом.
В случае с нехваткой средств сервер как раз собирался и смог, и вернул код из приложения.
Таким образом, если на счету не хватает средств, то должен быть возвращён код 400. И это ситуация, которая поменяется со временем. В следующий раз средств хватит и, и запрос выполнится успешно. И это противоречит утверждению:
Ответ в стандарте — если клиент получает 400, то он не должен пытаться повторить запрос с теми же параметрами.
Это означает, что 400 нельзя кидать для ситуаций, которые могут меняться со временем и для любых rntime эксепшенов.
Остаётся вопрос, можем ли мы обобщить это так, что любые ошибки приложения (кроме отказов инфраструктуры) должны возвращать 4хх?
Здравствуйте, 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щи!
И да будет милостив модератор.