Що краще IllegalStateException або безшумне виконання методу? [зачинено]


16

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

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


21
Не використовуйте винятки для поведінки моделі, яка не є винятковою.
Олександр - Відновіть Моніку

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

1
Замість того, щоб ПРОСТО мовчати не вдалося, чому б ТАКОЖ не записати аномалію як попередження? Безумовно, що Java, яку ви використовуєте, підтримує різні рівні журналу (ПОМИЛКА, ПРЕДУПРЕЖДЕННЯ, ІНФОРМАЦІЯ, ДЕБУГ).
Ерік Seastrand

3
Зауважте, що Set.add не кидає винятку, якщо елемент вже є в наборі. Його мета полягає в тому, щоб "забезпечити, щоб елемент знаходився в наборі", а не "додавав елемент до набору", тому він досягає своєї мети, не роблячи нічого, якщо елемент вже є в наборі.
користувач253751

2
Слово дня: ідентифікація
Жуль

Відповіді:


37

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

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

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

Отже, якби це було до мене, я пропустив би виняток.


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

6
Зважаючи на це, кидання винятку здавалося б педантичним та несправедливим. Це зробило б те, що API відчував себе соціальним еквівалентом спілкування з набридливою дитиною в шкільному автобусі. > Винятки не розраховані на те, щоб покарати вас: вони покликані повідомити вам про те, що сталося щось несподіване. Я особисто був би дуже вдячний за MediaPlayer.stop()метод, який кинув IllegalStateExceptionзамість того, щоб витрачати години налагодження коду і цікавитись, чому в пеклі "нічого не відбувається" (тобто "це просто не працює").
блудниця

3
Звичайно, якщо ваша конструкція каже, що перехід до зворотного циклу недійсний, ТАК ТАК, ви повинні кинути виняток. Однак моя відповідь стосувалася дизайну, де цей перехід цілком нормальний.
MetaFight

1
StopOrThrow()звучить жахливо. Якщо ви спускаєтесь по цій алеї, чому б не використати стандартну схему TryStop()? Крім того, зазвичай, коли поведінка API незрозуміла (як і більшість з них), від розробників не очікується просто здогадуватися або експериментувати, вони, як очікується, переглянуть документацію. Ось чому мені так подобається MSDN.
MetaFight

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

17

Є два види дії, які можна побажати виконувати:

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

  2. Установіть щось на певний стан, без огляду на попередній стан.

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

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


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

Приклад bool TryStop()з void Stop()кодом зробив би це справді чудовою відповіддю.
RubberDuck

2
@RubberDuck: Це не буде гарною схемою. Назва TryStopозначає, що виняток не слід кидати, якщо гравця не можна примусити до зупиненого стану. Спроба зупинити гравця, коли він уже зупинений, не є винятковою умовою, але це умова, яка може зацікавити того, хто телефонує.
supercat

1
Тоді я неправильно зрозумів вашу відповідь @supercat
RubberDuck

2
Я хотів би зазначити, що тестування та налаштування цього способу НЕ є порушенням закону зменшення, якщо API також надає спосіб перевірити стан відтворення, не змінюючи його. Скажімо, це може бути зовсім інший метод isPlaying().
candied_orange

11

Загального правила немає. У цьому конкретному випадку користувач вашого API має намір зупинити програвач відтворення медіа. Якщо плеєр не відтворює медіа, це MediaPlayer.stop()може нічого не робити, і мета методу, що викликає, все одно буде досягнута - медіа не грає.

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


11

Мета винятків - не сигналізувати про те, що сталося щось погане; це сигналізувати про це

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

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

Тому не варто кидати виняток у цій ситуації.


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

5

Подивіться на це так:

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


3

Правило полягає в тому, що ви робите те, що вимагає контракт вашого методу.

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

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

try {
    player.stop();
} catch(IllegalStateException e) {
    // do nothing, player was already stopped
}

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

Гравець може спостерігатися, і , можливо , вже повідомляє спостерігачів про деякі речі - наприклад , заявляють спостерігачів , коли він transitons від playingдо stoppingдо stopped. У цьому випадку мати метод кинути виняток не обов'язково. Виклик stopпросто нічого не зробить, якщо гравець уже зупинений, а дзвінок, коли його не зупинено, сповістить спостерігачів про перехід.

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

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


3

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

Поміркуйте, як оброблятиметься виняток, якби його кинути.

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

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

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

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


2

Ось як це робить Android: MediaPlayer

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


1

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

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

Чому метод "не повинен називатися"? Це здається самонав’язаним обмеженням. Якщо на ваш API немає «шкоди» чи впливу, то виняток не є. Якщо метод справді не слід називати, оскільки він може створювати недійсні або непередбачувані стани, тоді слід викинути виняток.

Наприклад, якщо я підійшов до програвача DVD і натиснув "стоп", перш ніж натиснути "старт", і він зазнав аварії, це не мало б сенсу для мене. Він повинен просто сидіти там або, що ще гірше, визнати "зупинку" на екрані. Помилка буде дратувати і жодним чином не допомагати.

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

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

