Чому б не використовувати винятки як регулярний потік контролю?


193

Щоб уникнути всіх стандартних відповідей, які я міг би мати на Google, я наведу приклад, на який ви всі можете напасти на волю.

C # і Java (і надто багато інших) мають багато типів поведінки "переповнення", яка мені зовсім не подобається (наприклад, type.MaxValue + type.SmallestValue == type.MinValueнаприклад int.MaxValue + 1 == int.MinValue:).

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

Перекритий Addметод:

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

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

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

ІМХО "Ніколи не використовуйте їх для регулярних програмних потоків" у всіх, здається, є не так добре, як сила цієї реакції може виправдати.

Або я помиляюся?

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

  1. Ясно
  2. Шануй контракт свого методу

Стріляти в мене.


2
+1 Я почуваюсь так само. Окрім продуктивності, єдиною вагомою причиною уникнути винятків для контролю потоку є те, коли код абонента буде набагато більш читабельним із значеннями повернення.

4
є: поверніть -1, якщо щось трапилося, поверніть -2, якщо щось інше і т. д. ... справді читабельніше, ніж винятки?
кендер

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

8
Я заперечую, що кидання виключення іноді може бути вашим єдиним варіантом. Я, наприклад, бізнес-компонент, який ініціалізує свій внутрішній стан у своєму конструкторі, запитуючи базу даних. Бувають випадки, коли в базі даних відсутні відповідні дані. Закидання винятку всередині конструктора - єдиний спосіб ефективно скасувати побудову об'єкта. Це чітко зазначено в контракті (Javadoc в моєму випадку) класу, тому я не маю жодної проблеми, що клієнтський код міг (і повинен) зловити цей виняток під час створення компонента і продовжувати звідти.
Стефан Хаберль

1
Оскільки ви сформулювали гіпотезу, наголос на тому, щоб ви також наводили підтверджуючі докази / причини. Для початку назвіть одну з причин, за якою ваш код перевершує значно коротший ifзаяву, що самодокументує. Це вам буде дуже важко. Іншими словами: ваша помилка є вашою, і висновки, які ви робите з цього, є помилковими.
Конрад Рудольф

Відповіді:


171

Ви коли-небудь намагалися налагодити програму, яка збільшує п'ять винятків за секунду в звичайному процесі роботи?

У мене є.

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

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

Моя думка: якщо ви використовуєте винятки для звичайних ситуацій, як ви знаходите незвичні (тобто виключення al) ситуації?

Звичайно, є й інші вагомі причини не використовувати занадто багато винятків, особливо це стосується продуктивності


13
Приклад: коли я налагоджую програму .net, я запускаю її з візуальної студії і прошу VS перерватися на всі винятки. Якщо ви покладаєтесь на винятки як очікувану поведінку, я більше не можу цього робити (оскільки це перерве 5 разів / сек), і знайти проблематичну частину коду набагато складніше.
Brann

15
+1, щоб вказати, що ви не хочете створювати стоги для винятку, в яких можна знайти справжню виняткову голку.
Грант Вагнер

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

11
@ Peeter: Налагодження налагодження без порушення винятків складно, і ловити всі винятки болісно, ​​якщо їх багато за дизайном. Я думаю, що дизайн, який робить налагодження важким, майже частково зламаний (іншими словами, дизайн має щось спільне з налагодженням, IMO)
Brann

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

158

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

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

Однак деякі мови (зокрема Python) використовують винятки як конструкції управління потоком. Наприклад, ітератори піднімають aStopIteration виняток, якщо немає додаткових елементів. Навіть стандартні мовні конструкції (такі як for) покладаються на це.


11
ей, винятки не дивують! І ти якось суперечить собі, коли кажеш "це погана ідея", а потім продовжуєш говорити "але це гарна ідея в python".
hasen

6
Я все ще не впевнений у собі: 1) Ефективність була окрім питання, безліч програм, які не бахт, могли би не менше турбуватися (наприклад, інтерфейс користувача) 2) Дивно: як я сказав, це просто вражаюча причина, що вона не використовується, але залишається питання: чому б не використовувати ідентифікатор в першу чергу? Але, оскільки це відповідь
Пітер

