Мені сказали, що Винятки слід застосовувати лише у виняткових випадках. Як я можу дізнатися, чи випадок мій винятковий?


99

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

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

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

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

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


3
можливий дублікат? Коли кинути виняток . Хоча там було закрито, але я думаю, що це тут підходить. Це ще трохи філософії, деякі люди та громади схильні бачити винятки як своєрідний контроль потоку.
thorsten müller

8
Коли користувачі тупі, вони надають недійсні дані. Коли користувачі розумні, вони грають, надаючи неправильний ввід. Тому недійсне введення користувача не є винятком.
mouviciel

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

4
"Винятковий"! = "Рідко трапляється"
ConditionRacer

3
Я вважаю, що винятки Еріка Ліпперта у вексингу є гідною порадою.
Брайан

Відповіді:


87

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

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

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


10
Добре проведіть, там. На насправді, в реальному світі додатках я розробив, зниження продуктивності зробили зробити різницю, і я повинен був змінити його НЕ кидати виключення для певних операцій синтаксичного аналізу.
Роберт Харві

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

11
@RobertHarvey Трюк в Java полягає в тому, щоб кидати заздалегідь виготовлені об'єкти виключень, а не throw new .... Або киньте спеціальні винятки, де fillInStackTrace () перезаписаний. Тоді ви не повинні помічати погіршення продуктивності, не кажучи про хіти .
Інго

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

