Справа проти перевірених винятків


456

Протягом кількох років я не міг отримати гідної відповіді на наступне запитання: чому деякі розробники так проти перевірених винятків? Я мав численні розмови, читав речі в блогах, читав, що мав сказати Брюс Еккель (перший чоловік, якого я побачив, висловлювався проти них).

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

Кожна розмова, яку я закінчую, закінчується тим самим запитанням без відповіді ... дозвольте мені встановити:

Загалом (від того, як розроблялася Java),

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

Поширений аргумент, який я чую, це те, що якщо виняток трапиться, то все, що розробник збирається зробити, це вийти з програми.

Ще один поширений аргумент, який я чую, - це те, що перевірені винятки ускладнюють код рефактора.

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

Для натовпу "важко переробляти", це вказує на те, що належний рівень абстракції не був обраний. Замість того, щоб оголосити метод кидає IOException, IOExceptionслід перетворитись на виняток, який більше підходить для того, що відбувається.

У мене немає проблеми з обгортанням Main з catch(Exception)(або в деяких випадках, catch(Throwable)щоб гарантувати, що програма може вийти вишукано - але я завжди лову конкретні винятки, які мені потрібно. Це дозволяє мені, принаймні, показувати відповідний повідомлення про помилку.

Питання, на яке люди ніколи не відповідають:

Якщо ви кидаєте RuntimeException підкласи замість Exception підкласів, то як ви знаєте, що ви повинні зловити?

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

Якщо ви ловите, Throwableтоді ви трактуєте системні винятки та помилки VM (тощо) однаково. Це здається мені неправильним.

Якщо відповідь полягає в тому, що ви ловите лише ті винятки, які ви знаєте, кинуті, то як ви знаєте, які з них кинуті? Що відбувається, коли програміст X кидає новий виняток і забув його зловити? Це здається мені дуже небезпечним.

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

Отже, якщо вам не подобаються перевірені винятки, чи можете ви пояснити, чому б і не відповісти на питання, на яке не отримаєте відповідь?

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


46
Я не думаю, що це абсолютно суб’єктивно - це мовна особливість, яка була покликана мати конкретне використання, а не для того, щоб кожен вирішував, що це для себе. І він не особливо аргументований, він заздалегідь звертається до конкретних спростувань, які люди могли легко придумати.
Гарет

6
Давай. Розглянута як мовна особливість, ця тема була і може бути розглянута об'єктивно.
Курт Шельфут

6
@cletus "відповідаючи на власне запитання", якби у мене була відповідь, я б не ставила цього питання!
TofuBeer

8
Чудове запитання. У C ++ взагалі немає перевірених винятків, і, на мою думку, ця функція виключення робить непридатною. Ви опинитесь в ситуації, коли вам доведеться впіймати кожен виклик функції, який ви робите, тому що ви просто не знаєте, чи може щось кинути.
Димитрій К.

4
Найсильніший аргумент, який я знаю для перевірених винятків, полягає в тому, що вони спочатку не були на Яві, і що коли вони були представлені, вони виявили сотні помилок у JDK. Це дещо раніше, ніж Java 1.0. Я особисто не був би без них і жорстоко не погоджувався з Брюсом Еккелем та іншими щодо цього.
Маркіз Лорн

Відповіді:


276

Я думаю, що я читав те саме інтерв'ю Брюса Еккеля, яке ви робили, - і мене завжди клопочуть. Насправді аргумент висловив інтерв'юйований (якщо це справді посада, про яку ви говорите) Андерс Хейльсберг, геній MS, що стоїть за.

http://www.artima.com/intv/handcuffs.html

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

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

Під «інакше представленим користувачеві» я маю на увазі, якщо ви використовуєте виняток під час виконання програми, лінивий програміст просто ігнорує його (проти лову з порожнім блоком вилову) і користувач побачить це.

Короткий зміст аргументу полягає в тому, що "Програмісти не використовуватимуть їх належним чином, а неправильне їх використання - це гірше, ніж їх немає" .

У цьому аргументі є деяка правда, і я насправді підозрюю, що мотивація Goslings за те, щоб не ставити перевірки операторів на Java, випливає з аналогічного аргументу - вони плутають програміста, оскільки його часто зловживають.

Але врешті-решт, я вважаю це хибним аргументом Хейльсберга і, можливо, створеним для пояснення відсутності, а не продуманого рішення.

Я заперечую, що хоча надмірне використання перевірених винятків є поганою справою і має тенденцію призводити до неохайного поводження з користувачами, але правильне їх використання дозволяє програмісту API дати велику користь програмісту клієнта API.

Тепер програміст API повинен бути обережним, щоб не кидати всюди перевірені винятки, інакше вони просто будуть дратувати клієнтського програміста. Дуже ледачий клієнт-програміст вдасться зловити, (Exception) {}як попереджає Хейльсберг, і вся користь буде втрачена і настане пекло. Але в деяких обставинах просто немає заміни на хороший перевірений виняток.

Для мене класичним прикладом є відкритий файл API. Кожна мова програмування в історії мов (принаймні, у файлових системах) десь має API, який дозволяє відкривати файл. І кожен клієнт-програміст, що використовує цей API, знає, що їм доводиться мати справу з тим, що файл, який вони намагаються відкрити, не існує. Дозвольте перефразувати це: кожен клієнт-програміст, що використовує цей API, повинен знати, що їм доведеться мати справу з цією справою. І ось в чому полягає проблема: чи може програміст API допомогти їм зрозуміти, що вони повинні вирішувати це, коментуючи самостійно, чи вони можуть наполягати на тому, щоб клієнт займався цим.

У С ідіома іде щось подібне

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

де fopenвказується на невдачу, повернувши 0 і С (нерозумно) дозволяє вам ставитися до 0 як до булевого і ... В основному, ви дізнаєтесь цю ідіому і все в порядку. Але що робити, якщо ви новачок і не вивчили ідіому. Тоді, звичайно, починаєш

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

і навчитися важкому шляху.

Зауважте, що ми говоримо лише про сильно набрані мови: Існує чітке уявлення про те, що таке API в сильно набраній мові: це обмежена функція (методи), яку ви можете використовувати з чітко визначеним протоколом для кожного.

Цей чітко визначений протокол, як правило, визначається підписом методу. Тут fopen вимагає, щоб ви передали йому рядок (або знак * у випадку C). Якщо ви надаєте щось інше, ви отримуєте помилку під час компіляції. Ви не дотримувались протоколу - ви не використовуєте належним чином API.

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

Я намагаюся зробити таке: Програміст API за допомогою статично введеної мови заохочує клієнта правильно використовувати API, запобігаючи компіляції їх клієнтського коду, якщо він робить явні помилки.

(Динамічно набраною мовою, як-от Ruby, ви можете передавати що завгодно, скажімо, float, як ім'я файлу - і воно буде компілюватися. Навіщо клопотатись з користувачем за перевіреними винятками, якщо ви навіть не збираєтесь контролювати аргументи методу. наведені тут аргументи стосуються лише мов статичного типу.)

Отже, як щодо перевірених винятків?

Ну ось один з API Java, який ви можете використовувати для відкриття файлу.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

Бачите цей улов? Ось підпис для цього методу API:

public FileInputStream(String name)
                throws FileNotFoundException

Зауважте, що FileNotFoundExceptionце перевірений виняток.

Програміст API каже вам це: "Ви можете використовувати цей конструктор для створення нового FileInputStream, але ви

а) повинен передавати ім'я файлу як рядок
b) повинен приймати можливість того, що файл не може бути знайдений під час виконання "

І в цьому вся суть, що стосується мене.

Основне - це те, що в запитанні йдеться про "Речі, які перебувають поза контролем програміста". Першою моєю думкою було те, що він / вона має на увазі речі, які перебувають поза контролем програмістів API . Але насправді перевірені винятки при правильному використанні дійсно повинні стосуватися речей, які не підпадають під контроль клієнтського програміста та програміста API. Я думаю, що це ключ до того, щоб не зловживати перевіреними винятками.

Я думаю, що відкритий файл добре ілюструє суть. Програміст API знає, що ви можете дати їм ім'я файлу, яке виявляється неіснуючим на момент виклику API, і що вони не зможуть повернути вам те, що ви хотіли, але доведеться викинути виняток. Вони також знають, що це трапляється досить регулярно і що клієнт-програміст може очікувати, що ім'я файлу буде правильним під час написання дзвінка, але це може бути неправильно під час виконання з причин, що не є їх контролем.

Таким чином, API робить це явним: Будуть випадки, коли цей файл не існує в той момент, коли ви мене дзвоните, і вам, чорттю, було краще розібратися з ним.

Це було б зрозуміліше з зустрічною справою. Уявіть, що я пишу API таблиці. У мене є модель таблиці десь з API, що включає цей метод:

public RowData getRowData(int row) 

Тепер, як програміст API, я знаю, що будуть випадки, коли якийсь клієнт передає негативне значення для рядка або значення рядка поза таблицею. Тож я міг би спокусити кинути перевірений виняток і змусити клієнта впоратися з ним:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(Я, звичайно, не назвав би це "перевіреним".)

Це неправильне використання перевірених винятків. Клієнтський код буде заповнений дзвінками для отримання даних про рядки, кожен з яких повинен буде використовувати спробу / ловити, і для чого? Чи збираються вони повідомити користувачеві, що шукали неправильний рядок? Напевно, ні - тому що незалежно від інтерфейсу користувача, який оточує перегляд моєї таблиці, він не повинен дозволяти користувачеві переходити до стану, коли запитується незаконний рядок. Отже, це помилка з боку клієнтського програміста.

Програміст API все ще може передбачити, що клієнт буде кодувати такі помилки і повинен обробляти його з виключенням під час виконання, як-от IllegalArgumentException.

За винятком перевірених випадків getRowData, це очевидно, що це призведе до того, що ледачий програміст Хейльсберга просто додає порожній лов. Коли це станеться, незаконні значення рядків не будуть очевидні навіть для налагодження тестера або клієнтського розробника, скоріше вони призведуть до помилок, що важко визначити джерело. Ракети Arianne підірвуться після запуску.

Гаразд, ось у чому полягає проблема: я кажу, що перевірений виняток FileNotFoundException- це не просто хороша річ, а важливий інструмент інструментарію API програмістів для визначення API найбільш корисним для клієнта програмістом способом. Але CheckedInvalidRowNumberExceptionце - велика незручність, що призводить до поганого програмування, і цього слід уникати. Але як сказати різницю.

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

  1. Поза контролем клієнта або закрито та відкрито:

    Перевірені винятки слід використовувати лише в тому випадку, коли випадок помилки виходить з-під контролю як API, так і клієнтського програміста. Це стосується того, наскільки відкрита чи закрита система. У обмеженому інтерфейсі, де клієнтський програміст має контроль, скажімо, над усіма кнопками, командами клавіатури тощо, які додають та видаляють рядки із подання таблиці (закрита система), це помилка програмування клієнта, якщо він намагається отримати дані з неіснуючий ряд. У файловій операційній системі, де будь-яка кількість користувачів / додатків може додавати та видаляти файли (відкрита система), можливо, файл, який запитує клієнт, був видалений без їх відома, тому від них слід очікувати, що він має справу з ним. .

  2. Повсюдність:

    Перевірені винятки не повинні використовуватися для виклику API, який часто здійснює клієнт. Я часто маю на увазі з багатьох місць у коді клієнта - не часто в часі. Таким чином, клієнтський код, як правило, не намагається багато відкривати один і той же файл, але мій перегляд таблиці отримує RowDataповсюдно за допомогою різних методів. Зокрема, я буду писати багато подібних кодів

    if (model.getRowData().getCell(0).isEmpty())

    і буде боляче кожен раз занурюватися в спробу / ловити.

  3. Інформування користувача:

    Перевірені винятки слід використовувати в тих випадках, коли ви можете уявити корисне повідомлення про помилку, яке представляється кінцевому користувачеві. Це "і що ти будеш робити, коли це станеться?" питання, яке я піднімав вище. Це стосується також пункту 1. Оскільки ви можете передбачити, що щось, що знаходиться поза вашою системою клієнт-API, може призвести до відсутності файлу, ви можете обґрунтовано повідомити про це користувача:

    "Error: could not find the file 'goodluckfindingthisfile'"

    Оскільки ваш незаконний номер рядка був викликаний внутрішньою помилкою та з вини користувача, ви не зможете надати їм корисної інформації. Якщо ваш додаток не дозволяє виняткам із виконання часу потрапляти на консоль, це, ймовірно, передасть їм якесь потворне повідомлення, наприклад:

    "Internal error occured: IllegalArgumentException in ...."

    Якщо коротко, якщо ви не думаєте, що ваш клієнт-програміст може пояснити свій виняток таким чином, що допомагає користувачеві, то, ймовірно, ви не повинні використовувати перевірені винятки.

