Дело против проверенных исключений - PullRequest
418 голосов
/ 05 марта 2009

В течение ряда лет я не мог получить достойный ответ на следующий вопрос: почему некоторые разработчики так против проверенных исключений? У меня было много разговоров, читал что-то в блогах, читал то, что говорил Брюс Экель (первый человек, которого я видел, выступал против них).

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

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

В целом (из того, как был разработан Java),

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

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

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

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

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

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

Вопрос, на который люди никогда не отвечают, таков:

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

Если ответом является catch Exception, то вы также имеете дело с ошибками программиста так же, как системные исключения. Это кажется мне неправильным.

Если вы перехватываете Throwable, то вы обрабатываете системные исключения и ошибки ВМ (и тому подобное) одинаково. Это кажется мне неправильным.

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

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

Итак, если вам не нравятся отмеченные исключения, можете ли вы объяснить, почему нет, И, пожалуйста, ответьте на вопрос, на который нет ответа?

Редактировать: я не ищу совета о том, когда использовать какую-либо модель, я ищу , почему люди выходят из RuntimeException, потому что им не нравится выходить из Exception и / или почему они ловят исключение, а затем перебрасывать RuntimeException вместо добавления бросков к их методу. Я хочу понять мотивацию неприязни к проверенным исключениям.

Ответы [ 32 ]

4 голосов
/ 31 декабря 2011

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

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

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

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

Автор "Ответов":

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

А главная мысль или статья:

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

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

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

4 голосов
/ 02 июня 2014

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

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

Сбои, как правило, возможны в коде, и EJB, web и Swing / AWT контейнеры уже учитывают это, предоставляя самый внешний обработчик исключений «сбой запроса». Самая основная правильная стратегия - откатить транзакцию и вернуть ошибку.

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

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

Если наше приложение не является БД ... мы не должны пытаться исправить БД. Это нарушило бы принцип инкапсуляции .

Особенно проблематичными были области JDBC (SQLException) и RMI для EJB (RemoteException). Вместо выявления исправляемых непредвиденных обстоятельств в соответствии с первоначальной концепцией «проверенных исключений» эти широко распространенные проблемы вынужденной системной надежности, которые на самом деле не устраняются, должны быть широко объявлены.

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

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

Наконец, «проверенные исключения» в значительной степени несовместимы с функциональным программированием на FP.

Их настойчивое требование «обрабатывать немедленно» противоречит как передовой практике «обработки с опозданием», так и любой структуре FP, которая абстрагирует циклы / или поток управления.

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

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

Другими стратегиями обработки исключений являются «повтор», «переподключение» или «пропуск» на уровне предприятия, подсистемы или уровня запроса. Все это общие стратегии обеспечения надежности, которые работают хорошо / лучше с исключениями времени выполнения.

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

См:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

3 голосов
/ 05 марта 2009

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

Другая проблема с проверенными исключениями заключается в том, что они, как правило, используются не по назначению. Прекрасным примером этого является метод java.sql.Connection close(). Он может выдать SQLException, даже если вы уже явно указали , что вы сделали с подключением. Какую информацию может закрыть (), возможно, передать, что вы заботитесь?

Обычно, когда я закрываю () соединение *, оно выглядит примерно так:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

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

* В качестве дополнительного комментария, Connection.close () PooledConnection даже не закрывает соединение, но вам все равно нужно перехватить SQLException, поскольку оно является проверенным исключением. *

3 голосов
/ 16 июля 2015

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

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

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

3 голосов
/ 05 марта 2009

Здесь один аргумент против проверенных исключений (от joelonsoftware.com):

Причина в том, что я считаю исключения не лучше, чем "goto's", считающиеся вредными с 1960-х годов, в том смысле, что они создают резкий переход из одной точки кода в другую. На самом деле они значительно хуже, чем у Гото:

  • Они невидимы в исходном коде. Глядя на блок кода, в том числе функции, которые могут или не могут генерировать исключения, нет способ увидеть, какие исключения могут быть выброшены и откуда. Это означает что даже тщательная проверка кода не выявляет потенциальных ошибок.
  • Они создают слишком много возможных точек выхода для функции. Написать правильно код, вы действительно должны думать о каждом возможном пути кода через твоя функция. Каждый раз, когда вы вызываете функцию, которая может вызвать исключение и не ловить его на месте, вы создаете возможности для неожиданные ошибки, вызванные внезапно завершившимися функциями, оставив данные в несогласованном состоянии или другие пути кода, которые вы не сделали думать о.
2 голосов
/ 05 марта 2009

Я думаю, что это отличный вопрос, а не спорный. Я думаю, что сторонние библиотеки должны (в общем) генерировать непроверенные исключения. Это означает, что вы можете изолировать свои зависимости от библиотеки (то есть вам не нужно ни перебрасывать их исключения, ни выбрасывать Exception - обычно это плохая практика). Слой Spring DAO является отличным примером этого.

С другой стороны, исключения из основного Java API в целом следует проверять, могут ли они когда-либо обрабатываться. Возьми FileNotFoundException или (мой любимый) InterruptedException. Эти условия почти всегда должны обрабатываться специально (т. Е. Ваша реакция на InterruptedException не совпадает с вашей реакцией на IllegalArgumentException). Тот факт, что ваши исключения проверены, заставляет разработчиков задуматься о том, является ли условие пригодным для обработки или нет. (Тем не менее, я редко видел, чтобы InterruptedException обрабатывался правильно!)

