Re[5]: Result objects - все-таки победили Exceptions?
От: hi_octane Беларусь  
Дата: 07.01.25 19:13
Оценка:
SP>а почему не должны? Мы должны зависеть от абстракций, но не от реализаций. StreamWriteException — абстракция. Не вижу здесь проблемы. Если клиент не работает с сериализатором напрямую, пусть принимает на вход ещё более общую ошибку: IOError н-р.
А потому что NetworkStream может заиспользовать сертификаты, а менеджер сертификатов вообще относится к шифрованию, и совершенно не обязан CertificateException наследовать от IOError или ещё кого-то, особенно из-за сериализатора. Можно конечно заворачивать ошибку в ошибку, но зачем, какую проблему решаем?

SP>И вообще не ясно, как решают проблему непроверяемые исключения? Добавилась у вас новая ошибка MySpecificStreamError. Кто-то её перехватит? Или подождут релиза когда у клиента вылетит необработанное? Вот как-то не помню чтобы я сильно мучался с проверяемыми исключениями в java (было это правда давно), зато отчётливо помню как напарывался на необработанные исключения.

Да, это косяк многих языков с исключениям. Но он, как мне кажется, совсем не решается на той стороне где его описывают. Весь checked (в моих фантазиях) надо переводить туда, где пишется сам try. Т.е. добавить какой-то новый, особенный, reliable_try, от которого а компилятор/линкер/VM/CLR потребуют (и сами через вызовы проследят), что все исключения которые могут вылететь — имеют catch.
Re[5]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 07.01.25 19:36
Оценка:
·>Распараллелить кусок кода, и вот теперь у тебя проблема как пробрасывать исключения между тредами и что с ними делать потом.

Если "параллелить" по принципу "нашлепка сверху" (см. async/await), и исключения делать так же, то, действительно, так и есть. Но если параллельность (concurrency) с самого начала сделана как следует (с message passing в синтаксисе языка), и исключения тоже — в поведении есть логика. Выброс исключения в одном потоке, в зависимости от намерений программиста, может вызывать требуемые эффекты, причем очень элегантно. Базовые блоки (типа supervision) идут как часть stdlib, так что исключение может вести к повтору (retry) с заведомо рабочего состояния, или к прекращению выполнения операции (с возвратом ошибки), или вовсе конвертироваться в сообщение, которое будет передано процессу-родителю (trap exit).

Без исключений всю эту логику придется попросту навелосипедить.
Re[5]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 07.01.25 19:45
Оценка:
AD> Лично мне нравится подход с ? тем, что визуально просматривая код, ты всегда видишь где ошибка передаётся на уровень выше.

Исключения по сути делают ? по умолчанию. Потому что это более частый случай, очень редко когда ошибку можно обработать на уровне вызывающего кода. Куда чаще ее нужно пробросить выше.

Можно было бы придумать оператор ! , но не так как в perl'e || или в Расте с паникой, а чтоб он означал "ошибку обязан обработать здесь". Но семантически это не имеет смысла, если код обработки и так уже есть, оператор ! не нужен. В конечном итоге, все равно получается как в Elixir, там этот "nothrow" настолько прижилися, что во многих интерфейсах почти всегда есть два варианта, с ! (throw) и без такового. Не новая концепция, но определенно рабочая.
Re[3]: Result objects - все-таки победили Exceptions?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 07.01.25 20:20
Оценка:
Здравствуйте, Константин Б., Вы писали:

_>>Моду на коды ошибок вместо исключений ввели разработчики Rust


КБ>Так облажаться в первом же предложении. Result objects — это не коды ошибок.

КБ>Когда вы уже поймете что ваш сишный опыт абсолютно уже не релевантен.

Как я понял, это код ошибки + полезный результат, упакованное вместе. Те же яйца, вид сбоку
Маньяк Робокряк колесит по городу
Re[6]: Result objects - все-таки победили Exceptions?
От: ArtDenis Россия  
Дата: 07.01.25 20:48
Оценка: -1
Здравствуйте, SkyDance, Вы писали:

AD>> Лично мне нравится подход с ? тем, что визуально просматривая код, ты всегда видишь где ошибка передаётся на уровень выше.


SD>Исключения по сути делают ? по умолчанию. Потому что это более частый случай, очень редко когда ошибку можно обработать на уровне вызывающего кода. Куда чаще ее нужно пробросить выше.