Так це мої правила. Дещо надумане, і без сумніву, будуть винятки (будь-ласка, допоможіть мені уточнити їх, якщо хочете). Але мій головний аргумент полягає в тому, що є випадки, FileNotFoundExceptionколи перевірений виняток є настільки ж важливою і корисною частиною контракту API, як і типи параметрів. Тож ми не повинні відмовлятися від цього лише тому, що воно неправильно використовується.

Вибачте, не означав зробити це так довго і вафляво. Дозвольте закінчити двома пропозиціями:

A: Програмісти API: використовуйте перевірені винятки, щоб зберегти їх корисність. У разі сумнівів використовуйте неперевірений виняток.

B: Клієнтські програмісти: звикайте створювати завершений виняток (google it) на початку вашої розробки. JDK 1.4 і пізніші версії надають конструктор RuntimeExceptionдля цього, але ви також можете легко створити свій власний. Ось конструктор:

public RuntimeException(Throwable cause)

Потім увійдіть у звичку щоразу, коли вам доводиться обробляти перевірений виняток, і ви лінуєтесь (або ви вважаєте, що програміст API надмірно використовував перевірений виняток в першу чергу), не просто проковтуйте виняток, перегорніть його і скинути його.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

Помістіть це в один із маленьких шаблонів коду IDE і використовуйте його, коли ви лінуєтесь. Таким чином, якщо вам справді потрібно попрацювати з перевіреним винятком, ви змушені будете повернутися і вирішити його, побачивши проблему під час виконання. Тому що, повірте мені (та Андерсу Хейльсбергу), ви ніколи не збираєтесь повертатися до того TODO у своєму

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}

110
Відкриття файлів насправді є хорошим прикладом для надзвичайно контрпродуктивних перевірених винятків. Оскільки більшу частину часу код, який відкриває файл, НЕ МОЖЕ робити нічого корисного щодо винятку - це краще виконати на кілька шарів до стека викликів. Перевірений виняток змушує вас захаращувати підписи методу просто для того, щоб ви могли робити все, що ви зробили б у будь-якому випадку - обробляйте виняток у тому місці, де це має сенс зробити це.
Майкл Боргвардт

115
@Michael: Погодився, що ти можеш обробляти ці винятки IO на кілька рівнів вище, але я не чую, щоб ти заперечував, що як клієнт API, ти повинен їх передбачити. З цієї причини я вважаю, що перевірені винятки підходять. Так, вам доведеться оголошувати "кидки" на кожному методі до стека викликів, але я не погоджуюся, що це безлад. Це корисна декларативна інформація в підписах вашого методу. Ви говорите: мої методи низького рівня можуть зіткнутися з відсутнім файлом, але вони залишать вам обробку. Я не бачу нічого втрачати, і тільки хороший, чистий код, щоб отримати
Rhubarb

23
@Rhubarb: +1, дуже цікава відповідь, показує аргументи для обох сторін. Чудово. Про ваш останній коментар: зауважте, що оголошення викидів для кожного методу в стек викликів може бути не завжди можливим, особливо якщо ви впроваджуєте інтерфейс на цьому шляху.
Р. Мартіньо Фернандес

8
Це дуже сильний аргумент (і я згоден з цим загалом), але є випадок, коли він не працює: загальні зворотні виклики. Якщо у мене є бібліотека, яка викликає якийсь визначений користувачем код, немає можливості (що я знаю, в Java) для цієї бібліотеки поширювати перевірені винятки з коду користувача, який вона викликає до коду користувача, який її викликає. Ця проблема також поширюється на інші частини системи типів багатьох мов, що не мають статичного типу, які не підтримують належні загальні типи. Цю відповідь було б покращено згадкою про це (а можливо, і відповіддю).
Манкарсе

7
Неправильно @Rhubarb. Перевірені винятки були призначені для "надзвичайних ситуацій", які можна було б вирішувати, але завжди були несумісними з найкращою практикою "кидати рано, ловити пізно". Відкриття файлу - це вишневий приклад, який можна обробити. Більшість IOException (наприклад, написання байта), SQLException, RemoteException неможливо обробляти змістовно. Помилка або повторне повторення має бути на рівні "бізнес" або "запит", а перевірені винятки - як це використовується в бібліотеках Java - є помилкою, яка ускладнює це. literatejava.com/exceptions/…
Thomas W

184

Справа в перевірених винятках полягає в тому, що вони насправді не є винятком із звичайного розуміння концепції. Натомість вони є альтернативними поверненими значеннями API.

Вся ідея винятків полягає в тому, що помилка, викинута кудись вниз по ланцюгу дзвінків, може перекинутися і оброблятися кодом десь далі вгору, без втручається коду, щоб не турбуватися про це. З іншого боку, перевірені винятки вимагають, щоб кожен рівень коду між метальником і ловлем заявив, що вони знають про всі форми винятку, які можуть пройти через них. Насправді це мало чим відрізняється від того, якщо перевірені винятки були просто спеціальними зворотними значеннями, на які повинен був перевірити абонент. Наприклад, [псевдокод]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Оскільки Java не може робити альтернативні значення повернення або прості вбудовані кортежі як значення повернення, перевірені винятки є розумною відповіддю.

Проблема полягає в тому, що багато коду, в тому числі великі зразки стандартної бібліотеки, неправильно використовують перевірені винятки для реальних виняткових умов, які, можливо, ви дуже хочете наздогнати кілька рівнів. Чому IOException не є RuntimeException? На будь-якій іншій мові я можу дозволити винятку IO, і якщо я нічого не впораюся з цим, моя програма зупиниться, і я отримаю зручний слід стека. Це найкраще, що може статися.

Можливо, два способи на прикладі, з якого ви хочете зафіксувати всі IOExceptions з усього процесу запису в потік, перервати процес і перейти до коду повідомлення про помилки; в Java ви не можете цього зробити, не додаючи "кидає IOException" на кожному рівні виклику, навіть рівні, які самі не роблять IO. Такі методи не повинні знати про обробку винятків; повинні додавати винятки до своїх підписів:

  1. зайво збільшує зчеплення;
  2. робить підписи інтерфейсу дуже крихкими для зміни;
  3. робить код менш читабельним;
  4. настільки дратує, що загальна реакція програміста полягає в тому, щоб перемогти систему, зробивши щось жахливе, наприклад, "кидає виняток", "ловити (виняток e) {}" або загортати все в RuntimeException (що робить налагодження складніше).

І тоді є безліч просто смішних винятків з бібліотеки, таких як:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

Коли вам доводиться захаращувати код таким смішним сирим, як це, недарма перевірені винятки отримують купу ненависті, хоча насправді це просто простий поганий дизайн API.

Інший особливий поганий вплив має на Inversion of Control, де компонент A забезпечує зворотний виклик для загального компонента B. Компонент A хоче мати можливість випускати виняток із зворотного виклику до місця, де він викликав компонент B, але він не може тому що це змінило б інтерфейс зворотного виклику, який виправлено B. A може це зробити, лише обернувши реальний виняток у RuntimeException, який ще є більш корисним шаблоном для обробки винятків для запису.

Перевірені винятки, що реалізовані в Java та її стандартній бібліотеці, означають котлован, котельню, котельню. У вже багатослівній мові це не виграш.


14
У вашому прикладі коду було б найкраще ланцюжок винятків, щоб оригінальну причину можна було знайти під час читання журналів: киньте CanNeverHappenException (e);
Еско Луонтола

5
Я не погоджуюсь. Винятки, перевірені чи ні, є винятковими умовами. Приклад: метод, який отримує об'єкт через HTTP. Повернене значення - це об'єкт або нічого, всі речі, які можуть погіршитись, є винятковими. Трактування їх як повернених значень, як це робиться в C, призводить лише до плутанини і поганого дизайну.
Містер Сміт

15
@Mister: Що я говорю, це те, що перевірені винятки, реалізовані на Java, на практиці більше схожі на значення, що повертаються, як у C, ніж традиційні "винятки", які ми можемо розпізнати з C ++ та інших мов попередньої Java. І що IMO це насправді призводить до плутанини та поганого дизайну.
bobince

9
Погодьтеся, що стандартні бібліотеки зловживають перевіреними винятками, безумовно, додають плутанині та поганій поведінці. І, часто, саме з поганої документації, наприклад, методу розриву, як disconnect (), викидається IOException, коли "виникає якась інша помилка вводу / виводу". Ну, я відключався! Я просочую ручку чи інший ресурс? Чи потрібно повторити спробу? Не знаючи, чому це сталося, я не можу здійснити дії, яку я повинен би вжити, і тому я мушу здогадуватися, чи варто мені просто проковтнути його, спробувати або під заставу.
charstar

14
+1 для "Альтернативні значення повернення API". Цікавий спосіб перегляду перевірених винятків.
Zsolt Török

75

Замість того, щоб переосмислити всі (багато) причини проти перевірених винятків, я виберу лише одну. Я втратив підрахунок кількості разів, коли я написав цей блок коду:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

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

Я також втратив підрахунок кількості разів, коли я бачив це:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

Чому? Тому що комусь довелося з цим боротися і ліниво. Це було не так? Звичайно. Це трапляється? Абсолютно. Що робити, якщо натомість це був неперевірений виняток? Додаток щойно помер (що краще проковтнути виняток).

І тоді у нас є напійний код, який використовує винятки як форму управління потоком, як це робить java.text.Format . Bzzzt. Неправильно. Користувач, який вводить "abc" у числове поле форми, не є винятком.

Гаразд, я гадаю, що це було три причини.


4
Але якщо виняток вилучено належним чином, ви можете повідомити користувача, виконувати інші завдання (журнал?) Та вийти з програми контрольованим чином. Я згоден, що деякі частини API могли бути розроблені краще. І з причини лінивого програміста, я думаю, що як програміст ви 100% відповідаєте за свій код.
Містер Сміт

3
зауважте, що try-catch-rethrow дозволяє вказати повідомлення - я зазвичай використовую його для додавання інформації про зміст змінних стану. Частий приклад - IOExceptions, щоб додати absolPathName () відповідного файлу.
Thorbjørn Ravn Andersen

15
Я думаю, що IDE, такі як Eclipse, багато в чому винні, скільки разів ви бачили порожній блок вилову. Дійсно, вони повинні замовчуватися за замовчуванням.
artbristol

12
"99% часу я нічого не можу з цим зробити" - неправильно, ви можете показати користувачеві повідомлення з повідомленням: "Не вдалося підключитися до сервера" або "Пристрій IO не вдалося", замість того, щоб просто відпустити програму. через невелику мережкову гикавку. Обидва ваші приклади - це мистецтво праці від поганих програмістів. Ви повинні атакувати поганих програмістів, а не перевіряти самі винятки. Це так, як я атакую ​​інсулін за те, що не допомагаю від діабету, коли використовую його як заправку для салатів.
AxiomaticNexus

