Польза проверяемых исключений весьма сомнительна, а вред очевиден

Время споров прошло.
— начало главы «Используйте непроверяемые исключения» книги «Чистый код» Роберта Мартина.

Как бы ни хотел Майк Физерс (автор цитаты в эпиграфе) поставить точку в споре «checked vs unchecked exceptions», сегодня, более чем пять лет спустя, мы вынуждены признать, что эта тема до сих пор остается «холиварной». Сообщество программистов разделилось на два лагеря. Обе стороны приводят веские аргументы. Среди тех и других есть разработчики экстра-класса: Bruce Eckel критикует концепцию проверяемых исключений, James Gosling — защищает. Похоже, этот вопрос никогда не будет окончательно закрыт.

Пять лет назад совет отказаться от проверяемых исключений вызвал у меня недоумение и возмущение. Учебники по Java объясняли, когда их использовать, а тут такое… У меня совсем не было опыта разработки на Java, поэтому я мог только принять на веру одно из двух, и я отверг совет из «Чистого кода». Я никогда так не ошибался в своей жизни.

Но в этой статье речь пойдет о серьезных причинах считать, что удобство проверяемых исключений — миф. Множество статей посвящено этому вопросу, но я постараюсь рассмотреть проблему с немного другой стороны, привести свои аргументы и примеры. Закрывать тему рано.

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

Как было задумано


Единственная причина существования разных типов исключений — разные способы их обработки:

  • Штатные ошибки, обработка которых является частью бизнес логики. Пример: пользователь ввел неверный пароль — отобразить соответствующее сообщение и попросить ввести снова. Для таких ситуаций, по замыслу авторов Java, нужно использовать checked exceptions.
  • Непредвиденные ошибки, в случае которых мы говорим пользователю «у нас что-то сломалось», логируем stack trace и открываем ticket в багтрекере. В этом случае речь идет о непроверяемых исключениях.

Раз уж штатные ситуации обязательно должны быть обработаны, Гослинг и компания решили возложить контроль на компилятор: мол, забыли обработать — ошибка, запускать нельзя. Как это работает, мы знаем: либо ловим исключение, либо указываем его в списке throws.

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

Ради чего нам предлагают терпеть эти навязчивые «throws», неудобства с лямбдами в Java 8 и т.п.? Рассмотрим аргументы за checked exceptions:

  • «Механизм проверяемых исключений гарантирует, что все штатные ситуации будут обработаны. Это позволяет создавать более надежное ПО.»
  • «В языках без checked exceptions разработчики могут не указать возможные исключения в сигнатуре метода, в результате не всегда понятно, каких подвохов от него ждать. В Java компилятор этого не допустит.»

Теперь возражения.

To check or not to check?


Рассмотрим сигнатуру конструктора FileInputStream:

public FileInputStream(File file) throws FileNotFoundException

Разработчик решил, что отсутствие файла — нормальная ситуация при попытке его открыть, поэтому он заботливо выбрасывает проверяемое исключение FileNotFoundException, чтобы мы точно его обработали.

Проблема здесь в том, что в большинстве случаев мы вообще не хотим ничего обрабатывать. Например, наше веб-приложение пытается открыть файл конфигурации, без которого оно не сможет нормально работать, а его нет. Для нас FileNotFoundException такая же фатальная ошибка, как какой-нибудь NPE, но мы вынуждены объявить его в десятках мест выше только для того, чтобы где-то на самом верху поймать и залогировать:

catch(Exception e){
    LOGGER.error("Fatal error", e);
    return new Response(500, "Oops, unexpected error on server");
}

Смотрите, обработчику же пофиг, какое исключение к нему придет. Тогда в чем здесь польза «проверяемости»?

— Но ведь все исключения ловить неправильно, надо ловить только проверяемые!
— А RuntimeException молча глотать?
— Но их сервер сам залогирует и вернет 500!
— Ну так если FileNotFoundException обернуть в непроверяемое, будет то же самое, только его не надо дополнительно ловить руками и везде прописывать в throws.

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

Но что если это штатная ситуация с особой обработкой?


Допустим, некоторые ошибки мы все же обрабатываем: пришел ValidationException — возвращаем статус 400 с сообщением, AccessDeniedException — 403, ResourceNotFoundException — 404 и т.д.

Если они все unchecked, их обработка элементарна:

catch(ValidationException e){
    return new Response(400, e.getMessage());
}
catch(AccessDeniedException e){
    return new Response(403, e.getMessage());
}
catch(ResourceNotFoundException e){
    return new Response(404, e.getMessage());
}

Сделаем все эти исключения проверяемыми.

Проблемы начинаются в достаточно больших приложениях: обрабатываемых исключений становится много, и метод верхнего слоя легко может обрасти списком throws с десятком исключений. Скорее всего, никому из разработчиков это не нравится, и они идут на хитрость: наследуют все свои проверяемые исключения от одного предка — ApplicationNameException. Теперь они обязаны ловить в обработчике еще и его (checked же!):

catch(ValidationException e){
    return new Response(400, e.getMessage());
}
catch(AccessDeniedException e){
    return new Response(403, e.getMessage());
}
catch(ResourceNotFoundException e){
    return new Response(404, e.getMessage());
}
catch(ApplicationNameException e){
    // todo
}

Что делать в последнем catch? Выше мы уже обработали все штатные ситуации, которые предусмотрели, но здесь ApplicationNameException для нас значит не больше, чем Exception: «какая-то непонятная ошибка». Так и обрабатываем:

catch(ValidationException e){
    return new Response(400, e.getMessage());
}
catch(AccessDeniedException e){
    return new Response(403, e.getMessage());
}
catch(ResourceNotFoundException e){
    return new Response(404, e.getMessage());
}
catch(ApplicationNameException e){
    LOGGER.error("Unknown error", e.getMessage());
    return new Response(500, "Oops");
}

А теперь самое интересное: один из методов начинает выбрасывать новый тип исключений, которые должны быть обработаны, а мы забыли добавить соответствующий catch. Если все наши исключения непроверяемые, новое будет обработано как NPE. «Ага!» — злорадно потирают руки адепты checked exceptions. Но постойте, ведь у вас произойдет то же самое: вы отнаследуете новый тип от ApplicationNameException и всё скомпилируется, но вы так же можете забыть добавить специальный обработчик.

Вот так и получается: либо километровый список throws, либо потеря гарантии проверяемости. Оно вам надо?

Проверяемые исключения часто приводят к использованию антипаттернов


Перечислю те, что видел в реальных проектах (названия изменены, примеры упрощены).

throws Exception


Надоели длинные списки throws? Бросай везде просто Exception! Компилятор схавает:

public void doSome() throws Exception{
// do some
}

Какую информацию несет «throws Exception»? Что-то сломалось. Чем это лучше RuntimeException?

Поймал — молчи


try{
// some code
} catch(IOException e){
}

Экономим на stack traces


try{
// some code
} catch(IOException e){
   throw new MyAppException("Error");
}

Контроль документирования


Говорите, компилятор заставляет документировать проверяемые исключения? Просто объявите «throws Exception» — он от вас отвяжется.

Может, дело в самих разработчиках?


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

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

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

Мысль о том, что механизм checked exceptions сделает ваш код более надежным — это надежда на то, что компилятор избавит вас от необходимости продумывать логику работы программы и тестировать ее.

Выводы


Обрабатывайте только те исключения, которые действительно нужно обработать. Указывайте их в сигнатурах методов. Это можно делать и с unchecked exceptions, серьезно.

Подумайте, действительно ли необходимы проверяемые исключения в вашем проекте или от них больше вреда, чем пользы. Относитесь критически ко всему, в том числе — к рекомендациям создателей Java. У вас свой проект, свои требования и особенности. Его делаете вы, а не Джеймс Гослинг, и он вам не поможет. Решать вам.

Благодарности


Огромное спасибо создателям Java за то, что не заставляете нас оборачивать каждое разыменование ссылки:

String s = "hello";
if(s != null){
    System.out.println(s.length());
}

Напоследок


Сильная типизация против сильного тестирования.
Share post

Similar posts