Редагувати:

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


Його не слід називати, оскільки його дзвінок вказує на помилку у абонента.
користувач253751

@immibis: Не обов’язково. Наприклад, той, хто телефонує, може бути слухачем якоїсь кнопки інтерфейсу користувача. (Користувачеві вам, мабуть, не сподобається повідомлення про помилку, якщо ви випадково натисніть кнопку зупинки, якщо медіа вже зупинено.)
meriton

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

@immibis - я відредагував свою відповідь. Я сподіваюся, що це допомагає.
Джим

1

Що саме робити MediaPlayer.play()і MediaPlayer.stop()робити? - це слухачі подій для введення користувача або це фактично методи, які запускають певний медіапотік у системі? Якщо всі вони є слухачами для введення користувачем, то їм цілком розумно нічого не робити (хоча було б непогано кудись увійти). Однак, якщо вони впливають на модель , якою керує ваш інтерфейс користувача, вони можуть викинути, IllegalStateExceptionтому що у гравця повинен бути якийсь стан isPlaying=trueабо isPlaying=falseстан (він не повинен бути фактичним булевим прапором, як написано тут), і тому, коли ви дзвоните, MediaPlayer.stop()коли isPlaying=false, метод насправді не може "зупинити", MediaPlayerтому що об'єкт не знаходиться у відповідному стані, який слід зупинити - див. описjava.lang.IllegalStateException клас:

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

Чому кидати винятки може бути добре

Багато людей, здається, вважають, що винятки загалом погані, оскільки їх ніколи не слід кидати (пор. Джоель про програмне забезпечення ). Однак, скажімо, у вас є MediaPlayerGUI.notifyPlayButton()виклик MediaPlayer.play(), який викликає , і MediaPlayer.play()викликає купу іншого коду і десь внизу лінії, з якою він взаємодіє, наприклад, PulseAudio , але я (розробник) не знаю куди, тому що я не написав увесь цей код.

Потім, одного дня, працюючи над кодом, натискаю «грати», і нічого не відбувається. Якщо MediaPlayerGUIController.notifyPlayButton()щось записує, я принаймні можу заглянути в журнали і перевірити, чи натиснута кнопка була зареєстрована ... але чому вона не відтворюється? Ну, скажіть, насправді щось не так з обгортками PulseAudio, які MediaPlayer.play()викликають. Я знову натискаю кнопку "відтворити", і цього разу отримую IllegalStateException:

 Exception in thread "main" java.lang.IllegalStateException: MediaPlayer already in play state.

     at org.superdupermediaplayer.MediaPlayer.play(MediaPlayer.java:16)
     at org.superdupermediaplayer.gui.MediaPlayerGUIController.notifyPlayButton(MediaPlayerGUIController.java:25)
     at org.superdupermediaplayer.gui.MediaPlayerGUI.main(MediaPlayerGUI.java:14)

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

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


0

Я вважаю за краще розробляти API таким чином, щоб споживачеві було важче або неможливо помилитися. Наприклад, замість того, щоб мати MediaPlayer.play()та MediaPlayer.stop(), ви можете вказати, MediaPlayer.playToggle()які перемикання між "зупиненими" та "граючими". Таким чином, метод завжди безпечно викликати - немає ризику потрапити в нелегальний стан.

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

  • прагматично про це і не кидати жодної помилки. Це, безумовно, спрощує багато коду, наприклад, замість того, щоб писати if (list.contains(x)) { list.remove(x) }потрібно лише вам list.remove(x)). Але, це також може приховати помилок.
  • або ви можете бути педантичним і сигналізувати про помилку, що список не містить цього елемента. Код може в рази ускладнюватися, тому що вам постійно доводиться перевіряти, чи виконуються попередні умови перед тим, як викликати метод, але перевага полягає в тому, що можливі помилки простіше відстежити.

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


3
Я не погоджуюся з playToggle()тим, що простіше використовувати: Щоб досягти бажаного ефекту (грає гравець чи не грає), вам потрібно буде знати його поточний стан. Отже, якщо ви отримуєте вхід користувача з проханням зупинити програвача, спочатку вам потрібно буде запитати, чи грає він зараз, а потім перемкнути його стан чи ні, залежно від відповіді. Це набагато громіздкіше, ніж просто викликати stop()метод і робити з ним.
cmaster - відновити моніку

Ну, я ніколи не говорив, що його легше використовувати - я стверджував, що це безпечніше. Крім того, ваш приклад передбачає, що користувачеві будуть надані окремі кнопки зупинки та відтворення. Якби це насправді так, то так, використання playToggleбуло б дуже громіздким. Але якщо користувачеві також надали кнопку перемикання, то це playToggleбуло б простіше у використанні, ніж відтворення / зупинка.
Педро Родрігес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.