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

Как бы ни хотел Майк Физерс (автор цитаты в эпиграфе) поставить точку в споре «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());
}

Напоследок


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

Комментарии (305)


  1. hmpd
    12.10.2015 19:57
    +17

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


    1. grossws
      12.10.2015 20:17
      +9

      Столь же штатная, как, например, отсутствие файла, который запросили по http, когда надо в итоге вернуть 404.

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

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

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


      1. hmpd
        12.10.2015 21:14
        +1

        Оптимизация скорости/памяти — это, конечно, другой вопрос. Здесь любые средства хороши, и речь уже не идет о читаемости и сопровождаемости кода. Хотя вот Блох в своей Effective Java писал, кажется, что исключения в Джаве очень «дорогие» в плане ресурсов.

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


    1. vintage
      12.10.2015 20:46
      +15

      Это на уровне приложения штатная ситуация, а в коде проверяющем контракты — исключительная.

      Странно, что в компанию к проверяемым исключениям не добавили следующие жизненно необходимые фичи:
      1. сигнатура функции должна содержать имена всех внешних переменных, которые она может явно или неявно изменить, чтобы знать на что она влияет.
      2. сигнатура функции должна содержать имена всех переменных от которых зависит результат её исполнения, чтобы знать что на неё влияет.
      3. сигнатура класса должна содержать не только имя непосредственного родителя, но и имена всех предков, с указанием какой метод/поле от какого предка наследуется, чтобы сразу видеть что откуда взято.


      1. shuron
        13.10.2015 09:58

        В яве методы…


      1. general
        13.10.2015 10:21
        +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. ajazz
          13.10.2015 13:40
          +1

          Если валидность известна можно использовать метод URI.create(«google.com»).


        1. grossws
          13.10.2015 14:17

          Создать объект вне метода — нельзя!
          Строго говоря, можно. Например, в initializer'е или static initializer'е. Но крайне неудобно. Из-за этого появляются всякие вещи типа:
          class Foo {
            private static URI baseUri;
            static {
              try {
                baseUri = new URI("/some/uri");
              } catch (URISyntaxException e) {
                throw new RuntimeException(e);
              }
            }
          
            // ...
          }
          


          1. grossws
            13.10.2015 15:02

            Имел ввиду URL, в URI есть create, а в URL аналога нет.


    1. s-kozlov
      12.10.2015 21:16
      +4

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

      if(password == null || password.equals("")){
          showError("Password not specified");
      }
      

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


    1. symbix
      12.10.2015 22:04
      +1

      Пример с неверным паролем — не очень, да. Лучше рассмотреть вариант с пустым обязательным полем. При валидации на уровне application — штатная ситуация, а если дошло до уровня model — там совершенно логично выбросить исключение.


  1. vintage
    12.10.2015 21:21
    +9

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

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


    1. s-kozlov
      12.10.2015 21:23
      +2

      Во всём нужно искать золотую середину.


      1. poxu
        12.10.2015 21:29
        +6

        В данном случае золотая середина это на 100% покрытая юнит тестами программа, написанная на языке программирования с сильной статистической типизацией.


        1. s-kozlov
          12.10.2015 21:32

          Это, кстати, очень похоже на ТРИЗовское Идеальное Конечное Решение: и сами ушами не хлопаем, и компилятор помогает.


          1. ScratchBoom
            12.10.2015 23:01
            -5

            А вы в курсе, что ТРИЗ это лженаука? Нет никаких оснований считать, что используя ТРИЗ можно добиться лучших успехов в изобретательстве, чем не используя ТРИЗ.


            1. poxu
              12.10.2015 23:08
              +10

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


            1. questor
              12.10.2015 23:12
              +3

              Лже- — могу понять, почему вы сказали, -наука — не могу взять в толк. ТРИЗ не позиционировался никогда как наука, это набор (типовых) методик. Методика != наука. Давайте просто говорить «ТРИЗ не работает» (не доказана эффективность).

              Вообще же насколько я понимаю в программировании точно та же ситуция, сложно доказать, что используя <то-то и то-то, например… ну скажем — Agile> можно добиться лучших успехов, чем не используя то-то и то-то. Будет множество статей «за» и множество статей «против». Идеальная тема для разжигания — сказать, что что-то не работает.


              1. poxu
                12.10.2015 23:14
                +1

                Ну не совсем так. Что использование статистической типизации позволяет избежать появления большого количества ошибок доказано и проверено на практике.


                1. questor
                  12.10.2015 23:18

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


                  1. ScratchBoom
                    12.10.2015 23:38
                    -2

                    Где это доказано? Какие существенные изобретения сделаны с помощью ТРИЗ? Какие изобретения есть у создателя ТРИЗ шулера-Альтшулера?


                    1. stychos
                      13.10.2015 12:31

                      Собственно, ТРИЗ/АРИЗ =)


                      1. ScratchBoom
                        13.10.2015 12:33
                        -2

                        Что же это за ТРИЗ и АРИЗ такие, что он с помощью них ничего стоящего изобрести не смог?


                        1. stychos
                          13.10.2015 12:36
                          +2

                          С точки зрения многих обывателей, и IT-технологии и десятки миллионов специалистов в этой области ничего стоящего не изобрели.


                        1. s-kozlov
                          14.10.2015 13:44

                          У нас как-то на кафедре два доктора наук спорили:
                          — Если этот ваш алгоритм такой замечательный, почему его Яндекс не использует?
                          — А Вы это у Яндекса спросите.

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


                          1. ScratchBoom
                            14.10.2015 13:58

                            А с чего вы взяли, что этот ваш АРИЗ такой замечательный, что его стоит использовать?


                            1. s-kozlov
                              14.10.2015 17:02

                              1. Это не мой АРИЗ.
                              2. А у изобретателей большой выбор? Есть что-то менее продуктивное, чем перебор вариантов?


                              1. ScratchBoom
                                14.10.2015 17:06

                                Хороша оговорочка xD


                          1. vintage
                            14.10.2015 14:54

                            А в чём суть этой теории? В двух словах, а то всё, что я находил — какое-то словоблудие.


                1. f0rk
                  13.10.2015 05:50
                  +2

                  > использование статистической типизации
                  вы имеете в виду статическую? (просто уже в двух комментах вижу эту опечатку)


                  1. poxu
                    13.10.2015 09:28
                    +2

                    Эта опечатка показалась мне очень смешной, но никто не заметил. Тогда я опечатался ещё раз, в надежде, что мой юмор найдёт своего ценителя. И он нашёлся :).

                    А так, да, я про статическую типизацию.


                    1. stychos
                      13.10.2015 12:39
                      +2

                      Так в чём проблема, давайте придумаем статистическую, когда типы переменных будут определяться и приводится по общей статистике упоминания переменной в коде в совокупности с вычисляемыми точными типами. А для того чтобы точно привести тип, надо будет писать что-то вроде use var as integer please (впрочем, это уже точно где-то было, да и первое наверняка тоже).


    1. bigfatbrowncat
      14.10.2015 02:09
      +1

      > для того, чтобы IDE хоть что-то понимала в том, что вы понаписали :-)
      Ну значит, слава Ктулху, мода наконец-то сделала виток в сторону разумного подхода «дадим компьютеру понять, что мы там кодим».

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


  1. RolexStrider
    12.10.2015 21:32
    +3

    Пример: пользователь ввел неверный пароль — отобразить соответствующее сообщение и попросить ввести снова. Для таких ситуаций, по замыслу авторов Java, нужно использовать checked exceptions.

    Для таких ситуаций, по замыслу авторов Java, нужно использовать что-то вроде boolean isValid();


  1. khim
    12.10.2015 21:36
    +2

    На самом деле всю длинную статью можно свести к простому тезису: проверяемое исключение — оксюморон. Эти слова вообще несовместимы друг с другом.

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

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

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

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

    Вот, собственно, об этом — и вся статья.


    1. s-kozlov
      12.10.2015 21:44

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


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

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


      Что если мы проверили существование файла, но открыть его еще не успели, а пользователь выдернул флешку?


      1. khim
        12.10.2015 23:38
        +1

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

        В каких-то редких случаях вы можете знать что делать (если скажем запись на диск вы делаете аккуратно, а на флешке транзакционная файловая система), но это — скорее исключение, чем правило (иначе это бы не было, чёрт побери, исключением!). Так зачем, чёрт побери, всех и каждого заставлять обрабатывать исключения, если они всё равно ничего полезного с ним сделать не смогут? Это ж вы заранее должны угадать — сможет вызывающая вас программа обработать исключение или нет. А как? Вы ж потому и кидаете исключение, что не знаете что с ним делать!


        1. VolCh
          13.10.2015 06:33
          +8

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


        1. s-kozlov
          13.10.2015 08:45
          +1

          Когда можно обойтись проверкой, надо делать проверку.
          Ловить NullPointerException вместо проверки на null — за такое даже лабораторную нельзя принимать.


          1. gurinderu
            13.10.2015 10:59

            Почему же? Если у вас есть метод в котором в делается куча проверок на null с одной и той же логикой, то в теории можно сделать логику построенную на exception's. Это будет более читаемо и возможно сравнимо по производительности. Все зависит от количества проверок.


            1. VolCh
              13.10.2015 11:16
              +2

              Семантически NullPointerException вроде должен выбрасываться когда мы null не ожидаем, но он пришёл. Если мы его ожидаем, то нужно делать явную проверку.


              1. gurinderu
                13.10.2015 12:26
                -3

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


                1. VolCh
                  13.10.2015 18:44

                  Не на каждый чих, а когда мы ожидаем null или объект. И пробрасывать исключения в таких случаях не нужно, как правило — это штатная ситуация.


            1. s-kozlov
              13.10.2015 11:50

              Если логика одна и та же, не лучше ли для этого сделать отдельный метод и вызывать его?


      1. VladVR
        13.10.2015 00:12

        В данном случае, очевидно, отсутствие файла — штатная ситуация, отказ накопителя — нештатная.

        Кроме всего прочего, кстати, исключение еще и очень медленный механизм, если на проверку пароля бросать исключение, то такой портал слишком легко DOS-ить.


    1. s-kozlov
      12.10.2015 21:48
      +1

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


  1. SerCe
    12.10.2015 21:47
    +9

    Гигантская проблема с проверяемыми исключениями — нарушение инкапсуляции!

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

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

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

    Даже в java 8 лямбды проектировались уже с учетом только unchecked exceptions, так что, имхо, выбор современного разработчика очевиден.


    1. s-kozlov
      12.10.2015 21:54
      +3

      Именно. Даже если тип исключения в данном случае никого не волнует, меня заставляют о нем думать.


    1. Delphinum
      12.10.2015 23:29
      +5

      А что вам мешает обернуть FileNotFoundException более высокоуровневым исключением перед выбросом его «наверх»?


      1. khim
        13.10.2015 00:16
        +6

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

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


        1. Delphinum
          13.10.2015 01:25
          +4

          Вы, кажется, из тех, кто тушит пожар керосином

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

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

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

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

          Как то мне слабо представляются callback фукнции XML-парсера, которые выбрасывают настолько разные исключения, что их нельзя объединить одним классом.


    1. defuz
      13.10.2015 16:15
      +3

      Ну так если вы используете unchecked-исключения, то у вас инкапсуляция тоже нарушается, только теперь еще и неявно. Или я что-то не понимаю?


      1. s-kozlov
        13.10.2015 16:23
        +2

        Если исключение действительно кому-то нужно, на границе слоев его обычно надо завернуть в высокоуровневое. Тут без разницы, используем мы механизм checked exceptions или нет.


  1. bigfatbrowncat
    12.10.2015 21:51
    +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. s-kozlov
      12.10.2015 22:06
      +1

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

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

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

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


      1. hell0w0rd
        13.10.2015 06:35
        +2

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

        И как понять, что не так? По названию файла?


        1. s-kozlov
          13.10.2015 08:57
          -3

          А что еще Вы можете предложить?


          1. defuz
            13.10.2015 16:40
            +4

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

            На мой взгляд, если вы испытываете боль от checked-исключений, это сигнал о том, что вы что-то делаете не так. И ругаться в данном случае на используемый язык – это тоже самое, что ругаться на статическую типизацию из-за того, что ваш код не компилируется из-за type-check ошибок.


            1. defuz
              13.10.2015 16:50
              +2

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

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

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


              1. khim
                13.10.2015 17:48
                +1

                Вот попробуйте попрограммировать на Java — а потом рассказывайте сказки про то, что у вас всё на Python как на Java.

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

                О чём, собственно, и все приведённые примеры!


                1. defuz
                  13.10.2015 18:09
                  +1

                  Мой комментарий как раз о том, что в Python не так как в Java. И что в Python одни исключения нужно преобразовавать в другие как раз «только там, где этого требует бизнес-логика». И я хотел сказать, что такой подход тоже ощущается как боль, поскольку язык никак в этом не помогает (и на мой взгляд, это гораздо хуже, чем даже отсутствие проверки типов).


                  1. BasicWolf
                    15.10.2015 22:32

                    Будьте любезны, приведите конкретный пример кода, в котором Вы ощущаете боль от подхода Python.


                    1. defuz
                      16.10.2015 12:42
                      +2

                      Первое, что пришло в голову.
                      Код работает замечательно до тех пор, пока на вход поступают символы в диапазоне ASCII 1-127, а потом на вход приходит кирилический текст, и внезапно оказывается что весь написанный вами код рушится через каждые пять строк с UnicodeEncodeError, потому что вы не вызываете явно .encode('utf-8'). Сколько лет уже пишу на Python, каждый раз чувствую себя дураком, получая раз за разом одни и те же ошибки в рантайме.


                1. bigfatbrowncat
                  13.10.2015 22:44
                  +4

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

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

                  Основное количество доводов противников checked exceptions выглядят примерно так:

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


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


                  1. s-kozlov
                    14.10.2015 14:00
                    -1

                    Но зато потом вы получаете отличный уровень интроспекции. И надежный контроль ошибок.

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


                    1. Mingun
                      14.10.2015 21:33

                      А что, при ловле исключения A11 уже перестает быть A0? И даже добавление нового подкласса уже бросаемых исключений в сигнатуру никак не влияет на вызывающий код — он уже ловил (или пробрасывал) эти исключения. Можете даже заменить в сигнатуре A0 на список A1...A11 и старый вызывающий код ничего не заметит. Правда, попробуйте!


                      1. s-kozlov
                        15.10.2015 06:35

                        он уже ловил (или пробрасывал) эти исключения

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

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


                        1. Mingun
                          15.10.2015 18:28

                          Я не представляю, как можно добавить исключение и не добавить для него

                          свой, уникальный обработчик

                          если ради этого обработчика добавление исключения и затевалось.


                          1. s-kozlov
                            16.10.2015 17:23
                            +1

                            А зачем тогда вообще checked exceptions, если вы ничего не забываете?


                    1. VolCh
                      15.10.2015 07:11
                      +1

                      Надуманный пример. Я получаю таск типа «обработать ситуацию такую-то так-то», решаю добавить ещё одно исключение, добавляю его, добавляю его в сигнатуре, добавляю его выброс и на этом успокаиваюсь? Как минимум, таск не пройдёт тестирование, потому что обработки-то нет.


                      1. s-kozlov
                        16.10.2015 17:27

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


                  1. khim
                    14.10.2015 15:52
                    +3

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

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


            1. xflower
              13.10.2015 17:36
              +1

              А чем (сематнически) FileNotFoundException, полученный на верхнем уровне, будет отличаться от NullPointerException?


              1. defuz
                13.10.2015 17:50
                +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. xflower
                  13.10.2015 18:12
                  +1

                  NullPointerException здесь действительно приведён как пример unchecked exception. На его месте могло бы быть любое другое исключение. Скажем, IndexOutOfBoundsException.

                  Rust я знаю ещё хуже, чем вы Java, но как там решается проблема с IndexOutOfBound? Взятие индекса возвращает Optional?


                  1. defuz
                    13.10.2015 23:26
                    +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. bigfatbrowncat
                13.10.2015 22:49
                +1

                Позвольте, я отвечу.

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

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


                1. xflower
                  14.10.2015 13:44

                  Это хороший ответ.

                  Но ведь InvalidEntityRequestException звучит очень абстрактно. Почти как SomethingWentWrongException.
                  Не будет ли это тем самым преумножением сущностей сверх необходимого?


                  1. bigfatbrowncat
                    14.10.2015 20:16
                    +2

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

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


                    1. s-kozlov
                      16.10.2015 17:49

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

                      Согласен. Вот только там не будет никакой пользы от заворачивания исключения. Скорее даже вред, т.к. лог будет тупо больше, а информация та же (не путать с данными).


                      1. bigfatbrowncat
                        17.10.2015 01:53

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

                        Смотреть на стену вызовов без такой «разметки», разделяющей слои приложения — тоскливо.


                        1. s-kozlov
                          17.10.2015 09:37

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


            1. khim
              13.10.2015 17:46
              +2

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

              Потому что вас не волнует почему какая-нибудь подсистема улучшения картинок котиков не сработала. Не сработала — и всё тут. Зафиксировали и дальше поехали. Программист (или админ) может быть разберутся и что-нибудь подправят, но программа сама ничего сделать всё равно не может!


              1. defuz
                13.10.2015 18:02

                Я вроде бы плюс-минус тоже самое хотел сказать. Уточню, что под "… вы внезапно получите..." я имел ввиду не перехват исключения, а тот факт, что такое «низкоуровневое» исключение впринципе выплыло на более высокий уровень, в не зависимости от того, ловите ли вы его или нет.

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

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


                1. khim
                  13.10.2015 18:09

                  Проблема в том, что вариант 1 встречается наиболее часто, но проверяемые исключения заставляют его реализовывать не на границе между модулями (где этому преобразованию самое место), а прямо там, где вы вызываете низкоуровневую функцию.

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


                  1. Mingun
                    14.10.2015 21:49

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

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


                    1. khim
                      14.10.2015 21:55

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

                      Напротив — проверяемые исключения не дают вам выбора: вы обязаны «обработать» проверяемое исключение на месте: либо поймать его, либо внести в список исключений, которые метод кидает! Даже если этот метод — внутренний (для пакета или класса) и его сигнатура никого (кроме вас) не волнует!


                      1. Mingun
                        14.10.2015 23:13
                        +2

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


                        1. khim
                          15.10.2015 00:12

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

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


                          1. Mingun
                            15.10.2015 18:37

                            Про пробросить дальше я и имел ввиду

                            включить в список исключений, которые бросает ваша функция

                            Поэтому вопрос все тот же: чем вам это мешает, если мы уже разобрались с тем, что сигнатура функции вас не волнует?


                            1. khim
                              15.10.2015 21:30
                              +1

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

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

                              Да, можно попробовать добавить ещё костылей в разные всякие github'ы… а можно просто отказаться от «проверяемых» исключений.


            1. s-kozlov
              14.10.2015 14:08
              -2

              Какой нарушение инкапсуляции? Кого волнует, прилетит туда FileNotFoundException или VasyaProjectFatalException, если там будет стоять catch(RuntimeException e) или вообще ничего? Тип исключения увидят только люди в логах, когда будут отлаживать, но им-то какая польза от лишних оберток, если всё равно нужно понять, какое исключение было первым?


              1. defuz
                14.10.2015 16:52

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

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

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


                1. s-kozlov
                  14.10.2015 17:07

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

                  NPE тоже может «просочиться наружу», а по смыслу FileNotFoundException может быть такой же фатальное ошибкой. Тем не менее, ни один здоровый человек не будет оборачивать NPE во что-то более высокоуровневое, чтобы соответствовать вашим странным представлениям об инкапсуляции.


                  1. defuz
                    14.10.2015 18:12

                    Ни один здравый человек не допустил бы просачивание NPE наружу, если бы Java это не поощрала замалчиванием потенциальной ошибки.


                    1. s-kozlov
                      15.10.2015 07:23

                      Наружу — это куда конкретно?
                      У нас с вами разное представление о здоровых людях.
                      NPE здорового человека никогда не бывает «потенциальной» ошибкой. Это всегда косяк разработчиков и означает ничто иное, кроме фатальной ошибки (т.е. программа работает неверно).
                      NPE курильщика, кроме явного разыменования ссылки, может «вылетать» из проверки

                      if(ref == null){
                          throw new NullPointerException(ref);
                      }
                      


                      1. grossws
                        15.10.2015 11:41

                        Такая проверка довольно стандартна в конструкторах и сеттерах при явном контракте non-null. И NPE летит, когда пользователь его нарушает.


                        1. lair
                          15.10.2015 11:54
                          +1

                          Ммм, а в яве нет аналога ArgumentNullException (и вообще всей иерархии ArgumentException)?


                          1. grossws
                            15.10.2015 11:57
                            +1

                            Нет, традиционно при проверке аргументов используются NullPointerEx extends RuntimeEx и IllegalArgumentEx extends RuntimeEx. Оба являются unchecked. Отдельной иерархии нет.

                            Стоит добавить, что NPE может означать как разыменование null reference, так и передачу null туда, где она недопустима по контракту.


                          1. s-kozlov
                            16.10.2015 17:31

                            В том то и дело, что кидать надо не NPE, а IllegalArgumentException.


                            1. lair
                              16.10.2015 17:36

                              О, это как-то больше с моей картиной мира сочетается.


                            1. grossws
                              16.10.2015 18:39

                              Почему-то создатели очень большого количества библиотек делают иначе. Включая 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.

                              Так что я совсем не понял минуса на этот комментарий.


                              1. s-kozlov
                                17.10.2015 09:14

                                habrahabr.ru/post/183322/#comment_6370870

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


                                1. grossws
                                  17.10.2015 15:09

                                  Мало ли что там делают создатели библиотек. Вот 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 я не вижу, потому считаю это вопросом договорённости внутри команды.


                              1. s-kozlov
                                17.10.2015 09:30

                                Почему-то создатели очень большого количества библиотек делают иначе.

                                Мало ли что там делают создатели библиотек. Вот API амазона — найдете там хоть одно проверяемое исключение?
                                Я мог бы привести такие примеры в статье как аргумент, но это был бы убогий аргумент.


                            1. webkumo
                              16.10.2015 22:28

                              Вообще NPE — более специфичный (для такой ситуации), чем IllegalArgumentException, так что в большинстве случаев логичней кидать именно его.
                              А в итоге это ещё один холивор в Java. Немногим менее распространённый чем checked/unchecked exceptions.


                  1. defuz
                    14.10.2015 18:22
                    +1

                    FileNotFoundException может быть такой же фатальное ошибкой
                    Так ли часты случаи, когда нижние слои абстракции могут точно определить «фатальность» ошибки? Разве это не задача клиента, который вызывает ваш код?


                    1. s-kozlov
                      15.10.2015 07:16

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


                1. VolCh
                  14.10.2015 18:06
                  +1

                  «FileNotFoundException» – это просочившаяся наружу деталь реализации

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


                  1. defuz
                    14.10.2015 18:16

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


                    1. VolCh
                      14.10.2015 18:22

                      Ну, декораторы и прочие прокси отдельный случай — они должны реализовывать тот же интерфейс.


                    1. s-kozlov
                      16.10.2015 17:46

                      Простите, вы на границе каждого слоя перехватываете все RuntimeException, чтобы, не дай бог, ни одна деталь реализации не просочилась наружу?


      1. bigfatbrowncat
        13.10.2015 22:40
        +1

        неаккуратный программист понатыкает везде throws Exception, ублажив тем самым компилятор и на корню зарубив всю «самодокументируемость».


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

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

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

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

        Расскажете мне, как можно «не думать» о возможности «обнуляемости» полей модели?


        1. s-kozlov
          14.10.2015 14:14

          Про Option еще раз повторить?


          1. bigfatbrowncat
            17.10.2015 01:55

            Почитал про него, спасибо. Было интересно.

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

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


            1. s-kozlov
              17.10.2015 09:49

              Хотя навскидку я так и не понял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");
              

              Подробности тут


              1. vintage
                17.10.2015 10:08

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


                1. lair
                  17.10.2015 14:57

                  На сколько я понимаю, инкапсуляция тут идёт лесом?

                  Нет, а с чего бы?


                  1. vintage
                    17.10.2015 21:37
                    -2

                    С того, что вызовы методов вы заменили на вызовы глобальных функций, поведение которых не зависит от реализации, передаваемого им объекта.


                    1. lair
                      17.10.2015 21:57
                      +1

                      Во-первых, не я. Во-вторых, им ничего не мешает быть полиморфными. Ну и в-третьих, Option с равным успехом позволяет вызывает и методы самого объекта.


                    1. s-kozlov
                      18.10.2015 07:11

                      Уговорили

                      String name = computer.flatMap(с -> c.getSoundcard())
                                                .flatMap(s -> s.getUSB())
                                                .map(u -> u.getVersion())
                                                .orElse("UNKNOWN");
                      

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


                      1. dougrinch
                        18.10.2015 16:35

                        Эмн, так ведь это ровно то же самое.


    1. khim
      13.10.2015 00:05
      +6

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


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

      А теперь немного про преимущества. Я бы из них выделил и обрамил золотом одно:
      В языках без checked exceptions разработчики могут не указать возможные исключения в сигнатуре метода, в результате не всегда понятно, каких подвохов от него ждать. В Java компилятор этого не допустит.
      Стоит ли обрамлять в золото ложные утверждения? Наследуем ваш ConfigFileNotFoundException от java.lang.RuntimeException — и имеем все проблемы языков без проверяемых исключений без соответствующих преимуществ.


      1. defuz
        13.10.2015 17:05
        +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. khim
          13.10.2015 17:57
          +1

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

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

          Предположите что у вас инициализация сервиса нетривиальна и состоит из многих классов и методов. В языке без проверяемых исключений у вас всё равно вся обработка останется в initializeService (где ей и место), а вот в Java вас просто-таки заставят её засунуть во все места и во все спомогательные функции — либо в виде описаний throws, либо в виде try/catch.


          1. defuz
            13.10.2015 23:39
            +2

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

            Я смотрю еще и с позиции Rust, где с исключениями еще более строго, чем в Java, и довольно часто приходится писать кучу try!(...) и `match`, которые переваривают исключительные состояния. Это цена, которую я плачу за то, что могу быть увереным, что если моя программа вообще скомпилировалась, значит она не упадет внезапно с каким-нибудь NullPointerException в недрах проекта. Но если я не хочу по каким-либо причинам возится с исключениям, Rust позволяет мне просто вызвать unwrap(), что грубо говоря означает: «если что-то пошло не так, просто корректно завершайся».


          1. Mingun
            14.10.2015 21:58

            Если все, что видит внешний код — это initializeService, то какая разница, какая сигнатура у вызываемых им вспомогательных функций? Не могу поверить, что говорю это, но все же: ну засуньте в них уже этот ваш throws Exception, в initializeService ловите только его и успокойтесь уже наконец.


            1. maxp
              15.10.2015 12:53

              Вообще, создается впечатление, что у многих людей Alt-Enter на клавиатуре туго нажимается.
              Битва за throws у методов идет просто не на жизнь, а на смерть!


              1. s-kozlov
                16.10.2015 17:57
                +2

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


      1. bigfatbrowncat
        13.10.2015 22:53
        +1

        Интересное кино… А вы в курсе, что FileNotFoundException является наследником IOException? Или теоретизируете? Ловите один IOException — кто вам не дает?

        Я приводил пример для того, чтобы проиллюстрировать 3 тезиса:
        1. Если куча исключений обрабатывается почти одинаково, надо дать им одного общего предка и не множить код
        2. На каждом слое абстракции исключения должны быть разные и одни должны обрамлять другие
        3. Если вы так уперлись в нежелание писать throws, вы всегда можете обернуть проверяемое в непроверяемое и наслаждаться отсутствием проверяемых исключений.


        1. grossws
          14.10.2015 00:36

          Всё хорошо, но сейчас в io есть ещё UncheckedIOException extends RuntimeException.
          В nio есть всякие BufferUnderflowException/BufferOverflowException extends RuntimeException, есть отнаследованные от IllegalStateException, UnsupportedOperationException, IOException.
          Часть вещей в nio2 отнаследованы от IllegalStateException, часть от IOException, некоторые от ConcurrentModificationException.

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


          1. bigfatbrowncat
            14.10.2015 01:49
            -1

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

            Увы, пока мне кажется, что любимый мной за строгость язык «прогибается» под натиском любителей писать (и продумывать) поменьше. Это — печально. Обогнать Python и JS в плане лаконичности все равно не удастся, но если Java потеряет свою кристальную прозрачность, она обесценится.


            1. grossws
              14.10.2015 11:40

              IOE-то никуда не делось, равно как и часть иерархии в nio и nio.2, отнаследованная от него. Так что жить стало лучше, жить стало веселей ,)


            1. s-kozlov
              14.10.2015 14:17

              Я пока Java8-специфичный код не писал еще и, учитывая специфику проекта, не буду писать еще с год. Но потом, думаю, смогу по достоинству оценить все нововведения.

              Удачи в скрещивании проверяемых исключений с лямбдами.


              1. bigfatbrowncat
                14.10.2015 20:42

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


                1. webkumo
                  14.10.2015 23:32

                  В стримах (Collection.stream()) — неудобно без лямбд.


                1. s-kozlov
                  15.10.2015 06:51

                  Ими можно вовсе не пользоваться, если где-то это неудобно…

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

                  «Анонимная имплементация интерфейса, состоящего из одной функции» — это явовский костыль, и он нужен только потому, что в Java функции не являются полноценными объектами (в Java 8 их уже можно передать в метод, но до полноценных объектов им далеко). Лямбды — это костыль для частичного исправления этой проблемы.


                  1. bigfatbrowncat
                    17.10.2015 01:59
                    +2

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

                    В Java не хватало делегатов. Их, как я понимаю, добавили. Ну и отлично. Больше ничего в этом смысле для счастья и не нужно.


                    1. lair
                      17.10.2015 04:24

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

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

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

                      В C# вы были бы… удивлены.


                    1. s-kozlov
                      17.10.2015 09:56

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

                      И я не вижу, потому что лямбды ввели не для этого. Если бы была цель сделать именно вывод типов, его бы сделали нормально, а не только в лямбдах.
                      В нормальных ЯП функции являются полноценными объектами; когда там хотят передать функцию как аргумент, передают именно функцию, а не костыль в виде анонимной реализации интерфейса с одним методом. Вы согласны, что последний вариант — костыль?


                      1. vintage
                        17.10.2015 10:18

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


                        1. s-kozlov
                          18.10.2015 07:14
                          +1

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


            1. s-kozlov
              14.10.2015 14:21
              +2

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

              Этак можно дойти до мысли, что джависты — высшая раса, а остальные вообще думать не хотят, поэтому придумывают всякие плюшки вроде properties, чтобы писать поменьше. Ведь написание boilerplate кода так помогает обдумыванию и пониманию!


              1. defuz
                14.10.2015 18:42
                +2

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


                1. s-kozlov
                  15.10.2015 07:12

                  Например, смешивание uncheked и checked исключений – это дырявыя абстракция.

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

                  Вот бросание IOException из reader.read() — это очевидный признак дырявой абстракции. Низкоуровневая подробность (ненадежность чтения данных с носителя или по сети) пробивается наверх. (Этот пример не имеет отношения к проблеме checked/unchecked exceptions)


                  1. Mingun
                    15.10.2015 18:44

                    Ого! Ничего себе! Уже штатная ситуация разрыва сетевого канала в низкоуровневом API чтения является нежелательной подробностью!
                    То есть по вашему было бы лучше, если бы read вернул бы какой-то мусор?


                    1. Duduka
                      16.10.2015 11:51
                      +1

                      не нужно дефектный API использовать как оправдание, OS обязана была вернуть API меняющий поведение IO в зависимости от потребности, а не засорять код низкоуровневыми подробностями… если приложению хенгаут, это не событие приложения, а поведение OS, от того, что вам прилетит событие, легче не станет, в приложении появиться системозависимый код, много низкоуровневого кода…


                      1. Mingun
                        16.10.2015 12:24

                        Можете набросать такой API и способ его использования? А то пока я не вижу альтернатив тому, что есть. И поясните, что значит

                        OS должна вернуть API
                        , потому как API — это соглашение в работе между системами и что там можно кому-то возвращать, мне непонятно.


                        1. Duduka
                          16.10.2015 14:42
                          -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, но решили, что на встроенной технике толстая логика «не взлетит», чтож, пишите мегатонны самописных прикладных ос, с простынями обработки ошибок!


                    1. s-kozlov
                      16.10.2015 17:43

                      Закон дырявых абстракций
                      Там хороший пример с TCP и IP.

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


              1. bigfatbrowncat
                17.10.2015 02:05

                Ведь написание boilerplate кода так помогает обдумыванию и пониманию!


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

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

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


                1. s-kozlov
                  17.10.2015 10:00
                  +2

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

                  Вы похожи на водителя-старовера, которому ручная КПП «помогает» внимательнее следить за дорогой.


    1. Throwable
      13.10.2015 13:05
      +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, и что нужно позаботиться об этом.


      1. s-kozlov
        13.10.2015 13:26

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

        Даже тут они не особо нужны: danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html


        1. Throwable
          13.10.2015 14:44

          Можно и так, но это не совсем одно и то же. У монады 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. defuz
        13.10.2015 17:13
        +1

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


        1. khim
          13.10.2015 18:01

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


          1. defuz
            13.10.2015 23:06
            +1

            Я как раз и говорил, что в случае с dynamic dispatch этого избежать никак нельзя, и это не какое-то «технические» ограничение, а следствие самой идеи интерфейса.


      1. webkumo
        13.10.2015 23:02
        -1

        1. ByteArrayOutputStream — знаете… а его использование может быть оправдано архитектурой? Я вот, с ходу, вижу только одно применение — в тестировании. В остальных случаях — явно какая-то проблема с обработчиком, которму вы хотите такой «OutputStream» предложить. Ну а вообще — переопределённые методв в BAOS — они содержат throws IOException?
        Про RMI и использование его интерфейса для чего-то ещё — та же фигня. Где-то зарыта архитектурная проблема. Для не-RMI реализации, кстати, можно переопределить отсутствие бросаемых исключений.
        2. Действительно — в Java DK существует проблема единообразия… но она касается не только исключений.
        3. Не всегда исключение в ресурсе означает, что ресурс сдох (некорректное SQL выражение не означает, что соединение сломалось). Но и проблем тоже хватает.

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


      1. bigfatbrowncat
        13.10.2015 23:08
        +2

        в некоторых имплементациях интерфейса реальный эксепшн может вообще отсутствовать

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

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

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

        Если хотите сделать подкласс потоков, которые «не ломаются», сделайте его. Переопределите методы чтения, поймайте в общем предке исключение IOException и в блоке catch напишите «impossible».


        1. vintage
          13.10.2015 23:16
          +3

          Вы сейчас усиленно латаете дыры в абстракциях :-)
          Не задумывались, почему в Лиспе, например, нет ни исключений, ни кодов возврата?
          Почему-то при обсуждении проблем проверяемых исключений, ведутся холивары на тему должны ли исключения быть проверяемыми или нет, но никто даже помыслить не может, что можно и без исключений вообще.


          1. bigfatbrowncat
            14.10.2015 01:58

            Я Лисп не знаю. А то, что я говорю, предпочитаю называть не «латанием дыр в абстракциях», а скорее «принятием парадигмы мышления».

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

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

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

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

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


          1. bigfatbrowncat
            14.10.2015 02:01

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

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

            Где вы здесь видите «дырявую абстракцию»?


            1. vintage
              14.10.2015 08:57
              +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 });
              }
              })


              1. Mingun
                14.10.2015 22:14

                Ну, думаю все современные языки способны понять, что какой-то код будет мертвым и никогда не выполнится. А в случае java ваш первый пример вообще не скомпилируется, т.к. код return new TreeNode( «impossible!» ); недостижим. Вот если бы throw new Exception( info ); было обернуто в другую функцию, которая только и делает, что кидает это исключение, тогда другое дело. Но тогда и по виду вызывающей функции не скажешь, что вызываемая всегда завершается исключением, не так ли? Если бы мы это знали, это было бы нарушением инкапсуляции этой функции.


                1. vintage
                  14.10.2015 23:21

                  Ну, typescript, например, требует что-то возвращать, даже если return недостижим.

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

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

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


                  1. VolCh
                    15.10.2015 06:00

                    Основное предназначение функций — повторное использование кода


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


                    1. vintage
                      15.10.2015 08:51

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


                      1. VolCh
                        15.10.2015 11:56
                        +1

                        Функции/методы осуществляют структуризацию более низкого уровня, чем модули и классы.


                  1. lair
                    15.10.2015 11:57
                    +2

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

                    Основное предназначение функций — управление сложностью; в первую очередь, за счет information hiding.


                    1. vintage
                      15.10.2015 12:38

                      И апогей information hiding — анонимные функции :-)


                    1. s-kozlov
                      16.10.2015 18:01

                      Какая вообще разница, какой из двух предназначений основное, если они хорошо подходят и для того, и для другого?


                      1. lair
                        16.10.2015 18:08

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

                        (это упрощение, конечно)


                1. grossws
                  15.10.2015 11:39
                  -1

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


                  1. Mingun
                    15.10.2015 18:24
                    -1

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


                1. s-kozlov
                  16.10.2015 18:02

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


              1. bigfatbrowncat
                17.10.2015 02:12
                -2

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


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

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

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

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


                1. vintage
                  17.10.2015 10:01
                  +2

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


                1. s-kozlov
                  17.10.2015 10:07

                  Давайте не будем придумывать свои термины. Всё уже придумано до нас


            1. Throwable
              15.10.2015 12:57

              Дырявая абстракция — это когда я описываю абстракцию «круглое и съедобное» и вынужден добавить в нее исключение «червивое» только для того, чтобы на основе него потом смочь описать яблоко. При всем при том, чтоб сливы, виноград, персики и т.д. червивыми не бывают. И тем не менее, поскольку все они наследуются от «круглого и съедобного», должны проверяться на червивость.

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


  1. vsb
    12.10.2015 22:29
    +7

    Сама идея проверяемых исключений очень интересная. Переложить на компилятор такие проверки это благо. Такое же благо, как типизация переменных. Мы знаем, какие исключения бросает функция, компилятор проверяет, что мы обработали ошибки.

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

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

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

    Вот что точно сильно не радует — мода на коды возврата и их производные. Это ужасно. Это такой шажище назад.


    1. dokwork
      13.10.2015 10:36
      +1

      Вот на протяжении чтения статьи и комментариев думал над тем, что было бы здорово, если бы checked exception-ами признавались только те, что явно указаны в сигнатуре метода. Предположим что метод A выбрасывает checked exception E. Метод B вызывает метод A. Если в методе B явно в сигнатуре не указать исключение E, то далее по стеку вызова это исключение можно считать как unchecked.
      Сам постоянно сталкиваюсь с тем, что некоторые проверяемые исключения абсолютно бессмысленны. Например ошибки при создании URL. Когда я создаю URL из собственноручно прописанной строки с адресом, вероятность возникновения MalformedURLException кхм… крайне не велика, и весь огород с ее отлавливанием явно излишен. Но когда я создаю URL из строки, которая приходит из вне (вводится пользователем), я признателен компилятору за напоминание о потенциальной проблеме. Вот и получается, что в некоторых случаях одно и тоже исключение может быть checked и unchecked.


      1. dokwork
        13.10.2015 10:39

        заранее оговорюсь, что идея сыровата и пестрит нестыковками =)


        1. guai
          13.10.2015 12:57

          в projectLombok есть аннотация @SneakyThrows. Если я верно понял вашу мысль, то это примерно то же самое.


          1. dokwork
            13.10.2015 13:38

            Именно! «Все уже придумано до нас»


      1. Mingun
        14.10.2015 22:20

        Ну, это вы пока вводите и в здравом уме и трезвой памяти, знаете, что ваш URL корректный. А вот перепутаете с с с и усе.

        для тех, кто не сразу понял предыдущую фразу
        первая буква русская, вторая латинская


        1. s-kozlov
          16.10.2015 18:04

          Я не врач, но это похоже на паранойю.


          1. Mingun
            16.10.2015 18:08

            Вы правы, компилятору лучше быть параноиком, а то вдруг вы программу для ядерного реактора пишите…


            1. s-kozlov
              16.10.2015 18:21

              Я скорее доверю свою жизнь хорошо протестированной программе на каком-нибудь PHP, чем просто скомпилированной проге на Java с компилятором-параноиком.


  1. Revertis
    12.10.2015 22:32
    +1

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

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

    Короче, думайте головой.


  1. webkumo
    12.10.2015 23:50
    +3

    Хм… а той же Idea throws непроверяемых исключений считается code smell (если я правильно помню — по умолчанию правило, проверяющее эту шалость включено). И я в общем-то согласен — в JavaDoc это осмысленное явление, в сигнатуре метода — бесполезное.


    1. Regis
      13.10.2015 01:25
      +1

      Вы всегда читаете JavaDoc ко всем методам сторонних либ, которые используете? Скорее всего нет. Представьте, что како-то API запрашивающий данные из внешней системы может предполагать, что в определенных обстоятельствах нужно обязательно сделать какие-то дополнительные действия. Как ему гарантированно донести до программиста эту необходимость? Checked exceptions эту проблему решают. Пусть плохо, пусть не удобно, но всё же часто лучше что-то, чем ничего.


      1. s-kozlov
        13.10.2015 09:06
        +2

        Вы всегда читаете JavaDoc ко всем методам сторонних либ, которые используете? Скорее всего нет.

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


        1. bigfatbrowncat
          17.10.2015 02:14
          +1

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


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


          1. s-kozlov
            17.10.2015 10:03

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

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


      1. webkumo
        13.10.2015 09:09
        +1

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

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


    1. Delphinum
      13.10.2015 01:28

      И я в общем-то согласен — в JavaDoc это осмысленное явление, в сигнатуре метода — бесполезное.

      Пытался использовать Doc в качестве хранилища throws на языке, отличном от Java. Пришел к тем же проблемам, что и в Java с throws.


  1. dkukushkin
    13.10.2015 01:07
    +7

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

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

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

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

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

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

    throw new RuntimeException(fileNotFoundException);

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

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

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

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

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

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

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

    Это и прочее от неумения их готовить.


    1. grishkaa
      13.10.2015 05:38
      +1

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

      try{
          String string=new String(bytes, "UTF-8");
      }catch(UnsupportedEncodingException x){}
      
      Здесь кодировка захардкожена и работа этого метода проверена прямо сразу после его написания. Поскольку параметр не меняется, то и исключения этого никогда не будет. Тем не менее, всегда приходится добавлять пустой try-catch исключительно для удовлетворения компилятора, хотя я не могу придумать ситуацию, когда неподдерживаемая кодировка будет «штатной ошибкой».

      byte[] bytes=...;
      DataInputStream in=new DataInputStream(new ByteArrayInputStream(bytes));
      try{
         doSomething(in.readInt());
      }catch(IOException x){}
      
      Здесь ситуация аналогичная: этого исключения никогда не будет, потому что при чтении из массива ошибок ввода-вывода не бывает. То есть да, внутри DataInputStream может быть любой другой поток (из файла или из сокета), но это всё равно не отменяет того, что в некоторых случаях это исключение либо будет фатальным, либо не будет вообще. Следовательно, и оно тоже не дложно быть checked.


      1. dkukushkin
        13.10.2015 07:03
        +6

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

        Работу с кодировками, криптографию и пр. я называю слоем утилит.

        Слой утилит вводит свои спец. исключения, к примеру UnsupportedEncodingException или WrongKeyException.

        Естественно, если у входящие данные для слоя утилит являются константой — такие исключения не возникнут. Однако это вы знаете уже уровнем выше — из слоя, который вызывает слой утилит.

        И именно на данном уровне вы принимаете решение что делать с исключениями слоя утилит (которые ожидаемо могут возникнуть с точки зрения слоя утилит). В вашем случае либо погасить (пустой блок catch) либо обернуть в RuntimeException. Я бы обернул в RuntimeException, т.к. в дальнейшем код может измениться и кодировка уже не будет константой — не сможете быстро понять где возникает проблема.

        о это всё равно не отменяет того, что в некоторых случаях это исключение либо будет фатальным, либо не будет вообще. Следовательно, и оно тоже не дложно быть checked.

        Однако разработчик DataInputStream такого решения принять не может. Он не знает какой именно поток будут передавать.

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


      1. bigfatbrowncat
        13.10.2015 23:16
        +1

        По первому примеру:

        Да, злой компилятор заставил вас написать бессмысленную строку кода, потому что вы поддерживаете кодировку, а разработчики класса String почему-то допустили оплошность в дизайне и вместо enum сунули в качестве кодировки строковую константу. Это — действительно недоработка Java API. Но чем вам checked exception виноват? А если у вас на Си будет глючить код с указателями, вы будете обвинять указатели?

        По второму примеру:

        Если вы пишете хоть сколько-нибудь сложное приложение, то в нем обычно не бывает объединения в одном месте логики, работающей с потоками и конструирования самих потоков. Функция, читающая из двух потоков данные и перемежающая байт из одного байтом из другого, должна получать два интерфейса InputStream в качестве параметров. И тогда эту функцию можно применять во всех случаях и для всех типов потоков. Это называется полиморфизмом.

        Если у вас конструируются два потока какого-то конкретного типа, а потом вы с ними что-то делаете, то это — либо какое-то мелкое утилитное исключение (и, да, абсолютно плевать, одна тут строка написана или пять — код всё равно тривиален), или недостаток дизайна. А вот это — уже серьезно и полезно, чтобы компилятор вас в это ткнул носом.

        Забавно, что все противники checked exceptions приводят одни и те же примеры с потоками… Как будто что-то поинтереснее придумать нельзя.


    1. s-kozlov
      13.10.2015 09:15
      +2

      большинство людей ее не поняли

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

      throw new RuntimeException(fileNotFoundException);

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

      Метод выбрасывает штатные исключения А1,..., А10. Каждое нужно обработать по-особому. Но вам не охота городить километровые списки throws, поэтому вы наследуете из от А0 и объявляете в методе только его. Это не лучше throws Exception, т.к. при создании А11 со своим особым обработчиком вы забудете этот обработчик создать. Где вам тут поможет компилятор?


      1. dkukushkin
        13.10.2015 17:14

        Могу, но нахе зачем? Без checked exceptions вообще ничего такого не надо делать.

        Чтобы не забыть предусмотреть все исключительные случаи.

        Без checked вы отложите это решение на потом.

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

        Каждое нужно обработать по-особому. Но вам не охота городить километровые списки throws, поэтому вы наследуете из от А0 и объявляете в методе только его.

        Если нужно обработать по особому, значит нужно все случаи прописать, не лениться.


        1. vdasus
          14.10.2015 20:55
          +1

          Мне кажется все немного проще. Посмотрите не через призму айти, а через призму разумной целесообразности. Если что-то делается для того чтобы компьютеру было легче — это тупиковая ветвь. Если делаете так чтобы человеку было легче — то что надо. Мне кажется, что сама идея checked и unchecked exceptions не так уж и плоха. Вопрос в хорошей и _удобной_ реализации этой концепции.


          1. dkukushkin
            15.10.2015 00:20

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

            Если бы мы обладали 100% вниманием — этого не потребовалось бы.

            Что бы вы предложили изменить или улучшить в этой концепции?


            1. khim
              15.10.2015 03:06
              -2

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

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

              Если бы мы обладали 100% вниманием — этого не потребовалось бы.
              Вы уж определитесь: есть у вас 100% внимание или нет. Если есть — то проверяемые исключения не нужны, если нет — то они не работают. Так как они пытаются оттянуть внимание на себя ровно в тот момент, когда оно обращено на что-то другое. Неважно: изменяете вы код или создаёте новый, в голове у вас сидит какая-то задача, к обработке всех вариантов ошибок не имеющая ровно никакого отношения.

              Отловом непойманных исключений и соответствующей диагностикой стоит заняться когда у вас на это появляется время и вы можете взглянуть на отчёт какой-нибудь PVS-Студии, а не в тот момент, когда сообщение об ошибке рассматривается исключительно с позиций «как бы его заткнуть, что посмотреть — то, что вы тут сейчас сотворили вообще работает или нет?»…


              1. Mingun
                15.10.2015 18:47

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

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


  1. grobitto
    13.10.2015 01:27
    +1

    UnsupportedEncodingException — вот это вообще полный улёт. Каждый раз рисуя пустой catch добавляю к нему комментарий о том, что должно произойти с тем, кто придумал его checked.


    1. barker
      13.10.2015 09:01
      +6

      UnsupportedEncodingException — вот это вообще полный улёт. Каждый раз рисуя пустой catch добавляю к нему комментарий о том, что должно произойти с тем, кто придумал его checked.
      Сберегу вам немного нервов:
      String string=new String(bytes, StandardCharsets.UTF_8);


      1. grossws
        13.10.2015 15:30

        Это в jdk7+, да. До этого не было.


        1. bigfatbrowncat
          13.10.2015 23:24

          Ну вот, видите! Исправили ироды ошибку.


          1. grossws
            14.10.2015 00:42

            Только местами стало ещё менее последовательно. Особенно в угоду лямбдам и Stream API.


    1. s-kozlov
      13.10.2015 09:18
      +1

      Полный улет — это void close() throws Exception. Для этого даже придумали костыль IOUtils.closeQuietly. Спасибо, что хоть в лямбды такое не добавили.


      1. symbix
        13.10.2015 23:53
        +1

        Выглядит дико, но я понимаю причину: почти все постоянно забывают, что (как минимум в unix-системах) запись в файл нельзя считать произведенной до успешного завершения вызова close().


      1. bigfatbrowncat
        17.10.2015 02:17
        +1

        Ну не пишите вы на Java скрипты! Для этого есть Python, javascript… bash, наконец!

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


        1. s-kozlov
          17.10.2015 10:05

          Ну не пишите вы на Java скрипты! Для этого есть Python, javascript… bash, наконец!

          А если вы пишете не скрипт, то вам надо абсолютно строго проработать процедуру общения с файлом.

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

          И что в чем будет заключаться обработка?


          1. khim
            17.10.2015 16:10
            +1

            И что в чем будет заключаться обработка?
            Откат предыдущей операции. Типичный пример: у вас вылетает исключение из close при записи файла, когда вы выходите из программы. Вам с какой программой будет приятнее работать: с той, которая скажет «ой, у вас диск заполнился, выходить не буду, сделайте что-нибудь» или с той, которая молча оставит файл, над которым вы весь день работали, пустым?

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


          1. bigfatbrowncat
            18.10.2015 11:37
            +1

            И что в чем будет заключаться обработка?

            Я вам навскидку приведу ряд вариантов:
            1. Выдать ользователю сообщение об ошибке с надписью «файл не найден / запрещен к записи»
            2. Выдать сообщение типа «ошибка конфигурации, переустановите программу»
            3. (уже написали) Откатить действие над файлом и продолжить работу

            Понимаете, есть программы, в которых нештатная ситуация не должна приводить к вылету, а должна, напротив, обрабатываться и превращаться в штатную. Например, прошивка робота. Или любое приложение с GUI. Оно не должно падать при ошибке. Даже с логом. Оно должно сохранять штатный режим работы, при условии, что это возможно. А если невозможно, то перед падением попытаться спасти пользовательские файлы (как это делает уже лет пять MS Office, например).

            Вы так говорите «скрипт», как будто это что-то плохое.

            Не плохое, а утилитарное. Программы, пишущиеся для других программистов (скрипты) в своей логике обработки ошибок должны принципиально отличаться от пользовательских приложений. И Java (изначально) заточена именно под приложения.


  1. Regis
    13.10.2015 01:32

    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");
    }
    

    Почему 3 первых обработчика не объединены в один? Ах, код ошибки разный! Но ведь определение кода ошибки как и построение ответов для ошибок 4xx можно вынести в отдельный метод. Точно так же как мы поступаем с любым дублирующимся кодом. Почему на catch блоки нельзя распространить обычные практики?


    1. 0leGG
      13.10.2015 06:31

      Multiple catch же есть, всё можно.


    1. s-kozlov
      13.10.2015 09:20

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


      1. bigfatbrowncat
        18.10.2015 11:45

        Новичкам приходит в голову читать статьи, написанные опытными девелоперами, в которых те хают/превозносят ту или иную технологию или подход и верить на слово. Так получаются холиварщики и холивары.

        Основная причина, по которой я продолжаю здесь с вами спорить (и по которой не пишу в ответ статью вида «checked exceptions — крутая штука!!!») состоит в том, что статья с таким посылом, на мой взгляд, не имеет права на существование. Мнение по такому вопросу можно высказывать только нейтрально и ни в коем случае нельзя давить авторитетом. Иначе в ответ получишь такое же давление (это получается само собой, эмоционально), а новички получают противоположные посылы и в лучшем случае всё равно идут разбираться сами, а в хучшем — начинают такой же спор, только с аргументами типа «один дядя сказал». Поэтому, кстати, я не принимаю аргументы, в которых присутствуют чьи-то фамилии. Даже если это фамилии авторов известных компиляторов. (здесь известная цитата про Платона и истину)


  1. maxp
    13.10.2015 04:46
    +3

    Насчет обилия разных типов исключений в Java я немного согласен с автором.

    Но в остальном, создается впечатление, что про иерархии исключений он не слышал, и ничего кроме простейших вебсайтиков не писал. А лейтмотив всей статьи — «разработчики дураки, ничего сложнее одинокого try/catch правильно написать не муогут».


    1. s-kozlov
      13.10.2015 09:25

      Насчет обилия разных типов исключений в Java я немного согласен с автором.

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

      Насчет иерархий можете пояснить подробнее?


      1. maxp
        14.10.2015 04:31
        +1

        Я так понял, что основной поинт статьи в том, что приходится много писать в списке throws при объявлении метода.
        Но это как бы не совсем правда и очень зависит от того, как сделана иерархия. Пример с тривиальным вебсервером, возвращающим либо 4хх либо 5хх при ошибке здесь не очень подходит.

        Возьмем приложение, которое для своей работы пользуется данными из app_config и host_config. Получением этих данных занимается отдельный модуль, инкапсулирующий в себя всю мутотень ввода-вывода. Снаружи этого модуля мы точно не желаем видеть все богатство ioexception (формат, пути, файлы, права доступа, енкодинг и т.п.), а хотим видеть нечто более понятное. Собственно, для того мы и пишем этот модуль.

        Что-нибудь в виде AppException и унаследованные от него ConfigVersionException, ConfigFormatException, ConfigReadException. Причем, часть из них может быть фатальными, а часть нет, в зависимости от ситуации — например, неправильная версия app_config это в морг, а для host_config возможно есть варианты.

        Внутри же эт ого модуля FileNotFound может быть совсем не фатальным случаем ioexception, а поводом попробовать почитать другой файл (это сплошь и рядом).

        Это я все к тому, что сложные вещи нельзя рассматривать на примере hello-world'а.


        1. s-kozlov
          14.10.2015 14:38

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

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


          1. maxp
            14.10.2015 16:53

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


            1. s-kozlov
              14.10.2015 17:13

              Вред конкретно от checked exceptions в необходимости писать boilerplate код в 99 случаях из 100.
              Цифра с потолка, но явно больше 50%.
              Если бы соотношение было обратным, никто бы не возникал.


            1. s-kozlov
              14.10.2015 17:15
              +1

              Вред от разного вида эксепшенов очевиден только для тех, кто не понял как и для чего их использовать.

              Одерски не понял, Хейлсберг не понял, дядя Боб не понял, Экель не понял, а вы поняли?


              1. maxp
                14.10.2015 19:23
                +1

                Да где они пишут про вред-то?!
                Есть возможность — можно использовать при желании. Можно не использовать, если не нравится. Дело вкуса — Одерски одно говорит, Блох другое.

                Или я вот на Clojure пишу, а Scala мне не нравится. Мне теперь говорить, что Scala это вред?


                1. s-kozlov
                  15.10.2015 07:00

                  Или я вот на Clojure пишу, а Scala мне не нравится.

                  Когда вы пишете на Clojure, вы можете забыть про Scala, как про страшный сон.
                  А я вынужден городить дурацкие обертки вокруг new URL(«google.com»)


                  1. maxp
                    15.10.2015 07:53
                    -2

                    В смысле, сильно ломает дописать в методе throws Exception?
                    Если уж так хочется ничего не ловить, а про все ошибки читать в логах вебсервера.

                    Как тут уже писали другие товарищи, этот вариант далеко не всегда является наиболее подходящим.


                    1. grossws
                      15.10.2015 11:45
                      +1

                      Ага, к static initializer'у дописать throws Exception ,)


                    1. s-kozlov
                      16.10.2015 17:29

                      Нормального человека всегда ломает делать бессмысленные вещи.


  1. hell0w0rd
    13.10.2015 06:42
    +3

    Проблема здесь в том, что в большинстве случаев мы вообще не хотим ничего обрабатывать.

    Вот это вообще не правда. Приложение запускается и работает. Мы не можем позволить себе его ронять каждый раз. Что если мы читаем не файл конфигурации, а на каждый запрос читаем файл?


    1. s-kozlov
      13.10.2015 09:22
      +1

      Приложение мы не роняем.
      Где-то наверху явно или не явно должны ловиться все исключения и хотя бы логироваться. Но давайте не будем называть это обработкой.


      1. hell0w0rd
        13.10.2015 13:32

        ок, и как потом понимать, к чему относится исключение?
        Исключения заворачивают друг в друга, ровно для того же, для чего у нас есть FileReader -> JsonParser -> ConfigurationManager.


        1. s-kozlov
          13.10.2015 16:04

          А stack trace на что?


    1. khim
      13.10.2015 16:30
      +1

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

      Когда ваша программа стартует она работает с десятками, а то и сотнями файлов, которые все должны присутствовать, при их отсутствии нужно просто упасть: конфиги, логи, всевозможные логи. Да, потом, после запуска, вы будете 100500 раз читать файлы, заданные пользователем — но в программе это будет одно место. Вот там и только там исключение и нужно ловить и обрабатывать «по месту». А зачем в других местах вся эта машинерия нужна, спрашивается?


      1. bigfatbrowncat
        13.10.2015 23:37

        Ну так и напишите уровень абстракции для чтения ваших «100500 файлов». И пусть он обрабатывает IOException. А наружу отдавайте свой MissingConfigException. Непроверяемый.


        1. khim
          14.10.2015 16:01
          +2

          Супер. У нас есть великолепное решение для 1% случаев, а все остальные 99% люди должны обрабатывать через слои абстракций. И эти люди запрещают мне ковыряться в носу!

          Нельзя ли сделать наоборот: в тех 99% случаев, где мне это не нужно, всякие дурацкие исключения меня заботить не будут, а в том 1% случаев, где они нужны их поймают и обработают?


          1. s-kozlov
            14.10.2015 16:58
            +1

            В точку. Если бы проверяемые исключения мешали только в 1% случаев, а в 99% помогали, этого холивара бы не было. Но всё же наоборот. Это как антивирус, который в 99% случаев отправляет в карантин нормальный файл, и вам приходится возвращать его оттуда руками.


          1. bigfatbrowncat
            17.10.2015 02:23

            Давайте по-другому посмотрим на предмет.

            В тех «99% случаев», когда вас парит необходимость думать об обработке исключений, возьмите другой язык программирования, в котором это сделано иначе и радуйтесь. А то получается в духе «Я хочу писать на Ассемблере, но не хочу разбираться с прерываниями!»

            Java предназначена для того, чтобы строить сложные многоуровневые системы. А в них ваши «99%» внезапно становятся незначительными по сравнению с оставшимся «1%».


            1. s-kozlov
              17.10.2015 10:10

              Java предназначена для того, чтобы строить сложные многоуровневые системы. А в них ваши «99%» внезапно становятся незначительными по сравнению с оставшимся «1%».

              Ну да, ну да, куда уж до джавы всяким сишарпам и всем остальным, они же для наколеночных школьных скриптов предназначены.
              Да, на Java получаются сложные системы. Часто даже переусложненные.


              1. bigfatbrowncat
                17.10.2015 17:32

                C# имеет ровно ту же целевую аудиторию, что и Java. И единственная причина, из-за которой желающих заполучить checked exceptions в C# мало состоит в том, что там их просто никогда не было и большая часть разработчиков о них даже не слыхали.


                1. lair
                  17.10.2015 18:01
                  +3

                  Ну то есть идея, что разработчики считают, что checked exceptions в C# и не надо, вам не кажется правдоподобной?


                  1. bigfatbrowncat
                    17.10.2015 20:00

                    У каждого свое мнение. Статистики у меня нет.


                1. s-kozlov
                  18.10.2015 07:35

                  И единственная причина, из-за которой желающих заполучить checked exceptions в C# мало состоит в том, что там их просто никогда не было и большая часть разработчиков о них даже не слыхали.

                  И знаете, что-то земля не полнится слухами о более высокой надежности программ на Java по сравнению с C#. Вас просто запугали мифическими взрывами ракет и сбоями в матрице.


                  1. bigfatbrowncat
                    18.10.2015 11:54
                    +1

                    Попробуйте сопоставить объем написанного в мире на Java и C# кода. И вы всё поймете.

                    Меня никто никогда ничем не запугивал. Более того, я начал изучать Java уже в зрелом возрасте, до этого имея несколько лет разработки (разной степени профессиональности) на C, C++, C#. Причем последний был моим любимым языком, к слову. Это был период, когда начинался Mono, а .NET был версии 2 (третий я зацепил лишь чуть-чуть).

                    Когда я начал писать на Java (меня, кстати, на работе заставили), сперва она меня очень раздражала после C# своей многословностью, обилием формальностей и прочими подобными вещами.

                    Но по мере использования, я заметил, что кривая роста сложности поддержки программы на C# ощутимо круче, чем на Java. Даже в том случае, если программа на Java написана новичком (мной), а на C# — человеком, имеющим больше года опыта (мной же).

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

                    Маленький совет. Если хотите вежливой и конструктивной дискуссии, избегайте оценок личности оппонента. Во-первых, можете обидеть человека, а во-вторых, думать и делать выводы имеет право любой, вне зависимости от опыта и знаний. Я, кстати, в отличие от вас, ни на кого не ссылаюсь. Так что скорее это вас «запугали».


  1. nikitasius
    13.10.2015 10:28
    -2

    Использую вот такие конструкции:

    try{
    
    }catch(Exception e){
     wtf();
     e.printstacktrace(System.out);
     sendOopsToUser();
    }
    

    wtf() — печатает время и место откуда вылетело + что было в лог файл
    далее вывод в лог файл
    далее юзеру милый текстик, ибо нефиг ему видеть косяки.

    Все ахтунги же (например пустой пароль, отсутствие файлов, null'ы, всё-всё-всё, что может) проработаны в коде и эксепшн используется уж совсем для ядерного ца.


  1. olku
    13.10.2015 10:34
    -2

    Единственный объективный критерий полезности инструмента — количество пользующихся.
    А говорить об исключениях ЯП без контекста бессмысленно, потому что обработка ошибок — это поведение функционала.
    Когда программист не знает как обработать исключение в своей задаче (не нашел подходящего кейса в спецификации), то вполне нормально его пробросить выше. Возможно, на следующем уровне абстракции знают, что с ним делать. А если не знают, то и Ява-машина разведет руками, то есть трейсом.
    В общем, лес исключений на самом верху проряжается описанием обработки ошибок на уровнях ниже.


    1. VolCh
      13.10.2015 10:52
      +2

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


      1. olku
        13.10.2015 11:09

        Я понял Вашу мысль, хотя аллегория с СССР не очень. Есть много альтернатив, для нее 91 год наступил давно. :)


    1. s-kozlov
      13.10.2015 12:00

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

      Так можно и качество музыки измерять исключительно популярностью.
      «Миллионы мух не могут ошибаться» не аргумент.


      1. olku
        13.10.2015 12:21
        -3

        Извините, статистика — аргумент. А критерии качества бывают разные. Впрочем, дискуссия про вкусы себя исчерпала.


        1. s-kozlov
          13.10.2015 12:38

          Что ж, если у вас есть статистика, давайте.

          Особенно интересно узнать

          • процентое соотношение сторонников и противников checked exceptions среди джавистов, срезы по стажу программирования
          • статистическое подтверждение более высокой надежности программ с checked exceptions по сравнению с остальными


          1. olku
            13.10.2015 12:50
            -1

            :)) Вы несколько передергиваете.

            Так можно и качество музыки измерять исключительно популярностью.

            research.google.com/bigpicture/music/#
            Количество переходит в качество. Один популярный господин обрисовал это в двух словах. Диалектику ИТ-шникам преподают сейчас?


            1. xflower
              13.10.2015 15:22
              +1

              Устыдился.
              Пошёл изучать предмет:

              Таким образом, развитие выступает как единство двух стадий — непрерывности и скачка.


              Тут написано, что все процессы происходят либо непрерывно, либо скачком, либо неким их сочетанием.
              Я правильно понял, что эта мысль — вершина диалектического материализма?


              1. olku
                13.10.2015 15:36

                Будем считать, что это статья про единство и борьбу противоположностей! :)


            1. bigfatbrowncat
              13.10.2015 23:44

              Стесняюсь спросить. А исполнители типа Кати Лель в этой диаграмме к какому жанру отнесены?


    1. bigfatbrowncat
      13.10.2015 23:40
      +4

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

      Только, пожалуйста, не нужно с таким критерием подходить к инструментам типа синхрофазотронов или, скажем, электронных микроскопов.

      Ваш критерий даже для потребительских ценностей отдает душком неприятным. Слыхали понятие «попса»?

      Хорошее всегда было, есть и будет не для всех. Это не значит, что любое «не для всех» — хорошее. Но считать, что более популярное всегда лучше, чем менее популярное — полагать, что «миллионы мух не могут ошибаться».


  1. guai
    13.10.2015 12:31

    В цейлоне выкинули checked exception, туда им и дорога. Человеческая система типов покрывает их кейс с головой. Можно из метода вернуть File|FileNotFound. И не придется потом следить: «так, я поменял код, а теперь надо бы проверить, все ли эксепшены всё еще кидаются, как и описано». Метаинфа есть в аннотациях, если надо


    1. s-kozlov
      13.10.2015 12:41
      +2

      Даже если не рассматривать такую экзотику, ни в одном мейнстримном ЯП, кроме Java, нет checked exceptions, а ведь некоторые из них испытали сильное влияние джавы.
      В C# их не ввели, даже рука не дрогнула.
      Одерски тоже объяснял, почему их нет в Scala.


      1. guai
        13.10.2015 14:47

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


        1. s-kozlov
          13.10.2015 16:06
          +1

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


      1. bigfatbrowncat
        13.10.2015 23:48
        -1

        По приведенной ссылке читаю примерно следующее:
        «Мы не поняли, зачем нужны проверяемые исключения и не сформировали мнения на их счет. Будем думать еще, возможно, в следующих версиях что-то такое появится».

        А вообще, C# ближе к паскалю. Там строгости кодирования меньше, чем в Java. Но и инфраструктура там примитивнее. Например, насколько мне известно, в ASP.NET до сих пор не считается зазорным делать кодовые вставки в шаблоны. В Java от JSP отказались в пользу JSTL лет 10 как.


        1. s-kozlov
          14.10.2015 14:43
          +1

          В Java от JSP отказались в пользу JSTL лет 10 как.

          Что, простите?
          JSP — JavaServer Pages.
          JSTL — JavaServer Pages Standard Tag Library.
          Отказались от HTML в пользу тега div?

          Или там очепятка?


          1. bigfatbrowncat
            18.10.2015 12:02

            Кхм… ну почитайте про то, что такое JSTL, что ли.

            Вкратце.

            Писать, как в ПыХаПэ конструкции вида

            <?какойтотэг
                кодНаЯзыкеJava();
            >
            

            которые нарушают принцип MVC и делают из View макароны, в Java отказались. Вместо этого предлагается всё развешивать на динамических тегах, обрабатываемых на серверной стороне вашим кодом (набор этих тегов + рекомендация — и есть JSTL). Это защищает View от поломок — пробиться сквозь теги и накосячить намного сложнее.

            В ASP.NET на момент, когда я в последний раз смотрел (год назад) эта практика считалась нормой. Что, лично для меня, означает отставание в развитии.


            1. lair
              18.10.2015 13:18

              Что, лично для меня, означает отставание в развитии.

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

              Ну и да, описанная вами конструкция совершенно не обязательно нарушает MVC. Вот вам типичный пример из того самого ASP.net MVC, который вы пинаете:

              <div>@Html.EditorFor(m => m.Text)</div>
              


              И каким образом он нарушает MVC (который Model2)? Представление знает о модели, которую рендерит, и больше ни о чем.


              1. bigfatbrowncat
                18.10.2015 15:29

                Возможность соблюдать MVC, разумеется, есть. Я говорил лишь о том, что он не форсируется.


                1. lair
                  18.10.2015 16:00

                  В JSTL тоже не форсируется, как можно ниже видеть. Ну или по крайней мере, форсируется недостаточно.


            1. lair
              18.10.2015 13:23
              +2

              Скажите, а вот это — валидный код на JSTL?

              <c:forEach var="item" items="${sessionScope.cart.items}">
                  ...
              </c:forEach>
              


              А вот этот?
              <c:set var="sufficientInventory" value="true" />
              <sql:transaction>
                  <c:forEach var="item" items="${sessionScope.cart.items}">
                      <c:set var="book" value="${item.item}" />
                      <c:set var="bookId" value="${book.bookId}" />
              
                      <sql:query var="books"
                           sql="select * from PUBLIC.books where id = ?" >
                          <sql:param value="${bookId}" />
                      </sql:query>
                      <jsp:useBean id="inventory"
                          class="database.BookInventory" />
                      <c:forEach var="bookRow" begin="0"
                          items="${books.rowsByIndex}">
                          <jsp:useBean id="bookRow"  type="java.lang.Object[]" />
                          <jsp:setProperty name="inventory" property="quantity"
                              value="${bookRow[7]}" />
              
                          <c:if test="${item.quantity > inventory.quantity}">
                              <c:set var="sufficientInventory" value="false" />
                              <h3><font color="red" size="+2">
                              <fmt:message key="OrderError"/>
                               There is insufficient inventory for
                               <i>${bookRow[3]}</i>.</font></h3>
                          </c:if>
                      </c:forEach>
                  </c:forEach>
              
                  <c:if test="${sufficientInventory == ’true’}" />
                      <c:forEach var="item" items="${sessionScope.cart.items}">
                        <c:set var="book" value="${item.item}" />
                        <c:set var="bookId" value="${book.bookId}" />
              
                          <sql:query var="books"
                               sql="select * from PUBLIC.books where id = ?" >
                              <sql:param value="${bookId}" />
                          </sql:query>
              
                          <c:forEach var="bookRow" begin="0"
                              items="${books.rows}">    
                                        <sql:update var="books" sql="update PUBLIC.books set
                                  inventory = inventory - ? where id = ?" >
                                  <sql:param value="${item.quantity}" />
                                  <sql:param value="${bookId}" />
                              </sql:update>
                          </c:forEach>
                      </c:forEach>
                      <h3><fmt:message key="ThankYou"/>
                          ${param.cardname}.</h3><br>
                    </c:if>
              </sql:transaction>
              


              1. vintage
                18.10.2015 13:34

                Ух какой треш. История подсказывает нам, что любой язык шаблонизации рано или поздно превращается в язык программирования :-)


                1. bigfatbrowncat
                  18.10.2015 15:34

                  У некоторых — да. Но, слава богу, не у всех.


              1. bigfatbrowncat
                18.10.2015 15:33

                Первый — нормален. Второй, очевидно, нет.

                При разделении View и Controller я обычно руководствуюсь простым правилом: если что-то может быть перенесено в контроллер, это надо туда перенести.

                То, насколько этот код жуток и нечитабелен (пример 2), должно намекнуть автору на то, что он делает что-то не так.

                На ASP.NET, напротив, вкорячить кучу логики в View не составляет не малейшей проблемы. Понятно, что я имел в виду?

                А в том, что можно правильно соблюдать MVC на ASP.NET, я не сомневался.


                1. lair
                  18.10.2015 16:03

                  При разделении View и Controller я обычно руководствуюсь простым правилом: если что-то может быть перенесено в контроллер, это надо туда перенести.

                  Ну, если уж зашла речь, то логику еще можно переносить во ViewModel (и ей там регулярно место), а так же выносить из контроллера дальше (чем контроллер тоньше, тем легче тестироваться).

                  На ASP.NET, напротив, вкорячить кучу логики в View не составляет не малейшей проблемы. Понятно, что я имел в виду?

                  Неа. Куча логики сама по себе не представляет собой проблемы; вопрос в семантике за этой логикой.


    1. bigfatbrowncat
      14.10.2015 02:04

      В цейлоне выкинули checked exception, туда им и дорога.

      Честно-честно. Первый раз прочитал как
      «В цейлоне выкинули checked exception, туда им, этим горе-разработчикам и дорога.


      1. guai
        14.10.2015 10:03
        +1

        Восприятие — затейливая штука :)
        За главного у этих «горе-разработчиков» Гевин Кинг, который гибернэйт запилил в своё время. И я полагаю, что цейлон — первый ява-киллер, у которого получится.


        1. s-kozlov
          14.10.2015 14:48
          +1

          От себя добавлю в список «горе-разработчиков» по версии bigfatbrowncat Мартина Одерски, создателя Scala, Java Generics и одного из разработчиков javac.


          1. bigfatbrowncat
            17.10.2015 01:45
            +1

            Ну зачем же так радикально? Generics — отличная вещь (которой, к слову, тоже мало, кто умеет хорошо пользоваться). Javac — вполне приличный компилятор (хотя мне лично ecj большепо душе)

            Даже у гениев бывают неудачные идеи. Это — не повод записывать их в «горе-разработчики». Я, к слову, такого не говорил.


            1. s-kozlov
              17.10.2015 09:34

              Даже у гениев бывают неудачные идеи.

              Абсолютно согласен. Например, null reference у Хоара и checked exceptions у Гослинга.
              Это — не повод записывать их в «горе-разработчики».

              За это ловите плюс.


              1. bigfatbrowncat
                17.10.2015 20:16
                -1

                А еще есть такое явление, как идеи, не понятые большинством. Тут у гениев всегда самые большие проблемы. Основные достижения человека записывают в промахи, а всякую ерунду — в успехи. Как с фотоэффектом у Эйнштейна ;)


                1. s-kozlov
                  18.10.2015 07:38
                  +2

                  А можно подробнее про Эйнштейна? Какую из его теорий не признали современники?


                  1. bigfatbrowncat
                    18.10.2015 10:30

                    На самом деле, если не иронизировать, а говорить серьезно, то практический вклад фотоэффекта, конечно, высок. Однако же для любого человека, знакомого с квантовой механикой вот уже лет 30 фотоэффект — такая очевидная вещь (без преувеличения), что говорить о каком-либо серьезном прорыве в науке в связи с его открытием сложно. В особенности по сравнению с…

                    Такой вещью, как ОТО. Эйнштейн расширил представление современников о реальности настолько же, насколько это до него сделал Кеплер (в пропоркии к имеющимся знаниям, разумеется). Но Общая Теория Относительности его современниками была воспринята как замудреная фантазия. Несмотря на то, что она получает всё больше подтверждений уже сегодня.

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

                    Его, конечно, оценили по заслугам. Но того факта, что Нобеля он получил не за то, за что должен был — это не отменяет.


                    1. s-kozlov
                      18.10.2015 10:43
                      +2

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

                      Это сейчас есть люди, знакомые с квантовой механикой 30 лет и более. А тогда (в 1905 году) самой квантовой механике было не больше 5 лет от роду. Новизну надо мерить тогдашними мерами, а не сегодняшними.
                      А то так можно сказать, что законы механики Ньютона не были прорывом в науке, потому что они являются частным случаем ОТО.
                      Эйнштейн расширил представление современников о реальности настолько же, насколько это до него сделал Кеплер (в пропоркии к имеющимся знаниям, разумеется).

                      Мелко берете: скорее не Кеплер, а Ньютон. И не расширил, а перевернул.


                      1. bigfatbrowncat
                        18.10.2015 15:39
                        +1

                        Это сейчас есть люди, знакомые с квантовой механикой 30 лет и более

                        Я вам больше скажу. Эйнштейн появившуюся квантовую механику не считал справедливой и презирал за вероятностную концепцию. И, да, я отдаю себе отчет в тогдашнем уровне науки и восприятия.

                        Я сравниваю два открытия, сделанных одним и тем же человеком. И реакцию общественности, которая явно не приметила слона. Я ради этого сравнения привел вам пример. Хотя мы уже ушли в оффтоп…

                        Мелко берете: скорее не Кеплер, а Ньютон

                        На самом деле, я оговорился, имея в виду, конечно, не Кеплера, а Галилея. А достижение Ньютона — начало анализа в Физике — вещь с точки зрения фундаментальных принципов менее существенная, нежели Закон Инерции.

                        И, кстати, не перевернул, а именно расширил. Физику нельзя перевернуть. Это — не поп-культура. Теория Ньютона с Галилеем до сих пор справедлива. Просто у нее уточнилась область применимости. И даже если когда-нибудь придет еще один гений и сможет придумать что-то за пределами СТО, то СТО всё равно будет работать при определенном диапазоне условий.


                        1. vintage
                          18.10.2015 16:03

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


                  1. bigfatbrowncat
                    18.10.2015 10:31

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

                    А оценили побочную мелочь. И наградили за нее.


  1. xflower
    13.10.2015 14:15
    +1

    Основной аргумент в пользу проверяемых исключений — это, действительно, различать «штатный глюк» и «нештатный».

    В примере с Config throws ConfigFileNotFoundException подразумевается, что если файл не нашёлся — тогда да, должна срабатывать какая-то внутренняя логика, браться конфигурация по умолчанию или создаваться первоначально.
    Если же файл не удалось считать, потому что у него битый формат и во время разбора вылез NullPointerException (или OutOfMemory или ClassNotFound или что-то ещё), то брать конфигурацию по умолчанию означает заметать мусор под коврик. Надо громко ругнуться и вылететь, желательно оставив memory dump на пару гигабайт.

    Это в теории.

    На практике обычно пользователю класса Config глубоко наплевать отчего именно конфиг не считался.


    1. bigfatbrowncat
      13.10.2015 23:50
      +1

      обычно пользователю класса Config глубоко наплевать отчего именно конфиг не считался.

      Это до тех пор, пока у него не появляется задача считать конфиг верно. «Пользователь класса» и «пользователь приложения» — два очень-очень разных человека с совсем разными задачами и подходом к коду.

      Энд-юзеру надо, чтобы приложение работало всегда. А разработчику — чтобы оно сообщало о каждой мельчайшей подозрительной ситуации с громким трескучим падением, «оставляя дамп на пару гигабайт». Противоречие, однако :)


      1. xflower
        14.10.2015 13:39

        Это правда.

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


      1. xflower
        14.10.2015 13:49

        Да, по поводу «считать конфиг верно».

        Тут есть некая сложность.

        Более-менее очевидно, что если файл конфига не найден — это штатная ситуация.
        Несколько менее, но тоже очевидно, что если формат конфига битый — это нештатная ситуация

        • а если конфиг ссылается на другой несуществующий файл?
        • а если он ссылается на файл в несуществующей директории?
        • а если он ссылается на файл на несуществующем хосте?
        • а если сетевой интерфейс вообще не поднят?


        Понятно, что можно придумать какие-то правила и их закодировать.
        А потом человек, придумавший правила уволится и знания буду потеряны в дебрях документации.


  1. vanxant
    13.10.2015 19:34

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


    1. bigfatbrowncat
      13.10.2015 23:56

      С полиморфизмом и дженериками джавы ситуация полностью аналогична

      Не согласен. Дженерики не являются шаблонами, так как во-первых представляют собой отдельную сущность языка (да, я знаю про type erasure, но он не имеет отношения к синтаксису), а во-вторых, полностью валидируются на этапе компиляции.

      Шаблоны в C++ — это не более, чем облагороженный #define. Там возможна передача черт-знает-чего в виде шаблонных параметров и много разных хаков, о которых компилятор понятия не имеет.

      Не сравнивайте.

      В C++ на каждую парадигму есть «по крайней мере два мнения». У языка отсутствует довлеющая идеология — он не требует от разработчика определенного стиля мышления. Хорошо это или плохо — давайте спорить не будем. Но такая вещь как checked exceptions явно навязывает ограничения пользователю языка. Что полностью противоречит идеологии C++.


  1. srez
    14.10.2015 12:26

    Рантаймы не то, чтобы убивают приложение, они убивают поток в приложении. А особенность реализации их в Яве приводит к тому, что это зачастую просто зажевываются. Скажем, что напишет такой код?
    runAsync(() -> { throw new RuntimeException(); });
    Ничего.

    Приложение работает, а часть функционала вдруг перестала работать. Отлично.

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

    С другой стороны, 8я Ява убила этот холивар. В 7ке и ранее, я был за проверяемые эксепшены, то в 8ке ими просто невозможно пользоваться. Но в итоге, у меня большая проблема на здоровых программах, ибо я никогда не уверен, какой конкретно эксепшен может вылететь из моего АПИ, раньше это делал компилятор.


    1. s-kozlov
      14.10.2015 14:52

      В 7ке и ранее, я был за проверяемые эксепшены, то в 8ке ими просто невозможно пользоваться.

      У меня аналогичная ситуация. Лямбды стали последней каплей.
      Но в итоге, у меня большая проблема на здоровых программах, ибо я никогда не уверен, какой конкретно эксепшен может вылететь из моего АПИ, раньше это делал компилятор.

      А что мешает документировать в АПИ unchecked exceptions?


      1. srez
        15.10.2015 12:21

        То, что я и мои коллеги должны внимательно следить за этой документацией. А раньше следил компилятор. На практике, в большом сложном приложении это почти невозможно делать без ошибок. Лямбда скажем ушла в очередь, в другой поток и там кинула рантайм, который надо вернуть обратно и выкинуть наружу. Понять, что именно может бросить этот код стало очень сложно.


        1. s-kozlov
          16.10.2015 17:33

          С потоками вообще весело. Там вам не то что проверяемые, а вообще никакие исключения особо не помогут.


          1. vintage
            16.10.2015 19:27

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


            1. s-kozlov
              17.10.2015 09:22

              Я неправильно выразился. Хотел сказать не о потоках, а о современных технологиях многопоточного программирования.
              Например, возьмем библиотеку Akka. Там вообще речь не идет ни о потоках, ни о волокнах: это низкоуровневые детали. Разные акторы могут работать в одном потоке, в разных потоках, на разных машинах.
              Один из главных недостатков исключений — привязка к потоку. Как только система распадается на кучу небольших компонентов (акторов), а главным взаимодействием между компонентами становится отправка сообщений, а не вызов методов, исключения, по большому счету, идут в топку.


              1. vintage
                17.10.2015 10:33
                -1

                Значит я вас не правильно понял и наши ошибки взаимокомпенсировались :-)

                Нет, исключения не идут в топку. Если сторонний сервис возвращает сообщение с ошибкой — в волокне бросается исключение. Если сторонний сервис падает — в волокне бросается исключение. Если сторонний сервис не доступен — в волокне бросается исключение. Если Akka не поддерживает такую логику работы, ну значит библиотека не очень хорошая :-)


                1. Duduka
                  17.10.2015 11:59
                  +3

                  Отправка сообщения! Не может «сторонний сервис возвращает сообщение с ошибкой» в асинхронном режиме, для Вас хорошая библиотека — библиотека, которая симулирует последовательное исполнение? Кому, когда слать сообщение об ошибке, когда обрабатывать их в разных нитях, одновременно или получать «шторм» возвратов?


                  1. vintage
                    17.10.2015 13:44

                    Может. Одна задача — одно волокно. В одной нити может крутиться множество волокон. Особенность волокон в том, что они умеют останавливаться в ожидании какого-либо события, при этом не теряя стек выполнения. Это очень практичный паттерн, рекомендую его попробовать. Популярный сейчас Go именно на нём построен (горутина — это волокно).


                1. s-kozlov
                  18.10.2015 07:21

                  Если Akka не поддерживает такую логику работы, ну значит библиотека не очень хорошая :-)

                  Вы просто не умеете ее готовить.(с) В Akka используются совсем другие способы обработки ошибок.