Коли слід кинути нелегальний аргументException?


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). Або де очікується, що поганий аргумент - це помилка програміста, подібна до an NullPointerException. Я б не використовував його для впровадження перевірки у бізнес-коді. Я, звичайно, не використовував би це для прикладу електронної пошти.


8
Я думаю, що порада "де очікується, що поганий аргумент є помилкою програміста", найбільш відповідає тому, як я бачив, що це використовується, тому прийнявши цю відповідь.
djechlin

22

Говорячи про "поганий вклад", слід врахувати, звідки береться вхід.

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

Якщо вхід походить з вашої власної системи, наприклад, вашої бази даних або деяких інших частин вашої програми, ви повинні мати можливість розраховувати на те, що вона буде дійсною (вона повинна бути підтверджена до того, як вона туди потрапила). У цьому випадку цілком нормально викидати неперевірений виняток, як IllegalArgumentException, який не повинен бути спійманим (загалом, ви ніколи не повинні ловити неперевірені винятки). Помилка програміста в тому, що в першу чергу потрапило недійсне значення;) Вам потрібно це виправити.


2
Чому «ніколи не слід ловити неперевірені винятки»?
Корай Тугай

9
Тому що неперевірений виняток мається на увазі викинути внаслідок помилки програмування. Абонента методу, який викидає такі винятки, не можна обґрунтовано очікувати, що він відновиться після цього, і тому зазвичай не має сенсу їх ловити.
Том

1
Because an unchecked exception is meant to be thrown as a result of a programming errorдопомогли мені очистити багато чого в голові, дякую :)
svarog

14

"Економно" закидання винятків із виконання часу насправді не є хорошою політикою - Ефективна Java рекомендує використовувати перевірені винятки, коли від обґрунтованого очікується, що користувач може відновитись . (Помилка програміста є конкретним прикладом: якщо конкретний випадок вказує на помилку програміста, то вам слід викинути неперевірений виняток; ви хочете, щоб програміст мав трасування стека місця виникнення проблеми логіки, а не намагався вирішити це самостійно.)

Якщо немає надії на відновлення, сміливо використовуйте неперевірені винятки; немає сенсу їх ловити, тож це прекрасно.

З вашого прикладу не на 100% зрозуміло, у якому випадку цей приклад є у вашому коді.


Я думаю, що "розумно очікується відновлення" дуже невдало. Будь-яка операція foo(data)могла трапитися як частина, for(Data data : list) foo(data);в якій абонент міг би захотіти якомога більше успіху, хоча деякі дані неправильно сформовані. Включає і програмні помилки, якщо моя програма не працює, це означає, що транзакція не буде проходити, це, мабуть, краще, якщо це означає, що ядерне охолодження перебуває в автономному режимі, це погано.
djechlin

StackOverflowErrorі такі випадки, від яких не можна обгрунтовано очікувати виклику. Але це здається, що будь-який випадок логічного рівня даних або додатків слід перевірити. Це означає, що виконуйте перевірки нульових покажчиків!
djechlin

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

1
Поясню, якщо виклик API з неправильними параметрами (помилка) здійснює сторонній клієнт, то клієнт повинен вийти з ладу. Якщо це сервер API, той помилка викликає метод з неправильними параметрами, тоді сервер API повинен вийти з ладу. Перевірка: en.wikipedia.org/wiki/Fail-fast
Ігнасіо Солер Гарсія

1

Розглядайте 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.