Я имел виду, что ? показывают программисту места в коде, где ход потока выполнения может измениться, а в случае исключений этого не видно (если не считать всякие checked exceptions).

Хорошо ли это? Я думаю да. Знак вопроса не создаёт излишнего визуального шума и даёт при этом полезную информацию тому, кто читает код. Тут могут возразить те сторонники исключений, кому нафиг не сдалась инфа о том, что функция может бросить исключение. Но мне эта инфа нужна и знак ? в этом сильно помогает.
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[3]: Result objects - все-таки победили Exceptions?
От: Ilya81  
Дата: 07.01.25 21:13
Оценка:
Здравствуйте, hi_octane, Вы писали:

I>>А если catch только в конце — тогда как понять, что и где произошло, неужели stack разбирать?

_>В языках с исключениями никто не заставляет делать catch на каждом шаге. Оптимистично делают кучу связанных друг с другом по смыслу шагов, а если какой-то шаг вылетает — у исключения, в норме, есть вполне информативный тип, по которому проверяют, и решают как обработать. Один метод, который забирает из сети, парсит и сохраняет в файл — может не иметь ни одной строчки try, но бросать 3 типа исключений. И снаружи будет всего один код обработки всего сразу и в одном месте. А этот код уже смотрит: SocketException — проблемы с сетью, FileNotFound — проблемы с файлом, и т.д.

Проблемы с сетью какие — вообще до back-end не достучаться оказалось, или какие-то данные не загрузились? А в первом случае — на устройстве пользователя вообще связи нет, или подключение сейчас к кривому wi-fi, который половину адресов не находит, а может что-то только что под блокировку попало? А если подробности зависят от того, с чего запрос начинался, в exception это как искать.

_>А иногда можно и ничего не делать, повторить операцию, и бывает прокатывает.


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

_>Этот подход, вообще-то, уже отлично проявил себя в базах данных. Начали большую транзакцию, не вышло — ну и откатили. Но выкати БД, с заявой "в нашей идеологии если транзакция не прошла — то ложится вся база", и, мягко говоря, люди не поймут


Не понял идеи. Если не вышло, не возникает ли вопрос, какого *эпитет* не вышло. Или типа предлагать попробовать позже, авось когда-нибудь сработает, может, через час, может через год, может через пару-тройку тысячелетий.

_>Кстати, мало кто знает, почему в Rust трейт std::Error (кто не в теме — аналог интрефеса для ошибки) вполне официально советует сообщение об ошибке давать в нижнем регистре, с минимумом пунктуации. А всё потому, что периодически информацию об ошибке только из сообщения и получают.


В смысле из сообщения в exception разбирать, что произошло? Если так, то это даже похлеще, чем разбирать call stack.

I>>Try catch на каждом шаге — мало того, что громоздко, оно вроде ещё и по части производительности так себе.

_>Именно что вроде. Как только начинают проверять реальный код — цифры могут быть очень разными. И программы с исключениями, и программы с кодами ошибок пишутся оптимистично — т.е. из расчёта что ошибок будет минимум (а исключений, соответственно, будет мало).

Про производительность я, конечно, не уверен, но что если inet у пользователя сбоит именно сейчас, или что-то только что оказалось под блокировкой? Но если в конце только ставить catch то там разве что и вправду какие-то экстраординарные методы нужны, чтоб понять, что всё-таки было. А если информация дополняется в процессе, то понятно, что делалось и где сбойнуло.

Если где-то framework делался во времена, когда ещё не были понятны все недостатки try-catch, то, естественно, поменять после уже сложно. Но если всё-таки однажды находятся способы сделать лучше, по мне это хорошо. Если оно и впрямь лучше, конечно, как в swift есть enum с параметрами, а где такого нет, то, конечно, дополнять информацию непросто будет.
Re[6]: Result objects - все-таки победили Exceptions?
От: · Великобритания  
Дата: 07.01.25 21:40
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>·>Распараллелить кусок кода, и вот теперь у тебя проблема как пробрасывать исключения между тредами и что с ними делать потом.

SD>Если "параллелить" по принципу "нашлепка сверху" (см. async/await), и исключения делать так же, то, действительно, так и есть. Но если параллельность (concurrency) с самого начала сделана как следует (с message passing в синтаксисе языка),
Это от парадигмы зависит. Вызов метода в ООП — это и есть message passing. Т.е. по сути — коллбэки. Так себе.

