Для чего хороши проверенные исключения в Java? [закрыто]

14

За прошедшие годы проверенные исключения Java получили плохую репутацию. Показательным признаком является то, что буквально это единственный язык в мире, который имеет их (даже не на других языках JVM, таких как Groovy и Scala). Известные библиотеки Java, такие как Spring и Hibernate, также не используют их.

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

Есть ли другие способы использования, которые я не понимаю?

Брэд Купит
источник
Все основные библиотеки Java используют проверенные исключения довольно широко.
Joonas Pulakka
3
@ Джуна, я знаю, и это боль!
Брэд Купит

Ответы:

22

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

Для меня преимущество проверенных исключений состоит в том, что авторы библиотеки времени выполнения Java УЖЕ решили для меня, какие общие проблемы я мог бы ожидать, чтобы иметь возможность обрабатывать в вызывающей точке (в отличие от верхнего уровня catch-print- print- блок) и как можно раньше подумайте, как справиться с этими проблемами.

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

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

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

} catch (FileNotFoundException e) {
  throw new RuntimeException("Important file not present", e);
}

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

Люди могут сказать: «Мы можем просто запустить это в отладчике для воспроизведения», но я обнаружил, что очень часто производственные ошибки не могут быть воспроизведены позже, и мы не можем запускать отладчики в производстве, за исключением очень неприятных случаев, когда по существу ваша работа поставлена ​​на карту.

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


РЕДАКТИРОВАТЬ: Это касается и дизайнеров библиотеки. Одна библиотека, которую я использую ежедневно, содержит много, много проверенных исключений, которые могли бы быть разработаны намного лучше, делая их менее утомительными для использования.


источник
+1. Дизайнеры Spring хорошо поработали, переведя множество исключений Hibernate в непроверенные исключения.
Fil
К сведению: Фрэнк Соммерс упомянул в этой теме, что отказоустойчивое программное обеспечение часто опирается на тот же механизм. Возможно, вы можете разделить ваш ответ на восстанавливаемый случай и смертельный случай.
rwong
@ rwong, не могли бы вы объяснить подробнее, что вы имеете в виду?
11

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

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

В этом свете не совсем верно утверждать, что проверенные исключения не нашли свое применение в других языках. Они просто не приняли форму, которую использует Java. В приложениях на Haskell обычно используют алгебраические типы данных, чтобы различать успешное и неудачное завершение функции. Хотя само по себе это не исключение, намерение во многом совпадает с проверенным исключением; это API, разработанный для того, чтобы заставить потребителя API обрабатывать как успешное в неудачном случае, например:

data Foo a =
     Success a
   | DidNotWorkBecauseOfA
   | DidNotWorkBecauseOfB

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

Крейг Штунц
источник
+1 за проверенные исключения, касающиеся безопасности типов. Я никогда не слышал эту идею раньше, но она имеет такой смысл.
Ixrec
11

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

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

Теоретически, они могли бы сделать проверенные исключения полезными, применяя их только на уровне всей программы - то есть, «собирая» программу, строили дерево всех путей управления в программе. Различные узлы в дереве будут помечены 1) исключениями, которые они могут генерировать, и 2) исключениями, которые они могут перехватить. Чтобы убедиться, что программа верна, вы должны были пройтись по дереву и убедиться, что каждое исключение, которое было сгенерировано на любом уровне дерева, было перехвачено на каком-то более высоком уровне дерева.

Причина, по которой они этого не сделали (я полагаю, в любом случае), заключается в том, что это будет мучительно сложно - на самом деле, я сомневаюсь, что кто-то написал систему сборки, которая может сделать это для чего угодно, кроме очень простого (граничащего с игрушкой) ") языки / системы. Для Java такие вещи, как динамическая загрузка классов, делают это еще сложнее, чем во многих других случаях. Хуже того (в отличие от чего-то вроде C) Java (по крайней мере, обычно) не компилируется статически / не связана с исполняемым файлом. Это означает, что ничто в системе на самом деле не имеет глобальной осведомленности даже для того, чтобы пытаться выполнять такого рода принудительные меры, пока конечный пользователь фактически не начнет загружать программу для ее запуска.

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

Как таковые, проверенные исключения хуже, чем бесполезные, но альтернативные варианты проверенных исключений еще хуже. Хотя основная идея для проверенных исключений кажется хорошей, на самом деле она не очень хороша и оказывается особенно плохо подходящей для Java. Для чего-то вроде C ++, который обычно компилируется / связывается в законченный исполняемый файл без ничего похожего на рефлексию / динамическую загрузку классов, у вас будет немного больше шансов (хотя, откровенно говоря, даже тогда их правильное применение без того же беспорядка, который они в настоящее время причина в Java все еще вероятно была бы вне текущего уровня техники).