Еще одна вещь - RuntimeException не всегда «где разработчик что-то не так». При попытке создать enum с использованием valueOf возникает исключение недопустимого аргумента, и enum с таким именем отсутствует. Это не обязательно ошибка разработчика!

2 голосов
/ 10 мая 2013

Хорошим доказательством того, что Checked Exception не нужны, являются:

  1. Много фреймворка, который работает для Java. Как Spring, который оборачивает исключение JDBC в непроверенные исключения, выбрасывая сообщения в журнал
  2. Множество языков, которые пришли после Java, даже сверху на платформе Java - они не используют их
  3. Проверенные исключения, это добрый прогноз о том, как клиент будет использовать код, который выдает исключение. Но разработчик, который пишет этот код, никогда не узнает о системе и бизнесе, в котором работает клиент кода. Например, методы Interfcace, которые вынуждают генерировать проверенное исключение. В системе существует 100 реализаций, 50 или даже 90 реализаций не выдают это исключение, но клиент все равно должен перехватить это исключение, если он ссылается на этот интерфейс. Эти 50 или 90 реализаций, как правило, обрабатывают эти исключения внутри себя, помещая исключения в журнал (и это хорошее поведение для них). Что нам с этим делать? Мне бы лучше иметь некоторую базовую логику, которая бы выполняла всю эту работу - отправлять сообщения в журнал. И если я, как клиент кода, почувствую, что мне нужно обработать исключение - я сделаю это. Я могу забыть об этом, верно, но если я использую TDD, все мои шаги покрыты, и я знаю, чего хочу.
  4. Другой пример, когда я работаю с I / O в Java, это заставляет меня проверять все исключения, если файл не существует? что мне с этим делать? Если он не существует, система не перейдет к следующему шагу. Клиент этого метода не получит ожидаемое содержимое из этого файла - он может обработать исключение времени выполнения, в противном случае я должен сначала проверить Checked Exception, поместить сообщение в журнал, а затем выбросить исключение из метода. Нет ... нет - я бы лучше сделал это автоматически с RuntimeEception, который делает это / загорается автоматически. Нет никакого смысла обрабатывать это вручную - я был бы рад, что увидел сообщение об ошибке в журнале (AOP может помочь с этим .. что-то, что исправляет Java). Если, в конце концов, я угадаю, что система должна показывать всплывающее сообщение конечному пользователю - я покажу это, не проблема.

Я был бы счастлив, если бы java предоставил мне выбор , что использовать при работе с основными библиотеками, такими как I / O. Like предоставляет две копии одних и тех же классов - один обернут в RuntimeEception. Тогда мы можем сравнить, что люди будут использовать . На данный момент, тем не менее, многим лучше пойти на какую-то платформу поверх Java или другой язык. Как и Скала, JRuby, что угодно. Многие просто верят, что СОЛНЦЕ было правильно.

2 голосов
/ 09 октября 2017

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

Допустим, вы определили MyAppException extends Exception. Это исключение верхнего уровня, унаследованное всеми исключениями, выданными вашим приложением. Каждый метод объявляет throws MyAppException, что немного неудобно, но управляемо. Обработчик исключений регистрирует исключение и как-то уведомляет пользователя.

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

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

1 голос
/ 02 августа 2012

Мы видели некоторые ссылки на главного архитектора C #.

Вот альтернативная точка зрения парня из Java о том, когда использовать проверенные исключения. Он признает многие негативы, о которых упоминали другие: Действующие исключения

0 голосов
/ 18 мая 2009

Я много читал об обработке исключений, даже если (большую часть времени) я не могу сказать, что я счастлив или огорчен существованием проверенных исключений, это мое мнение: проверенные исключения в низкоуровневом коде ( IO, работа в сети, ОС и т. Д.) И непроверенные исключения в высокоуровневых API / уровне приложений.

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

Проект, над которым я работаю, берет много библиотек и интегрирует их в один API, API, который полностью основан на непроверенных исключениях. Эти платформы предоставляют API высокого уровня, который в начале был полон проверенных исключений и имел только несколько непроверенных исключений (исключение инициализации, исключение ConfigurationEx и т. д.), и я должен сказать, что это не очень дружественно . Большую часть времени вам приходилось ловить или перебрасывать исключения, которые вы не знаете, как обрабатывать, или вас это даже не волнует (не путать с вами следует игнорировать исключения), особенно на стороне клиента, где один клик может вызвать 10 возможных (проверенных) исключений.

Текущая версия (3-я) использует только непроверенные исключения и имеет глобальный обработчик исключений, который отвечает за обработку всего необработанного. API предоставляет способ регистрации обработчиков исключений, который решает, будет ли исключение считаться ошибкой (в большинстве случаев это так), что означает запись и уведомление кого-либо, или это может означать что-то другое - например, это исключение AbortException, что означает прерывание текущего потока выполнения и не регистрировать ошибки, потому что это нежелательно. Конечно, чтобы все пользовательские потоки работали, нужно обработать метод run () с помощью try {...} catch (all).

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

В этом нет необходимости, если вы используете WorkerService для планирования заданий (Runnable, Callable, Worker), который обрабатывает все за вас.

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

...