2
@YasmaniLlanes Ви не завжди можете робити це. Іноді у вас є інтерфейс, якого слід дотримуватися. І це особливо актуально, коли ви розробляєте хороші API, що підтримуються, тому що ви не можете просто розповсюджувати побічні ефекти всюди. І це, і складність, яку вона введе, дуже сильно кусають вас. Так що так, 99% часу, нічого з цим робити не можна.
MasterMastic

50

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

Моя головна яловичина з перевіреними винятками - це те, що вони руйнують поліморфізм. Неможливо змусити їх добре грати з поліморфними інтерфейсами.

Візьміть хороший Listінтерфейс Java . У нас є загальні вбудовані в пам’ять реалізації як ArrayListі LinkedList. У нас також є скелетний клас, AbstractListякий спрощує розробку нових типів списку. Для списку лише для читання нам потрібно реалізувати лише два методи: size()і get(int index).

Цей приклад WidgetListкласу читає деякі Widgetфайли фіксованого розміру типу (не показано) з файлу:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

Розкриваючи віджети за допомогою звичного Listінтерфейсу, ви можете отримати елементи ( list.get(123)) або повторити список ( for (Widget w : list) ...), не знаючи про WidgetListсебе. Цей список можна передати будь-яким стандартним методам, які використовують загальні списки, або зафіксувати його в Collections.synchronizedList. Код, який використовує його, не повинен ні знати, ні дбати, чи "Віджети" складені на місці, надходять з масиву, чи читаються з файлу, бази даних, або з усієї мережі, або з майбутнього реле підпростори. Він все одно буде працювати правильно, оскільки Listінтерфейс правильно реалізований.

За винятком цього немає. Наведений вище клас не компілюється, оскільки методи доступу до файлів можуть викидати IOExceptionперевірений виняток, який потрібно "вловити або вказати". Ви не можете вказати це як кинуте - компілятор не дозволить вам, оскільки це порушить контракт Listінтерфейсу. І немає жодного корисного способу, який би WidgetListсам впорався з винятком (про що я поясню пізніше).

Мабуть, єдине, що потрібно зробити, - це виловлювати та перезавантажувати перевірені винятки як деякі неперевірені винятки:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Редагувати: Java 8 додав UncheckedIOExceptionклас саме для цього випадку: для лову та повторного викидання IOExceptionчерез межі поліморфних методів. Вигляд підтверджує мою думку!))

Тому перевірені винятки просто не працюють у таких випадках. Ви не можете їх кинути. Дітто для розумного Mapпідкріплення бази даних або реалізація java.util.Randomпідключеного до джерела квантової ентропії через порт COM. Як тільки ви намагаєтесь зробити щось нове з реалізацією поліморфного інтерфейсу, концепція перевірених винятків виходить з ладу. Але перевірені винятки настільки підступні, що вони все одно не залишать вас у спокої, тому що вам все одно доведеться ловити і повторно скидати будь-які методи нижчого рівня, захаращуючи код і захаращуючи слід стека.

Я вважаю, що всюдисущий Runnableінтерфейс часто підтримується в цей кут, якщо він називає щось, що кидає перевірені винятки. Він не може кинути виняток, як є, тому все, що він може зробити, - це захаращувати код, ловлячи і повторюючи, як RuntimeException.

Насправді ви можете кидати незадекларовані перевірені винятки, якщо вдаєтесь до хакерів. JVM під час виконання не хвилює перевірені правила виключення, тому нам потрібно обдурити лише компілятор. Найпростіший спосіб зробити це - зловживати дженериками. Це мій метод для нього (назва класу показано, тому що (перед Java 8) це потрібно в синтаксисі виклику для загального методу):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Ура! Використовуючи це, ми можемо кинути перевірений виняток будь-якої глибини стека, не оголошуючи його, не загортаючи його в RuntimeException, і не захаращуючи слід стека! Знову використовуючи приклад "WidgetList":

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

На жаль, остаточна образа перевірених винятків полягає в тому, що компілятор відмовляється дозволити зловити перевірений виняток, якби, на його хибну думку, його не можна було кинути. (У неперевірених винятках це правило не існує.) Щоб зловити викрадений підкидання, ми повинні це зробити:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

Це трохи незручно, але, з іншого боку, це все-таки трохи простіше, ніж код для вилучення перевіреного винятку, який був завернутий у RuntimeException.

На щастя, throw t;заява тут легальна, навіть незважаючи на те, що тип tперевірений, завдяки правилу, доданому в Java 7, про повторне відкидання вилучених винятків.


Коли перевірені винятки відповідають поліморфізму, протилежний випадок також є проблемою: коли метод специфікується як потенційно кидання перевіреного винятку, але переосмислена реалізація цього не робить. Наприклад, абстрактний клас OutputStream«S writeметоди все вказати throws IOException. ByteArrayOutputStreamце підклас, який записує в масив пам'яті замість справжнього джерела вводу / виводу. Його переосмислені writeметоди не можуть викликати IOExceptions, тому вони не мають throwsзастереження, і ви можете зателефонувати їм, не турбуючись про вимогу catch-or-define.

За винятком не завжди. Припустимо, у Widgetнього є метод збереження його в потік:

public void writeTo(OutputStream out) throws IOException;

Оголошення цього способу прийняти звичайну OutputStreamріч - це правильно, тому його можна використовувати поліморфно з усіма видами виходів: файлами, базами даних, мережею тощо. І масиви в пам'яті. Однак у масиві пам'яті існує помилкова вимога обробляти виняток, який насправді не може статися:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

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

Але зачекайте, перевірені винятки насправді настільки дратують, що вони навіть не дозволять вам робити зворотне! Уявіть, що ви зараз ловите будь-який IOExceptionвикинутий writeвиклик на OutputStream, але ви хочете змінити оголошений тип змінної на a ByteArrayOutputStream, компілятор зриває вас за спробу виловити перевірений виняток, який, за його словами, не може бути кинутий.

Це правило викликає деякі абсурдні проблеми. Наприклад, один з трьох writeметодів OutputStreamє НЕ перевизначені ByteArrayOutputStream. Зокрема, write(byte[] data)це зручний метод, який записує повний масив, викликаючи write(byte[] data, int offset, int length)зсув 0 і довжину масиву. ByteArrayOutputStreamпереосмислює триаргументаційний метод, але успадковує метод зручності в одному аргументі як є. Спадковий метод робить саме правильно, але він включає небажане throwsзастереження. Це, можливо, було недоглядом у дизайні ByteArrayOutputStream, але вони ніколи не можуть її виправити, оскільки це порушить сумісність джерела з будь-яким кодом, який виловлює виняток - виняток, який ніколи, ніколи і ніколи не буде кинутий!

Це правило дратує і під час редагування, і налагодження. Наприклад, іноді я коментую виклик методу тимчасово, і якщо він міг би кинути перевірений виняток, зараз компілятор поскаржиться на існування локальних tryта catchблоків. Тому я також повинен прокоментувати їх, і тепер, коли редагувати код всередині, IDE буде відступати на неправильний рівень, оскільки {і }коментується. Гах! Це невелика скарга, але здається, що єдине, що перевірені винятки коли-небудь роблять, це викликати неприємності.


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

Натомість ми отримуємо таку ідіому, яка розгулює як спосіб закрити компілятор:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

У графічному інтерфейсі або автоматизованій програмі надруковане повідомлення не побачиться. Гірше, що воно виключається з рештою коду після винятку. Чи виняток насправді не є помилкою? Тоді не друкуйте його. Інакше за мить підірветься щось інше, до цього часу оригінальний об’єкт виключення вже не буде. Ця ідіома не краща, ніж BASIC On Error Resume Nextабо PHP error_reporting(0);.

Викликати якийсь клас реєстратора не набагато краще:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

Це так само ліниво, як e.printStackTrace();і все ще пхається з кодом у невизначеному стані. Крім того, вибір конкретної системи реєстрації або іншого обробника залежить від додатка, тому це шкодить повторному використанню коду.

Але зачекайте! Існує простий і універсальний спосіб знайти обробник, що відповідає додатку. Це вище стека викликів (або він встановлений як обробник винятків теми, що виникла ). Тому в більшості місць все, що вам потрібно зробити, - це викинути виняток вище стека . Наприклад, throw e;. Перевірені винятки просто заважають.

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


Для вашого методу розміру за допомогою WidgetList я б кешував розмір у змінній і встановлював його в конструкторі. Конструктор вільний кинути виняток. Це не спрацює, якщо файл змінюється під час використання WidgetList, що, мабуть, буде погано, якби це було.
TofuBeer

2
SomeStupidExceptionOmgWhoCares добре, хтось піклується достатньо, щоб кинути його. Тож або його ніколи не слід було кидати (поганий дизайн), або ви дійсно повинні впоратися з цим. Те саме стосується поганої реалізації класу до 1,0 (вихідний потік байтового масиву), де дизайн, на жаль, був поганим.
TofuBeer

2
Власне ідіома була б директивою, яка б зафіксувала будь-які визначені винятки, кинуті вкладеними викликами підпрограми та перезапустила їх, загорнуті в RuntimeException. Зауважте, що рутина може бути одночасно оголошена як throws IOExceptionта ще й вказати, що будь-який IOExceptionвикинутий з вкладеного виклику слід вважати несподіваним і завершеним.
supercat

15
Я професійний розробник C # з досвідом Java, який натрапив на цю посаду. Мене бентежить питання, чому хтось підтримає цю химерну поведінку. У .NET, якщо я хочу зловити певний тип винятку, я можу це зрозуміти. Якщо я хочу просто дозволити йому підкинути стек, нічого робити не треба. Я хотів би, щоб Java не була такою вигадливою. :)
aikeru

Щодо "інколи я коментую виклик методу тимчасово" - я навчився використовувати if (false)для цього. Це дозволяє уникнути проблеми із застереженням про скидання, і попередження допомагає мені швидше переходити назад. +++ З цього приводу я згоден з усім, що ви написали. Перевірені винятки мають деяку цінність, але це значення є незначним порівняно з їх вартістю. Майже завжди вони просто заважають.
maaartinus

45

Ну, справа не в тому, щоб показувати стек-трек або мовчки розбиватися. Йдеться про можливість повідомляти про помилки між шарами.

Проблема з перевіреними винятками полягає в тому, що вони спонукають людей ковтати важливі деталі (а саме клас виключень). Якщо ви вирішите не ковтати цю деталь, вам доведеться продовжувати додавати декларації про кидки у всій програмі. Це означає 1), що новий тип винятків вплине на безліч підписів функцій, і 2) ви можете пропустити конкретний екземпляр винятку, який ви насправді хочете зловити (скажімо, ви відкриєте вторинний файл для функції, яка записує дані в файл. Вторинний файл необов’язковий, тому ви можете ігнорувати його помилки, але, оскільки підпис throws IOException, це легко не помітити).

Я фактично маю справу з цією ситуацією зараз у додатку. Ми перепакували майже винятки як AppSpecificException. Це зробило підписи справді чистими, і нам не довелося турбуватися про вибух throwsпідписів.

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

Ось чому я великий прихильник обробці винятків у python. Ви можете зловити лише те, що хочете та / або вмієте. Все інше бульбашки, як ніби ви самі їх перекручуєте (що все одно зробили).

