Здравствуйте, Sinclair, Вы писали:
S>И вот тут оказывается, что если у нас на дне дерева вызовов возникает специфическая ситуация, которую мы хотим обработать поближе к корню, то у нас связаны руки.
S>Середина этого дерева знать не знает про эту специфическую ситуацию; её спроектировали в те времена, когда никому и в голову не приходило, что там внутри может что-то сломаться таким способом. S>В мире checked exceptions у нас есть три плохих способа это решить: S>1. Пройтись вверх по стеку вызовов и везде подправить сигнатуры, добавив MySpecificException к списку S>Это зачастую невозможно (потому что в стеке — четыре библиотеки от семи разных поставщиков, из которых девятеро уже давно ушли с рынка и их код никто не маинтейнит), да и вредно (потому что часть этих библиотек используется ещё в сорока местах нашего проекта, которым совершенно неинтересна обязанность обрабатывать ещё и MySpecificException) S>2. Отнаследоваться от какого-то исключения, которое в сигнатурах уже есть S>Это если в сигнатурах есть хоть что-то, и это что-то — не final. S>3. Завернуть MySpecificException в UniversalException S>Это если в сигнатурах есть UniversalException, в котором предусмотрено место для InnerException S>4. Выбросить потомка RuntimeException
S>В итоге практически реализуемым становится четвёртый способ, который сводит всю идею проверяемых исключений на нет.
Вполне согласен, но вот то, что вариант 4 сводит на нет всю идею проверяемых исключений — можно поспорить.
Мы же пишем код очень низкий по стеку вызовов. А это чаще всего означает, что мы вкладываем некую новую функциональность, которая изначально не была заложена во всех этих четырех библиотеках от семи разных источников. Ну например, работа с данными, которые поступают из файлов, которые лежат в какой-то встраиваемой ФС, а мы решили встроить новую ФС, а в ней нам нужно завести какое-то исключение, которого ни в одной из существующих ФС никогда не было. Такая уж странная ФС.
Если его и впрямь нельзя уложить в одно из исключений ранее существовавших ФС, то это значит, что мы работаем с чем-то принципиально новым. И тогда понятно, что прежний код (все четыре библиотеки от семи источников), в общем-то, не рассчитан на это нововведение. И обработать эту ошибку он не может (вариант с его переписыванием в виде (1) не обсуждаем). А вообще-то должен бы именно он это делать. А мы фактически хотим пробросить эту ошибку, минуя все слои, которые могли бы ее обработать, на один из верхних уровней, где обработать ее по существу едва ли удастся, удастся лишь признать неудачу. В самом деле, ну поймали мы ее там, и что дальше ? Разве что повторить операцию с какими-то изменениями... А это и значит, что возникло исключение, которого код не ждал. То есть фактически unchecked exception. И тут действительно можно выбросить RuntimeException. Потому что ситуация по сути мало чем отличается от деления на 0, которое вдруг произошло, когда мы добавили на нижнем уровне новый код. Не рассчитаны они на такое деление и не собираются его обрабатывать. А ArifmeticException поймать можно будет только на верхнем уровне. Поймать и сказать : "упс!"
Но это никак не отменяет тот факт, что FileNotFoundException будет нормально выбрасываться этой новой ФС и нормально обрабатываться второй из этих 4 библиотек, и будут там предприняты нужные действия, и все исправлено , а корень так и не узнает, что была такая проблема.
S>Именно поэтому в дотнете нету checked exceptions.
Подозреваю, что не поэтому, а потому что унаследовались от C++, а в нем тогда (не знаю как сейчас) throws в сигнатуре метода ровным счетом ничего не делало. Так, для красоты...
А с uncheked exception другая проблема. Я с ней именно на C# когда-то столкнулся. Все сделано по документации, все работает, и вдруг вылетает XyzException. В документации ни слова о том, что оно может быть выброшено. Да это и неудивительно, так как оно выбрасывается не вызываемым методом самим, а методом третьей из 4 библиотек семи разработчиков, о которых вызываемый метод и не знает ничего. Ну ладно, чертыхнулся, написал для него catch (хорошо, хоть было понятно, что за исключение и что делать) и задумался — а какие еще исключения этот метод может выбросить не сам, а в одной из этих 4 библиотек ? Дай ответ... Не дает ответа.
S>А вот если идти по пути "result object", то мы остаёмся в поле традиционной системы типов. S>Вот есть у нас библиотечная функция map, которая принимает коллекцию и трансформер f вида Func<T, R>. Нам совершенно всё равно, может ли возникнуть ошибка при вычислении f над конкретным экземпляром T или нет. S>Потому что всё это "спрятано" внутри типа R. Если f — это функция 1/x, то она возвращает тип "number | undefined" (или там "number | DivisionByZero"). Тип результата map соответственно будет Iterable<number | undefined> и обязанность принять решение, что с этим делать, остаётся у того, кто вызывает функцию map, а не у автора этой функции. Ровно так, как этого хотели авторы идеи исключений. S>А если f — это функция x ^ 0xF, то у неё тип результата — просто number, и вызов map с ней в качестве аргумента получит Iterable<number>. S>Написать функцию IEnumerable<R> map(IEnumerable<T> source, Func<T, R> f) c проверяемыми исключениями не представляется возможным. В ней функция f не имеет права ничего выбрасывать, потому что map пообещала ничего не выбрасывать.