Чому исключение.printStackTrace () вважається поганою практикою?


126

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

Напр. У чековому стилі RegexpSingleline:

Ця перевірка може бути використана [...] для пошуку поширених поганих практик, таких як виклик ex.printStacktrace ()

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

  1. Кінцеві користувачі ніколи не повинні бачити слід стека (з метою користування та безпеки)

  2. Створення сліду стека є досить дорогим процесом (хоча навряд чи це буде проблемою в більшості "виняткових" обставин)

  3. Багато фреймворків для друку надрукують трасування стека для вас (у нас немає і ні, ми не можемо легко це змінити)

  4. Друк сліду стека не є обробкою помилок. Він повинен поєднуватися з іншим реєстрацією інформації та обробкою винятків.

Які ще причини уникнення друку сліду стека у вашому коді?


12
Як хтось, кому доводиться регулярно вирішувати проблеми, я ніколи не пропускав би друк стек-треків, коли щось пішло не так. Так, не показувати це користувачеві, але так, скиньте його в журнал помилок.
Пол Гриме

4
Вам справді потрібні інші причини? Я думаю, ви досить добре відповіли на своє запитання. Однак стек-трек слід надрукувати за винятками. :)
Ніл

Відповіді:


125

Throwable.printStackTrace()записує слід стека в System.errPrintStream. System.errПотік і основний стандарт «помилка» вихідний потік процесу віртуальної машини Java може бути переадресований

  • виклик, System.setErr()який змінює призначення, на яке вказує System.err.
  • або шляхом перенаправлення вихідного потоку помилок процесу. Потік виходу помилки може бути перенаправлений у файл / пристрій
    • вміст якого персонал може ігнорувати,
    • файл / пристрій може не мати можливості обертання журналу, роблячи висновок про необхідність перезапуску процесу для закриття відкритої ручки файлу / пристрою, перш ніж архівувати наявний вміст файлу / пристрою.
    • або файл / пристрій фактично відкидає всі записані до нього дані, як це відбувається /dev/null.

Виходячи з вищесказаного, виклик Throwable.printStackTrace()становить лише дійсне (не добре / велике) поведінку в обробці виключень

  • якщо вас не System.errпризначають протягом усього періоду життя програми,
  • і якщо вам не потрібно обертання журналу під час роботи програми,
  • і якщо прийнята / спроектована практика ведення журналу програми полягає у записі System.err(та стандартний потік виводу помилок JVM).

У більшості випадків вищезазначені умови не виконуються. Можливо, не відомо про інший код, що працює в JVM, і не можна передбачити розмір файлу журналу або тривалість виконання процесу, і добре розроблена практика ведення журналів обертається навколо написання журнальних файлів "машино-розбірних" (a бажана, але необов'язкова функція в лісорубі) у відомому місці призначення для надання підтримки.

Нарешті, слід пам’ятати, що висновок Throwable.printStackTrace()напевно буде переплетений з іншим вмістом, записаним System.err(та, можливо, навіть System.outякщо обидва перенаправлені на один і той же файл / пристрій). Це роздратування (для однопотокових додатків), з яким треба боротися, оскільки дані навколо винятків не легко піддаються аналізу в такій події. Гірше, велика ймовірність того, що багатопотокове додаток створить дуже заплутані журнали, оскільки Throwable.printStackTrace() це не є безпечним для потоків .

Не існує механізму синхронізації, який би синхронізував запис трасування стека, System.errколи одночасно викликається кілька потоків Throwable.printStackTrace(). Для вирішення цього фактично потрібно, щоб ваш код синхронізувався на моніторі, пов’язаному з System.err(а також System.out, якщо цільовий файл / пристрій однаковий), і це досить висока ціна, щоб заплатити за безпеку файлу журналу. Для прикладу, ConsoleHandlerта StreamHandlerкласи відповідають за додавання записів журналу до консолі в системі реєстрації, наданій компанією java.util.logging; фактична операція публікації записів журналів синхронізована - кожен потік, який намагається опублікувати запис журналу, також повинен придбати замок на моніторі, пов'язаний зStreamHandlerекземпляр. Якщо ви хочете мати однакову гарантію використання непереплетених записів журналу за допомогою System.out/ System.err, ви повинні забезпечити те саме - повідомлення публікуються в цих потоках послідовно.

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


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


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

1
@Chris Так, бувають випадки, коли ви не можете їх використовувати, System.out.printlnі, Throwable.printStackTraceзвичайно, потрібно судження розробника. Я трохи переживав, що частина про безпеку різьби була пропущена. Якщо ви подивитесь на більшість реалізацій журналів, ви помітите, що вони синхронізують частину, де записуються записи журналів (навіть до консолі), хоча вони не набувають моніторів на System.errабо System.out.
Vineet Reynolds

2
Чи не помиляюсь я зазначити, що жодна з цих причин не застосовується до скасування printStackTrace, які приймають за параметр об'єкт PrintStream або PrintWriter?
ESRogs

1
@Geek, останній - дескриптор файлу процесу зі значенням 2. Перший - це лише абстракція.
Vineet Reynolds

1
У вихідному коді JDK printStackTrace () він використовує синхронізований для блокування System.err PrintStream, тому він повинен бути безпечним для потоків методом.
EyouGo

33

Ви торкаєтесь декількох проблем тут:

1) Дослідження стека ніколи не повинні бути видимими для кінцевих користувачів (для досвіду користувача та безпеки)

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

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