Я знову і знову виявляв, і протягом згаданого нами проекту обробка виключень підпадає на 3 категорії:

  1. Ловіть і обробляйте конкретний виняток. Це, наприклад, для реалізації логіки спроб.
  2. Ловіть і перекидайте інші винятки. Все, що відбувається тут, - це звичайний журнал, і зазвичай це банальне повідомлення типу "Неможливо відкрити ім'я $ file". Це помилки, з якими нічого не можна зробити; тільки вищі рівні знають достатньо, щоб впоратися з цим.
  3. Зловити все та відобразити повідомлення про помилку. Зазвичай це в самому корені диспетчера, і все це робиться для того, щоб він міг донести помилку абоненту через механізм невиключення (діалогове вікно, спливаюче об'єднання помилки RPC тощо).

5
Ви могли зробити спеціальні підкласи AppSpecificException, щоб дозволити розділення, зберігаючи підписи простого методу.
Thorbjørn Ravn Andersen

1
Також дуже важливим доповненням до пункту 2 є те, що воно дозволяє додавати інформацію до винятку, що потрапив (наприклад, шляхом введення в RuntimeException). Набагато, набагато краще, щоб ім’я файлу, не знайденого в сліді стека, ніж приховане вглиб файлу журналу.
Thorbjørn Ravn Andersen

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

4
Це зовсім не те, що я говорю. Ваше останнє речення насправді погоджується зі мною. Якщо все загорнуте в AppSpecificException, воно не спливає (і сенс / контекст втрачено), і, так, клієнт API не інформується - саме це відбувається з перевіреними винятками (як вони у Java) , тому що люди не хочуть мати справу з функціями з великою кількістю throwsдекларацій.
Річард Левассер

6
@Newtopian - винятки можна значною мірою обробляти лише на рівні "бізнес" або "запит". Має сенс вийти з ладу або спробувати велику деталізацію, не для кожного крихітного потенційного відмови. З цієї причини найкраща практика з винятку щодо обробки винятків узагальнюється як "кидати рано, пізно ловити". Перевірені винятки ускладнюють управління надійністю на належному рівні та заохочують велику кількість неправильно кодованих блоків улов. literatejava.com/exceptions/…
Thomas W

24

SNR

По-перше, перевірені винятки зменшують "коефіцієнт сигнал-шум" для коду. Андерс Хейльсберг також розповідає про імперативне проти декларативне програмування, яке є подібним поняттям. У будь-якому випадку врахуйте такі фрагменти коду:

Оновіть інтерфейс користувача з потоку, який не використовує інтерфейс користувача на Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Оновіть інтерфейс користувача з потоку без інтерфейсу користувача у C #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

Що мені здається набагато зрозумілішим. Коли ви почнете робити все більше і більше роботи інтерфейсу в Swing, перевірені винятки починають ставати справді дратівливими і марними.

Перерва у в'язниці

Для реалізації навіть найпростіших реалізацій, таких як інтерфейс списку Java, перевіряються винятки як інструмент для проектування за контрактом. Розглянемо список, який підтримується базою даних або файловою системою або будь-якою іншою реалізацією, яка видає перевірене виключення. Єдиною можливою реалізацією є спіймання перевіреного винятку та повторне введення його як неперевірений виняток:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

А тепер ви повинні запитати, який сенс у всьому цьому коді? Перевірені винятки просто додають шум, виняток був спійманий, але не оброблений, і дизайн за контрактом (з точки зору перевірених винятків) зламався.

Висновок

  • Вилучення винятків відрізняється від поводження з ними.
  • Перевірені винятки додають шум до коду.
  • Обробка винятків працює добре в C # без них.

Я про це блогував раніше .


3
У прикладі і Java, і C # просто дозволяють поширюватися на винятки, не обробляючи їх (Java через IllegalStateException). Різниця полягає в тому, що ви можете обробити FileNotFoundException, але малоймовірно, що обробка InvocationTargetException або InterruptException буде корисною.
Люк Кінане

3
І C-способом, як я можу знати, що виняток I / O може статися? Також я ніколи не кидав би виняток із запуску ... Я вважаю, що це зловживання обробкою виключень. Вибачте, але для цієї частини вашого коду я просто ще бачу вашу сторону.
TofuBeer

7
Ми доїжджаємо туди :-) Тож із кожним новим випуском API вам доведеться перебирати всі ваші дзвінки та шукати будь-які нові винятки, які можуть статися? Це може статися з інтерфейсами API компанії, оскільки вони не повинні турбуватися про зворотну сумісність.
TofuBeer

3
Ви мали на увазі зменшення співвідношення сигнал / шум?
neo2862

3
@TofuBeer Не змушений оновлювати код після того, як інтерфейс додаткового API змінив гарну річ? Якби у вас були лише неперевірені винятки, ви б закінчилися програмою / неповною програмою, не знаючи цього.
Франсуа Буржуа

23

Артима опублікував інтерв'ю з одним з архітекторів .NET, Андерсом Хейльсбергом, де гостро висвітлюються аргументи проти перевірених винятків. Короткий дегустатор:

Застереження про кидки, принаймні так, як воно реалізовано в Java, не обов'язково змушує вас обробляти винятки, але якщо ви не керуєте ними, воно змушує вас точно визнати, через які винятки можуть пройти. Він вимагає, щоб ви або зловити оголошені винятки, або помістити їх у власний пункт про кидки. Щоб подолати цю вимогу, люди роблять смішні речі. Наприклад, вони прикрашають кожен метод "кидає виняток". Це просто перемагає функцію, і ви щойно змусили програміста написати ще більше гоблей гармати. Це нікому не допомагає.


2
Насправді головний архітектор.
Пастка

18
Я прочитав це, для мене його аргумент зводиться до "там погані програмісти".
TofuBeer

6
TofuBeer, зовсім не так. Вся справа в тому, що багато разів ви не знаєте, що робити з винятком, який викликає викликаний метод, і випадок, який вас справді цікавить, навіть не згадується. Ви відкриваєте файл, отримуєте виняток IO, наприклад ... це не моя проблема, тому я підкидаю його. Але метод виклику верхнього рівня просто захоче припинити обробку та повідомити користувача, що існує невідома проблема. Перевірена виняток зовсім не допомогла. Це могло статися одна з мільйона дивних речей.
Дан Розенстарк

4
@yar, якщо вам не подобається перевірений виняток, тоді зробіть "киньте новий RuntimeException (" ми цього не очікували, роблячи Foo.bar () ", e)", і виконайте це.
Thorbjørn Ravn Andersen

4
@ ThorbjørnRavnAndersen: Основна слабкість дизайну в Java, яка .net, на жаль, скопійована, полягає в тому, що він використовує тип винятку як основний засіб для вирішення питання щодо того, чи слід діяти, і основний спосіб вказівки на загальний тип речі це пішло не так, коли насправді ці два питання є значною мірою ортогональними. Важливо не те, що пішло не так, а те, що є об'єктами стану. Крім того, і .net, і Java за замовчуванням припускають, що діяти і вирішувати винятки, як правило, те саме, що насправді вони часто різні.
supercat

20

Я спочатку погодився з вами, так як я завжди був прихильником перевірених винятків, і почав думати, чому мені не подобається не перевіряти винятки в .Net. Але потім я зрозумів, що я не впливаю на перевірені винятки.

Щоб відповісти на ваше запитання, так, мені подобається, що мої програми показують стеки стеків, бажано, справді некрасиві. Я хочу, щоб програма вибухнула у жахливій купі найпотворніших повідомлень про помилки, які ви могли коли-небудь побачити.

А причина в тому, що якщо це робиться, я маю це виправити, і я повинен це виправити відразу. Хочу відразу ж знати, що є проблема.

Скільки разів ви насправді обробляєте винятки? Я не говорю про вилучення винятків - я кажу про поводження з ними? Занадто просто написати наступне:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

І я знаю, що ви можете сказати, що це погана практика, і що "відповідь" полягає в тому, щоб робити щось за винятком (дозвольте здогадатися, увійти?), Але в реальному світі (тм) більшість програмістів просто не роблять цього. це.

Так що так, я не хочу ловити винятки, якщо мені цього не потрібно, і я хочу, щоб моя програма вибухнула ефектно, коли я накручувалась. Мовчазна невдача - найгірший можливий результат.


Java заохочує вас робити подібні речі, так що вам не доведеться додавати кожен тип винятку до кожного підпису методу.
yfeldblum

17
Смішно .. з тих пір, як я правильно сприйняв перевірені винятки і правильно їх використав, мої програми перестали вибухати величезною купою, що розпалювала ваше незадоволення клієнтів. Якщо під час розробки у вас є великі некрасиві погані сліди стека, то замовник зобов’язаний отримати їх також. Залюбки побачити його обличчя, коли він побачить ArrayIndexOutOfBoundsException з милею високого сліду стека на його розбитій системі замість невеликого повідомлення про лоток, який говорить про те, що кольорову конфігурацію для кнопки XYZ неможливо проаналізувати, тому за замовчуванням використовується замість цього програмне забезпечення, яке щасливо гуде разом
ньютопський

1
Можливо, те, що потрібно Java - це декларація "cantHandle", яка б вказувала, що метод або блок "спробувати / ловити" код не готовий обробляти певний виняток, що відбувається всередині нього, і що будь-який такий виняток, який відбувається за допомогою інших засобів, ніж явний кидок у межах цього методу (на відміну від називаного методу) повинен бути автоматично загорнутий і перетоплений у RuntimeException. IMHO, перевірені винятки рідко повинні розповсюджувати стек викликів без завершення.
supercat

3
@Newtopian - я пишу серверне і високонадійне програмне забезпечення & роблю це вже 25 років. Мої програми ніколи не вибухали, і я працюю з високодоступними, повторними і знову підключеними, фінансовими та військовими системами на основі інтеграції. Я маю абсолютну об'єктивну основу, щоб віддати перевагу виняткам із виконання. Перевірені винятки ускладнюють дотримання правильної найкращої практики "кидати рано, пізно ловити". Правильна надійність та поводження з помилками знаходяться на рівні "бізнес", "з'єднання" або "запит". (Або періодично під час аналізу даних). Перевірені винятки заважають робити це правильно.
Thomas W

1
Винятки, про які ви тут говорите, - це те, RuntimeExceptionsщо вам справді не доведеться ловити, і я погоджуюся, що ви повинні дозволити програмі підірватись. Винятки, які ви завжди повинні ловити та обробляти, - це такі перевірені винятки IOException. Якщо ви отримаєте IOException, у вашому коді нічого не можна виправити; Ваша програма не повинна вибухати лише тому, що відбулася мережева гикавка.
AxiomaticNexus

20

Стаття « Ефективні винятки Java» добре пояснює, коли використовувати неперевірені та коли використовувати перевірені виключення. Ось кілька цитат із цієї статті, щоб виділити основні моменти:

Надзвичайна ситуація: очікувана умова, що вимагає альтернативної відповіді від методу, яка може бути виражена у плані призначення методу. Викликаючий метод очікує подібних умов і має стратегію впоратися з ними.

Помилка: незапланована умова, яка не дозволяє методу досягти наміченого призначення, яке неможливо описати без посилання на внутрішню реалізацію методу.

(ТАК не дозволяє таблиць, тому ви можете прочитати наступне з оригінальної сторінки ...)

Навмисний випадок

  • Вважається таким: Частина дизайну
  • Очікується, що це трапляється: регулярно, але рідко
  • Кого це хвилює: Код висхідного потоку, який викликає метод
  • Приклади: Альтернативні режими повернення
  • Найкраще картографування: перевірений виняток

Помилка

  • Вважається таким: неприємний сюрприз
  • Очікується, що станеться: Ніколи
  • Кого це хвилює: люди, яким потрібно виправити проблему
  • Приклади: помилки програмування, несправності апаратного забезпечення, помилки конфігурації, відсутні файли, недоступні сервери
  • Найкраще картографування: неперевірений виняток

Я знаю, коли їх використовувати, я хочу знати, чому люди, які не дотримуються цієї поради ... не слідкуйте за цією порадою :-)
TofuBeer

