SP>а почему не должны? Мы должны зависеть от абстракций, но не от реализаций. StreamWriteException — абстракция. Не вижу здесь проблемы. Если клиент не работает с сериализатором напрямую, пусть принимает на вход ещё более общую ошибку: IOError н-р.
А потому что NetworkStream может заиспользовать сертификаты, а менеджер сертификатов вообще относится к шифрованию, и совершенно не обязан CertificateException наследовать от IOError или ещё кого-то, особенно из-за сериализатора. Можно конечно заворачивать ошибку в ошибку, но зачем, какую проблему решаем?
SP>И вообще не ясно, как решают проблему непроверяемые исключения? Добавилась у вас новая ошибка MySpecificStreamError. Кто-то её перехватит? Или подождут релиза когда у клиента вылетит необработанное? Вот как-то не помню чтобы я сильно мучался с проверяемыми исключениями в java (было это правда давно), зато отчётливо помню как напарывался на необработанные исключения.
Да, это косяк многих языков с исключениям. Но он, как мне кажется, совсем не решается на той стороне где его описывают. Весь checked (в моих фантазиях) надо переводить туда, где пишется сам try. Т.е. добавить какой-то новый, особенный, reliable_try, от которого а компилятор/линкер/VM/CLR потребуют (и сами через вызовы проследят), что все исключения которые могут вылететь — имеют catch.
Re[5]: Result objects - все-таки победили Exceptions?
·>Распараллелить кусок кода, и вот теперь у тебя проблема как пробрасывать исключения между тредами и что с ними делать потом.
Если "параллелить" по принципу "нашлепка сверху" (см. async/await), и исключения делать так же, то, действительно, так и есть. Но если параллельность (concurrency) с самого начала сделана как следует (с message passing в синтаксисе языка), и исключения тоже — в поведении есть логика. Выброс исключения в одном потоке, в зависимости от намерений программиста, может вызывать требуемые эффекты, причем очень элегантно. Базовые блоки (типа supervision) идут как часть stdlib, так что исключение может вести к повтору (retry) с заведомо рабочего состояния, или к прекращению выполнения операции (с возвратом ошибки), или вовсе конвертироваться в сообщение, которое будет передано процессу-родителю (trap exit).
Без исключений всю эту логику придется попросту навелосипедить.
Re[5]: Result objects - все-таки победили Exceptions?
AD> Лично мне нравится подход с ? тем, что визуально просматривая код, ты всегда видишь где ошибка передаётся на уровень выше.
Исключения по сути делают ? по умолчанию. Потому что это более частый случай, очень редко когда ошибку можно обработать на уровне вызывающего кода. Куда чаще ее нужно пробросить выше.
Можно было бы придумать оператор ! , но не так как в perl'e || или в Расте с паникой, а чтоб он означал "ошибку обязан обработать здесь". Но семантически это не имеет смысла, если код обработки и так уже есть, оператор ! не нужен. В конечном итоге, все равно получается как в Elixir, там этот "nothrow" настолько прижилися, что во многих интерфейсах почти всегда есть два варианта, с ! (throw) и без такового. Не новая концепция, но определенно рабочая.
Re[3]: Result objects - все-таки победили Exceptions?
Здравствуйте, Константин Б., Вы писали:
_>>Моду на коды ошибок вместо исключений ввели разработчики Rust
КБ>Так облажаться в первом же предложении. Result objects — это не коды ошибок. КБ>Когда вы уже поймете что ваш сишный опыт абсолютно уже не релевантен.
Как я понял, это код ошибки + полезный результат, упакованное вместе. Те же яйца, вид сбоку
Здравствуйте, SkyDance, Вы писали:
AD>> Лично мне нравится подход с ? тем, что визуально просматривая код, ты всегда видишь где ошибка передаётся на уровень выше.
SD>Исключения по сути делают ? по умолчанию. Потому что это более частый случай, очень редко когда ошибку можно обработать на уровне вызывающего кода. Куда чаще ее нужно пробросить выше.
Я имел виду, что ? показывают программисту места в коде, где ход потока выполнения может измениться, а в случае исключений этого не видно (если не считать всякие checked exceptions).
Хорошо ли это? Я думаю да. Знак вопроса не создаёт излишнего визуального шума и даёт при этом полезную информацию тому, кто читает код. Тут могут возразить те сторонники исключений, кому нафиг не сдалась инфа о том, что функция может бросить исключение. Но мне эта инфа нужна и знак ? в этом сильно помогает.
Здравствуйте, 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?
Здравствуйте, 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?
AD>Хорошо ли это? Я думаю да. Знак вопроса не создаёт излишнего визуального шума и даёт при этом полезную информацию тому, кто читает код. Тут могут возразить те сторонники исключений, кому нафиг не сдалась инфа о том, что функция может бросить исключение. Но мне эта инфа нужна и знак ? в этом сильно помогает.
Дело в том, что любая функция в любой момент может бросить исключение. Не OutOfMemory, так OutOfStack. И обработать это исключение можно только многими уровнями выше. Поэтому для меня это лишь бессмысленный шум, своеобразный карго-культ.
Re[7]: Result objects - все-таки победили Exceptions?
·>Это от парадигмы зависит. Вызов метода в ООП — это и есть message passing.
Разве что в теории. На практике, в известных мне реализациях ООП вызов осуществляется синхронно, и синхронно же ждется, пока вызов что-то вернет. Это уж совсем не message passing.
·>В терминах ООП — это уже какой-нибудь декоратор.
Такой декоратор должен быть потокобезопасным и весьма изощренным. То есть, ожидаемо, он должен реализовывать половину того, что может быть реализовано общим образом в синтаксисе и рантайме языка.
·>Проблема исключений, что они нелокальны. Действия в одном куске кода внезапно перескакивают в совершенно другой кусок кода, без явной связи между скопами.
Это не проблема, а назначение! Правила "перескакивания" (та самая явная связь) между областями ответственности как раз и задаются с помощью синтаксиса языка. А где язык этого не может, городят декораторы.
·>error handling — одна из самых сложных проблем программирования.
Ну, ей далеко до нейминга и кэшинга!
Re[5]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
Q>>Тут мы подходим к тому, что у ошибок может быть разная сематика. Тот случай который я описал, если возникло исключение — это означает, что в программе баг и правильным способом будет закрыть программу с записью в лог об исключении. В случае если у объекта часть состояния находится за пределами программы(сетевые подключения, файлы и т.д) т.е. то за что программа не может нести ответственность, тут обычно требуется более сложная логика обработки ошибок. Тут мы можем определить разные классы exception и производить обработку для каждого вида ошибок
_>Я в твоём сообщении не могу отделить ошибки от исключений. А хочется определённости — в итоге-то что? Парсеру json, если пришёл битый, что делать — кидать "просто ошибку" или исключение, которое приведёт к закрытию программы? Ну и чтоб два раза не вставать — выход за границу массива это ошибка или исключение, которое должно всё сломать? А невозможность выделить память?
_>Самый прикол, что решение "исключения существуют, и это нормально, главное что мы их можем перехватывать и обрабатывать" — даёт универсальный и единообразный подход к самым разным проблемам. А вот подход — "у нас в языке исключений нет, ну может чуть-чуть, на донышке" — приводит к тупику. Решение rust как раз в духе, в моём понимании, дибилизма — делайте всего по 2.
Тут смысл в чем: в первом случае у нас объект находится полностью в нашей программе и выброс исключения означает ошибку в коде, во втором случая часть объекта как бы находится за пределами программы, например какой ни будь DataAdapter, в этом случае исключение может говорить об проблемах в БД, а не в программе. У этих ошибок разный смысл. Тоже самое с json парсером.
Программа – это мысли спрессованные в код
Re[6]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
_>А потому что NetworkStream может заиспользовать сертификаты, а менеджер сертификатов вообще относится к шифрованию, и совершенно не обязан CertificateException наследовать от IOError или ещё кого-то, особенно из-за сериализатора. Можно конечно заворачивать ошибку в ошибку, но зачем, какую проблему решаем?
Все что не является частью контракта — оборачиваете в Runtime Exception. Они не проверяемые. Все просто.
Проверяемые — это там где вам хочется впихнуть Result object.
=сначала спроси у GPT=
Re[6]: Result objects - все-таки победили Exceptions?
S>>Слышали ли вы что-нибудь про наследование? _>Слышал. Вот есть базовый Stream, у него checked фиксированный набор исключений. И вдруг появляется новый NetworkStream, которому надо добавить новый CertificateException. А добавить низзя: базовый стрим менять не положено — на его checked уже весь клиентский код завязался. У пользователей уже свои checked зависят от тех checked что были у старого Stream. Приехали. В Java это проблему решили "элегантно" — забили на checked. Могли бы иначе — решили бы иначе.
Не знаком с концепцией checked exp, но почему CertificateException нельзя добавить на уровень NetworkStream, а не лезть в базовый класс? Непонятно, правд, как быть с полиморфизмом, если тип Stream, а передаётся NetworkStream...
Кодом людям нужно помогать!
Re[8]: Result objects - все-таки победили Exceptions?
Здравствуйте, hi_octane, Вы писали:
S>>Так сделайте наследника StreamException — в чем вопрос _>Ага, и тут у разработчиков менеджера сертификатов дилемма — делать CertificateException наследником StreamException, или HTTPException, или послать всех нафиг
Здравствуйте, SkyDance, Вы писали:
SD>·>Это от парадигмы зависит. Вызов метода в ООП — это и есть message passing. SD>Разве что в теории. На практике, в известных мне реализациях ООП вызов осуществляется синхронно, и синхронно же ждется, пока вызов что-то вернет. Это уж совсем не message passing.
sync/async — ортогонально message passing.
SD>·>В терминах ООП — это уже какой-нибудь декоратор. SD>Такой декоратор должен быть потокобезопасным и весьма изощренным. То есть, ожидаемо, он должен реализовывать половину того, что может быть реализовано общим образом в синтаксисе и рантайме языка.
А зачем обязательно в синтаксисе? Достаточно какой-нибудь concurrent queue на библиотечном уровне, пусть реализованный изощрённо, без разницы.
SD>·>Проблема исключений, что они нелокальны. Действия в одном куске кода внезапно перескакивают в совершенно другой кусок кода, без явной связи между скопами. SD>Это не проблема, а назначение! Правила "перескакивания" (та самая явная связь) между областями ответственности как раз и задаются с помощью синтаксиса языка. А где язык этого не может, городят декораторы.
Угу, назначение. Как и у оператора goto, например. Проблемой это быть не перестаёт.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: Result objects - все-таки победили Exceptions?
Э нет. 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?
Здравствуйте, SkyDance, Вы писали:
SD>Вызываемая функция не знает, хочет ли вызывающий код проверять, или просто пробросить наверх.
Так это уже вызывающий код решает — обернуть в RuntimeException — снять акцентуацию. Сделать inner (исключение — часть контракта, вложенная). Или же оставить проверяемым.
=сначала спроси у GPT=
Re[9]: Result objects - все-таки победили Exceptions?
Здравствуйте, 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 пообещала ничего не выбрасывать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.