Comments 329

    +17
    Пример: пользователь ввел неверный пароль — отобразить соответствующее сообщение и попросить ввести снова. Для таких ситуаций, по замыслу авторов Java, нужно использовать checked exceptions.
    Поправьте, если ошибаюсь, но неправильный пароль — это штатная ситуация, и исключение здесь бросать неправильно.
      +9
      Столь же штатная, как, например, отсутствие файла, который запросили по http, когда надо в итоге вернуть 404.

      И примеров, когда на неправильный login/password кидается checked exception я видел предостаточно. Равно как и примеров, когда возвращается null.

      Есть ещё более извратные примеры такого подхода. Например, итерация в python заканчивается выбрасыванием специального error.

      Считать ли control flow с бросанием exception/error нормальным или нет — вопрос идеологический и в разных языках и/или библиотеках может приниматься различное решение. Из примеров на яве — в lucene для завершения работы некоторых итераторо-подобных объектов кидают exception, т. к. это существенно дешевле (несмотря на сбор stack trace), чем проверять hasNext на каждой итерации.
        +1
        Оптимизация скорости/памяти — это, конечно, другой вопрос. Здесь любые средства хороши, и речь уже не идет о читаемости и сопровождаемости кода. Хотя вот Блох в своей Effective Java писал, кажется, что исключения в Джаве очень «дорогие» в плане ресурсов.

        Еще забавные момент из Блоха: он не рекомендовал оставлять catch-блоки пустыми, разве что для FileNotFoundException (но это было еще до try-with-resources)
        +15
        Это на уровне приложения штатная ситуация, а в коде проверяющем контракты — исключительная.

        Странно, что в компанию к проверяемым исключениям не добавили следующие жизненно необходимые фичи:
        1. сигнатура функции должна содержать имена всех внешних переменных, которые она может явно или неявно изменить, чтобы знать на что она влияет.
        2. сигнатура функции должна содержать имена всех переменных от которых зависит результат её исполнения, чтобы знать что на неё влияет.
        3. сигнатура класса должна содержать не только имя непосредственного родителя, но и имена всех предков, с указанием какой метод/поле от какого предка наследуется, чтобы сразу видеть что откуда взято.
          0
          В яве методы…
            +5
            А меня больше всего напрягают checked exception в конструкторе(!) класса URI.
            Я банально не могу написать такой код в Java-классе:
            private URI googleUri = new URI(«google.com»);

            Ведь конструктор объявлен так: public URI(String str) throws URISyntaxException
            а URISyntaxException — checked exception.

            И если с кодом FileInputStream("/home/user/data") еще не все ясно на этапе компиляции — файл может как существовать, так и нет. То валидность URI известна!

            Checked-исключения в конструкторе, а тем более в default — это издевательство!
            • Создать объект вне метода — нельзя!
            • Если default constructor выкидывает checked exception, то не создашь нормального конструктора у класса-наследника
              +1
              Если валидность известна можно использовать метод URI.create(«google.com»).
                0
                Создать объект вне метода — нельзя!
                Строго говоря, можно. Например, в initializer'е или static initializer'е. Но крайне неудобно. Из-за этого появляются всякие вещи типа:
                class Foo {
                  private static URI baseUri;
                  static {
                    try {
                      baseUri = new URI("/some/uri");
                    } catch (URISyntaxException e) {
                      throw new RuntimeException(e);
                    }
                  }
                
                  // ...
                }
                
                  0
                  Имел ввиду URL, в URI есть create, а в URL аналога нет.
              +4
              Если проверка находится рядом с отображением сообщения, то исключение здесь, конечно, не нужно
              if(password == null || password.equals("")){
                  showError("Password not specified");
              }
              

              Но что если проверка где-то в глубине. Надо откатить стек на несколько шагов назад, возможно, освободить ресурсы и т.д. С исключениями это удобно, поэтому их используют и для штатных ошибок.
              Хотя есть и другой подход. Вы, случайно, не на него намекаете?
                +1
                Пример с неверным паролем — не очень, да. Лучше рассмотреть вариант с пустым обязательным полем. При валидации на уровне application — штатная ситуация, а если дошло до уровня model — там совершенно логично выбросить исключение.
                +9
                Сильная типизация против сильного тестирования.

                Примечательно, что многие сейчас переходят с динамически типизированного JavaScript на статически типизированный TypeScript, чтобы не писать километровые JSDoc для того, чтобы IDE хоть что-то понимала в том, что вы понаписали :-)

                  +2
                  Во всём нужно искать золотую середину.
                    +6
                    В данном случае золотая середина это на 100% покрытая юнит тестами программа, написанная на языке программирования с сильной статистической типизацией.
                      0
                      Это, кстати, очень похоже на ТРИЗовское Идеальное Конечное Решение: и сами ушами не хлопаем, и компилятор помогает.
                        –5
                        А вы в курсе, что ТРИЗ это лженаука? Нет никаких оснований считать, что используя ТРИЗ можно добиться лучших успехов в изобретательстве, чем не используя ТРИЗ.
                          +10
                          Не путайте лженаку с эвристическими методами. Эдак можно и до того, что юнит тесты в разработке не помогают, договориться.
                            +3
                            Лже- — могу понять, почему вы сказали, -наука — не могу взять в толк. ТРИЗ не позиционировался никогда как наука, это набор (типовых) методик. Методика != наука. Давайте просто говорить «ТРИЗ не работает» (не доказана эффективность).

                            Вообще же насколько я понимаю в программировании точно та же ситуция, сложно доказать, что используя <то-то и то-то, например… ну скажем — Agile> можно добиться лучших успехов, чем не используя то-то и то-то. Будет множество статей «за» и множество статей «против». Идеальная тема для разжигания — сказать, что что-то не работает.
                              +1
                              Ну не совсем так. Что использование статистической типизации позволяет избежать появления большого количества ошибок доказано и проверено на практике.
                                0
                                Согласен. Только ведь и «знание готовых паттернов изобретательства» позволяет сэкономить время и уберечь от большого количества ошибок — и это тоже доказано и проверено на практике. (Надеюсь, аналогия прозрачна)
                                  –2
                                  Где это доказано? Какие существенные изобретения сделаны с помощью ТРИЗ? Какие изобретения есть у создателя ТРИЗ шулера-Альтшулера?
                                  • UFO just landed and posted this here
                                      –2
                                      Что же это за ТРИЗ и АРИЗ такие, что он с помощью них ничего стоящего изобрести не смог?
                                      • UFO just landed and posted this here
                                          0
                                          У нас как-то на кафедре два доктора наук спорили:
                                          — Если этот ваш алгоритм такой замечательный, почему его Яндекс не использует?
                                          — А Вы это у Яндекса спросите.

                                          Это вы у изобретателей спросите, почему они до сих пор пользуются методом научного тыка и его производными.
                                            0
                                            А с чего вы взяли, что этот ваш АРИЗ такой замечательный, что его стоит использовать?
                                              0
                                              1. Это не мой АРИЗ.
                                              2. А у изобретателей большой выбор? Есть что-то менее продуктивное, чем перебор вариантов?
                                                0
                                                Хороша оговорочка xD
                                              0
                                              А в чём суть этой теории? В двух словах, а то всё, что я находил — какое-то словоблудие.
                                      +2
                                      > использование статистической типизации
                                      вы имеете в виду статическую? (просто уже в двух комментах вижу эту опечатку)
                                        +2
                                        Эта опечатка показалась мне очень смешной, но никто не заметил. Тогда я опечатался ещё раз, в надежде, что мой юмор найдёт своего ценителя. И он нашёлся :).

                                        А так, да, я про статическую типизацию.
                                        • UFO just landed and posted this here
                            +1
                            > для того, чтобы IDE хоть что-то понимала в том, что вы понаписали :-)
                            Ну значит, слава Ктулху, мода наконец-то сделала виток в сторону разумного подхода «дадим компьютеру понять, что мы там кодим».

                            Смотрите:
                            C — никакой интроспекции, куча void* и арифметики с памятью
                            C++ — классы, объекты, афифметика с поинтерами объявлена ересью
                            Perl — жуткие регекспы и запредельная свобода синтаксиса, никакой интроспекции
                            Java — строгость во все поля, компилятор понимает ваш код лучше вас :)
                            JS — динамика всего-на-свете, переопределяем функции из объектов «на лету», снова минимум интроспекции (хотя дебаггеры в современных браузерах поразительны)
                            ? — тут должен быть новый строгий язык
                            +3
                            Пример: пользователь ввел неверный пароль — отобразить соответствующее сообщение и попросить ввести снова. Для таких ситуаций, по замыслу авторов Java, нужно использовать checked exceptions.

                            Для таких ситуаций, по замыслу авторов Java, нужно использовать что-то вроде boolean isValid();
                              +2
                              На самом деле всю длинную статью можно свести к простому тезису: проверяемое исключение — оксюморон. Эти слова вообще несовместимы друг с другом.

                              Что такое исключение? Это нечно, то мы не знаем как обрабатывать и не знаем — нужно ли обрабатывать вообще: «бяда-бяда-не-знам-што-делать-да».

                              Что такое проверяемое исключение? Это нечто, что мы обязаны так или иначе обработать. «Бяда-бяда-знам-што-нам-делать-да».

                              Дык эта… мы таки знаем что делать или не знаем? Ответ очевиден: иногда знаем, но гораздо чаще — нет. Возьмите тот самый FileNotFoundException и типичный web-сервер. Вот там, где открывается страничка, имя которой пришло от пользователя — мы знаем что делать: состряпать сообщение об ошибке и вернуть её пользователю. Но во всех других случаях — мы понятия не имеем куда эту ошибку совать. Неважно что там у нас не открылось — кеш, файл настроек или ещё одна из 100500 вещей, нужная для запуска большого приложения: в любом случае дальше ехать некуда.

                              Да собственно это и разработчикам языка быстро стало понятно: иначе у нас каждый метод оброс бы обработкой всяких переполнений. Но они, с упорством достойным лучшего применения, пытаются доказать миру что их «чудный механизм» работает. Нет, не работает. По крайней мере не работает в Java. Если у вас что-то должно обрабатываться всегда — то это что-то вообще не должно являться исключением (именно поэтому итератор имеет функцию hasNext, а не исключение StopIteration как в Python'е). Ну а раз что-то где-то когда-то может не обрабатываться… то зачем заставлять людей это необрабатываемое «что-то» ловить, перебрасывать и вообще делать кучу ненужных манипуляций?

                              Вот, собственно, об этом — и вся статья.
                                0
                                Если у вас что-то должно обрабатываться всегда — то это что-то вообще не должно являться исключением (именно поэтому итератор имеет функцию hasNext, а не исключение StopIteration как в Python'е).


                                Далеко не всегда можно обойтись проверкой.

                                if(file.exists()){
                                    InputStream input = new FileInputStream(file);
                                }
                                


                                Что если мы проверили существование файла, но открыть его еще не успели, а пользователь выдернул флешку?
                                  +1
                                  Я немного про другое. Если вам всегда нужно обрабатывать какую-то особую ситуацию (конец списка, отсутствие значения и т.д.), то вы должны избегать исключений вообще (не ловить NullPointerException, а проверить на null, к примеру). Исключения же возникают тогда, когда вы, в общем, не понимаете как с проблемой бороться. Ну выдернул пользователь флешку, вы поймали исключение… и чего дальше? Данные на флешке, скорее всего, испорчены — но что вы можете с этим сделать?

                                  В каких-то редких случаях вы можете знать что делать (если скажем запись на диск вы делаете аккуратно, а на флешке транзакционная файловая система), но это — скорее исключение, чем правило (иначе это бы не было, чёрт побери, исключением!). Так зачем, чёрт побери, всех и каждого заставлять обрабатывать исключения, если они всё равно ничего полезного с ним сделать не смогут? Это ж вы заранее должны угадать — сможет вызывающая вас программа обработать исключение или нет. А как? Вы ж потому и кидаете исключение, что не знаете что с ним делать!
                                    +8
                                    С разделением программ на слои часто нештатная ситуация на одном уровне является штатной на другом. Для метода открытия файла отсутствие файла — исключение, а для метода чтения конфига может быть штатной ситуацией, инициирующей применение дефолтных значений или даже создание конфига с ними, а то и запуск мастера конфигурирования.
                                      +1
                                      Когда можно обойтись проверкой, надо делать проверку.
                                      Ловить NullPointerException вместо проверки на null — за такое даже лабораторную нельзя принимать.
                                        0
                                        Почему же? Если у вас есть метод в котором в делается куча проверок на null с одной и той же логикой, то в теории можно сделать логику построенную на exception's. Это будет более читаемо и возможно сравнимо по производительности. Все зависит от количества проверок.
                                          +2
                                          Семантически NullPointerException вроде должен выбрасываться когда мы null не ожидаем, но он пришёл. Если мы его ожидаем, то нужно делать явную проверку.
                                            –3
                                            Если на каждый чих делать проверку на null, вместо пробрасывания и обработки исключения выше по стеку, можно потерять очень много процессорного времени. Я не говорю, что проверки не нужно делать, все зависит от ситуации в вашей программе.
                                              0
                                              Не на каждый чих, а когда мы ожидаем null или объект. И пробрасывать исключения в таких случаях не нужно, как правило — это штатная ситуация.
                                            0
                                            Если логика одна и та же, не лучше ли для этого сделать отдельный метод и вызывать его?
                                        0
                                        В данном случае, очевидно, отсутствие файла — штатная ситуация, отказ накопителя — нештатная.

                                        Кроме всего прочего, кстати, исключение еще и очень медленный механизм, если на проверку пароля бросать исключение, то такой портал слишком легко DOS-ить.
                                        +1
                                        Статья не о том, чтобы отказаться от исключений в пользу проверок.
                                        Я старался донести мысль, что необходимость проверки обработки штатных ошибок — надуманная проблема.
                                        +9
                                        Гигантская проблема с проверяемыми исключениями — нарушение инкапсуляции!

                                        Типичный сценарий — на нижнем уровне (где-нибудь в методе log) мы не можем корректно обработать FileNotFoundException и нам приходится пробрасывать его выше в блоке throws, в итоге в методе doTransaction стоит throws FileNotFoundException, тогда как на этом уровне нас не совсем должна волновать внутренняя реализация метода, но имплементация, спасибо throws, торчит наружу!

                                        В итоге стоит выбор, либо писать тьму boilerplate кода с оборачиванием одного исключения в другое, либо обернуть каждый checked exception в наш unchecked.

                                        По этому поводу отлично написал Rob Martin, Clean code

                                        Даже в java 8 лямбды проектировались уже с учетом только unchecked exceptions, так что, имхо, выбор современного разработчика очевиден.
                                          +3
                                          Именно. Даже если тип исключения в данном случае никого не волнует, меня заставляют о нем думать.
                                            +5
                                            А что вам мешает обернуть FileNotFoundException более высокоуровневым исключением перед выбросом его «наверх»?
                                              +6
                                              Вы, кажется, из тех, кто тушит пожар керосином. Основная проблема проверяемых исключений — это то, что они вносят «знание» о своём присутствии в код, который, в общем-то о них ничего знать не должен. Оборачивание FileNotFoundException в более высокоуровневое проверяемое исключение проблему только усугубляет. Можно обернуть в непроверяемое, то тогда возникает вопрос: а за что боремся-то? Может изначально как-нибудь без проверяемых исключений обойтись?

                                              Заворачивать низкоуровневые исключения в более высокоуровневые нужно и полезно — но только на границах компонентов. Чтобы скрыть детали реализации. Одна беда — эти границы не проходят аккуратно по стеку вызова функций. Callback переданный в какой-нибудь XML-парсер может кидать исключения, которые XML-парсеру совершенно неинтересны — но интересны тому, кто вызвать XML-парсер. В этом месте вся идея проверяемых исключений и решение проблемы «заворачиванием» исключений друг в друга «рассыпается». Можно, конечно, в callback'е исключение «завернуть», а в основном модуле «развернуть», но это уже в чистом виде профанация всей идеи обработки исключений: так и до кодов ошибок недалеко!
                                                +4
                                                Вы, кажется, из тех, кто тушит пожар керосином

                                                Я из тех, кто хочет знать, какие последствия может повлечь за собой использование того или иного кода. Хотите напишите мне о возможных исключениях (ошибках) в документации, хотите в throws, мне разницы нет, главное сообщите мне, что мне ждать.
                                                который, в общем-то о них ничего знать не должен

                                                Не согласен. Код и программист должны знать, с чем они работают. Исключения это отличный, инкапсулированный объект для этого.
                                                Оборачивание FileNotFoundException в более высокоуровневое проверяемое исключение проблему только усугубляет

                                                Почему?
                                                Callback переданный в какой-нибудь XML-парсер может кидать исключения, которые XML-парсеру совершенно неинтересны — но интересны тому, кто вызвать XML-парсер

                                                Можно, конечно, в callback'е исключение «завернуть», а в основном модуле «развернуть», но это уже в чистом виде профанация всей идеи обработки исключений

                                                Как то мне слабо представляются callback фукнции XML-парсера, которые выбрасывают настолько разные исключения, что их нельзя объединить одним классом.
                                              +3
                                              Ну так если вы используете unchecked-исключения, то у вас инкапсуляция тоже нарушается, только теперь еще и неявно. Или я что-то не понимаю?
                                                +2
                                                Если исключение действительно кому-то нужно, на границе слоев его обычно надо завернуть в высокоуровневое. Тут без разницы, используем мы механизм checked exceptions или нет.
                                              +32
                                              Мой любимый холивар :) В свое время при выборе Java vs C# я выбрал Java, пожертвовав любимым переопределением операторов и свойствами именно из-за того, что в ней были проверяемые исключения. Попробую объяснить, почему.

                                              Я много раз читал аргументы против проверяемых исключений и всякий раз меня не покидало ощущение, что их пишут люди, чья манера мышления в каком-то смысле противоположна моей. Вы эти аргументы почти все тут привели:

                                              1. Про ненайденный файл

                                              public FileInputStream(File file) throws FileNotFoundException

                                              Так как Java — объектно-ориентированный язык программирования, давайте рассуждать в терминах объектно-ориентированных. С точки зрения объекта класса FileInputStream исключение ненайденного файла действительно является «неприятной, но штатной ситуацией». Поэтому оно совершенно справедливо считается checked. Но это справедливо для абстрактного читателя файла.

                                              А у вас — другой случай. У вас есть читатель конфига.
                                              Назовем его Config. И для него (этого класса) отсутствие файла конфигурации — катастрофа (кстати, неясно, почему...) и поэтому мы напишем вот так:
                                              public class Config {
                                                  public class ConfigFileNotFoundException extends RuntimeException { 
                                                      ConfigFileNotFoundException(Exception base) { super("Config file not found", base); }
                                                  }
                                                  
                                                  private FileInputStream configInputStream;
                                                  public Config(File file) {
                                                      try {
                                                          configInputStream = new FileInputStream(file);
                                                      } catch (FileNotFoundException e) {
                                                          throw new ConfigFileNotFoundException(e);
                                                      }
                                                  }
                                                  ...
                                              }
                                              


                                              Таким простым и незамысловатым способом вы обернули исключение, являвшееся checked в то, которое является unchecked, не потеряв при этом stacktrace (да-да, они складываются «матрешкой» не просто так). И пользователь вашего класса Config не должен будет ловить ненужное. Я не знаю ни одной причины, почему написанный мной код плох. А вы?

                                              2. Длинный пример с обработкой исключений сервера.

                                              Сперва, сформулирую простое утверждение, которое считаю аксиомой: если при использовании некоторого ЯП вам приходится писать много однообразных конструкций, это намекает, что вы что-то делаете не так. В данном случае вы не проработали систему исключений. И, да, это проблема проектирования, от которого вас Java не спасет — она лишь тактично намекнет вам, что вы упустили самое интересное. Попробую «придумать», как сделать лучше:

                                              Как известно, в HTTP есть 2 типа ошибок: 4xx, и 5xx. При этом очевидно, что ошибки типа 4xx — проверяемые. А 5xx — катастрофа, которую надо отправлять в лог. Так что я предлагаю обработку такого вида:
                                              class ClientException extends Exception { ... }
                                              

                                              try {
                                                  processServerRequest(request);
                                              } catch (ClientException e) {
                                                  log.writeClientException(e);
                                                  return new Response(e.code, e.message);
                                              } catch (Exception e) {
                                                  return new Response(500, e.message); // Тут заворачиваем любое падение в HTTP 500 по умолчанию, если нужно подробнее, то перед этим catch надо написать еще.
                                              }
                                              

                                              Все ваши исключения валидации запроса должны наследоваться от ClientException, кидаться явно. И ни одно из них не будет упущено. Даже если вы добавите еще 5. И, уж конечно, если ваше новое исключение заслуживает отдельного обработчика, это придется решать вам самому, тут уж вам никакие checked не помогут. Но и их отсутствие тоже не поможет вам. Так зачем пренебрегать отличным помощником лишь только потому, что он не решает все проблемы разом? «Ага! — сказали суровые сибирские мужики».

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

                                              А теперь немного про преимущества. Я бы из них выделил и обрамил золотом одно:
                                              В языках без checked exceptions разработчики могут не указать возможные исключения в сигнатуре метода, в результате не всегда понятно, каких подвохов от него ждать. В Java компилятор этого не допустит.


                                              Если лаконичнее, то это — самодокументируемость кода. Необходимость ловить все потенциально уловимые нештатные ситуации позволяет избежать не только ошибок по невнимательности (которые у меня, например, лидируют в «хит-параде»). Она позволяет грамотно встраиваться в очень сложные системы.

                                              Я в данный момент ковыряюсь в коде Eclipse. Да-да, этой самой IDE, а точнее — четырехгигабайтном куске Java-кода, написанного почти безупречно во многих смыслах. И могу сказать, что в крупном проекте грамотная проработка нештатных ситуаций — задача огромной сложности. И любая, даже самая минимальная помощь компилятора в этом вопросе — однозначное добро. И это никак нельзя уравновешивать джуниорскими глупостями типа
                                              try{
                                              // some code
                                              } catch(IOException e){
                                              }


                                              И, напоследок, на тему вашего последнего ироничного замечания. Контроль обнуляемости в Java действительно не был додуман. Но умные люди давно уже выдумали @Nullable и @NotNull. И их пользу-то как раз никто не оспаривал, вроде.
                                                +1
                                                Таким простым и незамысловатым способом вы обернули исключение, являвшееся checked в то, которое является unchecked, не потеряв при этом stacktrace (да-да, они складываются «матрешкой» не просто так). И пользователь вашего класса Config не должен будет ловить ненужное. Я не знаю ни одной причины, почему написанный мной код плох. А вы?

                                                Потому что приходится каждый раз делать руками дурацкую обертку, которая была бы не нужна, будь FileNotFoundException непроверяемым.
                                                Если лаконичнее, то это — самодокументируемость кода.

                                                Мой пример с «самодокументированием», видимо, прошел мимо. Чуть подробнее: неаккуратный программист понатыкает везде throws Exception, ублажив тем самым компилятор и на корню зарубив всю «самодокументируемость». Нужен ли такой контроль хорошему разработчику — вопрос спорный, от этого и холивары. Тут каждый решает для себя сам. Я, кстати, со временем изменил свою точку зрения на диаметрально противоположную.
                                                Контроль обнуляемости в Java действительно не был додуман. Но умные люди давно уже выдумали @Nullable и NotNull.

                                                Есть мнение, что в 21-м веке надо думать не о проверках на null, а о том, что вместо этой ошибки на миллиард долларов использовать Option. Но это уже отдельный холивар.
                                                  +2
                                                  Потому что приходится каждый раз делать руками дурацкую обертку, которая была бы не нужна, будь FileNotFoundException непроверяемым.

                                                  И как понять, что не так? По названию файла?
                                                    –3
                                                    А что еще Вы можете предложить?
                                                      +4
                                                      У меня возникает острое ощущение, что если вы говорите «каждый раз делать руками дурацкую обертку, которая была бы не нужна», то вы банально не заботитесь об архитектуре вашего проекта. Что хорошего в том, что на самом верхнем уровне абстракции вашего проекта вы внезапно получите FileNotFoundException? Семантически, это не имеет никакого смысла, и являеться банальным нарушением инкапсуляции. И тут уже не имеет никакого значения, как вы это получили, с checked-исключениями (нагородив по цепочке throws в сигнатурах функций) или с unchecked (получив ровно тоже самое, только теперь это поведение еще и неявно).

                                                      На мой взгляд, если вы испытываете боль от checked-исключений, это сигнал о том, что вы что-то делаете не так. И ругаться в данном случае на используемый язык – это тоже самое, что ругаться на статическую типизацию из-за того, что ваш код не компилируется из-за type-check ошибок.
                                                        +2
                                                        Вставлю еще своих 5 копеек. Я пишу на Python, где checked-исключений нет. C вашей точки зрения это рай: не хочешь обрабатывать FileNotFoundException, интепретатору пофигу, твой код прекрасно работает и так, во всяком случае до тех пор, пока это исключение не срабатывает.

                                                        И это действительно удобно, когда вы пишете какой-нибудь скрипт, цель которого «отрабовать». Если же вы пишите большой проект, то приходится фактически имитировать работу checked исключений, указывая их в doc-string к функциям/методам и оборачивая одни исключения в другие перемещаясь снизу-вверх по слоям проекта, хотя интепретатор Python ничего такого от вас не требует.

                                                        Так что выходит, что отношение к checked-исключениям зависит в первую очередь от того, с какой целью вы пишете код. Если для вас на первом месте скорость разработки – то checked исключения зло, поскольку заставляют явно указывать то, о чем вам лишний раз думать не хочеться, но если вы заботитесь о хорошем дизайне – то это однозначное добро, поскольку защищают от собственных ошибок.
                                                          +1
                                                          Вот попробуйте попрограммировать на Java — а потом рассказывайте сказки про то, что у вас всё на Python как на Java.

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

                                                          О чём, собственно, и все приведённые примеры!
                                                            +1
                                                            Мой комментарий как раз о том, что в Python не так как в Java. И что в Python одни исключения нужно преобразовавать в другие как раз «только там, где этого требует бизнес-логика». И я хотел сказать, что такой подход тоже ощущается как боль, поскольку язык никак в этом не помогает (и на мой взгляд, это гораздо хуже, чем даже отсутствие проверки типов).
                                                              0
                                                              Будьте любезны, приведите конкретный пример кода, в котором Вы ощущаете боль от подхода Python.
                                                                +2
                                                                Первое, что пришло в голову.
                                                                Код работает замечательно до тех пор, пока на вход поступают символы в диапазоне ASCII 1-127, а потом на вход приходит кирилический текст, и внезапно оказывается что весь написанный вами код рушится через каждые пять строк с UnicodeEncodeError, потому что вы не вызываете явно .encode('utf-8'). Сколько лет уже пишу на Python, каждый раз чувствую себя дураком, получая раз за разом одни и те же ошибки в рантайме.
                                                              +4
                                                              Если вы пользуетесь компилятором правильно, то он вам чаще друг, чем враг.

                                                              Да, бывают ситуации, при которых в java приходится написать строку-другую «лишнего» кода. Но зато потом вы получаете отличный уровень интроспекции. И надежный контроль ошибок.

                                                              Основное количество доводов противников checked exceptions выглядят примерно так:
                                                              эта бяка требует написать try-catch вокруг API-метода, а мне леееень!!! Заставьте ее заткнуться!


                                                              В проекте, где есть хотя бы 10000 строк кода, я предпочту написать 20 строк вместо одной лишь ради того, чтобы потом не просидеть два дня в дебаггере, отлавливая хитрый сбой с вероятностью обнаружения 1/20.
                                                                –1
                                                                Но зато потом вы получаете отличный уровень интроспекции. И надежный контроль ошибок.

                                                                О боже, ну неужели пример с ApplicationNameException не очевиден?
                                                                У вас есть исключения А1… А10, вы их наследуете от А0 и пишете throws A0, чтобы не городить длинный список. Или вы таки городите? Потом создаете А11 extends A0, который требует своего обработчика, отличного от уже созданных, но обработчик создать забываете. Код компилируется, т.к. написано throws A0 и бросание из метода А11 удовлетворяет этому.
                                                                Что это, если не провал идеи «проверяемости»?
                                                                  0
                                                                  А что, при ловле исключения A11 уже перестает быть A0? И даже добавление нового подкласса уже бросаемых исключений в сигнатуру никак не влияет на вызывающий код — он уже ловил (или пробрасывал) эти исключения. Можете даже заменить в сигнатуре A0 на список A1...A11 и старый вызывающий код ничего не заметит. Правда, попробуйте!
                                                                    0
                                                                    он уже ловил (или пробрасывал) эти исключения

                                                                    Повторяю для особо невнимательных: А11 нужен свой, уникальный обработчик. Ему не подойдет обработчик А0, т.к. А0 слишком абстрактный. Во многих случаях почти такой же абстрактный, как Exception: «какая-то ошибка».
                                                                    Поймать исключение и обработать его — это разные вещи.
                                                                    Можете даже заменить в сигнатуре A0 на список A1...A11 и старый вызывающий код ничего не заметит.

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

                                                                      если ради этого обработчика добавление исключения и затевалось.
                                                                        +1
                                                                        А зачем тогда вообще checked exceptions, если вы ничего не забываете?
                                                                    +1
                                                                    Надуманный пример. Я получаю таск типа «обработать ситуацию такую-то так-то», решаю добавить ещё одно исключение, добавляю его, добавляю его в сигнатуре, добавляю его выброс и на этом успокаиваюсь? Как минимум, таск не пройдёт тестирование, потому что обработки-то нет.
                                                                      0
                                                                      К людям, которые пишут десятки исключений в списке throws, даже когда можно отнаследоваться от общего предка для удобства, статья не относится. Им я уже не знаю, чем можно помочь.
                                                                    +3
                                                                    В проекте, где есть хотя бы 10000 строк кода, я предпочту написать 20 строк вместо одной лишь ради того, чтобы потом не просидеть два дня в дебаггере, отлавливая хитрый сбой с вероятностью обнаружения 1/20.
                                                                    Проблема в том, что в результате у вас в проекте окажется уже 200000 строк кода, в которых вы «утоните».

                                                                    Я скорее предпочту подумать над тем, как бы разбить проект на несколько частей, каждую из которых я смогу понять, чем над тем как бы добавить в проект побольше подпорок, которые на поверку окажуся граблями, которые потом будут бить меня по лбу. Тогда и сидеть в дебаггере не придётся ;-)
                                                                +1
                                                                А чем (сематнически) FileNotFoundException, полученный на верхнем уровне, будет отличаться от NullPointerException?
                                                                  +1
                                                                  Оговорюсь, что на Java я не писал очень давно, по-этому могу лишь предполагать, что ваш вопрос исходит из того, что NullPointerException – unchecked исключение.

                                                                  Мой ответ – тем, что получение FileNotFoundException на верхнем уровне являеться факапом дизайна проекта, а получение NullPointerException – факапом языка программирования. То есть да, я не хочу защищать Java и мне очень не нравится компромисность по отношению к исключениям.

                                                                  Может ли быть как-то иначе? Думаю, да. В качестве примера можно привести Rust. В нем checked исключения еще более явные, поскольку являются частью возвращаемого значения:
                                                                  impl File {
                                                                      fn open(path: Path) -> Result<File, IOError>
                                                                  }
                                                                  

                                                                  Что касается null, то в Rust такого значения просто нет. Для значения вида «что-то или ничего» есть специальный тип Option:
                                                                  impl HashMap<K, V> {
                                                                      fn get(&self, k: &K) -> Option<&V> 
                                                                  }
                                                                  

                                                                  Так что обратится к такому значению, не распаковав его (читай – не проверив не null) в принципе невозможно.

                                                                  Я не знаком со Scala, но догадываюсь, что там есть подобные механизмы. Что касается Java в ее сегодняшнем состоянии, возможно действительно было бы лучше, если checked исключений не было вообще, чем были такие исключения.
                                                                    +1
                                                                    NullPointerException здесь действительно приведён как пример unchecked exception. На его месте могло бы быть любое другое исключение. Скажем, IndexOutOfBoundsException.

                                                                    Rust я знаю ещё хуже, чем вы Java, но как там решается проблема с IndexOutOfBound? Взятие индекса возвращает Optional?
                                                                      +1
                                                                      Есть сразу три варианта того, как это можно сделать:
                                                                      unsafe fn get_unchecked(&self, index: usize) -> &T;
                                                                      fn get(&self, index: usize) -> Option<&T>;
                                                                      fn index(&self, index: usize) -> &T;
                                                                      
                                                                      Первый метод вообще не делает проверку и является unsafe методом (т.е. его нельзя вызвать в обычном коде), второй делает проверку и возвращает Option, а третий тоже делает проверку, и возвращет просто значение либо бросает panic (это такой вид исключения, который нельзя словить, но который корректно завершает поток выполнения).

                                                                      Rust я знаю ещё хуже, чем вы Java
                                                                      Я бы сказал строже, а не хуже. Все-таки исключений в стиле Java в Rust нет вообще. Еще важным моментом является то, что у типов Result и Option есть метод unwrap(), который вернет вам непосредстеное значение, либо выбросит panic. Это удобно, если вы по каким-либо причинам не хотите обрабатывать исключение или вручную пробрасывать его.
                                                                    +1
                                                                    Позвольте, я отвечу.

                                                                    NullPointerException — это ошибка разработчика. Да, всегда. Без исключений. Тот факт, что он unchecked, намекает, что использовать исключение для проверки на null — нехорошо. Про то, что контроль null должен быть статический, я выше уже написал. Но до этого, увы, догадались совсем недавно.

                                                                    Для бизнес-логики уровня приложения не существует понятия FileNotFoundException, потому что для него не существует понятия «файл». Если у меня есть 3 слоя (модель, управляющий, представление), то на уровне модели исключение FileNotFound должно быть преобразовано в EntityNotFound, на уровне контроллера — в InvalidEntityRequest, а на уровне представления — InvalidUserName. И стектрейс должен содержать все три исключения, завернутые одно в другое.
                                                                      0
                                                                      Это хороший ответ.

                                                                      Но ведь InvalidEntityRequestException звучит очень абстрактно. Почти как SomethingWentWrongException.
                                                                      Не будет ли это тем самым преумножением сущностей сверх необходимого?
                                                                        +2
                                                                        Я не знаю вашей модели. К тому же, я думаю, что если внутри исключения будет стектрейс с причинами вплоть до падения запроса в БД, это — вполне приличный отчет об ошибке.

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

                                                                          Согласен. Вот только там не будет никакой пользы от заворачивания исключения. Скорее даже вред, т.к. лог будет тупо больше, а информация та же (не путать с данными).
                                                                            0
                                                                            Я не знаю, как вы, а я, видя стектрейс, сперва пробегаюсь по нему глазами, ища «cause». А, собственно, стек читаю обычно только у исключения, выброшенного из того уровня, который в данный момент отлаживаю. К примеру, если я получу InvalidEntity, вызванный FileNotFound, я буду смотреть на стек от Entity, но не от File (так как второй уйдет корнями в JDK и в нем для меня ничего интересного).

                                                                            Смотреть на стену вызовов без такой «разметки», разделяющей слои приложения — тоскливо.
                                                                              0
                                                                              О вкусах не спорят. Но что-то я не припомню случая, когда в исключении-обертке была бы вся необходимая информация об ошибке и не нужно было бы смотреть истинную причину (завернутое исключение).
                                                                    +2
                                                                    Что хорошего в том, что на самом верхнем уровне абстракции вашего проекта вы внезапно получите FileNotFoundException? Семантически, это не имеет никакого смысла, и являеться банальным нарушением инкапсуляции.
                                                                    Нет. Это значит что вы всё сделали не через то место. Да, «на самом верхнем уровне» вы можете поймать FileNotFoundException. Но, как вы правильно заметили смысла в этом нет никакого. Вы ловите вместо этого IOException, а FileNotFoundException со своим stacktrace'ом засовываете в логи.

                                                                    Потому что вас не волнует почему какая-нибудь подсистема улучшения картинок котиков не сработала. Не сработала — и всё тут. Зафиксировали и дальше поехали. Программист (или админ) может быть разберутся и что-нибудь подправят, но программа сама ничего сделать всё равно не может!
                                                                      0
                                                                      Я вроде бы плюс-минус тоже самое хотел сказать. Уточню, что под "… вы внезапно получите..." я имел ввиду не перехват исключения, а тот факт, что такое «низкоуровневое» исключение впринципе выплыло на более высокий уровень, в не зависимости от того, ловите ли вы его или нет.

                                                                      Другими словами, на мой взгляд есть три варианта того, что делать с исключениями на низком уровне:

                                                                      1. Завернуть в исключение более выского уровня и передать наверх (подсистема улучшения картинок котиков не сработала. Не сработала — и всё тут.)
                                                                      2. Выбросить unchecked исключение, если мы точно знаем, что дальнейшее выполнение невозможно (пример – stack overflow). Но такие ситуации редки, поскольку решение об остановке приложения в основном должны приниматся на верхнем уровне.
                                                                      3. Обработать исключение как штатную ситуацию. (Существуют миллион примеров, когда отсутствие файла, который мы пытаемся прочесть – это нормально).
                                                                        0
                                                                        Проблема в том, что вариант 1 встречается наиболее часто, но проверяемые исключения заставляют его реализовывать не на границе между модулями (где этому преобразованию самое место), а прямо там, где вы вызываете низкоуровневую функцию.

                                                                        Исключения, вообще-то, идеологически призваны «отвязать» то место, где возникает ошибка от того места, где они обрабатываются. Проверяемые исключения этой идей противоречат — их нужно обрабатывать сразу, в том методе, где они возникают. Или «протаскивать вверх по цепочке» меняя сигнатуру всех методов. Почти как обработка кодов возврата!
                                                                          0
                                                                          Так в месте вызова язык еще не знает, что для вас этот вызов — еще не граница вашего модуля. Зато для файлового API — как раз граница. Согласно вашей логике, именно тут и нужно выбрасывать проверяемое исключение, что и делается.

                                                                          Никто вас не заставляет таскать низкоуровневые исключения, типа FileNotFoundException наверх до самой main! Наоборот, ВЫ должны решить, где проходит грань, и где пора преобразовать это исключение в ConfigNotFoundException или нечто подобное, которое вы и будите тащить дальше.
                                                                            0
                                                                            Наоборот, ВЫ должны решить, где проходит грань, и где пора преобразовать это исключение в ConfigNotFoundException или нечто подобное, которое вы и будите тащить дальше.
                                                                            Так работают исключения во всех нормальных языках. Так же работают нормальные, «непроверяемые» в Java.

                                                                            Напротив — проверяемые исключения не дают вам выбора: вы обязаны «обработать» проверяемое исключение на месте: либо поймать его, либо внести в список исключений, которые метод кидает! Даже если этот метод — внутренний (для пакета или класса) и его сигнатура никого (кроме вас) не волнует!
                                                                              +2
                                                                              Если вас не волнует сигнатура, кто мешает пробросить исключение дальше? Обработаете там, где волнует. А если не хотите менять сигнатуру, значит она вас таки волнует, а значит соизвольте подумать, что делать с исключением.
                                                                                0
                                                                                Если вас не волнует сигнатура, кто мешает пробросить исключение дальше?
                                                                                Компилятор. В этом, чёрт побери, весь смысл всей статьи и всего обсуждения.

                                                                                Вы не можете игнорировать проверяемое исключение и «обработать его там, где волнует» в Java: его нужно либо обработать, либо включить в список исключений, которые бросает ваша функция. Иначе код просто не скомпилируется.
                                                                                  0
                                                                                  Про пробросить дальше я и имел ввиду
                                                                                  включить в список исключений, которые бросает ваша функция

                                                                                  Поэтому вопрос все тот же: чем вам это мешает, если мы уже разобрались с тем, что сигнатура функции вас не волнует?
                                                                                    +1
                                                                                    Я представитель «старой школы» и я, представьте себе, глядя на код в истории проекта хочу понимать, что он делает. И я не хочу чтобы эта история засорялась бог знает чем. Я также не хочу, чтобы код состоял на 90% из какой-то мути, которая неизвесто кому и зачем нужна.

                                                                                    Мне плевать на сигнатуру, это правда — но мне не плевать на то, что её изменение приводит к тому, что бо́льшая часть изменений в проекте — это изменение оных сигнатур за которыми невозможно увидеть реальные изменения.

                                                                                    Да, можно попробовать добавить ещё костылей в разные всякие github'ы… а можно просто отказаться от «проверяемых» исключений.
                                                                      –2
                                                                      Какой нарушение инкапсуляции? Кого волнует, прилетит туда FileNotFoundException или VasyaProjectFatalException, если там будет стоять catch(RuntimeException e) или вообще ничего? Тип исключения увидят только люди в логах, когда будут отлаживать, но им-то какая польза от лишних оберток, если всё равно нужно понять, какое исключение было первым?
                                                                        0
                                                                        Какой нарушение инкапсуляции?
                                                                        Исключения, которые может выбросить какой-то модуль – это такая же часть его API, как и возвращаемые значения. API должно скрывать реализацию. «FileNotFoundException» – это просочившаяся наружу деталь реализации.

                                                                        Если мы разрабатываем исполняемое приложение, то это волнует конечного пользователя. Что вы ему покажете? «Unknown error»? «FileNotFoundException»?

                                                                        Если мы разрабатываем библиотеку, то это волнует разработчика, который будет использовать библиотеку. Что ему делать с вашим «FileNotFoundException»? Какой файл ваша библиотека не нашла? С этим можно что-то сделать? Как на это отреагировать? Это файл конфигурации или файл базы данных?
                                                                          0
                                                                          Исключения, которые может выбросить какой-то модуль – это такая же часть его API, как и возвращаемые значения. API должно скрывать реализацию. «FileNotFoundException» – это просочившаяся наружу деталь реализации.

                                                                          NPE тоже может «просочиться наружу», а по смыслу FileNotFoundException может быть такой же фатальное ошибкой. Тем не менее, ни один здоровый человек не будет оборачивать NPE во что-то более высокоуровневое, чтобы соответствовать вашим странным представлениям об инкапсуляции.
                                                                            0
                                                                            Ни один здравый человек не допустил бы просачивание NPE наружу, если бы Java это не поощрала замалчиванием потенциальной ошибки.
                                                                              0
                                                                              Наружу — это куда конкретно?
                                                                              У нас с вами разное представление о здоровых людях.
                                                                              NPE здорового человека никогда не бывает «потенциальной» ошибкой. Это всегда косяк разработчиков и означает ничто иное, кроме фатальной ошибки (т.е. программа работает неверно).
                                                                              NPE курильщика, кроме явного разыменования ссылки, может «вылетать» из проверки
                                                                              if(ref == null){
                                                                                  throw new NullPointerException(ref);
                                                                              }
                                                                              
                                                                                0
                                                                                Такая проверка довольно стандартна в конструкторах и сеттерах при явном контракте non-null. И NPE летит, когда пользователь его нарушает.
                                                                                  +1
                                                                                  Ммм, а в яве нет аналога ArgumentNullException (и вообще всей иерархии ArgumentException)?
                                                                                    +1
                                                                                    Нет, традиционно при проверке аргументов используются NullPointerEx extends RuntimeEx и IllegalArgumentEx extends RuntimeEx. Оба являются unchecked. Отдельной иерархии нет.

                                                                                    Стоит добавить, что NPE может означать как разыменование null reference, так и передачу null туда, где она недопустима по контракту.
                                                                                      0
                                                                                      В том то и дело, что кидать надо не NPE, а IllegalArgumentException.
                                                                                        0
                                                                                        О, это как-то больше с моей картиной мира сочетается.
                                                                                          0
                                                                                          Почему-то создатели очень большого количества библиотек делают иначе. Включая rt.jar от sun/oracle. Хуже того, оно неконсистентно.

                                                                                          Примеры:
                                                                                          — java.io.Reader в конструкторе сразу кидает NPE, java.io.BufferedInputStream — нет (до первого read/readLine и т. п.);
                                                                                          — многие коллекции из java.util.Collections сразу кидают NPE;
                                                                                          — java.net.URI и j.n.URL имплицитно кидают NPE (при использовании методов на параметре, переданном в конструктор);
                                                                                          — java.net.Socket#new(Proxy) кидает IAE, если передать null;
                                                                                          — часть библиотеки в jdk7+ использует Objects.requireNonNull, который кидает NPE. Как пример, классы из java.time;
                                                                                          — java.nio.charset.Charset.forName кидает IAE.

                                                                                          Так что я совсем не понял минуса на этот комментарий.
                                                                                            0
                                                                                            habrahabr.ru/post/183322/#comment_6370870
                                                                                            Во-первых, увы, но стандартная библиотека все-таки не скрижали завета. Ее авторы тоже люди, и могут делать ошибки. И эти ошибки — в отличие от ваших и моих — очень сложно исправлять, ибо обратная совместимость же.
                                                                                              0
                                                                                              Мало ли что там делают создатели библиотек. Вот API амазона — найдете там хоть одно проверяемое исключение?
                                                                                              Мы в этой ветке обсуждаем NPE vs IAE, а не checked vs unchecked.

                                                                                              Возвращаясь к оригинальной теме ветки (NPE vs IAE) для меня куда большим авторитетом чем вы или cheremin, являются авторы JDK где в javadoc'е к NPE сказано следующее:
                                                                                              Applications should throw instances of this class to indicate other illegal uses of the {@code null} object.
                                                                                              Оно более специфично, чем указанное в javadoc'е для IAE:
                                                                                              Thrown to indicate that a method has been passed an illegal or inappropriate argument.

                                                                                              Каких-либо объективных причин использовать IAE и не использовать NPE я не вижу, потому считаю это вопросом договорённости внутри команды.
                                                                                              0
                                                                                              Почему-то создатели очень большого количества библиотек делают иначе.

                                                                                              Мало ли что там делают создатели библиотек. Вот API амазона — найдете там хоть одно проверяемое исключение?
                                                                                              Я мог бы привести такие примеры в статье как аргумент, но это был бы убогий аргумент.
                                                                                              0
                                                                                              Вообще NPE — более специфичный (для такой ситуации), чем IllegalArgumentException, так что в большинстве случаев логичней кидать именно его.
                                                                                              А в итоге это ещё один холивор в Java. Немногим менее распространённый чем checked/unchecked exceptions.
                                                                                      +1
                                                                                      FileNotFoundException может быть такой же фатальное ошибкой
                                                                                      Так ли часты случаи, когда нижние слои абстракции могут точно определить «фатальность» ошибки? Разве это не задача клиента, который вызывает ваш код?
                                                                                        0
                                                                                        У меня нет объективной статистики по этому вопросу.
                                                                                        Бывает, что уже на низком уровне понятно, что с ошибкой уже ничего не сделать. Бывает, что нет.
                                                                                      +1
                                                                                      «FileNotFoundException» – это просочившаяся наружу деталь реализации

                                                                                      Для метода типа file.open() — это не деталь реализации, а публичный интерфейс. Вот если использующий этот интерфейс клиент будет пробрасывать это исключение наружу без обработки, то тогда это будет нарушением инкапсуляции.
                                                                                        0
                                                                                        Да, спасибо за уточнение. Я имел ввиду именно тот случай, когда пользователь интерфейса пробрасывает исключение наружу. Хотя и в этом случае есть исключения: например, если мы реализуем паттерн декоратор.
                                                                                          0
                                                                                          Ну, декораторы и прочие прокси отдельный случай — они должны реализовывать тот же интерфейс.
                                                                                            0
                                                                                            Простите, вы на границе каждого слоя перехватываете все RuntimeException, чтобы, не дай бог, ни одна деталь реализации не просочилась наружу?
                                                                                +1
                                                                                неаккуратный программист понатыкает везде throws Exception, ублажив тем самым компилятор и на корню зарубив всю «самодокументируемость».


                                                                                Ни один язык никогда не защитит дурака от преднамеренной глупости. Но вот умного человека от неосторожной ошибки уберечь можно. Я вообще не считаю аргументы такого рода ценными. Языки программирования придумываются не для тех, кто пытается писать код по принципу «тяп-ляп лишь бы отделаться». Таким людям Java не годится. И слава богу. Для них есть ряд скриптовых языков, позволяющих писать что попало и как попало.

                                                                                в 21-м веке надо думать не о проверках на null

                                                                                Согласен. Именно поэтому эти аннотации и придумали.

                                                                                Есть проблема: часть параметров может быть пустыми, для другой части это запрещено моделью. Привычное неудобное решение — проверка времени выполнения. Желательное решение — проверка времени компиляции. Приведенные мной аннотации позволяют это валидировать в момент компиляции.

                                                                                Расскажете мне, как можно «не думать» о возможности «обнуляемости» полей модели?
                                                                                  0
                                                                                  Про Option еще раз повторить?
                                                                                    0
                                                                                    Почитал про него, спасибо. Было интересно.

                                                                                    Возможно, попробую.

                                                                                    Хотя навскидку я так и не понял6 в чем его преимущество перед аннотациями.
                                                                                      0
                                                                                      Хотя навскидку я так и не понял6 в чем его преимущество перед аннотациями.

                                                                                      Буду краток. При использовании null:
                                                                                      String version = "UNKNOWN";
                                                                                      if(computer != null){
                                                                                        Soundcard soundcard = computer.getSoundcard();
                                                                                        if(soundcard != null){
                                                                                          USB usb = soundcard.getUSB();
                                                                                          if(usb != null){
                                                                                            version = usb.getVersion();
                                                                                          }
                                                                                        }
                                                                                      }
                                                                                      

                                                                                      При использовании Option:
                                                                                      String name = computer.flatMap(Computer::getSoundcard)
                                                                                                                .flatMap(Soundcard::getUSB)
                                                                                                                .map(USB::getVersion)
                                                                                                                .orElse("UNKNOWN");
                                                                                      

                                                                                      Подробности тут
                                                                                        0
                                                                                        На сколько я понимаю, инкапсуляция тут идёт лесом?
                                                                                        В таком случае не проще ли написать:
                                                                                        String name = USB::getVersion( Soundcard::getUSB( Computer::getSoundcard( computer ) ) ) ?: «UNKNOWN»
                                                                                          0
                                                                                          На сколько я понимаю, инкапсуляция тут идёт лесом?

                                                                                          Нет, а с чего бы?
                                                                                            –2
                                                                                            С того, что вызовы методов вы заменили на вызовы глобальных функций, поведение которых не зависит от реализации, передаваемого им объекта.
                                                                                              +1
                                                                                              Во-первых, не я. Во-вторых, им ничего не мешает быть полиморфными. Ну и в-третьих, Option с равным успехом позволяет вызывает и методы самого объекта.
                                                                                                –1
                                                                                                Уговорили
                                                                                                String name = computer.flatMap(с -> c.getSoundcard())
                                                                                                                          .flatMap(s -> s.getUSB())
                                                                                                                          .map(u -> u.getVersion())
                                                                                                                          .orElse("UNKNOWN");
                                                                                                

                                                                                                Первоначальный пример взял с сайта оракла без изменений.
                                                                                                А вообще лямбды в Java — тот еще костылище. Даже как-то немного стыдно перед вами, шарперами.
                                                                                                  0
                                                                                                  Эмн, так ведь это ровно то же самое.
                                                                                                    0
                                                                                                    Но теперь очевидно, что тут полиморфизм во всей красе.
                                                                                                    0
                                                                                                    в шарпе сейчас вот такой синтаксис
                                                                                                    var version = computer?.getSoundcard()?.getUSB()?.getVersion() ?? "UNKNOWN";
                                                                                                    
                                                                                                      0
                                                                                                      Это для опшенов или нуллов?
                                                                                                        0
                                                                                                        Для null. За опшнами идите в F#.
                                                                                              –3
                                                                                              Самое смешное, что Option это тоже самое (в некотором смысле), что и checked exception. Только что вы всех клиентов апи заставили использовать свой новый подход.

                                                                                              Ну и пример отдаленно похож на яву, map, flatmap это методы списка, а не компьтера, и результат должен иметь тип список, а не строка, orElse — метод Option. В жизни так красиво не получится.
                                                                                                +3
                                                                                                flatMap и map — это методы, которые в той или иной ипостаси есть у каждой монады в приличном языке
                                                                                                  0
                                                                                                  А при чем тут язык? Наличие flatMap же вообще часть определения.
                                                                                                    0
                                                                                                    Ну, некоторые языки реализуют это «определение» не полностью. С другой стороны, можно считать, что в них просто нет монад.
                                                                                                  0
                                                                                                  Просто некрасивый пример. Очевидно же что подразумевалось
                                                                                                  Optional<Computer> computer;
                                                                                                  
                                                                                                    +1
                                                                                                    Ну и пример отдаленно похож на яву


                                                                                                    Здрасьти, это и есть ява.
                                                                                          +6
                                                                                          Я не знаю ни одной причины, почему написанный мной код плох. А вы?
                                                                                          А вы причину прямо-таки чуть ниже указали:
                                                                                          если при использовании некоторого ЯП вам приходится писать много однообразных конструкций, это намекает, что вы что-то делаете не так.


                                                                                          Если у вас инициализация подсистему парсит кучу XML'ей, грузит разные данные из разных файлов и прочее, то в языке без checked exceptions я поймаю одно исключение (аналог java.io.IOException) и аккуратно откажусь стартовать. В языке же с checked exceptions внизу придётся кидать 100500 исключений, в которые завёрнуты FileNotFoundException, а вверху — их ловить. Так… кто там заикался насчёт вреда однообразных конструкций?

                                                                                          А теперь немного про преимущества. Я бы из них выделил и обрамил золотом одно:
                                                                                          В языках без checked exceptions разработчики могут не указать возможные исключения в сигнатуре метода, в результате не всегда понятно, каких подвохов от него ждать. В Java компилятор этого не допустит.
                                                                                          Стоит ли обрамлять в золото ложные утверждения? Наследуем ваш ConfigFileNotFoundException от java.lang.RuntimeException — и имеем все проблемы языков без проверяемых исключений без соответствующих преимуществ.
                                                                                            +4
                                                                                            Если у вас инициализация подсистему парсит кучу XML'ей, грузит разные данные из разных файлов и прочее, то в языке без checked exceptions я поймаю одно исключение (аналог java.io.IOException) и аккуратно откажусь стартовать. В языке же с checked exceptions внизу придётся кидать 100500 исключений, в которые завёрнуты FileNotFoundException, а вверху — их ловить.
                                                                                            Не 100500 исключений, а ровно одно, семантический смысл которого ошибка при инициализации подсистемы. Да, в это исключение может быть завернуто 100500 различных исключений более низкого уровня для последующего «разбора полетов» и адекватных логов. Но на уровне рантайма на то нам и нужна инкапсуляция: тому слою, который вызывает инициализацию вашей под системы, не интерестно, как эта инициализация происходит и какие проблемы могут возникнуть на ее этапе. Единственное, что вас должно волновать на этом уровне – удачно прошла инициализация или нет, потому что только от этого зависит ваше дальнейшее поведение.

                                                                                            Чтобы было понятнее, приведу пример того, что я имею ввиду:
                                                                                            public void initializeService() throws ServiceInitializationException {
                                                                                                ...
                                                                                                catch(FileNotFoundException e){
                                                                                                    throw new ServiceInitializationException(e);
                                                                                                }
                                                                                                ...
                                                                                                catch(XMLParsingException e){
                                                                                                    throw new ServiceInitializationException(e);
                                                                                                }
                                                                                                ...
                                                                                            }
                                                                                            


                                                                                            И таких вариантов catch-throw у вас в коде инициализации может быть ровно столько, сколько у вас причин зафейлить инициализацию. Но для вызывающего все это выглядит одинаково – он просто получает ServiceInitializationException и понимает, что «что-то пошло не так».
                                                                                              +1
                                                                                              Не 100500 исключений, а ровно одно, семантический смысл которого ошибка при инициализации подсистемы.
                                                                                              То есть я в 100500 местах должен завернуть FileNotFoundException в ошибку при инициализации подсистемы для того, чтобы ублажить «бога проверяемых исключений», а ловить «на верхнем уровне» могу только одно исключение. Уже лучше, но… кто тут говорил
                                                                                              если при использовании некоторого ЯП вам приходится писать много однообразных конструкций, это намекает, что вы что-то делаете не так.

                                                                                              Вы всё правильно понимаете, но смотрите со своей, «питоньей» точки зрения. Где вы делаете нечто похожее на проверяемые исключения синтаксически, но весьма сильно отличающееся по сути.

                                                                                              Предположите что у вас инициализация сервиса нетривиальна и состоит из многих классов и методов. В языке без проверяемых исключений у вас всё равно вся обработка останется в initializeService (где ей и место), а вот в Java вас просто-таки заставят её засунуть во все места и во все спомогательные функции — либо в виде описаний throws, либо в виде try/catch.
                                                                                                +2
                                                                                                кто тут говорил
                                                                                                Так говорил bigfatbrowncat, и я с ним не согласен. Если честно, я вообще отрицаю, что дублирование кода это обязательно признак чего-то плохого. Я считаю, что тот факт, что вам нужно постоянно повторять catch-throw блоки – это цена, которую вы платите за те преимущества, которые вам дает такой подход. Но цена – понятие рыночное, и в каких-то случаях она может быть приемлемой, а в других – нет. И, насколько я понимаю, суть проблемы конкретно в Java в том, что никакой альтернативы (какого-либо другого подхода для работы или не-работы с исключениями) в общем-то нет.

                                                                                                Я смотрю еще и с позиции Rust, где с исключениями еще более строго, чем в Java, и довольно часто приходится писать кучу try!(...) и `match`, которые переваривают исключительные состояния. Это цена, которую я плачу за то, что могу быть увереным, что если моя программа вообще скомпилировалась, значит она не упадет внезапно с каким-нибудь NullPointerException в недрах проекта. Но если я не хочу по каким-либо причинам возится с исключениям, Rust позволяет мне просто вызвать unwrap(), что грубо говоря означает: «если что-то пошло не так, просто корректно завершайся».
                                                                                                  0
                                                                                                  Если все, что видит внешний код — это initializeService, то какая разница, какая сигнатура у вызываемых им вспомогательных функций? Не могу поверить, что говорю это, но все же: ну засуньте в них уже этот ваш throws Exception, в initializeService ловите только его и успокойтесь уже наконец.
                                                                                                    0
                                                                                                    Вообще, создается впечатление, что у многих людей Alt-Enter на клавиатуре туго нажимается.
                                                                                                    Битва за throws у методов идет просто не на жизнь, а на смерть!
                                                                                                      +2
                                                                                                      Ну да, а у тех, кому не нравится оператор присваивания в Паскале, просто двоеточие туго зажимается.
                                                                                                      Зато его не перепутаешь с оператором сравнения, как в Си.
                                                                                                      Но люди почему-то предпочитают поменьше писать всякой херни и побольше — осмысленного кода.
                                                                                                +1
                                                                                                Интересное кино… А вы в курсе, что FileNotFoundException является наследником IOException? Или теоретизируете? Ловите один IOException — кто вам не дает?

                                                                                                Я приводил пример для того, чтобы проиллюстрировать 3 тезиса:
                                                                                                1. Если куча исключений обрабатывается почти одинаково, надо дать им одного общего предка и не множить код
                                                                                                2. На каждом слое абстракции исключения должны быть разные и одни должны обрамлять другие
                                                                                                3. Если вы так уперлись в нежелание писать throws, вы всегда можете обернуть проверяемое в непроверяемое и наслаждаться отсутствием проверяемых исключений.
                                                                                                  0
                                                                                                  Всё хорошо, но сейчас в io есть ещё UncheckedIOException extends RuntimeException.
                                                                                                  В nio есть всякие BufferUnderflowException/BufferOverflowException extends RuntimeException, есть отнаследованные от IllegalStateException, UnsupportedOperationException, IOException.
                                                                                                  Часть вещей в nio2 отнаследованы от IllegalStateException, часть от IOException, некоторые от ConcurrentModificationException.

                                                                                                  Так что, в зависимости от кейса и используемого API придется ловить существенно больше, чем просто IOE.
                                                                                                    –1
                                                                                                    Ну так UncheckedIOException — это же как раз то, о чем мечтали товарищи, вопящие о необходимости писать лишние try-catch. Вот, получите-распишитесь. Кому-то теперь стало проще. Лично мне было проще без него. Я пока Java8-специфичный код не писал еще и, учитывая специфику проекта, не буду писать еще с год. Но потом, думаю, смогу по достоинству оценить все нововведения.

                                                                                                    Увы, пока мне кажется, что любимый мной за строгость язык «прогибается» под натиском любителей писать (и продумывать) поменьше. Это — печально. Обогнать Python и JS в плане лаконичности все равно не удастся, но если Java потеряет свою кристальную прозрачность, она обесценится.
                                                                                                      0
                                                                                                      IOE-то никуда не делось, равно как и часть иерархии в nio и nio.2, отнаследованная от него. Так что жить стало лучше, жить стало веселей ,)
                                                                                                        0
                                                                                                        Я пока Java8-специфичный код не писал еще и, учитывая специфику проекта, не буду писать еще с год. Но потом, думаю, смогу по достоинству оценить все нововведения.

                                                                                                        Удачи в скрещивании проверяемых исключений с лямбдами.
                                                                                                          0
                                                                                                          Лямбды? А… это вы про такую сокращенную запись анонимной имплементации интерфейса, состоящего из одной функции? Ну так они же нужны просто чтобы писать покороче. Ими можно вовсе не пользоваться, если где-то это неудобно…
                                                                                                            0
                                                                                                            В стримах (Collection.stream()) — неудобно без лямбд.
                                                                                                              0
                                                                                                              Ими можно вовсе не пользоваться, если где-то это неудобно…

                                                                                                              Если проверяемые исключения мешают пользоваться лямбдами и вообще, в основном, мешают, может, лучше отказаться от проверяемых исключений?
                                                                                                              Лямбды? А… это вы про такую сокращенную запись анонимной имплементации интерфейса, состоящего из одной функции? Ну так они же нужны просто чтобы писать покороче.

                                                                                                              «Анонимная имплементация интерфейса, состоящего из одной функции» — это явовский костыль, и он нужен только потому, что в Java функции не являются полноценными объектами (в Java 8 их уже можно передать в метод, но до полноценных объектов им далеко). Лямбды — это костыль для частичного исправления этой проблемы.
                                                                                                                +2
                                                                                                                Я вам так скажу. Для меня в любом языке лямбда-выражение — не более, чем сокращенный (и от этого более трудночитаемый) формат записи функции. Я, честно, не вижу никаких преимуществ у записи типа "(x,y)->(2*x+y)" перед «double f(double x, double y) { return 2*x + y; }» помимо того, что в первой меньше букв. А если в первую добавить типы, то она ничем не будет отличаться от второй.

                                                                                                                В Java не хватало делегатов. Их, как я понимаю, добавили. Ну и отлично. Больше ничего в этом смысле для счастья и не нужно.
                                                                                                                  0
                                                                                                                  Я, честно, не вижу никаких преимуществ у записи типа "(x,y)->(2*x+y)" перед «double f(double x, double y) { return 2*x + y; }» помимо того, что в первой меньше букв.

                                                                                                                  Не просто букв — меньше избыточного визуального шума, который не несет пользы.

                                                                                                                  Для меня в любом языке лямбда-выражение — не более, чем сокращенный (и от этого более трудночитаемый) формат записи функции.

                                                                                                                  В C# вы были бы… удивлены.
                                                                                                                    0
                                                                                                                    Я, честно, не вижу никаких преимуществ у записи типа "(x,y)->(2*x+y)" перед «double f(double x, double y) { return 2*x + y; }» помимо того, что в первой меньше букв.

                                                                                                                    И я не вижу, потому что лямбды ввели не для этого. Если бы была цель сделать именно вывод типов, его бы сделали нормально, а не только в лямбдах.
                                                                                                                    В нормальных ЯП функции являются полноценными объектами; когда там хотят передать функцию как аргумент, передают именно функцию, а не костыль в виде анонимной реализации интерфейса с одним методом. Вы согласны, что последний вариант — костыль?
                                                                                                                      0
                                                                                                                      На низком уровне: делегат — это структура хранящая ссылки на функцию и скоуп, которая ближе к объектам, чем к функциям.
                                                                                                                        +1
                                                                                                                        А еще ниже — указатели, доступ к регистрам, кровь, пули и гнев божий.
                                                                                                              +2
                                                                                                              Увы, пока мне кажется, что любимый мной за строгость язык «прогибается» под натиском любителей писать (и продумывать) поменьше.

                                                                                                              Этак можно дойти до мысли, что джависты — высшая раса, а остальные вообще думать не хотят, поэтому придумывают всякие плюшки вроде properties, чтобы писать поменьше. Ведь написание boilerplate кода так помогает обдумыванию и пониманию!
                                                                                                                +2
                                                                                                                Когда язык «прогибается», обычно это означает, что строгие принципы заменяются компроммисами, и абстракции начинают «течь». Например, смешивание uncheked и checked исключений – это дырявыя абстракция. А то, что лямбды не уживаются с checked-исключениями – нарушение консистентности языка. Ну а properties, о которых вы упомянули, это просто синтаксический сахар, они не создают ни дырявых абстракций, ни нарушают консистенции.
                                                                                                                  0
                                                                                                                  Например, смешивание uncheked и checked исключений – это дырявыя абстракция.

                                                                                                                  Что вы понимаете под дырявой абстракцией? Почему с вашей точки зрения смешивание checked и unchecked — это дырявая абстракция? Мне кажется, мы по-разному понимаем этот термин.

                                                                                                                  Вот бросание IOException из reader.read() — это очевидный признак дырявой абстракции. Низкоуровневая подробность (ненадежность чтения данных с носителя или по сети) пробивается наверх. (Этот пример не имеет отношения к проблеме checked/unchecked exceptions)
                                                                                                                    0
                                                                                                                    Ого! Ничего себе! Уже штатная ситуация разрыва сетевого канала в низкоуровневом API чтения является нежелательной подробностью!
                                                                                                                    То есть по вашему было бы лучше, если бы read вернул бы какой-то мусор?
                                                                                                                      +1
                                                                                                                      не нужно дефектный API использовать как оправдание, OS обязана была вернуть API меняющий поведение IO в зависимости от потребности, а не засорять код низкоуровневыми подробностями… если приложению хенгаут, это не событие приложения, а поведение OS, от того, что вам прилетит событие, легче не станет, в приложении появиться системозависимый код, много низкоуровневого кода…
                                                                                                                        0
                                                                                                                        Можете набросать такой API и способ его использования? А то пока я не вижу альтернатив тому, что есть. И поясните, что значит
                                                                                                                        OS должна вернуть API
                                                                                                                        , потому как API — это соглашение в работе между системами и что там можно кому-то возвращать, мне непонятно.
                                                                                                                          –2
                                                                                                                          1) операции асинхронны, read/write возвращаются фьючерсы… open — не открытие файла, а строит запрос стрима по типу sql или редекспа, но для файлов (если запрос по сети или со съемных устройств… должно наследоваться с соответствующей системы типов IO, обеспечивающей либо реконнект на дисконектах, либо транзакционные операции, либо игнор ошибок, если в приложении необходим такой функционал), read/write ((в си) одним из аргументов) не буффер, а callback на обработчик если запрос выполнен, в котором и используется ответ, (все получили, ничего не может рассоединиться) или не выполнится ничего… и нет close
                                                                                                                          2) IO — эта система различных типов взаимодействия с внешними устройствами, в никсах она стагнировала до состояния блочные устройства и почти блочные(псевдо устройства), поэтому весь ввод-вывод в никсах — кривой, либо медленно и универсально, либо хаки-кастыли к драйверам. Но устройства имеют разную модель поведения, и в нормальной программе вы не должны вникать в сложности реализации, а писать только программу реализующую решение, сложность IO должна быть инкапсулированна в типы IO, а не в один тип блочных устройств с API open-read-write-seek-close, слишком высокоуровневый для описания логики работы и низкоуровневый для реализации любой программы как миниOS… создатели юникса не парились с исключениями, API построен исходя из приципа — все должно быть ОК, если что упади, если нужно демоном подними/перезапусти, все что навертели в Sun — клиника, не вашим и не нашим, нельзя упасть и нельзя работать дальше.
                                                                                                                          JVM — возвращает среду, в которой и храниться состояние, так и средства доступа к OS как API, слой абстракции уже есть, jvm симулирует процессор и ос, достаточно было разработать внятный API, но решили, что на встроенной технике толстая логика «не взлетит», чтож, пишите мегатонны самописных прикладных ос, с простынями обработки ошибок!
                                                                                                                        0
                                                                                                                        Закон дырявых абстракций
                                                                                                                        Там хороший пример с TCP и IP.

                                                                                                                        Когда я читаю данные из ридера, я читаю просто поток данных. Меня не волнует, откуда и как они приходят: из файла, по сети или просто из строки. На этом уровне абстракции вообще нет никаких файловых систем, сокетов и прочего. Но почему при чтении может быть ошибка? Потому что более низкоуровневые протоколы ненадежны. Именно это громко провозглашает throws IOException в методах ридера.
                                                                                                                        Это не значит, что IO API неправильно спроектирован. Это значит, что абстракциям свойственно протекать.
                                                                                                                    0
                                                                                                                    Ведь написание boilerplate кода так помогает обдумыванию и пониманию!


                                                                                                                    Вы будете смеяться, но, да, помогает. Из-за того, что в Java программы выглядят длиннее, чем, скажем, в Python, думаешь над тем, что пишешь и что делаешь.

                                                                                                                    Хотя, я не знаю, что для вас boilerplate. Кто-то считает, что декларация типа переменной — это boilerplate и его надо заменить на вездесущий «var». Кто-то предпочтет не париться с шаблонными параметрами и не использовать дженерики — всё равно же после компиляции все равны будут. Кому-то вообще не хочется создавать аксессоры и он публикует филды в надежде, что пользователь после изменения поля data дернет функцию handleDataUpdated().

                                                                                                                    Каждому — свое. Я бы добавил в Java переопределение операторов и, возможно, стековые объекты (как в C#). А еще добавил бы туда свойства — как синтаксический сахар над аксессорами. Делегаты, как я понимаю, там уже и так есть. Что еще лишнего вы пишите? Что для вас boilerplate? Мне все вышеперечисленные фичи помогают продумывать архитектуру (вместе с пресловутыми checked exceptions)
                                                                                                                      +2
                                                                                                                      Вы будете смеяться, но, да, помогает. Из-за того, что в Java программы выглядят длиннее, чем, скажем, в Python, думаешь над тем, что пишешь и что делаешь.

                                                                                                                      Вы похожи на водителя-старовера, которому ручная КПП «помогает» внимательнее следить за дорогой.
                                                                                                            +4
                                                                                                            Совершенно верно. Проверяемые исключения относятся к области контрактного программирования. При помощи оберток методы могут скрывать внутренние детали имплементации. В данном случае конфиг может читаться не только из файла, а из базы или запрашиваться с удаленного сервера. В этом случае SQLException, FileNotFoundException, IOException можно обернуть единым checked ConfigResourceException. Это все известно, хорошо и правильно.

                                                                                                            А теперь реальные случаи, где действительно checked exceptions мешают и про которые автор не упомянул.

                                                                                                            1. Закладка exception в интерфейсах.

                                                                                                            Методы Input/OutputStream должны выбрасывать IOException. При этом в некоторых имплементациях интерфейса реальный эксепшн может вообще отсутствовать, как например в случае, если мы пишем в new DataOutputStream( new ByteArrayOutputStream())). Тем не менее компилятор здесь требует проверки.

                                                                                                            В других же случаях эксепшн-причина сильно зависит от реализации ресурса (socket, file, network, etc...), но всегда обертывается в универсальный IOException, сильно затрудняя при этом ее детальный отлов на более высоком уровне (приходится смотреть ex.getCause()).

                                                                                                            Другой пример — Java RMI, который для каждого remote-метода назойливо обязует прописывать throws RemoteException. Особенно достает, когда один и тот же интерфейс используется и для локальных, и для remote-вызовов.

                                                                                                            2. Java API пронизано checked-эксепшнами, и нет никакого единообразия в их применении.

                                                                                                            Например, задача парсинга:
                                                                                                            new Integer(«123») кидает unchecked ParseException, также как и java.text.Format.parseObject(). Почему тогда мы так бережно должны заботиться о new URL(«www.my.com») и каждый раз оборачивать checked конструктор в MalFormedURLException?

                                                                                                            3. Отвратительный дизайн ресурсов в Java. Именно для них, как утверждается, были первоначально введены checked exceptions, чтобы заставить программиста корректно закрывать ресурс.

                                                                                                            Реально, во-первых, до try-with-resources не было нормальной формы отловить эксепшн и закрыть ресурс, и при этом ничего не потерять. Хотя все можно было бы исправить простым требованием, что в случае exception ресурс становится unusable и освобождается автоматически провайдером. Т.е. не нужно вызывать при этом close().

                                                                                                            Во-вторых, большинство API, использующие ресурсы, выбрасывают generic checked exception, вроде java.sql.SQLException, совершенно не различая при этом, был ли это физический сбой ресурса (оборвался коннекшн, грохнулась база или диск), либо это ошибка в логике или транзакции (duplicate primary key, etc). Хотя обработка этих двух случаев кардинально отличается: в первом случае ресурс становится unusable, во втором — можно сделать откат и использовать дальше.

                                                                                                            Вобщем, imho, checked exception хорош, когда метод должен возвратить выше что-то еще, кроме основного результата. И его использование сродни использованию Optional: который также явно декларирует, что метод, кроме основного результата может возвратить null, и что нужно позаботиться об этом.
                                                                                                              0
                                                                                                              Вобщем, imho, checked exception хорош, когда метод должен возвратить выше что-то еще, кроме основного результата. И его использование сродни использованию Optional: который также явно декларирует, что метод, кроме основного результата может возвратить null, и что нужно позаботиться об этом.

                                                                                                              Даже тут они не особо нужны: danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html
                                                                                                                0
                                                                                                                Можно и так, но это не совсем одно и то же. У монады Try имеется тот же недостаток, что и у Option(al): она не ковариантна ко вложенному типу. В Java я легко могу написать:
                                                                                                                class AbstractBuffer {
                                                                                                                  String read() throws IOException;
                                                                                                                }
                                                                                                                class MemBuffer extends AbstractBuffer {
                                                                                                                  @Override String read();
                                                                                                                }
                                                                                                                

                                                                                                                Для экземпляров MemBuffer компилятор не будет требовать обрабатывать IOException. С Try не будет такой возможности: если MemBuffer.read() будет возвращать Success, его все-равно придется дополнительно «разворачивать», чтобы получить String.
                                                                                                                Кроме того, в одном блоке try-catch я могу оперировать сразу со многими вызовами «throws IOException», не заботясь о том, какой именно «вылетел». В случае же Try придется каждый вызов «разворачивать» отдельно со своим обработчиком.
                                                                                                                +1
                                                                                                                Методы Input/OutputStream должны выбрасывать IOException. При этом в некоторых имплементациях интерфейса реальный эксепшн может вообще отсутствовать, как например в случае, если мы пишем в new DataOutputStream( new ByteArrayOutputStream())). Тем не менее компилятор здесь требует проверки.
                                                                                                                Не могу согласиться, что это недостаток. Ведь на то и нужны интерфейсы, чтобы абстрагировать сигнатуру от реализации. И тот код, который будет использовать вашу имплементацию интерфейса через dynamic dispatch не должно и не может волновать, выбрасывает в принципе ваша реализация IOException или нет. Наоборот, он должен исходить из того, что получить исключение возможно, потому что из-за dynamic dispatch невозможно определить, какая конкретно из реализаций интерфейса сейчас используется.
                                                                                                                  0
                                                                                                                  Вы за деревьями леса не видите. Интерфейсы (как и любые абстракции) нужны не для реализации каких-то абстрактных идей, а для того, чтобы программу было легче читать и писать. Тут — они явно делают жизнь сложнее. Можно ли этого избежать — другой вопрос, но то, что это плохо — однозначно.
                                                                                                                    +1
                                                                                                                    Я как раз и говорил, что в случае с dynamic dispatch этого избежать никак нельзя, и это не какое-то «технические» ограничение, а следствие самой идеи интерфейса.
                                                                                                                  –1
                                                                                                                  1. ByteArrayOutputStream — знаете… а его использование может быть оправдано архитектурой? Я вот, с ходу, вижу только одно применение — в тестировании. В остальных случаях — явно какая-то проблема с обработчиком, которму вы хотите такой «OutputStream» предложить. Ну а вообще — переопределённые методв в BAOS — они содержат throws IOException?
                                                                                                                  Про RMI и использование его интерфейса для чего-то ещё — та же фигня. Где-то зарыта архитектурная проблема. Для не-RMI реализации, кстати, можно переопределить отсутствие бросаемых исключений.
                                                                                                                  2. Действительно — в Java DK существует проблема единообразия… но она касается не только исключений.
                                                                                                                  3. Не всегда исключение в ресурсе означает, что ресурс сдох (некорректное SQL выражение не означает, что соединение сломалось). Но и проблем тоже хватает.

                                                                                                                  Если Exception будет сродни Optional — его нужно будет заменить на Optional. Текущий функционал — вполне хорош при корректном использовании и корректной архитектуре. (но местами есть проблемы, в том числе в самой JDK).
                                                                                                                    +2
                                                                                                                    в некоторых имплементациях интерфейса реальный эксепшн может вообще отсутствовать

                                                                                                                    Вы упускаете из вида (или недопонимаете) смысл того, что называется «полиморфизм».

                                                                                                                    С точки зрения общего кода, работающего с потоками, любой ByteArrayOutputStream, и даже какой-нибудь AlwaysReturnZeroOutputStream — не более, чем поток. И если вы хотите абстрагироваться от реализации (а в этом суть и мощь ООП), то должны играть по правилам.

                                                                                                                    Раз существуют на свете потоки, способные ломаться и выдавать ошибку чтения, значит само понятие «поток» должно эту ошибку чтения в себя включать. Потоки, которые сломаться не могут, — приятные исключения из общего правила.

                                                                                                                    Если хотите сделать подкласс потоков, которые «не ломаются», сделайте его. Переопределите методы чтения, поймайте в общем предке исключение IOException и в блоке catch напишите «impossible».
                                                                                                                      +3
                                                                                                                      Вы сейчас усиленно латаете дыры в абстракциях :-)
                                                                                                                      Не задумывались, почему в Лиспе, например, нет ни исключений, ни кодов возврата?
                                                                                                                      Почему-то при обсуждении проблем проверяемых исключений, ведутся холивары на тему должны ли исключения быть проверяемыми или нет, но никто даже помыслить не может, что можно и без исключений вообще.
                                                                                                                        0
                                                                                                                        Я Лисп не знаю. А то, что я говорю, предпочитаю называть не «латанием дыр в абстракциях», а скорее «принятием парадигмы мышления».

                                                                                                                        Я несколько осведомлен в физике. Так вот, задача исследователя — выходить за рамки привычного стиля мышления. Корпускулярно-волновой дуализм, конфликт релятивистской механики с квантовой — эти все штуки как раз из науки. В инженерном деле, напротив, задача — принять некоторую парадигму и строить систему, жестко ей следуя.

                                                                                                                        Без исключений вообще можно. Есть язык, который без них обходится и который при этом я знаю. Называется он Си. В нем, правда, периодически необходимо писать goto. Это — другая парадигма мышления. На мой взгляд, менее прогрессивная. Верю, что в Lisp придумали какую-то другую модель. Возможно, в чем-то она лучше. Допускаю.

                                                                                                                        Мне ООП нравится тем, что оно близко к простой логике мышления человека. Людям свойственно делить весь мир на объекты, выделять среди них абстракции-классы, применять общее правило обращения с целым классом (привет, полиморфизм), игнорировать внутреннее устройство (инкапсуляция) и считать одни классы подклассами других (наследование).

                                                                                                                        Этот подход мне видится наиболее естественным, как и сама мысль о том, что в некоторой ситуации необходимо «вот прям щас всё бросить и заняться разгребанием катастрофы» (исключение).

                                                                                                                        А логика функциональных языков всегда казалась мне натянутой, потому что мир так не работает. У мира есть внутреннее состояние, а все действия (функции) так или иначе могут иметь побочные эффекты (которые чистое ФП, если я правильно помню, запрещает).
                                                                                                                          0
                                                                                                                          Продолжу об исключениях. Если я ем яблоко, то вне зависимости от того, какого оно сорта, цвета и формы, я должен проконтролировать, чтобы оно не было червивым. Это — «исключение класса ЧервивоеЯблоко».

                                                                                                                          И если даже ученые вывели новый сорт, который черви обходят за километр и не едят, то я об этом ничего не обязан знать и всё равно проверю, не погрызли ли его. Да, это лишнее действие. Но зато во-первых моя логика проста и единообразна, а во-вторых — надежна. Мало ли! Вдруг завтра появится новый червь-мутант…

                                                                                                                          Где вы здесь видите «дырявую абстракцию»?
                                                                                                                            +2
                                                                                                                            Дырявая абстракция — это когда вы вынуждены писать дополнительный код не потому, что у вас такие бизнес требования, а потому что у вас такие абстракции.

                                                                                                                            Это как если бы язык требовал (а некоторые и правда требуют) возвращать значение определённого типа, даже когда функция всегда кидает исключение:

                                                                                                                            treeParse({ onWarning: info => {
                                                                                                                            throw new Exception( info );
                                                                                                                            return new TreeNode( «impossible!» );
                                                                                                                            }})

                                                                                                                            А аналогом лиспа в «традиционных» языках могло быть бы следующее поведение:

                                                                                                                            treeParse( condition => {
                                                                                                                            switch( condition.type ) {
                                                                                                                            case 'Value Absent': return condition.setValue( defaults[ condition.nodeName ] );
                                                                                                                            case 'XSS': panic new Condition({ type: 'Security Error', condition });
                                                                                                                            case 'File Not Found': log( condition ); return makeDefaultTree();
                                                                                                                            default: panic new Condition({ type: 'Parse Error', condition });
                                                                                                                            }
                                                                                                                            })
                                                                                                                              0
                                                                                                                              Ну, думаю все современные языки способны понять, что какой-то код будет мертвым и никогда не выполнится. А в случае java ваш первый пример вообще не скомпилируется, т.к. код return new TreeNode( «impossible!» ); недостижим. Вот если бы throw new Exception( info ); было обернуто в другую функцию, которая только и делает, что кидает это исключение, тогда другое дело. Но тогда и по виду вызывающей функции не скажешь, что вызываемая всегда завершается исключением, не так ли? Если бы мы это знали, это было бы нарушением инкапсуляции этой функции.
                                                                                                                                0
                                                                                                                                Ну, typescript, например, требует что-то возвращать, даже если return недостижим.

                                                                                                                                Это похоже на карго-культ инкапсуляции. Основное предназначение функций — повторное использование кода, а не инкапсуляция, которая тут никаким боком.

                                                                                                                                class MyNode extends TreeNode {
                                                                                                                                override Node proceedWrondNode( Node node ) {
                                                                                                                                throw new Exception( «Wrong node » ~ node.name ~ " with value " ~ node.value );
                                                                                                                                }
                                                                                                                                }

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


                                                                                                                                  Это было давно. Сейчас основное назначение — структуризация и, крайне желательно, с инкапсуляцией, с, максимум, одним побочным эффектом.
                                                                                                                                    0
                                                                                                                                    Это задача модулей и классов, а не функций. Я понимаю, что вырожденный модуль — это одна функция, но из этого не следует, что каждая функция должна быть самостоятельным модулем.
                                                                                                                                      +1
                                                                                                                                      Функции/методы осуществляют структуризацию более низкого уровня, чем модули и классы.
                                                                                                                                    +2
                                                                                                                                    Основное предназначение функций — повторное использование кода, а не инкапсуляция, которая тут никаким боком.

                                                                                                                                    Основное предназначение функций — управление сложностью; в первую очередь, за счет information hiding.
                                                                                                                                      0
                                                                                                                                      И апогей information hiding — анонимные функции :-)
                                                                                                                                        0
                                                                                                                                        Какая вообще разница, какой из двух предназначений основное, если они хорошо подходят и для того, и для другого?
                                                                                                                                          +1
                                                                                                                                          Подходы к программированию разные. Если основное предназначение — повторное использование, то совершенно не важно, как называется функция.

                                                                                                                                          (это упрощение, конечно)
                                                                                                                                      –1
                                                                                                                                      Ну, думаю все современные языки способны понять, что какой-то код будет мертвым и никогда не выполнится.
                                                                                                                                      Т. е. проблему останова уже побороли, да?
                                                                                                                                        –1
                                                                                                                                        При чем здесь проблема останова? Обычный анализ графа потока управления. То, что можно найти мертвый код, не означает, что весь мертвый код будет найден, если вы об этом.
                                                                                                                                        0
                                                                                                                                        Ну, думаю все современные языки способны понять, что какой-то код будет мертвым и никогда не выполнится.
                                                                                                                                        языки способны понять
                                                                                                                                        –2
                                                                                                                                        Дырявая абстракция — это когда вы вынуждены писать дополнительный код не потому, что у вас такие бизнес требования, а потому что у вас такие абстракции.


                                                                                                                                        Насколько я понимаю это понятие, вы не правы. Дырявая абстракция — это слабое место архитектуры, которое создает необходимость конкретизировать абстрактный образ в некоторых случаях. Например, если вы в функцию передаете InputStream, но подразумеваете, что этот поток никогда не ломается и не выкидывает IOException, то ваша абстракция в этом месте «протекает». Вы опираетесь на конкретную реализацию.

                                                                                                                                        А в вашем случае речь идет не о дырявой абстракции, а о недоработке стандарта языка. В частности, за что я люблю Java, — это за то, что она четко отслеживает code flow на предмет неинициализированных переменных и, в частности, не требует return после throw, хотя во всех прочих случаях требует железно.

                                                                                                                                        Что такое typescript — в душе не знаю.

                                                                                                                                        И какое отношение инкапсуляция имеет к функциям тоже понять не могу, я ничего подобного, вроде, не говорил. Я говорил про инкапсуляцию уровня класса.
                                                                                                                                          +2
                                                                                                                                          Дырявая абстракция — это в принципе любое несоответствие выбранной модели и реальности. Абстракция проверяемых исключений полагается на то, что каждый слой приложения занимает в стеке не более одной позиции (один вызов функции), а самих слоёв сравнительно не много — в этом свете кажется уместным потребовать от вызывающего слоя обработать все исключения. Однако реальность интересней: каждый слой может состоять из множества вложенных вызовов функций, слои могу располагаться в стеке не только последовательно, но и вперемешку (колбэки, например), а самих слоёв может быть чуть менее, чем до фига. В итоге абстракция, призванная упростить разработку, только лишь её усложняет.
                                                                                                                                            0
                                                                                                                                            Давайте не будем придумывать свои термины. Всё уже придумано до нас
                                                                                                                                          0
                                                                                                                                          Дырявая абстракция — это когда я описываю абстракцию «круглое и съедобное» и вынужден добавить в нее исключение «червивое» только для того, чтобы на основе него потом смочь описать яблоко. При всем при том, чтоб сливы, виноград, персики и т.д. червивыми не бывают. И тем не менее, поскольку все они наследуются от «круглого и съедобного», должны проверяться на червивость.

                                                                                                                                          Помимо этого каждый из фруктов может иметь свое специфическое исключение, типа «незрелый», «гнилой» или «отравленный». Стандартный способ — описать абстрактное исключение «несъедобное», от которого наследуются «червивое», «гнилое», etc, и включить его в нашу абстракцию: «круглое съедобное» throws «несъедобное». Но если абстракция заведомо позиционируется как съедобная, зачем требовать каждого клиента нашей столовой тщательно осматривать фрукт перед съедением? Это будет только отпугивать публику. В случае же возникновения действительно исключительной ситуации, когда клиенту стало плохо, дело будут решать вышестоящие инстанции: врач, юрист или прокурор — в зависимости от тяжести ущерба.
                                                                                                                                      0
                                                                                                                                      С RMI есть и другая проблема, нам нужно было обрабатывать ошибки более детально, иногда нужен ретрай, иногда перебиндить сервис, иногда подождать пару сек, но во-первых нужно лезть за getCause, а во-вторых неизвестно какие еще рантайм ошибки может выбросить метод и когда, чтобы их заранее обработать.
                                                                                                                                    +7
                                                                                                                                    Сама идея проверяемых исключений очень интересная. Переложить на компилятор такие проверки это благо. Такое же благо, как типизация переменных. Мы знаем, какие исключения бросает функция, компилятор проверяет, что мы обработали ошибки.

                                                                                                                                    Конкретно реализация в Java — плохая. Почему плохая? Ну в принципе понятно почему, в статье всё описано и в принципе всё верно. Добавлю — появление класса java.io.UncheckedIOException в Java 8 это уже признание того, что идея “не взлетела” на официальном уровне.

                                                                                                                                    Как сделать хорошую реализацию? Честно — не знаю. Возможно стоит сделать понятие implicit throws. Если функция ничего не объявила, то она может кинуть что угодно из того, что кидает её тело. На первый взгляд кажется, что этого хватит для решения почти всех проблем с checked exceptions, но особенно я это не продумывал.

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

                                                                                                                                    Вот что точно сильно не радует — мода на коды возврата и их производные. Это ужасно. Это такой шажище назад.
                                                                                                                                      +1
                                                                                                                                      Вот на протяжении чтения статьи и комментариев думал над тем, что было бы здорово, если бы checked exception-ами признавались только те, что явно указаны в сигнатуре метода. Предположим что метод A выбрасывает checked exception E. Метод B вызывает метод A. Если в методе B явно в сигнатуре не указать исключение E, то далее по стеку вызова это исключение можно считать как unchecked.
                                                                                                                                      Сам постоянно сталкиваюсь с тем, что некоторые проверяемые исключения абсолютно бессмысленны. Например ошибки при создании URL. Когда я создаю URL из собственноручно прописанной строки с адресом, вероятность возникновения MalformedURLException кхм… крайне не велика, и весь огород с ее отлавливанием явно излишен. Но когда я создаю URL из строки, которая приходит из вне (вводится пользователем), я признателен компилятору за напоминание о потенциальной проблеме. Вот и получается, что в некоторых случаях одно и тоже исключение может быть checked и unchecked.
                                                                                                                                        0
                                                                                                                                        заранее оговорюсь, что идея сыровата и пестрит нестыковками =)
                                                                                                                                        • UFO just landed and posted this here
                                                                                                                                            0
                                                                                                                                            Именно! «Все уже придумано до нас»
                                                                                                                                          0
                                                                                                                                          Ну, это вы пока вводите и в здравом уме и трезвой памяти, знаете, что ваш URL корректный. А вот перепутаете с с с и усе.
                                                                                                                                          для тех, кто не сразу понял предыдущую фразу
                                                                                                                                          первая буква русская, вторая латинская

                                                                                                                                          Решением тут был бы аналог constexpr из C++, программирование на этапе компиляции, чтобы компилятор при компиляции проверил корректность строки и гарантировал, что URL действительно корректный, а не то, что вы считаете его корректным.
                                                                                                                                            0
                                                                                                                                            Я не врач, но это похоже на паранойю.
                                                                                                                                              0
                                                                                                                                              Вы правы, компилятору лучше быть параноиком, а то вдруг вы программу для ядерного реактора пишите…
                                                                                                                                                0
                                                                                                                                                Я скорее доверю свою жизнь хорошо протестированной программе на каком-нибудь PHP, чем просто скомпилированной проге на Java с компилятором-параноиком.
                                                                                                                                        +1
                                                                                                                                        Обрабатывайте только те исключения, которые действительно нужно обработать. Указывайте их в сигнатурах методов. Это можно делать и с unchecked exceptions, серьезно.

                                                                                                                                        Подумайте, действительно ли необходимы проверяемые исключения в вашем проекте или от них больше вреда, чем пользы. Относитесь критически ко всему, в том числе — к рекомендациям создателей Java. У вас свой проект, свои требования и особенности. Его делаете вы, а не Джеймс Гослинг, и он вам не поможет. Решать вам.

                                                                                                                                        Короче, думайте головой.
                                                                                                                                          +3
                                                                                                                                          Хм… а той же Idea throws непроверяемых исключений считается code smell (если я правильно помню — по умолчанию правило, проверяющее эту шалость включено). И я в общем-то согласен — в JavaDoc это осмысленное явление, в сигнатуре метода — бесполезное.
                                                                                                                                            +1
                                                                                                                                            Вы всегда читаете JavaDoc ко всем методам сторонних либ, которые используете? Скорее всего нет. Представьте, что како-то API запрашивающий данные из внешней системы может предполагать, что в определенных обстоятельствах нужно обязательно сделать какие-то дополнительные действия. Как ему гарантированно донести до программиста эту необходимость? Checked exceptions эту проблему решают. Пусть плохо, пусть не удобно, но всё же часто лучше что-то, чем ничего.
                                                                                                                                              +2
                                                                                                                                              Вы всегда читаете JavaDoc ко всем методам сторонних либ, которые используете? Скорее всего нет.

                                                                                                                                              Читаю. Но в большинстве случаев меня вообще не интересует, какие исключения они выбрасывают.
                                                                                                                                                +1
                                                                                                                                                Но в большинстве случаев меня вообще не интересует, какие исключения они выбрасывают.


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

                                                                                                                                                  Нет. Я не надеюсь на компилятор, особенно при парсинге чисел из строк. И пример в статье с ApplicationNameException предостерегает других от надежды на компилятор.
                                                                                                                                                +1
                                                                                                                                                Я — всегда читаю явадок новых используемых методов (при первых n использованиях), чтобы максимально корректно их использовать. Исключения — библиотеки без явадока. В Идее ничего не стоит посмотреть явадок на метод — при выборе метода он сам высвечивается (если включить опцию… либо можно включать хоткеем).

                                                                                                                                                PS я нигде не говорил, что отказаться от checked exceptions — хорошая идея. Мне самому они нравятся. Другое дело, что некоторые исключения действительно неудобны (тьма исключения JDBC тому пример, дерево IOException тоже печалит). С другой стороны — ещё ни разу не сталкивался с проблемой, когда исключения определяемые бизнес-логикой усложняет работу с методами.
                                                                                                                                                0
                                                                                                                                                И я в общем-то согласен — в JavaDoc это осмысленное явление, в сигнатуре метода — бесполезное.

                                                                                                                                                Пытался использовать Doc в качестве хранилища throws на языке, отличном от Java. Пришел к тем же проблемам, что и в Java с throws.
                                                                                                                                                +7
                                                                                                                                                Идея проверяемых исключений верная, только большинство людей ее не поняли и не умеют их готовить.

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

                                                                                                                                                Если для метода данное исключение не является основным бизнес-правилом (от чего зависит логика выполнения уровнем выше), то оборачиваете его либо в RuntimeException либо в новое бизнес-исключение.

                                                                                                                                                В этом и заключается главный секрет по использованию проверяемых исключений. Осознав это правило, использовать проверяемые исключения легко и приятно.

                                                                                                                                                Для нас FileNotFoundException такая же фатальная ошибка, как какой-нибудь NPE, но мы вынуждены объявить его в десятках мест выше только для того, чтобы где-то на самом верху поймать и залогировать:

                                                                                                                                                В вашей бизнес-логике FileNotFoundException теряет свою ожидаемость. Вот в том месте, когда он теряет свою ожидаемость, делаете такой финт:

                                                                                                                                                throw new RuntimeException(fileNotFoundException);

                                                                                                                                                Все! И теперь все сигнатуры верхних методов не содержат бессмысленного для них FileNotFoundException.

                                                                                                                                                наследуют все свои проверяемые исключения от одного предка — ApplicationNameException. Теперь они обязаны ловить в обработчике еще и его (checked же!):

                                                                                                                                                А почему бы не отловить базовый тип исключения в самом первом блоке catch, если у всех обработка аналогичная? Главное чтобы блок с базовым был раньше дочерних.

                                                                                                                                                Вот так и получается: либо километровый список throws, либо потеря гарантии проверяемости. Оно вам надо?

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

                                                                                                                                                Если нужна спец. обработка — вводим еще одного наследника базового класса. Не нужна — значит