Що таке помилки програмування та як їх відрізнити від помилок використання ? Це помилка програмування, якщо користувач передає неправильні аргументи програмі? З точки зору Java це може бути не помилка програмування, але з точки зору сценарію оболонки це помилка програмування. Тож у чому невірні аргументи args[]? Вони є випадковістю чи помилкою?
припинення

3
@TofuBeer - Оскільки дизайнери бібліотек Java вирішили поставити всі види невиправних помилок низького рівня як перевірені винятки, коли вони, очевидно, повинні були бути відмінені . FileNotFound - це єдина IOException, яку слід перевірити, наприклад. Що стосується JDBC - лише підключення до бази даних може бути обґрунтовано розглянуто як надзвичайний випадок . Усі інші SQLExceptions повинні були бути відмовними і не перевіреними . Поводження з помилками має бути належним чином на рівні "бізнес" чи "запит" - див. Кращу практику "кидайте рано, спіймайте пізно". Перевірені винятки є бар'єром для цього.
Thomas W

1
У вашому аргументі є один величезний недолік. "Непередбачувані випадки" НЕ повинні оброблятися через винятки, а через повернення значень ділового коду та методу. Винятки становлять, як сказано в цьому слові, НАЙКРАЙНІ ситуації, тому Несправності.
Маттео Моска

@MatteoMosca Коди повернення помилок, як правило, ігноруються, і цього достатньо, щоб їх дискваліфікувати. Насправді, все, що незвичайне, часто можна обробляти лише десь у стеку, і це є випадком використання для виключень. Я міг би уявити щось на кшталт File#openInputStreamповернення Either<InputStream, Problem>- якщо це ви маєте на увазі, то ми можемо погодитися.
maaartinus

19

Коротко:

Винятки - це питання дизайну API. - Ні більше, ні менше.

Аргумент для перевірених винятків:

Щоб зрозуміти, чому перевірені винятки можуть бути не надто корисними, давайте переглянемо питання і запитаємо: Коли або чому перевіряються винятки привабливими, тобто чому ви хочете, щоб компілятор застосовував декларацію про винятки?

Відповідь очевидна: Іноді потрібно вилучити виняток, і це можливо лише в тому випадку, якщо код, який викликається, пропонує певний клас винятку для тієї помилки, яка вас цікавить.

Отже, аргументом для перевірених винятків є те, що компілятор змушує програмістів оголошувати, які винятки викинуті, і, сподіваємось, програміст також задокументує конкретні класи винятків та помилки, що їх викликають.

У дійсності, хоча занадто часто пакет com.acmeлише викидає AcmeExceptionне певні підкласи. Тоді абоненти повинні обробляти, оголошувати або повторно AcmeExceptionsподавати сигнал , але все одно не можна бути впевненим, AcmeFileNotFoundErrorсталося це чи є AcmePermissionDeniedError.

Отож, якщо вас цікавить лише те AcmeFileNotFoundError, що вирішити питання, потрібно надіслати запит на функцію програмістам ACME і сказати їм реалізувати, оголосити та документувати цей підклас AcmeException.

Так навіщо турбуватися?

Отже, навіть із перевіреними винятками, компілятор не може змусити програмістів кидати корисні винятки. Це все ще лише питання якості API.

Як результат, мови без перевірених винятків зазвичай не гіршають. Програмісти можуть спокуситись кидати неспецифічні екземпляри загального Errorкласу, а не AcmeException, але якщо вони взагалі піклуються про їх якість API, вони навчиться вводити результат AcmeFileNotFoundError.

Загалом специфікація та документація винятків не сильно відрізняється від специфікації та документації, скажімо, звичайних методів. Це теж питання дизайну API, і якщо програміст забув реалізувати чи експортувати корисну функцію, API потрібно вдосконалити, щоб ви могли з нею працювати з користю.

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

Варто також зазначити, що Java VM не має перевірених винятків - лише компілятор Java перевіряє їх, а файли класів із зміненими деклараціями виключень сумісні під час виконання. Захищеність Java VM не покращується перевіреними винятками, лише стиль кодування.


4
Ваш аргумент аргументує проти себе. Якщо "іноді вам потрібно вилучити виняток", а якість API часто погана, без перевірених винятків ви не знатимете, чи дизайнер знехтував документувати, що певний метод кидає виняток, який потрібно знайти. З'єднайте це, AcmeExceptionа не кидаючи AcmeFileNotFoundErrorудачі, а потім удачі з'ясуйте, що ви зробили не так і де вам потрібно це зловити. Перевірені винятки забезпечують програмістам захист від поганого дизайну API.
Єва

1
Дизайн бібліотеки Java допустив серйозні помилки. "Перевірені винятки" були передбачуваними та відновлюваними непередбачуваними ситуаціями - такими як файл не знайдено, неможливість підключення. Вони ніколи не були призначені або пристосовані для системних збоїв низького рівня. Було б чудово змусити відкрити файл, який потрібно перевірити, але немає розумної спроби або відновлення, якщо не вдалося написати один байт / виконати SQL-запит і т.д. "рівень, який перевіряв винятки, робить безглуздо складним. literatejava.com/exceptions/…
Thomas W

17

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

Поки що мені було легше працювати з базою коду за допомогою перевірених винятків. Коли я використовую чужий API, приємно, що я точно бачу, які умови помилок я можу очікувати, коли я зателефоную до коду та оброблюю їх належним чином, або ввівши реєстрацію, відображаючи чи ігноруючи (Так, є дійсні випадки ігнорування винятки, такі як реалізація ClassLoader). Це дає коду, про який я пишу, можливість відновитись. Усі винятки з виконання я розповсюджую до тих пір, поки вони не кешуються та обробляються деяким загальним кодом обробки помилок. Коли я знаходжу перевірене виняток, яке я не хочу дуже обробляти на певному рівні, або вважаю логічну помилку програмування, тоді я загортаю його в RuntimeException і дозволю йому пузиритися. Ніколи, ніколи не ковтайте виняток без поважних причин (а вагомі причини для цього досить мізерні)

Коли я працюю з кодовою базою даних, яка не має перевірених винятків, мені стає важче дізнатися заздалегідь, що я можу очікувати при виклику функції, яка може зламати деякі речі жахливо.

Це, звичайно, питання переваги та майстерності розробника. Обидва способи програмування та керування помилками можуть бути однаково ефективними (або неефективними), тому я б не сказав, що існує Єдиний Шлях.

Загалом, мені легше працювати з перевіреними винятками, особливо у великих проектах з великою кількістю розробників.


6
Я роблю. Для мене вони є важливою частиною договору. Не потребуючи деталізації в документації API, я можу швидко знати найімовірніші сценарії помилок.
Уейн Хартман

2
Погодьтеся. Я відчув необхідність перевірених винятків у .Net одного разу, коли я намагався здійснювати мережеві дзвінки. Знаючи, що мережева гикавка може статися в будь-який момент, мені довелося ознайомитися з усією документацією API, щоб дізнатися, який виняток, що мені потрібно було зловити спеціально для цього сценарію. Якби C # перевіряв винятки, я б це відразу знав. Інші розробники C #, ймовірно, просто дозволять програмі вийти з ладу через просту мережну помилку.
AxiomaticNexus

13

Категорії винятків

Говорячи про винятки, я завжди посилаюсь на статтю блогу Еріка Ліпперта щодо векселів про винятки . Він ставить винятки в ці категорії:

  • Фатальний - Ці винятки не є вашою виною : ви не можете запобігти тоді і не можете розумно поводитися з ними. Наприклад, OutOfMemoryErrorабо ThreadAbortException.
  • Без винятку - ці винятки винні : ви повинні їх запобігти, і вони представляють помилки у вашому коді. Наприклад ArrayIndexOutOfBoundsException, NullPointerExceptionабо будь-який IllegalArgumentException.
  • Вексинг - ці винятки не є винятковими , не винна, ви не можете їх запобігти, але вам доведеться з ними боротися. Вони часто є результатом невдалого рішення дизайну, такі , як кидання NumberFormatExceptionз Integer.parseIntзамість того , забезпечуючиInteger.tryParseInt метод , який повертає логічне значення FALSE на синтаксичного аналізу відмови.
  • Екзогенні. Ці винятки, як правило, є винятковими , але не з вашої вини, ви не можете (розумно) запобігти їх, але ви повинні з ними боротися . Наприклад, FileNotFoundException.

Користувач API:

  • не повинні поводитися зі смертельними наслідками або з головою винятками винятків.
  • повинні боротися з неприємностями винятки, але вони не повинні зустрічатися в ідеальному API.
  • повинні поводитися з екзогенними винятками.

Перевірили винятки

Те, що користувач API повинен обробляти певний виняток, є частиною договору методу між абонентом та абонентом. У контракті, серед іншого, визначено кількість та типи аргументів, які очікує викликає, тип повернення, який може очікувати абонент, та винятки, з якими очікується обробляти абонента .

Оскільки в API не мають існувати виразні винятки, лише ці екзогенні винятки повинні бути перевірені винятками, щоб вони були частиною контракту методу. Порівняно мало винятків є екзогенними , тому будь-який API повинен мати порівняно мало перевірених винятків.

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

Проблеми з API

Але будь-який API, який перевірив роздратування та фатальні винятки (наприклад, JCL), поставить непотрібне навантаження на користувачів API. Такі винятки потрібно обробляти, але або виняток настільки поширений, що він не повинен був бути винятком в першу чергу, або нічого не можна зробити при поводженні з ним. І це змушує розробників Java ненавидіти перевірені винятки.

Крім того, багато API не мають належної ієрархії класів винятків, внаслідок чого всі види неекзогенних причин виключення представляються одним перевіреним класом винятків (наприклад, IOException ). І це також змушує розробників Java ненавидіти перевірені винятки.

Висновок

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

Тому не ненавидьте Java та її перевірені винятки. Натомість ненавидьте API, які зловживають перевіреними винятками.


І неправильно використовувати їх, не маючи ієрархії.
TofuBeer

1
FileNotFound та встановлення з'єднання JDBC / мережа - це надзвичайні ситуації і правильно перевіряти винятки, оскільки вони передбачувані та (можливо) відновлювані. Більшість інших IOExceptions, SQLExceptions, RemoteException тощо є непередбачуваними & непоправними помилками , і повинні були бути винятками під час виконання. Через помилковий дизайн бібліотеки Java, ми всі зіткнулися з цією помилкою, і зараз в основному використовуємо Spring & Hibernate (які отримали право на їх розробку).
Thomas W

Зазвичай ви повинні поводитися з винятковими головами винятками, хоча ви, можливо, не хочете називати це "поводженням". Наприклад, на веб-сервері я реєструю їх і показую 500 користувачеві. Оскільки виняток несподіваний, перед виправленням помилки є все, що я можу зробити.
maaartinus

6

Гаразд ... Перевірені винятки не є ідеальними та мають деякі застереження, але вони служать цілі. Під час створення API існують конкретні випадки відмов, які є контрактом цього API. Якщо в контексті сильно статично типізованої мови, такої як Java, якщо не використовується перевірені винятки, тоді необхідно покластись на спеціальну документацію та конвенцію, щоб передати можливість помилки. Це виключає всю користь, що компілятор може принести помилку в обробці, і ви повністю залишені на добру волю програмістів.

Отже, вилучаєте перевірений виняток, як це було зроблено в C #, як тоді можна програмно та структурно передати можливість помилки? Як повідомити клієнтський код про те, що такі та подібні помилки можуть траплятися і з якими потрібно вирішуватись?

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

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

