— все-таки топят за Result objects для бизнес-логики.
А ведь это всю цепочку поддерживать. Как-то много лишних букв добавляется.
Но! Exception как бы не определены в контрактах. Если ResultObject — это контракт, его понятно что нужно проверить и компилятор даже контролирует эту проверку. А Exception разве что в документации описан, которую могут не читать.
А ведь было же хорошее решение — т.н. проверяемые исключения в Java. Когда компилятор требовал проверки того или иного исключения, но так же была возможно обернуть в RuntimeException, если оно утратило смысл бизнес-логики или ожидаемого
Но, как оказалось, народ идеи не понял. Так и вернулись к понятным дебилоидам кодам возврата, т.к. проверяемые исключения осилить не смогли. Так же и в Kotlin их решили не делать.
Здравствуйте, Shmj, Вы писали:
S>Ну вот рекомендации в новомодных языках: https://docs.flutter.dev/app-architecture/design-patterns/result
S>- все-таки топят за Result objects для бизнес-логики.
S>А ведь это всю цепочку поддерживать. Как-то много лишних букв добавляется.
Современные result objects позволяют не описывать все варианты проблем по всей цепочке вызовов, а хранить их "динамически", восстанавливая
тип ошибки в месте обработки. В расте, например, весьма популярны Result<(), Box<dyn Error>> и anyhow::Result<()>
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, SomeOne_TT, Вы писали:
SO_>Современные result objects позволяют не описывать все варианты проблем по всей цепочке вызовов, а хранить их "динамически", восстанавливая SO_>тип ошибки в месте обработки. В расте, например, весьма популярны Result<(), Box<dyn Error>> и anyhow::Result<()>
А что не современные этого не позволяли? В чем разница? Что принципиально нового появилось?
Коды возврата использовали еще деды. Основная претензия была — из можно было забыть проверить.
С проверяемыми исключениями вам доводилось сталкиваться, как в Java?
=сначала спроси у GPT=
Re: Result objects - все-таки победили Exceptions?
Моду на коды ошибок вместо исключений ввели разработчики Rust, причём только потому, что не смогли совместить исключения и Borrow Checker. Других причин не было. Вся чушь, которую написали после: про скорость, или там надёжность с безопасностью — чистая идеологическая хрень. Написать "мы облажались, терпите" означало растерять фан-базу.
Деды программирования наелись кодов ошибок по полной задолго до нас. И, имея в тысячи раз меньше памяти и медленнее процессора, эти деды таки сделали исключения. Что ждёт rust и другие языки — наворачивание обёрток и макросов для "удобного проброса" кодов ошибок и стектрейсов наверх. И в итоге они получат те же исключения. Есть шанс, что, при нормальном метапрограммировании, получится даже круче и фичастее чем то, что сейчас разработчики компиляторов хардкодят. Например, декларативно задавать, какие операции перезапускаемые, или в конфигах описывать какие исключения обязательно должны пойти в лог. Или автоматическое сохранение и передача всяких Trace-Id.
Языки, у которых макросов и метапрограммирования нет, и при этом всё сделано на кодах ошибок — пригодны только для мелких проектов, и, даже если будут какое-то время популярны на хайпе, в итоге вымрут. Просто потому, что на языках, где писать легче, кода напишут больше. А больше кода — это больше проектов в которых нужны программисты, лучше подсказки ИИ, больше информации в интернете, и больше вакансий. С учётом этого, тратить время на языки без исключений и метапрограммирования, можно только за счёт работодателя, и за очень большие деньги. Во всех остальных случаях, любой хайп вокруг них можно смело игнорить.
S>А ведь было же хорошее решение — т.н. проверяемые исключения в Java. Когда компилятор требовал проверки того или иного исключения, но так же была возможно обернуть в RuntimeException, если оно утратило смысл бизнес-логики или ожидаемого
Хельсберг очень подробно когда-то объяснял, почему эту фичу в C# не потащили. Кмк, что реально нужно — возможность указать no throw, и throws(список). Чтобы те, кому нужна железная уверенность что всё перехватывается, могли желаемую надёжность получить. А те, кому достаточно вывести на экран сообщение "коннекта к базе нет", могли писать db.Connect() и радоваться что единственный try/catch в main решает все их проблемы.
Re: Result objects - все-таки победили Exceptions?
Здравствуйте, Shmj, Вы писали:
S>Но, как оказалось, народ идеи не понял. Так и вернулись к понятным дебилоидам кодам возврата, т.к. проверяемые исключения осилить не смогли. Так же и в Kotlin их решили не делать.
Дебилоиды это те, кто не понимает, что каждому инструменту своё применение.
Допустим, ProcessData() это дорогая операция (рендер на 12 часов для десктопной программы или облачные вычисления за 1000$ для серверной). А при попытке SaveData() происходит облом. Если это серверная программа, то база не соединяется, а если десктопная, то файловая операция не проходит. В этом случае надо вернуть код ошибки, чтобы алгоритм можно было переписать так (серверная программа):
while (NotOkay == SaveData(processedData) && Yes == AskUser("Попытаться обратно?"));
А если за каким-то хером внутри SaveData() оказывается, что processedData внезапно null или InvalidObject, то тогда у нас ИСКЛЮЧИТЕЛЬНАЯ ситуация. Весь алгоритм уже пошёл по звезде, и проверять результат бесполезно. В такой исключительной ситуации нужно выбрасывать исключение. Поэтому они и называются исключениями.
Единственная причина полностью отказаться от исключений — технические соображения. Лично я считаю, что в отсутствие виртуальной машины вреда от исключений больше, чем пользы. Хотя бы потому, что если у нас unmanaged OS и проект состоит из разных модулей, написанных на разных языках с разными стандартными классами исключений, их заколебёшься приводить к одному знаменателю. Да и нахрен не всралось исключение, если неперехваченное оно не даёт при желании хромать дальше с восьмой цифры (как бывает без ВМ).
Но поскольку дебилоидов слишком много, то и общаются они на уровне "а давайте всегда выкидывать исключения ДЛЯ КОНСИСТЕНТНОСТИ". Жопу бы им начать вытирать зубной щёткой. Для консистентности.
Здравствуйте, Shmj, Вы писали:
S>А ведь было же хорошее решение — т.н. проверяемые исключения в Java. Когда компилятор требовал проверки того или иного исключения, но так же была возможно обернуть в RuntimeException, если оно утратило смысл бизнес-логики или ожидаемого
Я как-то показывал типичный код на Java
void MyCoolMethod(int a, int b) throws Exception
{
int p;
try
{
// 100500 строк кода
}
catch (Exception ex)
{
}
}
Лень разработчикам указывать все исключения, которые могут прилететь. Я в своё время запарился с таким кодом работать.
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, Privalov, Вы писали:
P>Лень разработчикам указывать все исключения, которые могут прилететь. Я в своё время запарился с таким кодом работать.
...Pokémon exception handling after the show's catchphrase "Gotta Catch ‘Em All!"
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
_>Хельсберг очень подробно когда-то объяснял, почему эту фичу в C# не потащили. Кмк, что реально нужно — возможность указать no throw, и throws(список).
Так в Java есть не проверяемые — это наследники RuntimeException.
=сначала спроси у GPT=
Re[2]: Result objects - все-таки победили Exceptions?
A>try
A>{
A> const data = LoadData();
A> const processedData = ProcessData(data);
A> SaveData(processedData);
A>}
A>catch
A>{
A> log.Error("Всё пропало");
A>}
A>
Так нельзя писать. Во-первых, всегда нужно указывать типы исключений. Но беда в том, что вы обычно не думаете об этом — у вас нет четкой картинки в голове какие исключения может вызвать функция. Т.е. вы теряете контроль. А у меня всегда 100% я знаю какие исключения возможны, я всегда это держу в голове для каждой функции.
Далее. Исключения нужно отлавливать для каждого шага отдельно — т.е. не все в куче — а везде, на каждом из шагов где могут возникнуть исключения — нужно конкретные отлавливать и обрабатывать, если возмжно.
=сначала спроси у GPT=
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, Privalov, Вы писали:
P>Лень разработчикам указывать все исключения, которые могут прилететь. Я в своё время запарился с таким кодом работать.
Ну так испортить можно любую идею.
=сначала спроси у GPT=
Re[3]: Result objects - все-таки победили Exceptions?
S>Так в Java есть не проверяемые — это наследники RuntimeException.
Делить исключения на проверяемые и нет — ошибка. В разных частях проекта даже одинаковые исключения могут быть как проверяемые так и нет.
Re[3]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
S>>Так в Java есть не проверяемые — это наследники RuntimeException. _>Делить исключения на проверяемые и нет — ошибка. В разных частях проекта даже одинаковые исключения могут быть как проверяемые так и нет.
Проверяемые — это часть контакта. Т.е. они хороши для бизнес-логики или основной логической ошибки утилиты.
В точке где исключение меняет акцент — переходит из проверяемого в непроверяемого — оборачиваете в RuntimeException.
Просто многие не разобрались с парадигмой и она стала только во вред.
=сначала спроси у GPT=
Re[4]: Result objects - все-таки победили Exceptions?
TB>try {
TB> var result = some_func();
TB>} catch (error) {
TB> // kak-to obrabotat
TB> throw error;
TB>}
TB>на каждый чих — это бест практис
Если нет обработки — то просто возвращаете результат. А в случае с кодами ошибок — вы возвращаете обертку. Постоянно перед глазами бойлерплейт код этой обертки.
=сначала спроси у GPT=
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
_>Моду на коды ошибок вместо исключений ввели разработчики Rust, причём только потому, что не смогли совместить исключения и Borrow Checker. Других причин не было. Вся чушь, которую написали после: про скорость, или там надёжность с безопасностью — чистая идеологическая хрень. Написать "мы облажались, терпите" означало растерять фан-базу.
Мне кажется, это прямое влияние Go. А там оно из Alef-а, который прямой предшественник Go.
И сделали они это так, потому, что им исключения не нравятся по сути своей. Тем, что разрывают поток исполнения программы, делая его нелинейным, а потому трудным для понимания человеком.
_>Деды программирования наелись кодов ошибок по полной задолго до нас.
Go/Alef — это даже не деды, а прадеды. Ричи, Томпсон, Пайк...
Re[3]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
_>Моду на коды ошибок вместо исключений ввели разработчики Rust, причём только потому, что не смогли совместить исключения и Borrow Checker. Других причин не было. Вся чушь, которую написали после: про скорость, или там надёжность с безопасностью — чистая идеологическая хрень. Написать "мы облажались, терпите" означало растерять фан-базу.
Ох, держите меня семеро. Это ж надо так на Раст наехать. Вы на нём не пишете? Может стоит сначала пописать/попробовать?
В Расте всё это обёрнуто так, что теперь это растаскивают во все языки, включая С++. Означает ли это что Растовцы облажались? Или может наоборот, нащупали что то в пыльной коробке?
Сахарок с вопросительным знаком отлично ложится в синтаксис, что это даёт: Вид ошибки прописан в прототипе функции Явно прокидываем ошибку, а не "я не знаю кинет оно или нет" что частенько приводит к сюрпризам в проде и перечитыванию доков. Просто нужно написать "?". При этом задумываешься. Ровно то же самое когда разворачиваешь optional<> (который тоже обретает лютую популярность в С++): всё что нужно сделать, написать "*", но задумываешься, ведь там объекта может и не быть.
В коде явно видно как ошибка обрабатывается.
Всегда есть опция обработать ошибку на месте, причём без специального очень шумного try/catch/finally/throw синтаксиса а просто получить тип и сматчить его как угодно — часто в одну строку (map, map_or_else, and, or, and_then, ...). Работа с обычной переменной.
Re[2]: Result objects - все-таки победили Exceptions?
Здравствуйте, Privalov, Вы писали:
P>Лень разработчикам указывать все исключения, которые могут прилететь. Я в своё время запарился с таким кодом работать.
В современных IDE нет никакой проблемы, чтобы указать. IDEA просто не даст откомпилировать метод, в котором выбрасывается checked исключение, потребует либо поставить try-catch, либо добавить throw, и сама это сделает.
With best regards
Pavel Dvorkin
Re: Result objects - все-таки победили Exceptions?
Здравствуйте, Shmj, Вы писали:
S>Ну вот рекомендации в новомодных языках: https://docs.flutter.dev/app-architecture/design-patterns/result
S>- все-таки топят за Result objects для бизнес-логики.
S>А ведь это всю цепочку поддерживать. Как-то много лишних букв добавляется.
Что ни делай — результат один и тот же.
Если обрабатывать надо не в точке вызова метода, в котором ошибка, а выше, то так или иначе придется передавать эту ошибку выше. Если использовать исключения, код обеспечит компилятор, если коды ошибок — пиши сам.
Исключения, в общем, проще.
У них ИМХО только один недостаток — их нельзя игнорировать. Коды ошибок можно, если знаешь, что тут что с ошибкой, что без ошибки — все равно.
Я как-то писал на C# удаление 0-го элемента из listbox. Все было замечательно, пока не оказалось, что в листбоксе нет вообще элементов. Я об этом не подумал, так как на Win API если послать сообщение LB_DELETESTRING при нулевом количестве элементов, то, конечно, получишь LB_ERR, но его можно спокойно проигнорировать, так как все, что нужно — чтобы элемента не было. Был он — чтобы его не было, не было — тоже чтобы не было, а какой из этих 2 вариантов сработал — неважно. А на C# пришлось условие ставить.