SD>и исключения тоже — в поведении есть логика. Выброс исключения в одном потоке, в зависимости от намерений программиста, может вызывать требуемые эффекты, причем очень элегантно. Базовые блоки (типа supervision) идут как часть stdlib, так что исключение может вести к повтору (retry)

В терминах ООП — это уже какой-нибудь декоратор.

SD> с заведомо рабочего состояния, или к прекращению выполнения операции (с возвратом ошибки), или вовсе конвертироваться в сообщение, которое будет передано процессу-родителю (trap exit).

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

SD>Без исключений всю эту логику придется попросту навелосипедить.

error handling — одна из самых сложных проблем программирования.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 08.01.25 00:34
Оценка: +1
AD>Хорошо ли это? Я думаю да. Знак вопроса не создаёт излишнего визуального шума и даёт при этом полезную информацию тому, кто читает код. Тут могут возразить те сторонники исключений, кому нафиг не сдалась инфа о том, что функция может бросить исключение. Но мне эта инфа нужна и знак ? в этом сильно помогает.

Дело в том, что любая функция в любой момент может бросить исключение. Не OutOfMemory, так OutOfStack. И обработать это исключение можно только многими уровнями выше. Поэтому для меня это лишь бессмысленный шум, своеобразный карго-культ.
Re[7]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 08.01.25 00:45
Оценка: 1 (1)
·>Это от парадигмы зависит. Вызов метода в ООП — это и есть message passing.

Разве что в теории. На практике, в известных мне реализациях ООП вызов осуществляется синхронно, и синхронно же ждется, пока вызов что-то вернет. Это уж совсем не message passing.

·>В терминах ООП — это уже какой-нибудь декоратор.


Такой декоратор должен быть потокобезопасным и весьма изощренным. То есть, ожидаемо, он должен реализовывать половину того, что может быть реализовано общим образом в синтаксисе и рантайме языка.

·>Проблема исключений, что они нелокальны. Действия в одном куске кода внезапно перескакивают в совершенно другой кусок кода, без явной связи между скопами.


Это не проблема, а назначение! Правила "перескакивания" (та самая явная связь) между областями ответственности как раз и задаются с помощью синтаксиса языка. А где язык этого не может, городят декораторы.

·>error handling — одна из самых сложных проблем программирования.


Ну, ей далеко до нейминга и кэшинга!
Re[5]: Result objects - все-таки победили Exceptions?
От: Qulac Россия  
Дата: 08.01.25 09:35
Оценка:
Здравствуйте, hi_octane, Вы писали:

Q>>Тут мы подходим к тому, что у ошибок может быть разная сематика. Тот случай который я описал, если возникло исключение — это означает, что в программе баг и правильным способом будет закрыть программу с записью в лог об исключении. В случае если у объекта часть состояния находится за пределами программы(сетевые подключения, файлы и т.д) т.е. то за что программа не может нести ответственность, тут обычно требуется более сложная логика обработки ошибок. Тут мы можем определить разные классы exception и производить обработку для каждого вида ошибок


_>Я в твоём сообщении не могу отделить ошибки от исключений. А хочется определённости — в итоге-то что? Парсеру json, если пришёл битый, что делать — кидать "просто ошибку" или исключение, которое приведёт к закрытию программы? Ну и чтоб два раза не вставать — выход за границу массива это ошибка или исключение, которое должно всё сломать? А невозможность выделить память?


_>Самый прикол, что решение "исключения существуют, и это нормально, главное что мы их можем перехватывать и обрабатывать" — даёт универсальный и единообразный подход к самым разным проблемам. А вот подход — "у нас в языке исключений нет, ну может чуть-чуть, на донышке" — приводит к тупику. Решение rust как раз в духе, в моём понимании, дибилизма — делайте всего по 2.


Тут смысл в чем: в первом случае у нас объект находится полностью в нашей программе и выброс исключения означает ошибку в коде, во втором случая часть объекта как бы находится за пределами программы, например какой ни будь DataAdapter, в этом случае исключение может говорить об проблемах в БД, а не в программе. У этих ошибок разный смысл. Тоже самое с json парсером.
Программа – это мысли спрессованные в код
Re[6]: Result objects - все-таки победили Exceptions?
От: Shmj Ниоткуда  
Дата: 08.01.25 10:38
Оценка:
Здравствуйте, hi_octane, Вы писали:

_>А потому что NetworkStream может заиспользовать сертификаты, а менеджер сертификатов вообще относится к шифрованию, и совершенно не обязан CertificateException наследовать от IOError или ещё кого-то, особенно из-за сериализатора. Можно конечно заворачивать ошибку в ошибку, но зачем, какую проблему решаем?


Все что не является частью контракта — оборачиваете в Runtime Exception. Они не проверяемые. Все просто.

Проверяемые — это там где вам хочется впихнуть Result object.
=сначала спроси у GPT=
Re[6]: Result objects - все-таки победили Exceptions?
От: Sharov Россия  
Дата: 08.01.25 11:01
Оценка:
Здравствуйте, hi_octane, Вы писали:


S>>Слышали ли вы что-нибудь про наследование?

_>Слышал. Вот есть базовый Stream, у него checked фиксированный набор исключений. И вдруг появляется новый NetworkStream, которому надо добавить новый CertificateException. А добавить низзя: базовый стрим менять не положено — на его checked уже весь клиентский код завязался. У пользователей уже свои checked зависят от тех checked что были у старого Stream. Приехали. В Java это проблему решили "элегантно" — забили на checked. Могли бы иначе — решили бы иначе.

Не знаком с концепцией checked exp, но почему CertificateException нельзя добавить на уровень NetworkStream, а не лезть в базовый класс? Непонятно, правд, как быть с полиморфизмом, если тип Stream, а передаётся NetworkStream...
Кодом людям нужно помогать!
Re[8]: Result objects - все-таки победили Exceptions?
От: Ilya81  
Дата: 08.01.25 11:14
Оценка:
Здравствуйте, hi_octane, Вы писали:

S>>Так сделайте наследника StreamException — в чем вопрос

_>Ага, и тут у разработчиков менеджера сертификатов дилемма — делать CertificateException наследником StreamException, или HTTPException, или послать всех нафиг
struct StreamError: Error {
  details: StreamErrorDetails 
}
protocol StreamErrorDetails {}
enum NetworkStreamError: Error, StreamErrorDetails {
  case certificate(details: CertificateError)
//... something else 
}


И result здесь вроде даже удобнее, но можно и throw делать, если хочется.
Re[8]: Result objects - все-таки победили Exceptions?
От: · Великобритания  
Дата: 08.01.25 14:02
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>·>Это от парадигмы зависит. Вызов метода в ООП — это и есть message passing.

SD>Разве что в теории. На практике, в известных мне реализациях ООП вызов осуществляется синхронно, и синхронно же ждется, пока вызов что-то вернет. Это уж совсем не message passing.
sync/async — ортогонально message passing.

SD>·>В терминах ООП — это уже какой-нибудь декоратор.

SD>Такой декоратор должен быть потокобезопасным и весьма изощренным. То есть, ожидаемо, он должен реализовывать половину того, что может быть реализовано общим образом в синтаксисе и рантайме языка.
А зачем обязательно в синтаксисе? Достаточно какой-нибудь concurrent queue на библиотечном уровне, пусть реализованный изощрённо, без разницы.

SD>·>Проблема исключений, что они нелокальны. Действия в одном куске кода внезапно перескакивают в совершенно другой кусок кода, без явной связи между скопами.

SD>Это не проблема, а назначение! Правила "перескакивания" (та самая явная связь) между областями ответственности как раз и задаются с помощью синтаксиса языка. А где язык этого не может, городят декораторы.
Угу, назначение. Как и у оператора goto, например. Проблемой это быть не перестаёт.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 08.01.25 19:42
Оценка:
S>Проверяемые — это там где вам хочется впихнуть Result object.

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

Так что, выходит, у нас либо ? на вызывающей стороне, или !
IMHO ! куда удобнее, т.к. требуется заметно реже, чем checked.
Re[9]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 08.01.25 19:54
Оценка: 1 (1)
·>sync/async — ортогонально message passing.

Э нет. Message passing по определению async. Как только у тебя появился wait, да к тому же строго ограниченный определенным предыдущим message, то все, это уже никакой не message passing.

·>А зачем обязательно в синтаксисе?


Потому что иначе получается фантастическое уродство, типа как в Go. Смотри, как элегантно можно в Erlang:
  actor2 ! {do_smth, arg, 2},
  actor3 ! farewall,
  receive
    {success, Result} -> Result;
    %% а тут можно "поймать" исключение из связанного актора, например, так - {'EXIT', Reason} -> <обработка>
  after 1000 -> throw {"timed out"}
  end.