Багато хто стверджував, що неможливість завантаження файлу майже завжди була кінцем світу для цього процесу, і він повинен загинути жахливою та болісною смертю. Так що так ... звичайно ... добре ... ви будуєте API для чогось, і він завантажує файл в якийсь момент ... Я як користувач згаданого API може відповісти лише ... "Хто, чортве, ти вирішиш, коли мій програма повинна вийти з ладу! " Звичайно, враховуючи вибір, коли винятки збиваються і не залишають слідів або EletroFlabbingChunkFluxManifoldChuggingException зі слідом стека глибше, ніж траншея Marianna, я би взяв останню без жодного струму вагань, але чи це означає, що це бажаний спосіб боротьби з винятком ? Чи не можемо ми опинитися десь посередині, де виняток перероблявся б і перетворювався щоразу, коли він переходив на новий рівень абстракції, щоб насправді щось означало?

Нарешті, більшість аргументів, які я бачу, - це "я не хочу мати справу з винятками, багато людей не хочуть мати справу з винятками. Перевірені винятки змушують мене мати справу з ними, таким чином я ненавиджу перевірений виняток" Для усунення такого механізму взагалі та віднести це до прірви гото пекло просто нерозумно і йому не вистачає бажання та бачення.

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


2
Перевірені винятки були б корисні, якби було заявленим способом сказати, що жоден із викликів методу всередині блоку не передбачає викидання деяких (або будь-яких) перевірених винятків, і будь-які такі винятки повинні автоматично перетворюватися та повторно скасовуватися. Вони можуть бути ще кориснішими, якби дзвінки до методів, які були оголошені викидними перевіреними винятками, торгувались швидкістю / поверненнями викликів для швидкості обробки винятків (щоб очікувані винятки могли оброблятися майже так само швидко, як звичайний потік програми). Наразі жодна ситуація не застосовується.
supercat

6

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

Перевірені винятки в Java не вирішують останньої проблеми; C # і VB.NET викидають дитину разом з водою.

Приємний підхід, який займає середину дороги, описаний у цьому документі OOPSLA 2005 (або відповідному технічному звіті .)

Коротше кажучи, це дозволяє сказати:, method g(x) throws like f(x)що означає, що g кидає всі винятки f кидає. Voila, перевірив винятки без проблеми каскадних змін.

Хоча це науковий документ, я б закликав вас прочитати (його частини), оскільки це добре допомагає пояснити, які переваги та недоліки перевірених винятків є.


5

Це не аргумент проти чистої концепції перевірених винятків, але ієрархія класів, яку Java використовує для них, є фрік-шоу. Ми завжди називаємо речі просто "винятками" - що правильно, оскільки мовна специфікація їх також називає - але як виняток названий та представлений у системі типів?

Першим класом Exceptionуявляє? Ну ні, тому що Exceptions є винятками, і також винятки є Exceptions, за винятком тих винятків, які не є Exceptions, тому що інші винятки - це фактично Errors, які є іншим винятком, свого роду надзвичайним винятком, який ніколи не повинен відбуватися, крім коли це відбувається, і чого ви ніколи не повинні ловити, за винятком випадків, коли це потрібно. За винятком того, що це не все, тому що ви також можете визначити інші винятки, які не є ні Exceptions ні Errors, а лише Throwableвинятками.

Які з них є "перевіреними" винятками? Throwables - це перевірені винятки, за винятком випадків, коли вони також є Errors, які є неперевіреними винятками, і тоді є Exceptions, які також є Throwables і є основним типом перевірених винятків, за винятком того, що є також один виняток із цього, тобто якщо вони також RuntimeExceptions, тому що це інший вид неперевіреного винятку.

Для чого RuntimeExceptionс? Ну, як випливає з назви, вони є винятками, як і всі Exception, і вони трапляються під час виконання, як насправді всі винятки, за винятком того, що RuntimeExceptions є винятковими порівняно з іншими часом виконання Exception, оскільки вони не повинні відбуватися, за винятком коли ви робите деяку нерозумну помилку, хоча RuntimeExceptions ніколи не буває Error, тож вони є предметами, які є винятково помилковими, але які насправді не є Error. За винятком того RuntimeErrorException, що справді є RuntimeExceptionдля Errorс. Але чи не повинні всі винятки представляти помилкові обставини? Так, усі вони. За ThreadDeathвинятком виключно незвичного винятку, оскільки документація пояснює, що це "нормальне явище" і що це "Error

У будь-якому випадку, оскільки ми ділимо всі винятки в середині на Errors (які є винятковими винятками у виконанні, настільки невірно встановлені) та Exceptions (які мають менш виняткові помилки виконання, тому перевіряються, за винятком випадків, коли їх немає), тепер нам потрібні два різні види кожного з кількох винятків. Так що нам потрібно , IllegalAccessErrorі IllegalAccessException, і , InstantiationErrorі InstantiationException, і , NoSuchFieldErrorі NoSuchFieldException, і , NoSuchMethodErrorі NoSuchMethodException, і ZipErrorі ZipException.

За винятком того, що навіть коли перевіряється виняток, завжди є (досить прості) способи обдурити компілятор і кинути його, не перевіряючи його. Якщо ви зробите це, ви можете отримати UndeclaredThrowableException, за винятком інших випадків, коли воно може бути викинуто як " UnexpectedExceptionабо" UnknownException(як це не пов'язано з UnknownError"серйозними винятками"), або ExecutionException, або InvocationTargetException, і або ExceptionInInitializerError.

О, і ми не повинні забувати дивну нову Java 8 UncheckedIOException, яка є RuntimeExceptionвинятком, який дозволяє вам викинути концепцію перевірки виключень у вікно, загортаючи перевірені IOExceptionвинятки, спричинені помилками вводу / виводу (які не викликають IOErrorвинятку, хоча це існує теж), які надзвичайно важко обробляти, і тому їх потрібно не перевіряти.

Дякую Java!


2
Наскільки я можу сказати, ця відповідь лише говорить про "винятки Яви - це безлад" у повному іронії, мабуть, смішному. Що, схоже, не робити, це пояснити, чому програмісти прагнуть уникати спроб зрозуміти, як ці речі повинні працювати. Крім того, у реальних випадках (принаймні тих, з ким я мав шанс зіткнутися), якщо програмісти навмисно не намагаються ускладнити своє життя, винятки ніде не є такими складними, як ви описали.
CptBartender

5

Проблема

Найгірша проблема, з якою я бачу механізм обробки винятків, полягає в тому, що він вводить дублювання коду у великих масштабах ! Будемо чесними: у більшості проектів у 95% часу все, що розробникам дійсно потрібно робити за винятком, - це якось передавати це користувачеві (а в деяких випадках і команді розробників, наприклад, надсилаючи електронну пошту -mail із слідом стека). Тому зазвичай один і той же рядок / блок коду використовується у кожному місці, де обробляється виняток.

Припустимо, що ми робимо простий журнал у кожному блоці вилову для певного типу перевірених винятків:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

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

Зачекайте мить ... чи дійсно ми будемо редагувати всі ці кілька сотень локацій у коді ?! Ви розумієте мою думку :-).

Рішення

Що ми зробили для вирішення цього питання, це ввести концепцію обробників виключень (до яких я далі буду називати ЕГ), щоб централізувати обробку виключень. Кожному класу, якому потрібно обробляти винятки, екземпляр обробника винятків вводиться нашою рамкою введення залежності . Тож типова схема обробки винятків зараз виглядає приблизно так:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

Тепер, щоб налаштувати обробку винятків, нам потрібно лише змінити код в одному місці (EH-код).

Звичайно, для складніших випадків ми можемо реалізувати декілька підкласів EHs та використовувати функції, які надає нам наша програма DI. Змінюючи нашу конфігурацію DI, ми можемо легко переключити реалізацію EH в усьому світі або надати конкретні реалізації EH класам із особливими потребами обробки винятків (наприклад, за допомогою анотації Guice @Named).

Таким чином ми можемо без особливих зусиль диференціювати поведінку обробки винятків у розробці та випуску версії програми (наприклад, розробка - реєстрація помилки та зупинення програми, додавання - реєстрація помилки з більш детальною інформацією та дозволення додатку продовжувати її виконання).

Останнє одне

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


6
Винайдені винятки, щоб зробити з ними щось корисне. Запис їх у файл журналу чи візуалізація гарного вікна не корисний, оскільки вихідна проблема цим не вирішується. Робити щось корисне вимагає спробувати іншу стратегію рішення. Приклади: Якщо я не в змозі отримати свої дані з AI сервера, спробуйте його на сервері B. Або якщо алгоритм A виробляє переповнення купи, я спробую алгоритм B, який набагато повільніше, але може досягти успіху.
припинення

2
@ceving Так, це все добре і правдиво в теорії. Але тепер повернемося до практики слова. Скажіть, будь ласка, справді чесно, як часто ви це робите в своєму реальному проекті? Яка частина catchблоків у цьому реальному проекті робить щось дійсно «корисне» за винятком osiminsin? 10% було б добре. Звичайні проблеми, які генерують винятки, схожі на спробу зчитувати конфігурацію з файлу, який не існує, OutOfMemoryErrors, NullPointerExceptions, помилки цілісності обмежень бази даних тощо, і т. Д. Ви справді намагаєтесь витончено відновитись із усіх? Я вам не вірю :). Часто просто неможливо відновитись.
Пьотр Собчик

3
@PiotrSobczyk: Якщо програма вживає певних дій у результаті запиту на користувач, а операція не вдається певним чином, що нічого не пошкодило в системному стані, сповіщення користувача про те, що операцію неможливо було завершити, є абсолютно корисним спосіб вирішення ситуації. Найбільший недолік винятків у C # і .net полягає в тому, що немає послідовного способу з'ясувати, чи може щось у системному стані пошкодитися.
supercat

4
Правильно, @PiotrSobczyk. Найчастіше єдиною правильною дією у відповідь на виняток є відкат транзакції та повернення відповіді на помилку. Ідеї ​​"вирішення винятку" означають знання та повноваження, яких у нас немає (і не повинні), і порушують інкапсуляцію. Якщо наша програма не є БД, ми не повинні намагатися виправити БД. Помилка чистоти та уникнення написання помилкових даних, зберігання, є досить корисним .
Thomas W

@PiotrSobczyk Вчора я мав справу з винятком "не міг прочитати об'єкт" (який відбуватиметься лише тому, що база даних була оновлена ​​до програмного забезпечення - що ніколи не повинно відбуватися, але це можливо через помилку людини), не вдавшись до історична версія бази даних, гарантовано вказує на стару версію об'єкта.
однойменний

4

Андер розповідає про підводні камені перевірених винятків та про те, чому він вийшов із C # в епізоді 97 радіо Software Engineering.


4

Моя реєстрація на c2.com досі залишається незмінною у порівнянні з початковою формою: CheckedExceptionsAreIncompatibleWithVisitorPattern

Підсумовуючи:

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

Основним припущенням CheckedExceptions є те, що всі оголошені винятки можуть бути викинуті з будь-якої точки, яка викликає метод із цією декларацією. VisitorPattern виявляє це припущення як несправне.

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

Що стосується основної проблеми:

Моя загальна ідея - обробник верхнього рівня повинен інтерпретувати винятки та відображати відповідне повідомлення про помилку. Я майже завжди бачу винятки з IO, винятки з комунікації (чомусь розрізняють API), або помилки, що завдають завдань (програмні помилки або серйозна проблема на резервному сервері), тому це не повинно бути занадто важким, якщо ми дозволимо відстежувати стек для серйозних проблема з сервером.


1
У вас повинен бути щось на зразок DAGNodeException в інтерфейсі, потім вловити IOException і перетворити його в DAGNodeException: публічний недійсний виклик (аргумент DAGNode) кидає DAGNodeException;
TofuBeer

2
@TofuBeer, це саме моя думка. Я вважаю, що постійне обгортання та розгортання винятків гірше, ніж видалення перевірених винятків.
Джошуа