2) Генерування сліду стека є досить дорогим процесом (хоча навряд чи це буде проблемою в більшості "виняткових" обставин)

Генерування сліду стека відбувається, коли виняток створюється / кидається (тому викидання винятку відбувається з ціною), друк не є таким дорогим. Насправді ви можете перекрити Throwable#fillInStackTrace()ваш спеціальний виняток, роблячи викидання виключення майже таким же дешевим, як і проста заява GOTO.

3) Багато рамок реєстрації надрукують трасування стека для вас (у нас немає і ні, ми не можемо легко це змінити)

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

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

4) Друк сліду стека не є обробкою помилок. Він повинен поєднуватися з іншим реєстрацією інформації та обробкою винятків.

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

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


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


Ваша відповідь - та, яку я отримав.
Бласанка

"більшість" винятків "- обставини". приємно.
awwsmm

19

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

Ідея полягає в тому, щоб передавати все, що йде в журнали, через рамку реєстратора, щоб можна було керувати веденням журналу. Отже, замість того, щоб використовувати printStackTrace, просто використовуйте щось подібнеLogger.log(msg, exception);


11

Друк сліду стека винятку сам по собі не є поганою практикою, але тут, мабуть, проблема лише друку трасування стека, коли відбувається виняток - часто разів, просто надрукувати слід сліду стека.

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

Приклад

Розглянемо наступний приклад:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

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

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

У багатьох випадках e.printStackTrace()це вказівка ​​на те, що якийсь виняток проковтується, і обробка дозволяється протікати так, ніби не виникає жодної проблеми.

Чому це стало проблемою?

Ймовірно, одна з найбільших причин того, що погана обробка винятків стала більш поширеною, пов'язана з тим, як IDE, такі як Eclipse, автоматично генерують код, який виконуватиме e.printStackTraceобробку виключень:

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(Вищенаведене - це фактично try-catchавтоматично створене Eclipse для обробки InterruptedExceptionкинутого Thread.sleep.)

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


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

10

Я думаю, що ваш список причин досить вичерпний.

Один особливо поганий приклад, з яким я не раз стикався:

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

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

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

Нарешті, легшою нотою, Боже Ідеальне Виняток .


"виняток заборонено виходити" - ви мали на увазі, що виняток поширюється до стека? Чим журнал відрізняється від printStackTrace ()?
MasterJoe

5

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


Дійсна точка, хоча ми уважно стежимо за результатами виробництва консолі.
Кріс Найт

Чи можете ви пояснити, що ви маєте на увазі, уважно спостерігаючи? Як і хто? З якою частотою?
Ой Чін Бун

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

3

У серверних додатках стек-трак підірває ваш файл stdout / stderr. Він може ставати все більшим і більшим і наповнюватись марними даними, оскільки зазвичай у вас немає контексту та часової позначки тощо.

наприклад, catalina.out при використанні tomcat в якості контейнера


Гарна думка. Зловмисне використання printStackTrace () може підірвати наші файли журналу або принаймні заповнити його пачками непотрібної інформації
Chris Knight

@ChrisKnight - чи можете ви запропонувати статтю, яка пояснює, як файли реєстрації можуть бути підірвані непотрібною інформацією? Дякую.
MasterJoe

3

Це не погана практика, тому що щось не так у PrintStackTrace (), а тому, що це "запах коду". Більшість випадків дзвінок PrintStackTrace () є там, оскільки хтось не встиг правильно обробити виняток. Після правильної роботи з винятком ви, як правило, більше не переймаєтеся StackTrace.

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


0

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

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


0

Щоб уникнути проблеми із заплутаним вихідним потоком, на яку посилається @Vineet Reynolds

ви можете роздрукувати його в stdout: e.printStackTrace(System.out);

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