Джерри Гроб
источник
Ваше самое первое начальное утверждение о необходимости обработки исключения напрямую уже ложно; Вы можете просто добавить оператор throws, и этот оператор throws может быть родительского типа. Кроме того, если вы действительно не думаете, что должны справиться с этим, вы можете просто преобразовать его в RuntimeException. IDE обычно помогают вам с обеими задачами.
Maarten Bodewes
2
@owlstead: Спасибо - я, наверное, должен был быть более осторожным с формулировкой там. Дело не в том, что вам действительно нужно было ловить исключение, а в том, что каждый промежуточный уровень кода должен знать о каждом исключении, которое может пройти через него. В остальном: наличие инструмента, который позволяет быстро и легко создавать беспорядок в вашем коде, не меняет того факта, что он создает беспорядок в коде.
Джерри Коффин
Проверенные исключения были предназначены для непредвиденных обстоятельств - предсказуемых и восстанавливаемых результатов, отличных от успеха, в отличие от сбоев - непредсказуемых проблем низкого уровня или системных проблем. FileNotFound или ошибка при открытии соединения JDBC считались правильно непредвиденными обстоятельствами. К сожалению, разработчики библиотек Java совершили полную ошибку и также присвоили все другие исключения ввода-вывода и SQL для «проверенного» класса; несмотря на то, что они почти полностью невосстановимы. literatejava.com/exceptions/...
Thomas W
@owlstead: проверенные исключения должны проходить только через промежуточные уровни стека вызовов, если они выбрасываются в случаях , ожидаемых кодом промежуточного вызова . ИМХО, проверенные исключения были бы хорошими, если бы вызывающие абоненты должны были либо поймать их, либо указать, ожидались ли они ; неожиданные исключения должны автоматически переноситься в UnexpectedExceptionтип, производный от RuntimeException. Обратите внимание, что «бросает» и «не ожидает» не противоречат друг другу; если fooбросает BozExceptionи звонит bar, это не значит, что он ожидает barбросить BozException.
суперкат
10

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

dsimcha
источник
4
Легко исправить: throws FileNotFoundException. Исправление:catch (FileNotFoundException e) { throw RuntimeException(e); }
Томас Эдинг
5

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

  • Модуль A может быть построен поверх модуля B, где
  • Модуль B может быть построен поверх модуля C.
  • Модуль C может знать, как определить ошибку, и
  • Модуль А может знать, как обработать ошибку.

Хорошее разделение заинтересованных сторон нарушено, потому что теперь знания об ошибках, которые могли быть ограничены A и C, теперь должны быть разбросаны по B.

Есть хорошее обсуждение проверенных исключений в Википедии.

И у Брюса Экеля тоже есть хорошая статья . Вот цитата:

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

Я полагаю, что в случае C ++, если все методы имеют предложение throwsспецификации, вы можете иметь несколько хороших оптимизаций. Однако я не верю, что у Java могли бы быть эти преимущества, потому что RuntimeExceptionsони не проверены. Современный JIT в любом случае должен быть в состоянии оптимизировать код.

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

Возможно, иронично, что Java прошла через все эти проблемы, когда могла бы проверить nullвместо этого, что могло бы быть гораздо более ценным .

Макнейл
источник
ха-ха, никогда не думал о том, что проверка на нулевую вещь - это решение, которое они не приняли ... Но я наконец понял, что это НЕ врожденная проблема после работы в Objective-C, которая позволяет нулям вызывать методы.
Дэн Розенстарк
3
проверка на нуль - гораздо более трудная задача - вы ВСЕГДА должны проверять - плюс она не сообщает причину, что делает хорошо названное исключение.
5

Я люблю исключения.

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

  1. Не используйте их для обработки потока. Вам не нужен код, который пытается что-то сделать и использует исключение в качестве триггера, чтобы сделать что-то еще, потому что исключения являются объектами, которые нужно создавать и использовать память и т. Д. И т. Д. Только потому, что вы не могли потрудиться написать некоторые вроде проверки if () перед тем, как позвонить. Это просто лень и дает славному Исключению дурную славу.

  2. Не глотай их. Когда-либо. При любых обстоятельствах. В качестве абсолютного минимума вы добавляете что-то в файл журнала, чтобы указать, что произошло исключение. Попытка отследить код, который поглощает исключения, - это кошмар. Опять же, проклинаю вас, глотатели исключений, за то, что вы принесли им огромную репутацию!

  3. Относитесь к Исключениям с вашей головной уборной. Создайте свои собственные объекты исключений с осмысленными иерархиями. Сложите свою бизнес-логику и свои исключения рука об руку. Раньше я обнаруживал, что если я начну в новой области системы, я обнаружу необходимость подкласса Exception в течение получаса после кодирования, поэтому в эти дни я начинаю с написания классов Exception.

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

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