4
+1 Насправді я радий, що ти вказав на різницю між Python та C #. Я не думаю, що це суперечність. Python є набагато більш динамічним, і очікування використання винятків таким чином перекладається на мову. Це також є частиною культури EAFP Python. Я не знаю, який підхід є концептуально чистішим або більш послідовним, але мені подобається ідея написання коду, який робить те, що очікують від нього інші , що означає різні стилі на різних мовах.
Марк Е. Хааз

13
На відміну від goto, звичайно, винятків правильно взаємодіють зі стеком ваших викликів та з лексичним обстеженням, і не залишайте стек чи діапазони безладу.
Лукас Едер

4
Насправді більшість виробників VM очікують на винятки та поводжуються з ними ефективно. Як зазначає @LukasEder, винятки абсолютно не схожі на goto в тому, що вони структуровані.
Марцін

30

Моє правило:

  • Якщо ви можете зробити що-небудь, щоб відновити помилку, знайдіть винятки
  • Якщо помилка дуже поширена (наприклад, користувач намагався увійти за допомогою неправильного пароля), використовуйте returnvalues
  • Якщо ви не можете зробити що-небудь, щоб відновити помилку, залиште її неповторною (Або зачепіть її у своєму головному лові, щоб зробити напівграційне вимкнення програми)

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

Візьмемо цей приклад:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

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

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

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

Сподіваюся, я зрозумів ваше запитання правильно :)


4
re: Ваш другий приклад - чому б просто не поставити виклик BestMethodEver всередині блоку спробу після Build? Якщо Build () кидає виняток, він не буде виконаний, і компілятор задоволений.
Blorgbeard вийшов

2
Так, це, мабуть, і закінчиться, але розглянемо більш складний приклад, коли сам тип myInstance може викидати винятки. У вас з’явиться багато вкладених блоків спробу / лову :(
cwap

Ви повинні зробити переклад винятків (до типу винятку, відповідного рівню абстракції) у вашому блоці вилову. FYI: "Мульти-лов" повинен перейти на Java 7.
Jasonnerothin

FYI: У програмі C ++ ви можете вставити декілька виловів після спроби зловити різні винятки.
RobH

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

25

Перша реакція на багато відповідей:

ти пишеш для програмістів і принципу найменшого здивування

Звичайно! Але якщо просто не все зрозуміліше весь час.

Це не повинно дивувати, наприклад: поділ (1 / x) улов (поділByZero) зрозуміліший, ніж будь-який, якщо мені (у Конрада та інших). Той факт, що подібного програмування не очікується, є суто звичайним та дійсно все ще актуальним. Можливо, у моєму прикладі, якщо було б зрозуміліше.

Але DivisionByZero і FileNotFound з цього приводу зрозуміліші, ніж ifs.

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

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

винятки для звичайних ситуацій, як ви знаходите незвичні (тобто виняткові) ситуації?

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

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


1
1) Ви завжди перевіряєте xперед тим, як дзвонити 1/x? 2) Чи перетворюєте ви кожну операцію поділу в блок пробного лову, який потрібно ловити DivideByZeroException? 3) Яку логіку ви вкладаєте в блок ловлі, щоб відновитись DivideByZeroException?
Лайтман

1
За винятком DivisionByZero та FileNotFound - це погані приклади, оскільки вони є винятковими випадками, які слід розглядати як винятки.
0x6C38

Про файл, не знайдений у манері, немає нічого «надто виняткового», ніж люди, які тут «проти винятку». openConfigFile();може супроводжуватися спійманим FileNotFound з { createDefaultConfigFile(); setFirstAppRun(); }винятком FileNotFound, яке обробляється витончено; ніяких збоїв, давайте покращимо досвід кінцевого користувача, а не гірше. Ви можете сказати: "Але що, якщо це насправді не перший пробіг, і вони отримують це кожен раз?" Принаймні додаток запускається щоразу і не виходить з ладу при кожному запуску! У режимі 1 -10 "це жахливо": "перший запуск" кожен запуск = 3 або 4, крах кожного запуску = 10.
Loduwijk