2
Ну, ми повністю не погоджуємося тоді ... але ваша стаття все ще не відповідає на реальне основне питання про те, як ви не можете додаток відображати стек стека користувачеві, коли буде викинуто виняток із виконання.
TofuBeer

1
@TofuBeer - коли це не вдається, повідомте користувачеві, що це не вдалося, це правильно! Яка ваша альтернатива, окрім того, що «виправити» помилку з «null» або неповними / невірними даними? Зробити вигляд, що це вдалося, це брехня, яка просто погіршує ситуацію. Маючи 25-річний досвід роботи в системах з високою надійністю, логіку повторного використання слід використовувати лише обережно та, де це доречно. Я також очікував, що відвідувач може знову вийти з ладу, незалежно від того, скільки разів ви повторите його. Якщо ви не літаєте на літаку, перехід на другу версію того ж алгоритму недоцільний і неправдоподібний (і в будь-якому випадку може вийти з ладу).
Thomas W

4

Щоб спробувати вирішити лише питання, що не відповідає, виконайте такі дії:

Якщо ви кидаєте підкласи RuntimeException замість підкласів винятку, то як ви знаєте, що ви повинні зловити?

Питання містить сумнівні міркування ІМХО. Тільки тому, що API говорить вам, що він кидає, не означає, що ви з цим рішитесь однаково у всіх випадках. Інакше кажучи, винятки, які вам потрібно виловлювати, залежать від контексту, в якому ви використовуєте компонент, який викидає виняток.

Наприклад:

Якщо я пишу тестер з’єднань для бази даних або щось для перевірки дійсності користувача, що ввійшов у XPath, я, ймовірно, хотів би впіймати та повідомити про всі перевірені та неперевірені винятки, які викинуті операцією.

Якщо, однак, я пишу процесорний процесор, я, швидше за все, ставлюся до XPathException (перевіряється) так само, як і NPE: я б дозволю йому працювати до вершини робочої нитки, пропустити решту цієї партії, журнал питання (або надіслати його у відділ підтримки для діагностики) та залишити відгук для користувача, щоб зв’язатися із службою підтримки.


2
Саме так. Легкий і простий, таким чином має бути обробка виключень. Як каже Дейв, правильне поводження з винятками зазвичай робиться на високому рівні . "Кинь рано, лови пізно" - це принцип. Перевірені винятки ускладнюють це.
Thomas W

4

Ця стаття - найкраща частина тексту щодо обробки винятків на Java, яку я коли-небудь читав.

Він віддає перевагу неперевіреним перевіреним виняткам, але цей вибір пояснюється дуже ретельно і ґрунтується на сильних аргументах.

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

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

Автор "відповідей":

Абсолютно невірно припускати, що всі винятки з виконання не повинні вловлюватися та дозволяти їм розповсюджуватися до самої "вершини" програми. (...) Для кожної виняткової умови, яку потрібно вирішувати чітко - відповідно до системних / бізнес-вимог - програмісти повинні вирішити, куди її вчинити і що робити, коли умова буде вилучена. Це потрібно робити строго відповідно до реальних потреб програми, а не на основі попередження компілятора. Усі інші помилки повинні бути дозволені вільно розповсюджуватися на найвищий обробник, де вони будуть зареєстровані, і будуть вжиті витончені (можливо, припинення) дії.

І головна думка чи стаття:

Що стосується керування помилками в програмному забезпеченні, єдине безпечне і правильне припущення, яке коли-небудь може бути зроблене, - це те, що може статися збій у абсолютно будь-якій підпрограмі або модулі, що існує!

Тож якщо " ніхто не знав, що навіть ця помилка можлива ", з цим проектом щось не так. Такий виняток повинен обробляти щонайменше самим загальним обробником винятків (наприклад, тим, що обробляє всі винятки, не опрацьовані більш конкретними обробниками), як пропонує автор.

Настільки сумно, що не так багато паеплів, здається, відкривають цю чудову статтю :-(. Я рекомендую від усієї душі всім, хто вагається, який підхід краще взяти трохи часу та прочитати.


4

Перевірені винятки були в їх первісному вигляді спробою впоратися з надзвичайними ситуаціями, а не збитками. Похвальна мета полягала в тому, щоб виділити конкретні передбачувані моменти (не вдається підключитися, файл не знайдено тощо) та переконатися, що розробники їх обробляють.

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

Збої, як правило, можливі в коді, і контейнери EJB, web & Swing / AWT вже задовольняють це, забезпечуючи самий зовнішній обробник винятків з "невдалим запитом". Найбільш основна правильна стратегія - це відкат транзакції та повернення помилки.

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

Найбільшим аргументом проти "перевірених" винятків є те, що більшість винятків неможливо виправити. Простий факт - ми не володіємо кодом / підсистемою, яка зламалася. Ми не можемо побачити реалізацію, ми не несемо за неї відповідальність і не можемо її виправити.

Якщо наша програма не є БД .. ми не повинні намагатися виправити БД. Це порушило б принцип інкапсуляції .

Особливо проблемними були області JDBC (SQLException) та RMI для EJB (RemoteException). Замість того, щоб визначати виправні надзвичайні ситуації згідно з оригінальною концепцією «перевіреного винятку», ці вимушені проблеми всебічної системної надійності, фактично не виправні, підлягають широкому оголошенню.

Іншим серйозним недоліком дизайну Java було те, що обробку винятків слід правильно розміщувати на найвищому рівні "бізнес" чи "запит". Принцип тут - "кидай рано, пізно піймай". Перевірені винятки мало, але перешкоджають цьому.

У нас у Java очевидна проблема: вимагати тисяч блоків без спроб уловлення, причому значна частина (40% +) неправильно кодується. Майже жоден із них не реалізує справжнього поводження чи надійності, але накладає великі накладні кодування.

Нарешті, "перевірені винятки" в значній мірі несумісні з функціональним програмуванням FP.

Їх наполягання на «негайному обробці» суперечить як найкращій практиці «вилучення пізнього» щодо обробки винятків, так і будь-якій структурі ПП, яка абстрагує петлі / або потоки управління.

Багато людей говорять про "поводження" з перевіреними винятками, але розмовляють через капелюхи. Продовження після відмови з нульовими, неповними або некоректними даними зробити вигляд про успіх - це нічого не обробляти. Це несправедливість з технічної / надійної роботи найнижчої форми.

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

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

Нарешті, набагато кращим є збій, ніж запуск з невірними даними. Продовження або спричинить вторинні помилки, віддалені від початкової причини і важче налагодити; або в результаті призведе до помилкових даних. Люди звільняються за це.

Дивіться:
- http://literatejava.com/exceptions/ checked-exceptions-javas-biggest-mistake/


1
Моя думка полягає в тому, щоб як правило, провалитись як загальна стратегія. Неперевірені винятки допомагають, оскільки вони не змушують блокувати блоки захоплення. Ловіння та реєстрація помилок може бути залишена декількома найвіддаленішими обробниками, а не неправильно кодується тисячі разів у всій кодовій базі (що насправді і приховує помилки) . Для довільних збоїв абсолютно перевірені винятки є абсолютно правильними. Надзвичайні наслідки - передбачувані результати, такі як недостатня кількість коштів - є єдиними винятками, які правомірно варто перевірити.
Thomas W

1
Моя відповідь вже вище стосується цього. Перш за все, 1) Зовнішній обробник несправностей повинен уловлювати все. Крім того, лише для конкретних визначених сайтів; 2) Конкретні очікувані непередбачувані випадки можуть бути захоплені та оброблені - на безпосередній ділянці вони будуть кинуті. Це означає, що файл не знайдено, недостатньо коштів тощо в тому місці, з якого їх можна було відновити - не вище. Принцип інкапсуляції означає, що зовнішні шари не можуть / не повинні нести відповідальність за розуміння / відновлення після відмов глибоко всередині. По-третє, 3) Все інше слід викинути назовні - якщо це можливо, не перевірити.
Thomas W

1
Зовнішній обробник перехоплює виняток, записує його в систему і повертає відповідь "невдало" або показує діалогове вікно про помилку. Дуже просто, зовсім не важко визначити. Справа в тому, що кожен виняток, який не одразу та локально підлягає відновленню, - це невідшкодований збій через принцип інкапсуляції. Якщо код, про який потрібно знати, не може відновити його, тоді загальний запит не працює чітко та належним чином. Це правильний спосіб зробити це правильно.
Thomas W

1
Неправильно. Завдання зовнішнього обробника полягає в тому, щоб чисто і не вводити помилки на межі "запиту". Пошкоджений запит не працює належним чином, повідомляється про виняток, потік може продовжувати надавати наступний запит. Такий самий зовнішній обробник - це стандартна функція в контейнерах Tomcat, AWT, Spring, EJB та "головному" потоці Java.
Thomas W

1
Чому небезпечно повідомляти про "справжні помилки" на межі запиту чи на зовнішній обробник ??? Я часто працюю в інтеграції та надійності систем, де правильна інженерія надійності насправді важлива, і для цього використовую підходи "неперевіреного виключення". Я не дуже впевнений у тому, що ти насправді обговорюєш - здається, що ти можеш хотіти фактично провести 3 місяці неперевіреним способом виключення, відчути це, а потім, можливо, ми можемо обговорити далі. Дякую.
Thomas W

4

Як люди вже заявили, перевірені винятки не існують у байт-коді Java. Вони просто механізм компіляції, на відміну від інших перевірок синтаксису. Я бачу , що перевіряються винятку багато , як я бачу , що компілятор скаржиться на надмірний умовний: if(true) { a; } b;. Це корисно, але я, можливо, зробив це спеціально, тому дозвольте мені ігнорувати ваші застереження.

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

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


Правильно, я просто хотів підкреслити різницю між недосяжним кодом (який генерує помилку) та умовними умовами з передбачуваним результатом. Пізніше я вилучу цей коментар.
Холгер

3

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

Інша проблема перевірених винятків полягає в тому, що вони, як правило, неправильно використовуються. Прекрасним прикладом цього є в java.sql.Connection«S close()методом. Він може кинути "a" SQLException, навіть якщо ви вже прямо заявили, що ви закінчили з "Підключенням". Яка інформація може закрити (), можливо, передати, що ви хвилюєтесь?

Зазвичай, коли я закриваю () з'єднання *, воно виглядає приблизно так:

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

