Я думаю, що я читав те саме інтерв'ю Брюса Еккеля, яке ви робили, - і мене завжди клопочуть. Насправді аргумент висловив інтерв'юйований (якщо це справді посада, про яку ви говорите) Андерс Хейльсберг, геній 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
це - велика незручність, що призводить до поганого програмування, і цього слід уникати. Але як сказати різницю.
Я думаю, що це не точна наука, і я думаю, що це певною мірою лежить в основі і, можливо, виправдовує аргумент Хейльсберга. Але я не радий викидати дитину разом з водою для ванни, тому дозвольте мені витягнути тут деякі правила, щоб відрізняти хороші перевірені винятки від поганих:
Поза контролем клієнта або закрито та відкрито:
Перевірені винятки слід використовувати лише в тому випадку, коли випадок помилки виходить з-під контролю як API, так і клієнтського програміста. Це стосується того, наскільки відкрита чи закрита система. У обмеженому інтерфейсі, де клієнтський програміст має контроль, скажімо, над усіма кнопками, командами клавіатури тощо, які додають та видаляють рядки із подання таблиці (закрита система), це помилка програмування клієнта, якщо він намагається отримати дані з неіснуючий ряд. У файловій операційній системі, де будь-яка кількість користувачів / додатків може додавати та видаляти файли (відкрита система), можливо, файл, який запитує клієнт, був видалений без їх відома, тому від них слід очікувати, що він має справу з ним. .
Повсюдність:
Перевірені винятки не повинні використовуватися для виклику API, який часто здійснює клієнт. Я часто маю на увазі з багатьох місць у коді клієнта - не часто в часі. Таким чином, клієнтський код, як правило, не намагається багато відкривати один і той же файл, але мій перегляд таблиці отримує RowData
повсюдно за допомогою різних методів. Зокрема, я буду писати багато подібних кодів
if (model.getRowData().getCell(0).isEmpty())
і буде боляче кожен раз занурюватися в спробу / ловити.
Інформування користувача:
Перевірені винятки слід використовувати в тих випадках, коли ви можете уявити корисне повідомлення про помилку, яке представляється кінцевому користувачеві. Це "і що ти будеш робити, коли це станеться?" питання, яке я піднімав вище. Це стосується також пункту 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) */}