Ваші приклади - винятки. Ні, ви не завжди перевіряєте xперед тим, як дзвонити 1/x, адже це зазвичай добре. Винятковий випадок - це випадок, коли це не добре. Тут ми не говоримо про руйнування землі, але, наприклад, для базового цілого числа, заданого випадковим чином x, лише 1 з 4294967296 не зможе зробити поділ. Це виняток, і винятки - хороший спосіб впоратися з цим. Однак ви можете використовувати винятки, щоб реалізувати еквівалент switchзаяви, але це було б досить нерозумно.
Thor84no

16

Перед винятками в C було setjmpі longjmpте, що можна було б використати для здійснення аналогічного розгортання кадру стека.

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

Винятки дещо більш загальні в тому, що ви можете використовувати їх і в одній рамці стека. Це викликає аналогії з тим, gotoщо я вважаю неправильним. Готос - це щільно зв'язана пара (і так є setjmpі longjmp). Винятки дотримуються слабко пов'язаної публікації / підписки, яка набагато чистіша! Тому використання їх у межах одного фрейма стека навряд чи те саме, що використання gotos.

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

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

Моє улюблене використання - це послідовність throw new Success()у довгому фрагменті коду, який намагається одне за іншим, поки не знайде те, що шукає. Кожна річ - кожна логіка - може мати впорядковане гніздування, тому breakвони також не є будь-якими видами тестів на стан. if-elseКартина ламка. Якщо я відредагую elseсинтаксис чи зіпсую його якось іншим способом, то з’являється волохата помилка.

Використовуючи throw new Success() лінеаризацію потоку коду. Я використовую локально визначені Successкласи - перевірені звичайно - так що, якщо я забуду його зловити, код не скомпілюється. І я не спіймаю інших методів Success.

Іноді мій код перевіряє одну річ за іншою і вдається лише тоді, коли все в порядку. У цьому випадку у мене є аналогічна лінеаризація з використанням throw new Failure().

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

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

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

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

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


3
Використовуючи цю схему, більшість моїх грубих функцій мають в кінці два маленькі перехоплення - один для Успіху і один для Невдачі, і саме тут функція завершує такі речі, як підготовка правильної відповіді сервлетів або підготовка значень повернення. Приємно мати єдине місце для завершення гри. Для returnкожної такої функції альтернатива матиме дві функції. Зовнішній для підготовки сервлет-відповіді чи інших подібних дій, і внутрішній для обчислення. PS: Професор англійської мови, напевно, запропонував би я використовувати "дивовижну", а не "дивну" в останньому пункті :-)
некроман

11

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

Одна з важливих для мене причин полягає в тому, що коли я читаю try-catchструктуру управління в програмному забезпеченні, яке я підтримую або налагоджую, я намагаюся з’ясувати, чому оригінальний кодер використовував обробку виключень замість if-elseструктури. І я сподіваюся, що знайду хорошу відповідь.

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


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

9

Як щодо продуктивності? Під час тестування навантаження на веб-додаток .NET, ми набрали 100 модельованих користувачів на веб-сервері, поки ми не виправили виняток, що часто виникає, і це число збільшилося до 500 користувачів.


8

Josh Bloch широко займається цією темою в Ефективній Java. Його пропозиції висвітлюються і мають стосуватися і .Net (крім деталей).

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

Наприклад, другий метод простіший у використанні, ніж перший:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

У будь-якому випадку вам потрібно перевірити, чи переконується, що абонент належним чином використовує ваш API. Але у другому випадку ви вимагаєте цього (неявно). Програмні винятки все-таки будуть викинуті, якщо користувач не прочитав javadoc, але:

  1. Вам не потрібно документувати це.
  2. Не потрібно тестувати його (залежно від того, наскільки агресивна ваша стратегія тестування одиниць).
  3. Вам не потрібно, щоб абонент обробляв три випадки використання.

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

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


7

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

Найкращий спосіб зменшити витрати на створення винятків - це замінити метод fillInStackTrace () таким чином:

public Throwable fillInStackTrace() { return this; }