DanW
источник
2
+1 за хорошие инструкции. Кроме того, при необходимости используйте initCauseили инициализируйте исключение с исключением, которое вызвало его. Пример: у вас есть утилита ввода / вывода, которая нужна вам только в случае сбоя записи. Однако существует несколько причин, по которым запись может завершиться неудачно (не удалось получить доступ, ошибка записи и т. Д.). Создайте свое собственное исключение с причиной 1, чтобы исходная проблема не была проглочена.
Майкл К
3
Я не хочу быть грубым, но какое это имеет отношение к исключениям CHECKED? :)
Palto
Можно переписать, чтобы написать собственную иерархию проверенных исключений.
Maarten Bodewes
4

Проверенные исключения есть и в ADA.

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

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

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

Чтение файла может завершиться ошибкой. Вызовы RPC могут быть неудачными. Сетевой ввод-вывод может дать сбой. Данные могут быть неправильно отформатированы при разборе.

«Счастливый путь» для кода прост.

Я знал парня в университете, который мог бы написать отличный код «счастливого пути». Ни один из крайних случаев никогда не работал. В эти дни он делает Python для компании с открытым исходным кодом. Достаточно.

Если вы не хотите обрабатывать проверенные исключения, то вы действительно говорите:

While I'm writing this code, I don't want to consider obvious failure modes.

The User will just have to like the program crashing or doing weird things.

But that's okay with me because 

I'm so much more important than the people who will have to use the software

in the real, messy, error-prone world.

After all, I write the code once, you use it all day long.

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

Конечно, другие люди хотели бы, чтобы эта работа была выполнена.

Возможно, они хотели получить правильный ответ, даже если файловый сервер вышел из строя / флешка USB умерла.

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

Если вы программист-любитель (не программирование за деньги), не стесняйтесь программировать на C # или другом языке без проверенных исключений. Черт возьми, вырежьте посредника и запрограммируйте логотип. Вы можете нарисовать красивые рисунки на полу с черепахой.

Тим Виллискрофт
источник
6
Хорошая напыщенная речь у тебя есть.
Адам Лир
2
+1 "Ни один из крайних случаев не работал. В эти дни он работает на Python для компании с открытым исходным кодом. Нафф сказал". Классика
НимЧимпски
1
@palto: Java заставляет вас задуматься о нелегком пути. Это большая разница. Для C # вы можете сказать «Исключение задокументировано» и помыть руки. Но программисты подвержены ошибкам. Они забывают вещи. Когда я пишу код, я хочу на 100% напоминать о любых взломах, которые компилятор может сообщить мне.
Томас Эдинг
2
@ThomasEding Java заставляет меня обрабатывать исключение, когда метод вызывается. Очень редко я действительно хочу что-то сделать с этим в вызывающем коде, и обычно я хочу добавить исключение на уровень обработки ошибок моего приложения. Затем я должен исправить это, либо заключив исключение в исключение времени выполнения, либо создать собственное исключение. У ленивых программистов есть третий вариант: просто игнорируйте его, потому что мне все равно. Таким образом, никто не знает, что произошла ошибка, и в программе действительно странные ошибки. Этот третий не происходит с непроверенными исключениями.
Palto
3
@ThomasEding Это изменит интерфейс всего вышеперечисленного, что раскрывает детали реализации без необходимости на каждом уровне приложения. Вы можете сделать это, только если исключение - это то, что вы хотите восстановить, и это имеет смысл в интерфейсе. Я не хочу добавлять IOException к моему методу getPeople, потому что какой-то файл конфигурации может отсутствовать. Это просто не имеет смысла.
Palto
4

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

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

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

Уинстон Эверт
источник
2
«Игнорирование исключений» часто является правильным - во многих случаях лучшим ответом на исключение является сбой приложения. Для теоретического обоснования этой точки зрения прочитайте Построение объектно-ориентированного программного обеспечения Бертрана Мейера . Он показывает, что если исключения используются должным образом (для нарушений контракта), то, по сути, существует два правильных ответа: (1) разрешить сбой приложения или (2) устранить проблему, вызвавшую исключение, и перезапустить процесс. Читайте Мейера для деталей; трудно подвести итог в комментарии.
Крейг Штунц
1
@Craig Stuntz, игнорируя исключения, я имел в виду их проглатывание.
Уинстон Эверт
Ах, это другое. Я согласен, вы не должны их проглотить.
Крейг Штунц
«Игнорирование исключений часто является правильным - во многих случаях лучшим ответом на исключение является аварийное завершение работы приложения». Это действительно зависит от приложения. Если в веб-приложении есть необработанное исключение, самое худшее, что может произойти, это то, что ваш клиент сообщит о появлении сообщения об ошибке на веб-странице, и вы сможете развернуть исправление ошибки в течение нескольких минут. Если у вас есть исключение во время посадки самолета, вам лучше разобраться с ним и попытаться восстановиться.
Джорджио
@ Джорджио, очень верно. Хотя мне интересно, если в случае самолета или аналогичного, лучший способ восстановить это перезагрузить систему.
Уинстон Эверт