Когда следует генерировать исключение IllegalArgumentException?


98

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

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

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

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

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

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

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

И что еще хуже - хотя 0 <= pct && pct <= 100можно ожидать, что проверка клиентского кода будет выполняться статически, это не так для более сложных данных, таких как адрес электронной почты или, что еще хуже, что-то, что необходимо проверить в базе данных, поэтому в целом клиентский код не может предварительно проверить.

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

Ответы:


80

Документ API для IllegalArgumentException:

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

Глядя на как он используется в библиотеках JDK , я бы сказал:

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

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

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


8
Я думаю, что совет «где ожидается, что плохой аргумент является ошибкой программиста» наиболее согласуется с тем, как я видел это, поэтому принимаю этот ответ.
djechlin 07

22

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

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

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


2
Почему «никогда не следует ловить непроверенные исключения»?
Корай Тугай

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

1
Because an unchecked exception is meant to be thrown as a result of a programming errorпомогло прояснить много вещей в голове, спасибо :)
svarog

14

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

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

Однако из вашего примера не на 100% ясно, в каком случае этот пример находится в вашем коде.


Я думаю, что «разумно ожидать, что выздоровеешь» - это неважно. Любая операция foo(data)могла произойти как часть, for(Data data : list) foo(data);в которой вызывающий может пожелать, чтобы как можно больше успешно завершилось, даже если некоторые данные искажены. Включает также программные ошибки: если сбой моего приложения означает, что транзакция не будет выполнена, это, вероятно, лучше, если это означает, что ядерное охлаждение отключается, это плохо.
djechlin 04

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

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

Boolean.parseBoolean (..) вызывает исключение IllegalArugmentException, даже если «можно разумно ожидать восстановления вызывающего объекта». так что ... ваш код должен обработать это или вернуться к вызывающему.
Джерил Кук,

5

Как указано в официальном руководстве oracle, в нем говорится, что:

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

Если у меня есть приложение, взаимодействующее с базой данных с помощью JDBC, и у меня есть метод, который принимает аргумент как int itemи double price. priceДля соответствующего элемента считываются из таблицы базы данных. Я просто умножаю общее количество itemкупленных на priceстоимость и возвращаю результат. Хотя я всегда уверен, что со своей стороны (со стороны приложения), значение поля цены в таблице никогда не может быть отрицательным. Но что, если значение цены окажется отрицательным ? Это показывает, что есть серьезная проблема со стороной базы данных. Возможно неправильный ввод цены оператором. Это проблема, которую другая часть приложения, вызывающего этот метод, не может предвидеть и не может исправить. В этом случае следует бросить, в котором будет указано, чтоBUG в вашей базе данных. Итак, иIllegalArguementException()the price can't be negative .
Надеюсь, что ясно выразил свою точку зрения.


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

5

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

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

Они представляют собой 99,9% ошибок в приложении, потому что оно запрашивает невозможные операции, поэтому, в конце концов, они являются ошибками, которые должны привести к сбою приложения (так что это неисправимая ошибка).

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


Наоборот, если клиент API дает мне плохой вход, я не врезаться весь мой сервер API.
djechlin

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

То, что вы написали в комментарии, отличается от того, что вы написали в ответе.
djechlin 02

1
Позвольте мне объяснить, если вызов API с неправильными параметрами (ошибка) выполняется сторонним клиентом, клиент должен аварийно завершить работу. Если это сервер API с ошибкой при вызове метода с неправильными параметрами, сервер API должен аварийно завершить работу. Проверьте: en.wikipedia.org/wiki/Fail-fast
Игнасио Солер Гарсия

2

Рассматривайте IllegalArgumentExceptionкак проверку предварительных условий и учитывайте принцип проектирования: общедоступный метод должен как знать, так и публично документировать свои собственные предварительные условия.

Я согласен, что этот пример верен:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

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

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

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

/** @param String email An email with an address in the form abc@xyz.com
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

Этот дизайн может быть любым.

  • Если предварительные условия дорого описывать или если класс предназначен для использования клиентами, которые не знают, действительны ли их электронные письма, тогда используйте ParseException. Здесь назван метод верхнего уровня, scanEmailкоторый намекает, что конечный пользователь намеревается отправить неизученное электронное письмо, так что это, вероятно, правильно.
  • Если предварительные условия могут быть описаны в документации по функциям, и класс не предназначен для недопустимого ввода и, следовательно, указывается ошибка программиста, используйте IllegalArgumentException. Хотя «проверка» не отмечена, она перемещается в документ Javadoc, документирующий функцию, которой, как ожидается, клиент будет придерживаться. IllegalArgumentExceptionесли клиент не может заранее сказать, что их аргумент является незаконным, неправильно.

Примечание об исключении IllegalStateException : это означает, что «внутреннее состояние этого объекта (частные переменные экземпляра) не может выполнить это действие». Конечный пользователь не может видеть частное состояние, поэтому, грубо говоря, оно имеет приоритет IllegalArgumentExceptionв случае, когда клиентский вызов не имеет возможности узнать, что состояние объекта несовместимо. У меня нет хорошего объяснения, когда это предпочтительнее проверенных исключений, хотя такие вещи, как двойная инициализация или потеря соединения с базой данных, которое не восстанавливается, являются примерами.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.