Такий виняток не матиме заповнених стек-треків.


Тракція також вимагає, щоб абонент "знав про" (тобто мати залежність від) усіх Throwables у стеку. Це погана річ. Викиньте винятки, що відповідають рівню абстракції (ServiceExceptions в сервісах, DaoExceptions з методів Dao тощо). Просто перекладіть, якщо потрібно.
jasonnerothin

5

Я насправді не бачу, як ви керуєте потоком програми в цитованому вами коді. Ви ніколи не побачите іншого винятку, крім виключення ArgumentOutOfRange. (Отже, ваш другий пункт про вилучення ніколи не потраплятиме). Все, що ви робите, - це надзвичайно дорогий кидок, щоб імітувати вислів if.

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


5

Ось найкращі практики, які я описав у своїй публікації в блозі :

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

4

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


4

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

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


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

Тож ваша відповідь в основному "Це погано, тому що у Visual Studio є ця одна особливість ..."? Я займаюся програмуванням вже близько 20 років, і навіть не помітив, що був варіант "перерва на всі винятки". І все-таки "через цю одну особливість!" звучить як слабка причина. Просто простежте виняток до його джерела; сподіваємось, ви використовуєте мову, що робить це простіше - інакше проблема полягає в мовних особливостях, а не в загальному використанні самих винятків.
Loduwijk

3

Припустимо, у вас є метод, який виконує деякі обчислення. Є багато вхідних параметрів, які він повинен перевірити, щоб повернути число, що перевищує 0.

Використовуючи значення повернення для помилки перевірки сигналу, це просто: якщо метод повернув число менше 0, сталася помилка. Як сказати тоді, який параметр не перевірив?

Я пам’ятаю, що з моїх днів C багато функцій повертало такі помилки:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

тощо.

Це справді менш читабельно, ніж кидати та ловити виняток?


ось чому вони вигадали переліки :) Але магічні числа - це зовсім інша тема .. en.wikipedia.org/wiki/…
Ісак Саво

Чудовий приклад. Я збирався написати те саме. @IsakSavo: Енуми не корисні в цій ситуації, якщо очікується, що метод поверне якесь значення значення або об'єкт. Наприклад, getAccountBalance () повинен повертати об’єкт Money, а не об’єкт AccountBalanceResultEnum. Багато програм C мають подібний малюнок, коли одне значення дозорного (0 або null) являє собою помилку, і тоді вам потрібно викликати іншу функцію, щоб отримати окремий код помилки, щоб визначити, чому сталася помилка. (API MySQL C такий.)
Марк Е. Хааз

3

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

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

Сказав інший спосіб: тільки тому, що ти не можеш означати, що слід .


1
Ви хочете сказати, що немає необхідності у винятку, після того, як ми отримали ifдля звичайного використання або використання execptions не призначене, оскільки воно не призначене (круговий аргумент)?
Валь,

1
@Val: Винятки стосуються виняткових ситуацій - якщо ми виявимо достатньо, щоб кинути виняток і обробити його, у нас є достатньо інформації, щоб не кидати його і все-таки поводитися з ним. Ми можемо перейти до логіки керування та пропустити дорогу, зайву спробу / улов.
Брайан Уоттс

За цією логікою ви також можете не мати винятків і завжди робити вихід із системи замість того, щоб викидати винятки. Якщо ви хочете зробити що-небудь перед виходом, тоді зробіть обгортку і зателефонуйте їй. Приклад Java: public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }Тоді просто зателефонуйте, а не кидайте: ExitHelper.cleanExit();Якби ваш аргумент звучав, тоді це був би кращий підхід, і не було б винятків. Ви в основному говорите: "Єдиною причиною винятків є збій по-іншому".
Loduwijk

@Aaron: Якщо я обидва кидаю і ловлю виняток, у мене є достатньо інформації, щоб уникнути цього. Це не означає, що всі винятки раптово є фатальними. Інший код, який я не контролюю, може зафіксувати його, і це добре. Мій аргумент, який залишається здоровим, полягає в тому, що кидати та ловити виняток у тому ж контексті є зайвим. Я ні, ні я не заявив, що всі винятки повинні вийти з процесу.
Брайан Воттс