Крім того, не запускайте мене з різних методів розбору та NumberFormatException .... TryParse .NET, який не кидає винятків, набагато простіше у використанні, боляче повертатися до Java (ми використовуємо і Java, і C # де я працюю).

*Як додатковий коментар, PooledConnection's Connection.close () навіть не закриває з'єднання, але вам все одно доведеться спіймати SQLException, оскільки він є перевіреним винятком.


2
Право, будь-який з драйверів може ... питання швидше "чому програміст повинен дбати?" тому що він все одно отримав доступ до бази даних. Документи навіть попереджають вас, що ви завжди повинні здійснити () або відкатати () поточну транзакцію перед тим, як дзвонити close ().
Powerlord

Багато хто думає, що закриття файлу не може кинути виняток ... stackoverflow.com/questions/588546/… Ви на 100% впевнені, що не існує випадків, які б мали значення?
TofuBeer

Я на 100% впевнений, що не існує випадків, які б мали значення, і що абонент не став би пробувати / ловити.
Мартін

1
Прекрасний приклад із замикаючими з'єднаннями, Мартіне! Я можу лише перефразовувати вас: Якщо ми просто прямо сказали, що ми закінчили зв’язок, то чому слід турбувати те, що відбувається, коли ми його закриваємо. Існує більше таких випадків, що програмісту не дуже важливо, якщо трапляються винятки, і він абсолютно прав.
Пьотр Собчик

1
@PiotrSobczyk: Деякі драйвери SQL будуть прориватися, якщо один закриє з'єднання після запуску транзакції, але не підтверджує його і не повертає його назад. ІМХО, травлення в кращу їжу краще, ніж мовчки ігнорувати проблему, принаймні у тих випадках, коли випадання не призведе до втрати інших винятків.
supercat

3

Програміст повинен знати всі винятки, які може викинути метод, щоб правильно його використовувати. Тож, побиття його по голові за деякими винятками не обов'язково допомагає недбайливому програмісту уникнути помилок.

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

Статичний аналіз може бути приємним, але справді надійний статичний аналіз часто невпинно вимагає від програміста суворої роботи. Існує розрахунок вартості та вигоди, і планку потрібно встановити високою для перевірки, яка призводить до помилки часу компіляції. Було б корисніше, якби IDE взяв на себе роль повідомлення про те, які винятки може бути викинутий (у тому числі, які неминучі). Хоча, можливо, це не було б таким надійним без декларацій вимушених винятків, більшість винятків все-таки буде задекларовано в документації, і надійність попередження IDE не є настільки важливою.


2

Я вважаю, що це відмінне питання і зовсім не аргументативне. Я думаю, що сторонні бібліотеки повинні (взагалі) кидати неперевірені винятки. Це означає, що ви можете ізолювати свої залежності від бібліотеки (тобто вам не доведеться ні заново кидати їх винятки, ні кидати Exception- зазвичай це погана практика). Шар весни DAO - відмінний приклад цього.

З іншого боку, виключення з ядра Java API має взагалі бути перевірено , якщо вони могли б коли - або звертатися. Візьміть FileNotFoundExceptionабо (мій улюблений) InterruptedException. Ці умови майже завжди повинні вирішуватися конкретно (тобто ваша реакція на реакцію не InterruptedExceptionє такою ж, як ваша реакція на IllegalArgumentException). Те, що перевіряються ваші винятки, змушує розробників замислюватися над тим, чи умова підходить чи ні. (Це говорив, я рідко бачив, як з ними InterruptedExceptionповодиться правильно!)

Ще одне - RuntimeExceptionце не завжди "там, де у розробника щось не так". Виняток незаконного аргументу викидається під час спроби створити enumвикористання, valueOfа enumцього імені немає. Це не обов'язково помилка розробника!


1
Так, це помилка розробника. Вони, очевидно, не використовували правильне ім’я, тому їм доведеться повернутися назад і виправити свій код.
AxiomaticNexus

@AxiomaticNexus Жоден розумний розробник не використовує enumімена членів, просто тому, що enumзамість цього вони використовують об'єкти. Тож неправильне ім'я може надходити лише ззовні, будь то імпортний файл чи інше. Одним із можливих способів боротьби з такими іменами є виклик MyEnum#valueOfта спіймання IAE. Інший спосіб - використовувати попередньо заповнені Map<String, MyEnum>, але це деталі реалізації.
maaartinus

@maaartinus Є випадки, коли імена членів enum використовуються без рядка, що надходить ззовні. Наприклад, коли ви хочете динамічно перебирати всі члени, щоб зробити щось із кожним. Крім того, незалежно від того, чи є рядок ззовні чи ні, не має значення. Розробник має всю необхідну інформацію, щоб знати, чи передавання рядка x "MyEnum # valueOf" призведе до помилки перед передачею. Передавання рядка x "MyEnum # valueOf" у будь-якому випадку, коли це призвело б до помилки, очевидно було б помилкою з боку розробника.
AxiomaticNexus

2

Ось один аргумент проти перевірених винятків (від joelonsoftware.com):

Аргументація полягає в тому, що я вважаю, що винятки є не кращими за "goto's", які вважаються шкідливими з 60-х років, оскільки вони створюють різкий перехід від однієї точки коду до іншої. Насправді вони значно гірші, ніж гото:

  • Вони невидимі у вихідному коді. Дивлячись на блок коду, включаючи функції, які можуть або не можуть викидати винятки, немає ніякого способу побачити, які винятки можуть бути кинуті та звідки. Це означає, що навіть ретельна перевірка коду не виявляє потенційних помилок.
  • Вони створюють занадто багато можливих точок виходу для функції. Щоб написати правильний код, вам дійсно потрібно продумати кожен можливий шлях коду через вашу функцію. Кожен раз, коли ви викликаєте функцію, яка може викликати виняток, і не зафіксувати її на місці, ви створюєте можливості для сюрпризних помилок, викликаних функціями, які різко припиняються, залишаючи дані в непослідовному стані або інші шляхи коду, які ви не зробили думати про.

3
+1 Хоча ви хочете узагальнити аргумент у своїй відповіді? Вони - як невидимі готи і ранні виходи для вашої процедури, розкидані по всій програмі.
MarkJ

13
Це більше аргумент проти Винятку взагалі.
Ionuț G. Stan

10
ви насправді читали статтю !! По-перше, він розповідає про винятки взагалі, по-друге, розділ "Вони невидимі у вихідному коді" стосується конкретно винятку НЕВЕРОВАНО. Це вся суть перевіреного винятку ... так що ви знаєте, який код кидає, куди куди
ньютопієць

2
@Eva Вони не однакові. За допомогою goto-оператора ви можете побачити gotoключове слово. За допомогою циклу ви можете побачити фіксуючу дужку breakабо continueключове слово або . Усі вони переходять до точки в поточному методі. Але ви не завжди можете бачити це throw, тому що часто це не в поточному методі, а в іншому методі, який він називає (можливо, опосередковано)
finnw

5
@finnw Функції самі по собі є формою переходу. Зазвичай ви не знаєте, які функції викликають функції, які ви викликаєте. Якби ви програмували без функцій, у вас не виникло б проблем із невидимими винятками. Що означає, що проблема спеціально не пов'язана з винятками і не є вагомим аргументом проти винятків взагалі. Можна сказати, що коди помилок швидші, ви можете сказати, що монади чистіші, але аргумент goto є дурним.
Єва

2

Добре доводить, що перевірені винятки не потрібні:

  1. Дуже багато фреймворку, який виконує певну роботу для Java. Як і Весна, яка переносить JDBC виняток на неперевірені винятки, кидаючи повідомлення в журнал
  2. Багато мов, які з’явилися після Java, навіть зверху на платформі java - вони не користуються ними
  3. Перевірили винятки, це добрий прогноз щодо того, як клієнт буде використовувати код, який видає виняток. Але розробник, який пише цей код, ніколи не дізнається про систему та бізнес, над яким працює клієнт коду. Як приклад методи Interfcace, які змушують кидати перевірені винятки. У системі є 100 реалізацій, 50 або навіть 90 реалізацій не викидають цього винятку, але клієнт все-таки повинен вловлювати це виняток, якщо він посилається на цей інтерфейс користувача. Ці 50 чи 90 реалізацій, як правило, обробляють ці винятки всередині себе, ставлячи виключення до журналу (і це хороша поведінка для них). Що нам з цим робити? Я краще мати деяку фонову логіку, яка б виконала всю цю роботу - надсилаючи повідомлення в журнал. І якщо я, як клієнт коду, відчую, що мені потрібно впоратися з винятком - я це зроблю.
  4. Ще один приклад, коли я працюю з I / O в Java, він змушує мене перевірити всі винятки, якщо файл не існує? що мені робити з цим? Якщо його не існує, система не перейшла б до наступного кроку. Клієнт цього методу не отримає очікуваного вмісту з цього файлу - він може обробляти виключення під час виконання програми, інакше я спершу повинен перевірити Checked Exception, поставити повідомлення в журнал, а потім викинути виняток з методу. Ні ... ні - я б краще це робив автоматично за допомогою RuntimeEception, це робить / вимикається автоматично. Немає сенсу обробляти це вручну - я був би радий, що побачив повідомлення про помилку в журналі (AOP може допомогти з цим .. щось, що виправляє Java). Якщо, врешті-решт, я вважаю, що система повинна показувати спливаюче повідомлення кінцевому користувачеві - я покажу це, не проблема.

Я був щасливий, якщо java надасть мені можливість вибору, що використовувати під час роботи з основними вкладками, як I / O. Like надає дві копії одних і тих же класів - одну обернуту RuntimeEception. Тоді ми можемо порівняти, що б люди використовували . Поки, хоча, багатьом людям краще піти на якусь рамку на вершині Java або іншу мову. Як Scala, JRuby все, що завгодно. Багато хто просто вважає, що СОН не мав рацію.


1
Замість того, щоб мати дві версії класів, слід чітко вказати жоден із викликів методів, зроблених блоком коду, не передбачає викидів винятків певних типів, і що будь-які такі винятки повинні бути перетворені за допомогою певних заданих засобів та повторно (за замовчуванням створіть нове RuntimeExceptionз відповідним внутрішнім винятком). Прикро, що більш лаконічним є зовнішній метод throwsвиключення з внутрішнього методу, ніж примусовий випадок винятків із внутрішнього методу, коли останній спосіб дії частіше буде правильним.
supercat

2

Одне важливе, про що ніхто не згадував, - це те, як воно заважає інтерфейсам та лямбда-виразам.

Скажімо, ви визначаєте a MyAppException extends Exception. Це виняток верхнього рівня, успадкований усіма винятками, поданими вашою заявкою. Кожен метод оголошує, throws MyAppExceptionщо трохи неприємно, але керовано. Обробник винятків записує виняток і якось сповіщає користувача.

Все виглядає нормально, поки ви не захочете реалізувати якийсь інтерфейс, який не є вашим. Очевидно, він не заявляє про намір кинути MyApException, тому компілятор не дозволяє викидати виняток звідти.

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


1

Ми бачили кілька посилань на головного архітектора C #.

Ось альтернативна точка зору від хлопця Java щодо того, коли використовувати перевірені винятки. Він визнає багато негативів, які інші згадували: Ефективні винятки


2
Проблема з перевіреними винятками в Java випливає з більш глибокої проблеми, яка полягає в тому, що надто багато інформації інкапсульовано в ТИП винятку, а не у властивості примірника. Було б корисно перевірити винятки, якщо "перевірка" була атрибутом сайтів кидок / лову, і якщо можна декларативно вказати, чи повинен перевірений виняток, який уникає блоку коду, залишатися перевіреним винятком, чи його бачити будь-яким блоком, що додає, як неперевірений виняток; Блоки лову повинні також визначати, що вони хочуть лише перевірених винятків.
supercat

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

0

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

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

Проект, над яким я працюю, займає багато бібліотек та інтегрує їх під один і той же API, API, який повністю базується на неперевірених винятках. Ці рамки надають API високого рівня, який на початку був переповнений перевіреними винятками та мав лише кілька неперевірених винятки (виняток ініціалізації, ConfigurationException тощо), і, треба сказати, був не дуже доброзичливим . Більшу частину часу вам доводилося ловити або перекидати винятки, з якими ви не знаєте, як впоратися, або вам навіть не байдуже (не плутати з вами слід ігнорувати винятки), особливо на стороні клієнта, де єдина Клацання може викинути 10 можливих (перевірених) винятків.

Поточна версія (третя версія) використовує лише неперевірені винятки, і вона має глобальний обробник винятків, який несе відповідальність за обробку будь-якого невдалого. API надає спосіб зареєструвати обробників винятків, який вирішить, чи виняток вважається помилкою (більшість випадків це так), що означає "ввійти та повідомити когось", або це може означати щось інше - наприклад, цей виняток, AbortException, що означає розірвати поточний потік виконання і не записувати жодної помилки, оскільки бажано цього не робити. Звичайно, для опрацювання всіх користувацьких потоків слід обробити метод run () методом спробу {...} catch (all).

публічний недійсний запуск () {

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

}

Це не обов'язково, якщо ви використовуєте WorkerService для планування завдань (Runnable, Callable, Worker), який обробляє все за вас.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.