4
@Brendan Припустимо, виникла якась виняток, і код обробки помилок на 4 рівні нижче в стеку викликів. Якщо ви використовуєте код помилки, всі 4 функції над оброблювачем повинні мати тип коду помилки як повернене значення, і вам потрібно робити ланцюжок if (foo() == ERROR) { return ERROR; } else { // continue }на кожному рівні. Якщо ви кинете неперевірений виняток, немає шумного та зайвого "якщо помилка повернення помилки". Крім того, якщо ви передаєте функції як аргументи, використання коду помилки може змінити підпис функції на несумісний тип, навіть якщо помилка може не статися.
Доваль

72

Коли слід кинути виняток? Що стосується коду, я вважаю, що наступне пояснення дуже корисне:

Виняток становлять випадки, коли учасник не виконує завдання, яке він повинен виконати, як зазначено в його імені . (Джеффрі Ріхтер, CLR через C #)

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

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

public void Save(PersonData personData) {  }

Чи підказує ім'я методу про те, що проводиться певна перевірка? Ні. У цьому випадку недійсна особаData повинна кинути виняток.

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

public ValidationResult Validate(PersonData personData) {  }

Чи підказує ім'я методу про те, що проводиться певна перевірка? Так. У цьому випадку недійсний PersonData не повинен кидати виняток.

Для того, щоб об'єднати речі, обидва способи пропонують, щоб клієнтський код повинен виглядати так:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Коли незрозуміло, чи повинен метод викинути виняток, можливо, це пов’язано з погано обраним ім'ям методу чи підписом. Може бути, дизайн класу незрозумілий. Іноді вам потрібно змінити дизайн коду, щоб отримати чітку відповідь на питання, чи слід викинути виняток чи ні.


Лише вчора я зробив structназву "ValidationResult" і структурував свій код так, як ви описуєте.
Павло

4
Це не допомагає відповісти на ваше запитання, але я просто хочу зазначити, що ви неявно або цілеспрямовано дотримувались принципу розділення запитів команд ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Тео Лендорфф

Хороша ідея! Один недолік: У вашому прикладі перевірка фактично виконується двічі: один раз під час Validate(повернення False якщо недійсне) та один раз під час Save(кидання конкретного, добре задокументованого винятку, якщо недійсне). Звичайно, результат перевірки може бути кешований всередині об'єкта, але це додасть додаткової складності, оскільки результат перевірки потребує недійсності при змінах.
Хайнці

@Heinzi Я згоден. Він може бути відремонтований таким чином, що Validate()викликається всередині Save()методу, а конкретні деталі з цього документа ValidationResultможуть бути використані для побудови відповідного повідомлення для виключення.
Філ

3
Це краще, ніж прийнята відповідь, я думаю. Киньте, коли дзвінок не може зробити те, що потрібно було робити.
Енді

31

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

На цьому аргументі:

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

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

Тому я думаю, що "винятки повинні бути винятковими" - це жахливе правило.

Що ви повинні зробити, залежить від мови. Різні мови мають різні умови про те, коли слід викидати винятки. Наприклад, Python кидає винятки для всього, і коли я в Python, я слідкую за цим. C ++, з іншого боку, кидає відносно мало винятків, і я слідую за цим прикладом. Ви можете ставитися до C ++ або Java як Python і викидати винятки для всього, але ваша робота суперечить тому, як мова очікує, що вона буде використана.

Я віддаю перевагу підходу Python, але вважаю, що це погана ідея вбирати в нього інші мови.


1
@gnat, я знаю. Моя думка полягала в тому, що ви повинні слідувати умовам мови (в даному випадку Java), навіть якщо вони не є вашою улюбленою.
Вінстон Еверт

6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Добре сказано! Це одне з тих речей, які люди просто повторюють, не замислюючись над ними.
Андрес Ф.

2
"Очікуване" визначається не суб'єктивним аргументом чи умовою, а контрактом API / функції (це може бути явним, але часто просто мається на увазі). Різні функції / API / підсистеми можуть мати різні очікування, наприклад, для деякої функціональності вищого рівня очікуваний випадок, коли він може працювати (він може повідомити про це користувачеві через GUI), для інших функцій нижчого рівня це певно, що ні (а значить, слід кинути виняток). Ця відповідь, здається, пропускає той важливий момент ....
mikera

1
@mikera, так, функція повинна (лише) кидати винятки, визначені в її контракті. Але це не питання. Питання в тому, як ви вирішите, яким повинен бути цей контракт. Я вважаю, що правило, "винятки повинні бути винятковими", не допомагає приймати це рішення.
Вінстон Еверт

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

30

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

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

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

EDIT (щось ще слід врахувати):

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

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


8
Але чому? Чому винятки менш корисні для вирішення проблем, які очікуються більше?
Вінстон Еверт

6
@Pinetree, перевірка наявності файлу перед відкриттям файлу - погана ідея. Файл може перестати існувати між чеком і відкритим, файл не міг би мати дозвіл, який дозволив би відкрити його, а для перевірки наявності та відкриття файла потрібні два дорогі системні виклики. Вам краще спробувати відкрити файл, а потім вирішити проблему, якщо цього не зробити.
Вінстон Еверт

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

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

4
File.ReadAllBytes викине a, FileNotFoundExceptionколи буде вказано неправильне введення (наприклад, неіснуюче ім'я файлу). Це єдиний дійсний спосіб загрози цій помилці, що ще можна зробити, не приводячи до повернення кодів помилок?
оɔɯǝɹ

16

Довідково

Від Прагматичного програміста:

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

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

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

Пізніше вони обговорюють, чому обрали такий підхід:

[A] n виняток являє собою негайну нелокальну передачу контролю - це свого роду каскад goto. Програми, які використовують винятки як частину звичайної обробки, страждають від усіх проблем читабельності та ремонтопридатності класичного коду спагетті. Ці програми порушують інкапсуляцію: підпрограми та їх виклики більш щільно пов'язані через обробку винятків.

Щодо вашої ситуації

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

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


11

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

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


1
+1, дуже близький до того, що я збирався сказати. Я б сказав, що мова йде більше про сферу застосування, і дійсно не має нічого спільного з користувачем. Хорошим прикладом цього є різниця між двома .Net функціями int.Parse та int.TryParse, колишній не має іншого вибору, крім як викинути виняток на неправильному вході, пізніший ніколи не повинен кидати виняток
jmoreno

1
@jmoreno: Ерго, ви б використовували TryParse, коли код міг би зробити щось про нерозбірливий стан, і Парсе, коли він не міг.
Роберт Харві

7

Ви повинні врахувати два проблеми:

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

  2. добре реалізований інтерфейс має додаткову заклопотаність: перевірка призначеного для користувача введення і конструктивний зворотний зв'язок про помилки (назвемо цю частину Validator)

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

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

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

Ви помітите, що я не згадував, як ці проблеми реалізуються. Здається, ви говорите про своє, Assignerа ваш колега - про комбіноване Validator+Assigner. Як тільки ви зрозумієте, що існують дві окремі (або відокремлені) проблеми, принаймні ви можете обговорити це розумно.


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

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

Я думаю, що це є прямою відповіддю на

... як я можу знати, якщо моя справа є винятковою?

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


-1 Так, є два питання, але це не відповідає на питання "Як я можу знати, чи є моя виняток винятковою?"
RMalke

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

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

4

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


3

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

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

Отже, як загальне правило, якщо:

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

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

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


2

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

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

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

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

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

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

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

проти

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

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

Оскільки заняття часто не знають, яких умов їх клієнти бажають чи не очікують, часто корисно запропонувати дві версії методів, які можуть вийти з ладу способами, які очікують деякі абоненти, а інші викликають. Це дозволить використовувати такі методи чисто з обома типами абонентів. Зауважте також, що навіть "пробувати" методи повинні викидати винятки, якщо виникають ситуації, абонент, ймовірно, не очікує. Наприклад, tryReadIntegerне слід викидати виняток, якщо він стикається з чистою умовою закінчення файлу (якщо абонент цього не очікував, абонент використовував биreadInteger). З іншого боку, це, ймовірно, повинно викинути виняток, якщо дані не вдалося прочитати, оскільки, наприклад, пам'ять, що містить пам'ять, була відключена. Хоча такі події завжди слід визнавати можливими, малоймовірно, що код негайного виклику був би готовий зробити все корисне у відповідь; про це, звичайно, не слід повідомляти так само, як це було б умовою закінчення файлу.


2

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

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

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

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


2

Може виникнути вимога повідомляти про кожну помилку, яку ми можемо знайти у введенні, а не лише першу.

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

Поганий приклад:

Метод перевірки для Dogкласу за допомогою виключень:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Як це назвати:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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

Кращий підхід:

Виклик методу:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Метод перевірки:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

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

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

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