@BryanWatts Визнано. Багато інших вже сказали , що ви повинні використовувати тільки виключення для чого ви не можете оговтатися від, і розширення завжди повинен бути збій на винятки. Ось чому важко обговорити ці речі; існує не лише 2 думки, але і багато. Я все ще не згоден з вами, але не рішуче. Бувають випадки, коли кидання / спіймання разом - це найчитабельніший, доступний, найкрасивіший код; Зазвичай це трапляється, якщо ви вже збираєте інші винятки, тому у вас вже є спроба / ловити, а додавання ще 1 чи 2 уловів чистіші, ніж окремі перевірки помилок if.
Loduwijk

3

Як багато разів згадували інші, принцип найменшого здивування забороняє використовувати винятки надмірно для цілей контролю потоку. З іншого боку, жодне правило не є правильним на 100%, і завжди є випадки, коли виняток - це "правильний інструмент" - як і gotoсам, до речі, який постачається як у формі, так breakі continueв таких мовах, як Java, які часто є ідеальним способом вискочити з сильно вкладених петель, яких не завжди можна уникнути.

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

Це пояснює, як у jOOQ (бібліотеці абстракцій SQL для Java) такі винятки періодично використовуються для припинення процесу візуалізації SQL на ранніх термінах, коли виконується якась "рідкісна" умова.

Прикладами таких умов є:

  • Зустрічається занадто багато значень прив'язки. Деякі бази даних не підтримують довільну кількість значень прив'язки у своїх операторах SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). У цих випадках jOOQ перериває фазу візуалізації SQL і повторно надає оператор SQL з вбудованими значеннями прив’язки. Приклад:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    Якщо ми явно дістали значення прив’язки з запиту AST, щоб їх підраховувати кожен раз, ми витрачали б цінні цикли процесора на ті 99,9% запитів, які не страждають від цієї проблеми.

  • Деяка логіка доступна лише опосередковано через API, який ми хочемо виконати лише "частково". UpdatableRecord.store()Метод генерує INSERTабо UPDATEзаяву, в залежності від Recordвнутрішніх прапорів «S. Ззовні ми не знаємо, яка саме логіка міститься store()(наприклад, оптимістичне блокування, обробка слухачем подій тощо), тому ми не хочемо повторювати цю логіку, коли ми зберігаємо кілька записів у пакетному операторі, де ми хотіли б store()генерувати лише оператор SQL, а не виконувати його. Приклад:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    Якби ми розширили store()логіку на "повторно використаний" API, який можна налаштувати так, щоб необов'язково не виконувати SQL, ми б розглядали можливість створення досить важкого для обслуговування, важко повторного використання API.

Висновок

По суті, наше використання цих не локальних gotos - це саме те, що [Мейсон Уілер] [5] сказав у своїй відповіді:

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

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

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


2

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

Загалом, якщо ви знаєте, існує велика ймовірність виходу з ладу, яку ви можете перевірити на ... ви повинні зробити перевірку ... тобто якщо (obj! = Null) obj.method ()

У вашому випадку я недостатньо знайомий з бібліотекою C #, щоб знати, чи є час дати простим способом перевірити, чи немає позначки часу поза межами. Якщо це так, просто зателефонуйте, якщо (.isvalid (ts)) в іншому випадку ваш код в основному добре.

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


Точка Add'l: Якщо ваш виняток надає інформацію про помилку (такий, як "Param getWhatParamMessedMeUp ()"), він може допомогти користувачеві вашого API прийняти рішення про те, що робити далі. В іншому випадку ви просто даєте ім’я стану помилки.
Jasonnerothin

2

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

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

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

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

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

Нарешті, проведення попередніх перевірок показує, що ви знаєте чи очікуєте, що відбудеться, і робить це явним. Кодекс повинен бути чітким у намірі. Що б ви краще прочитали?