Очень выразительно. Мне недавно пришлось примерно такое на C# писать, так там 4 экрана кода и всякая магия c DI.

·>Угу, назначение. Как и у оператора goto, например. Проблемой это быть не перестаёт.


У goto проблема в отсутствии (enforced) правил поведения. У исключения такие правила есть.
Re[8]: Result objects - все-таки победили Exceptions?
От: Shmj Ниоткуда  
Дата: 08.01.25 19:59
Оценка:
Здравствуйте, SkyDance, Вы писали:

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


Так это уже вызывающий код решает — обернуть в RuntimeException — снять акцентуацию. Сделать inner (исключение — часть контракта, вложенная). Или же оставить проверяемым.
=сначала спроси у GPT=
Re[9]: Result objects - все-таки победили Exceptions?
От: SkyDance Земля  
Дата: 08.01.25 21:39
Оценка:
S>Так это уже вызывающий код решает — обернуть в RuntimeException — снять акцентуацию.

В сокращенном варианте именно это и достигается с помощью ? или !.
Re[10]: Result objects - все-таки победили Exceptions?
От: korvin_  
Дата: 08.01.25 22:53
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Э нет. Message passing по определению async.


По какому определению?
Re[3]: Result objects - все-таки победили Exceptions?
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.01.25 09:39
Оценка: 1 (1) +1
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>В современных IDE нет никакой проблемы, чтобы указать. IDEA просто не даст откомпилировать метод, в котором выбрасывается checked исключение, потребует либо поставить try-catch, либо добавить throw, и сама это сделает.
Примерно 25 лет тому назад парни из Редмонда проделали простое упражнение: посмотрели на кодовую базу реальных проектов на Java.
И внезапно оказалось, что почти все абьюзят checked exceptions. Они прекрасно выглядят в теории, но на практике никакая IDE не помогает решить их фундаментальную проблему: они запечены навечно в сигнатурах интерфейсов.

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

Середина этого дерева знать не знает про эту специфическую ситуацию; её спроектировали в те времена, когда никому и в голову не приходило, что там внутри может что-то сломаться таким способом.
В мире checked exceptions у нас есть три плохих способа это решить:
1. Пройтись вверх по стеку вызовов и везде подправить сигнатуры, добавив MySpecificException к списку
Это зачастую невозможно (потому что в стеке — четыре библиотеки от семи разных поставщиков, из которых девятеро уже давно ушли с рынка и их код никто не маинтейнит), да и вредно (потому что часть этих библиотек используется ещё в сорока местах нашего проекта, которым совершенно неинтересна обязанность обрабатывать ещё и MySpecificException)
2. Отнаследоваться от какого-то исключения, которое в сигнатурах уже есть
Это если в сигнатурах есть хоть что-то, и это что-то — не final.
3. Завернуть MySpecificException в UniversalException
Это если в сигнатурах есть UniversalException, в котором предусмотрено место для InnerException
4. Выбросить потомка RuntimeException

В итоге практически реализуемым становится четвёртый способ, который сводит всю идею проверяемых исключений на нет.
Именно поэтому в дотнете нету checked exceptions.

А вот если идти по пути "result object", то мы остаёмся в поле традиционной системы типов.
Вот есть у нас библиотечная функция map, которая принимает коллекцию и трансформер f вида Func<T, R>. Нам совершенно всё равно, может ли возникнуть ошибка при вычислении f над конкретным экземпляром T или нет.
Потому что всё это "спрятано" внутри типа R. Если f — это функция 1/x, то она возвращает тип "number | undefined" (или там "number | DivisionByZero"). Тип результата map соответственно будет Iterable<number | undefined> и обязанность принять решение, что с этим делать, остаётся у того, кто вызывает функцию map, а не у автора этой функции. Ровно так, как этого хотели авторы идеи исключений.
А если f — это функция x ^ 0xF, то у неё тип результата — просто number, и вызов map с ней в качестве аргумента получит Iterable<number>.
Написать функцию IEnumerable<R> map(IEnumerable<T> source, Func<T, R> f) c проверяемыми исключениями не представляется возможным. В ней функция f не имеет права ничего выбрасывать, потому что map пообещала ничего не выбрасывать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.