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

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

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

Например, предположим, что я создал следующий класс:

public class Account {
    private float balance;

    /* ... constructor, getter, and other fields and methods */

    public void transferTo(Account other, float amount) {
        if (amount > balance)
            throw new NotEnoughBalanceException();
        /* ... */
    }
}

Как мне создать свой NotEnoughBalanceException? Должно ли это продлить Exception или же RuntimeException? Или я должен просто использовать IllegalArgumentException вместо?

14 ответов

Решение

Есть много разногласий по этой теме. На моей последней работе мы столкнулись с некоторыми реальными проблемами, когда исключения среды выполнения были забыты до тех пор, пока они не появились в производстве (на agewards.com), поэтому мы решили использовать только проверенные исключения.

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

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

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

Мой опыт подсказывает мне, что я получаю более высокое качество, то есть код, который ПРОСТО РАБОТАЕТ, когда я использую проверенные исключения. Проверенные исключения могут загромождать код, но для этого есть методы. Мне нравится переводить исключения при прохождении границы слоя. Например, если я перехожу со своего уровня персистентности, я хотел бы преобразовать исключение SQL в исключение персистентности, поскольку следующий уровень не должен заботиться о том, что я сохраняюсь в базе данных SQL, но захочет знать, если что-то не может быть сохранено. Другой метод, который я использую, заключается в создании простой иерархии исключений. Это позволяет мне писать более чистый код на один уровень выше, так как я могу поймать суперкласс и иметь дело только с отдельными подклассами, когда это действительно важно.

В целом, я думаю, что совет Джошуа Блоха в " Эффективной Java" лучше всего суммирует ответ на ваш вопрос: используйте проверенные ожидания для восстанавливаемых условий и исключения времени выполнения для ошибок программирования (пункт 58 во 2-й редакции).

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

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

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

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

try {
  ...
} catch (IOException e) {
  // do nothing
}

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

try {
  ...
} catch (IOException e) {
  throw new RuntimeExceptione(e);
}

Зачем? Потому что условие (не обязательно IOException; это всего лишь пример) не может быть восстановлено, но все равно вынуждено было у меня в горле, и я часто вынужден делать выбор между выполнением вышеизложенного и загрязнением моего API, чтобы распространить проверенное исключение полностью наверх, где это (справедливо) смертельно и будет зарегистрировано.

Есть причина, по которой вспомогательные классы Spring DAO переводят проверенное исключение SQLException в непроверенное исключение DataAccessException.

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

Кроме того, проверенные исключения нарушают инкапсуляцию.

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

Проверенные исключения в Java были экспериментом... неудачным экспериментом. Мы должны просто сократить наши потери, признать, что мы допустили ошибку, и двигаться дальше. ИМХО.Net понял это правильно, имея только непроверенные исключения. С другой стороны, у него было преимущество второго пользователя - учиться на ошибках Java.

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

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

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

ИМХО резюмируем: исключения == странная черная магия.

бытие-конструктивно-редактирование:

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

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

менее enterprisey-предложение-редактирование:

Если вы делаете это для своего собственного удовольствия (и, похоже, дело обстоит именно так, см. Комментарии), я бы предложил вместо этого вернуть логическое значение. Использование будет примерно таким:

// ...
boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION);

if (!success) {
  UI.showMessage("Aww shucks. You're not that rich");
  return; // or something...
} else {
  profit();
}
// ...

Мое правило

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

    1. Пример для проверенного исключения: сеть не работает для приложения, которое может работать в автономном режиме
    2. Пример непроверенного исключения: база данных не работает в веб-приложении CRUD.

Есть много документации по этому вопросу. Вы можете найти многое, просматривая веб-страницы Hibernate, так как они изменили все исключения Hibernate 2.x с отмеченного на непроверенный в версии 3.x

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

Из непроверенных исключений - Спор:

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

Обратите внимание, что непроверенное исключение является одним из производных от RuntimeException и проверенное исключение является одним из производных от Exception,

Зачем бросать RuntimeException если клиент не может ничего сделать для восстановления после исключения? Статья объясняет:

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

Мне кажется, что проверенное исключение - это полезный контракт, который следует использовать с осторожностью. Классический пример того, где проверенное исключение является хорошей идеей, - InterruptedException. У меня такое чувство, что я хочу иметь возможность остановить поток / процесс, когда я хочу, чтобы он остановился, независимо от того, как долго кто-то указал Thread.sleep().

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

В ответ на комментарий Peter Štibraný: вот пример использования InterruptedException в качестве конкретного случая исключения, которое должно быть обработано, и вам необходимо иметь полезный обработчик по умолчанию. Вот что я настоятельно рекомендую, конечно, на моей настоящей работе. Вы должны по крайней мере сделать это:

catch (InterruptedException ie) {
    Thread.currentThread().interrupt();
}

Этот обработчик гарантирует, что код перехватит проверенное исключение и сделает именно то, что вы хотите: заставить этот поток остановиться. Следует признать, что если есть другой обработчик / обработчик исключений в восходящем направлении, не исключено, что он будет обрабатывать исключение менее хорошо. Тем не менее, FindBugs может помочь вам найти их.

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

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

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

Разработчики C# решили, что непроверенные исключения являются предпочтительными.

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

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

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

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

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

У Брюса Экеля, автора книги " Мышление на Java", есть хорошее эссе на эту тему.

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

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

Это известная проблема, что проверенные исключения чрезмерно используются в Java, но это не значит, что они все плохие. Вот почему это, например, неотъемлемая часть философии Spring ( http://www.springsource.org/about)

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

IllegalArgumentException это подкласс RuntimeException, что делает его непроверенным исключением. Я бы рассмотрел этот вариант, только если у вызывающей стороны есть какой-то удобный способ определить, являются ли аргументы метода допустимыми. В вашем конкретном коде неясно, имеет ли вызывающий доступ к balanceили все ли "проверка баланса и перевод средств" является атомарной операцией (если это не так, то у вызывающей стороны действительно нет удобного способа проверки аргументов).

РЕДАКТИРОВАТЬ: разъяснил мою позицию по метанию IllegalArgumentException,

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

Если вы являетесь разработчиком API (внутренним разработчиком), используйте отмеченные исключения, в противном случае используйте исключения времени выполнения.

Также обратите внимание, что использование исключений времени выполнения в некоторых ситуациях считается большой ошибкой, например, если вы хотите выбросить исключения времени выполнения из ваших сессионных компонентов (см.: http://m-hewedy.blogspot.com/2010/01/avoid-throwing-runtimeexception-from.html для получения дополнительной информации о проблеме от использования исключений времени выполнения в сессионных компонентах).

Другие вопросы по тегам