1
Зовсім не вірно: "По суті, ви використовуєте виняток для іншого оператора, якщо ви використовуєте його для управління потоком". Якщо ви використовуєте його для управління потоком, ви знаєте, що саме ви ловите, і ніколи не використовуєте загальний улов, але конкретний звичайно!
Пітер

2

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

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

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


2

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

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

Приклад: Microsoft Volta - це проект, що дозволяє писати веб-додатки в прямому .NET, і нехай рамки дбають про те, щоб визначити, які біти потрібно запускати. Одним із наслідків цього є те, що Volta потребує можливості компілювати CIL в JavaScript, щоб ви могли запускати код на клієнті. Однак є проблема: .NET має багатопотоковість, JavaScript не робить. Отже, Volta реалізує продовження в JavaScript за допомогою винятку JavaScript, а потім реалізує потоки .NET, використовуючи ці продовження. Таким чином, програми Volta, які використовують потоки, можуть бути складені для запуску в немодифікованому браузері - Silverlight не потрібен.


2

Одна естетична причина:

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

if (PerformCheckSucceeded())
   DoSomething();

З спробою / лову вона стає набагато більш багатослівною.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

Це 6 рядків коду занадто багато.


1

Але ви не завжди будете знати, що відбувається в методі, який ви викликаєте. Ви точно не знатимете, куди було викинуто виняток. Не вивчаючи об'єкт виключення більш детально….


1

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

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


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

У JVM, тобто. Не дорожче, ніж повернення. Піди розберися. Але питання полягає в тому, що б ви написали в операторі if, якщо не той самий код, який вже присутній у виклику функції, щоб повідомити про винятковий випадок від звичайного --- таким чином, дублювання коду.
Інго

1
Інго: виняткова ситуація, яку ви не очікуєте. тобто той, якого ви не думали як програміста. Тож моє правило: "написати код, який не кидає винятків" :)
Brann

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

1
Я згоден з вами, щоб не кидати винятків дико. Але, безумовно, те, що є "винятковим" - це питання визначення. Цей String.parseDouble видає виняток, якщо він не може принести корисний результат, наприклад, в порядку. Що ще має робити? Повернути NaN? Що з обладнанням IEEE, що не належить?
Інго

1

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

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

  • Запропонуйте методу повернути "прихований" прапор, який відрізняє нормальне повернення від винятку, і покликайте абонент перевірити цей прапор і гілку на "виняток", якщо він встановлений. Ця процедура додає 1-2 інструкції до випадків, що не мають винятку, але відносно невеликі накладні витрати, коли відбувається виняток.

  • Поставте інформацію або код обробці винятків, що телефонує, на фіксовану адресу відносно складеної зворотної адреси. Наприклад, за допомогою ARM, замість використання інструкції "Підпрограма BL", можна використовувати послідовність:

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

Для нормального виходу підпрограма просто зробить bx lrабо pop {pc}; у разі ненормального виходу підпрограма або віднімає 4 з LR перед виконанням повернення або використанняsub lr,#4,pc (залежно від зміни ARM, режиму виконання тощо). Цей підхід буде дуже погано функціонувати, якщо абонент не призначений для його розміщення.

Мова або рамки, які використовують перевірені винятки, можуть виграти від того, що з ними обробляються такі механізми, як №2 або №3 вище, тоді як неперевірені винятки обробляються за допомогою №1. Хоча реалізація перевірених винятків у Java є досить неприємною, вони не були б поганою концепцією, якби існували засоби, за допомогою яких сайт для викликів міг би сказати, по суті, "Цей метод оголошено як викид XX, але я цього не очікую коли-небудь робити це; якщо це так, перезавантажте як "неперевірений" виняток. У рамках, де перевірені винятки обробляються таким чином, вони можуть бути ефективним засобом контролю потоку для таких речей, як методи аналізу синтаксису, які в деяких контекстах можуть мати висока ймовірність провалу, але там, де невдача повинна повернути принципово іншу інформацію, ніж успіх. однак я не знаю про будь-які рамки, які використовують такий зразок. Натомість більш поширеною схемою є використання першого підходу вище (мінімальна вартість у випадку, що не стосується винятків, але висока вартість при вилученні винятків) для всіх винятків.

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