Як написати гарне повідомлення про виключення


101

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

throw new Exception("BulletListControl: CreateChildControls failed.");

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

Це змусило мене задуматися над тим, яке повідомлення я помістив у повідомлення про виключення. Спочатку я створюю винятковий клас, якщо його ще не існує, із загальної причини (наприклад PropertyNotFoundException- чому ), а потім, коли я кидаю його, повідомлення вказує, що пішло не так (наприклад, "Неможливо знайти властивість 'IDontExist' у вузлі 1234 "- що ). Де знаходиться в StackTrace. , Коли може закінчитися в журналі (якщо є). Як для розробника , щоб працювати (і виправити)

Чи є у вас інші поради щодо кидання винятків? Зокрема, щодо створення нових типів та повідомлення про виняток.


4
Це для файлів журналів або для подання користувачеві?
Джон Хопкінс

5
Тільки для налагодження. Вони можуть опинитися в журналі. Вони не будуть представлені користувачеві. Я не прихильник подавати повідомлення про виключення користувачеві.
Колін Макей

Відповіді:


70

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

Ось кілька прикладів.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

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

Context: Sending email to 'abc@xyz.com' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell 'abc@xyz.com' about this problem.

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

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

Друга чудова річ, яка приходить вам на думку, - це коли ви намагаєтесь описати рішення, яке виключаєте, і ви пишете "Перевірити Х, а якщо А, то Б ще С". Це дуже чіткий і очевидний знак того, що виняток перевіряється в неправильному місці. Ви програміст маєте здатність порівнювати речі в коді, так що оператори "якщо" повинні бути запущені в коді, навіщо залучати користувача до чогось, що можна автоматизувати? Швидше за все, це з глибшого коду, і хтось здійснив ледачу справу і кинув IOException з будь-якої кількості методів і виявив потенційні помилки з усіх них у блоці коду виклику, який не може адекватно описати, що пішло не так, що конкретноконтекст є і як це виправити. Це спонукає вас писати дрібніші помилки зерна, ловити їх та обробляти їх у потрібному місці у своєму коді, щоб ви могли правильно сформулювати ті кроки, які повинен зробити оператор.

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

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


1
+1 Це гарна система. Що важливіше: бути впевненим, що інформація поширюється, або використовуючи короткі слова?
Майкл К

3
+1 для мене подобається рішення, включаючи, контекст, проблему, рішення
WebDev

1
Цей прийом настільки корисний. Я обов'язково збираюся це використати.
Kid Diamond

Контекст - це непотрібні дані. Він уже присутній у сліді стека. Мати рішення є кращим, але не завжди можливим / корисним. Більшість проблем - це витончено зупинити додаток або проігнорувати очікувані операції та повернутися до циклу основного виконання програми, сподіваючись, що наступного разу, коли ви досягнете успіху ... Ім'я класу виключень має бути таким, що рішення стане очевидним, FileNotFoundабо ConnectExceptionви знаєте, що робити))
gavenkoa

2
@ThomasFlinkow У вашому прикладі у слідах буде init (), Execute () та очищення () у сліді стека. З хорошою схемою іменування в бібліотеці та чистим / зрозумілим API вам не потрібні строкові пояснення. І швидко вийти з ладу, не переносити порушений стан по всій системі. Сліди та записи журналів з унікальним ідентифікатором можуть пояснити потік / стан програми.
gavenkoa

23

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

"Повідомлення" винятку - це (повністю кваліфікована) назва класу винятку.

Виняток повинен містити всередині власних змінних якомога більше деталей про те, що саме сталося; наприклад, an IndexOutOfRangeExceptionповинен містити значення індексу, яке було визнано недійсним, а також верхнє та нижнє значення, що діяли на момент скидання винятку. Таким чином, використовуючи роздуми, ви можете автоматично створити повідомлення, яке читається так: IndexOutOfRangeException: index = -1; min=0; max=5і це разом із слідом стека має бути всією об'єктивною інформацією, яка вам потрібна для усунення проблеми. Форматування його в симпатичне повідомлення типу "індекс -1 не було між 0 і 5" не додає ніякого значення.

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

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

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

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


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

4
@SebastianRedl Правда. І те ж саме може бути застосовано до C # та Java, якщо nodeоб'єкт охороняється за допомогою пункту використання одноразового використання (у C #) або спробу використання ресурсів (у Java): об’єкт, що зберігається за винятком, буде видалений / закритий, що робить його незаконним отримати доступ до нього, щоб отримати з нього будь-яку корисну інформацію в тому місці, де обробляється виняток. Я припускаю, що в цих випадках якийсь підсумок об'єкта повинен зберігатися в межах виключення, а не самого об'єкта. Я не можу придумати нерозумний спосіб загально впоратись із цим у всіх випадках.
Майк Накіс

13

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

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


+1 - очікувані значення проти фактичних значень надзвичайно корисні. У прикладі, наведеному у запитанні, ви не повинні просто сказати, що метод не вдався, але чому він не вдався (в основному, точна команда, яка не вдалася, та обставини, що спричинили збій.)
Фелікс Домбек

4

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

Чому б не кинути виняток? Тому що викидання Exception не означає нічого, щоб описати ваш виняток і змушує ваш код виклику писати такий код, catch(Exception ex) { ... }який, як правило, не є корисним! :-).


2

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

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

Отже ... Або перелічені, або для .NET, додані до колекції даних зафіксованих винятків (cf @Plip!):

  • Параметри (це може стати цікавим - ви не можете додати до збору даних, якщо він не буде серіалізувати, а іноді один параметр може бути напрочуд складним)
  • Додаткові дані, повернені ADO.NET або Linq в SQL або подібні (це також може стати трохи цікавішим!).
  • Що б інше не було очевидним.

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


0

Які винятки для ?

(1) Повідомте користувачеві щось пішло не так?

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

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

наприклад, "Будь ласка, не натискайте цю кнопку знову"

(2) Повідомте розробника, коли пішов не так?

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

(3) Повідомити обробник винятків (тобто код), що щось пішло не так?

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

Повідомлення винятку абсолютно не має значення .


-3

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

Вміст повідомлення про виключення залежить від одержувача повідомлення - тому вам доведеться поставити себе у взутті цієї людини.

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

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

Крім того, не кидайте велику "ПОЛУШЕННУ ПОМИЛКУ!" значок. Це помилка - це не кінець світу.

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


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

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