Рубін QuickRef Райана Девіса говорить (без пояснень):
Не рятуйте виняток. ВСЕ. або я буду колоти тебе.
Чому ні? Що правильно робити?
Рубін QuickRef Райана Девіса говорить (без пояснень):
Не рятуйте виняток. ВСЕ. або я буду колоти тебе.
Чому ні? Що правильно робити?
Відповіді:
TL; DR : використовувати StandardError
замість загального вилучення винятків. Коли оригінальний виняток буде повторно піднято (наприклад, під час порятунку, щоб увімкнути лише виняток), з рятуванням Exception
, ймовірно, добре.
Exception
є коренем ієрархії виключень в Ruby , тому , коли ви rescue Exception
ви врятувати від усього , в тому числі підкласів , таких як SyntaxError
, LoadError
і Interrupt
.
Порятунок Interrupt
не дозволяє користувачеві використовувати CTRLCдля виходу з програми.
Порятунок SignalException
заважає програмі правильно реагувати на сигнали. Це буде неможливим, окрім як kill -9
.
Рятувати SyntaxError
означає, що ті eval
, хто зазнає невдачі, зроблять це мовчки.
Все це можна показати, запустивши цю програму, і намагається CTRLCабо kill
йому:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Порятунок від Exception
не є навіть за замовчуванням. Робимо
begin
# iceberg!
rescue
# lifeboats
end
не рятує від Exception
, а рятує від StandardError
. Як правило, слід вказати щось більш конкретне, ніж за замовчуванням StandardError
, але врятування від Exception
розширює сферу, а не звуження, і може мати катастрофічні результати та ускладнити пошук помилок.
Якщо у вас є ситуація, коли ви хочете врятуватись StandardError
і вам потрібна змінна за винятком, ви можете скористатися цією формою:
begin
# iceberg!
rescue => e
# lifeboats
end
що еквівалентно:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Один з небагатьох поширених випадків, коли врятуватись Exception
цілком доцільно - це вхід в систему / звітування, і в цьому випадку ви повинні негайно повторно підвищити виняток:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
в яві
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
і тодіrescue *ADAPTER_ERRORS => e
Реальне правило: Чи не викидати виключення. Об'єктивність автора вашої цитати сумнівна, про що свідчить той факт, чим вона закінчується
або я буду колоти тебе
Звичайно, майте на увазі, що сигнали (за замовчуванням) викидають винятки, і зазвичай тривалі процеси завершуються через сигнал, тож уловлювання Винятку та не припинення винятків з сигналу змусить вашу програму дуже важко зупинити. Тому не робіть цього:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
Ні, справді, не робіть цього. Навіть не запускайте це, щоб побачити, чи працює він.
Однак скажіть, що у вас є потоковий сервер, і ви хочете, щоб усі винятки не:
thread.abort_on_exception = true
). Тоді це цілком прийнятно у вашому потоці обробки з'єднання:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
Вищезазначене працює на варіант обробника винятків за замовчуванням Ruby, з тим перевагою, що він також не знищує вашу програму. Rails це робить у своєму оброблювачем запитів.
Винятки з сигналу піднімаються в основній нитці. Фонові нитки їх не отримають, тому немає сенсу намагатися їх зачепити там.
Це особливо корисно у виробничих умовах, коли ви не хочете, щоб ваша програма просто зупинялася, коли щось пішло не так. Тоді ви можете взяти скиди стеків у свої журнали та додати до свого коду, щоб вирішити конкретні винятки далі за ланцюжками дзвінків і більш витончено.
Зауважте також, що існує ще одна ідіома Рубі, яка має такий же ефект:
a = do_something rescue "something else"
У цьому рядку, якщо do_something
піднімає виняток, його ловить Рубі, викидає та a
присвоює "something else"
.
Як правило, не робіть цього, крім випадків, коли ви знаєте , що не потрібно хвилюватися. Один приклад:
debugger rescue nil
debugger
Функція досить хороший спосіб , щоб встановити точку зупину в коді, але при роботі поза відладчика, і Rails, він викликає виключення. Тепер теоретично вам не слід залишати налагоджувальний код, що лежить у вашій програмі (pff! Цього ніхто не робить!), Але ви, можливо, захочете залишити його там деякий час, але не постійно запускати налагоджувач.
Примітка:
Якщо ви запустили чужу програму, яка ловить винятки з сигналу та ігнорує їх (скажімо код вище), тоді:
pgrep ruby
або ps | grep ruby
шукайте PID вашої програми, а потім запустіть kill -9 <PID>
. Якщо ви працюєте з чужою програмою, яка з будь-якої причини переповнена цими блоками виключення ігнорування, то розміщення цього у верхній частині основної лінії - це одне можливе копіювання:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Це змушує програму реагувати на звичайні сигнали припинення, негайно припиняючи, минаючи обробники виключень, без очищення . Таким чином, це може призвести до втрати даних або подібного. Будь обережний!
Якщо вам потрібно зробити це:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
ви можете зробити це:
begin
do_something
ensure
critical_cleanup
end
У другому випадку critical cleanup
буде викликатися кожен раз, викинуто чи не виняток.
kill -9
.
ensure
запуск буде запущений незалежно від того, є виняток, піднятий чи ні, тоді як rescue
запуск буде виконуватися лише в тому випадку, якщо виняток був піднятий.
Чи не rescue Exception => e
(і не ререйз виключення) - або ви могли б відігнати міст.
Скажімо, ви знаходитесь в машині (працює Рубі). Нещодавно ви встановили нове рульове колесо із системою оновлення до повітря (яка використовує eval
), але ви не знали жодного з програмістів, зіпсованих у синтаксисі.
Ви знаходитесь на мосту і розумієте, що їдете трохи до перил, тому повертаєте ліворуч.
def turn_left
self.turn left:
end
ой! Це, мабуть, Не добре ™, на щастя, Рубі піднімає SyntaxError
.
Машина повинна негайно зупинитися - правда?
Ні.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
БІП біп
Попередження: Виняток спійманого синтаксису.
Інформація: помилка зафіксованого - процес, що триває.
Ви помітили що - то не так, і ви грюкнути на аварійних перерв ( ^C
: Interrupt
)
БІП біп
Попередження: Виняток перерви, що перервався.
Інформація: помилка зафіксованого - процес, що триває.
Так - це мало допомогло. Ви досить близько до залізниці, тому ви поставили машину в парк ( kill
ing:) SignalException
.
БІП біп
Попередження: Виняток вилученого сигналу.
Інформація: помилка зафіксованого - процес, що триває.
В останню секунду ви витягуєте клавіші ( kill -9
), і машина зупиняється, ви ляскаєте вперед в кермо (подушка безпеки не може надутись, тому що ви граціозно не зупинили програму - ви її припинили) та комп'ютер ззаду вашого автомобіля забивається на сидіння перед ним. Половина повна банка коксу розливається по паперах. Продукти в задній частині подрібнюються, а більшість покривається яєчним жовтком і молоком. Автомобіль потребує серйозного ремонту та прибирання. (Втрата даних)
Сподіваємось, у вас є страхування (резервне копіювання). О так - оскільки подушка безпеки не надулася, ви, мабуть, поранені (вас звільняють тощо).
Але зачекайте! Там єбільшепричини, чому ви можете скористатися rescue Exception => e
!
Скажімо, ви цей автомобіль, і ви хочете переконатися, що подушка безпеки надувається, якщо машина перевищує безпечний момент зупинки.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Ось виняток із правила: ви можете зловити, Exception
лише якщо повторно підняти виняток . Отже, краще правило - ніколи не ковтати Exception
, а завжди повторювати помилку.
Але додавання порятунку легко забути на такій мові, як Ruby, і якщо викласти рятувальну заяву безпосередньо перед тим, як повторно поставити проблему, це відчувається трохи НЕ СУХО. І ви не хочете забувати raise
твердження. І якщо це зробити, удача, намагаючись знайти цю помилку.
На щастя, Ruby є приголомшливим, ви можете просто використовувати ensure
ключове слово, яке гарантує, що код працює. ensure
Ключове слово буде працювати код незалежно від того , що - якщо виняток, якщо один не є, єдиним винятком є , якщо світ закінчується (або інші малоймовірні події).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Бум! І цей код повинен працювати в будь-якому випадку. Єдина причина, яку ви повинні використовувати, rescue Exception => e
- це якщо вам потрібен доступ до винятку, або якщо ви хочете, щоб код працював лише за винятком. І не забудьте повторно підняти помилку. Кожного разу.
Примітка. Як зазначав @Niall, переконайтеся, що завжди працює. Це добре, тому що іноді ваша програма може брехати вам і не кидати винятків, навіть коли виникають проблеми. При вирішальних завданнях, таких як надуття подушок безпеки, вам потрібно переконатися, що це відбувається незалежно від того. Через це перевірка кожного разу, коли зупиняється машина, викинуто виняток чи ні, є хорошою ідеєю. Незважаючи на те, що надування подушок безпеки є дещо рідкісним завданням у більшості програм програмування, це насправді досить часто в більшості завдань очищення.
ensure
альтернативу rescue Exception
вводить в оману - приклад передбачає, що вони є рівнозначними, але як зазначено ensure
, відбудеться виняток чи ні, тож тепер ваші подушки безпеки надутимуться, тому що ви пройшли понад 5 км / год, хоча нічого не пішло не так.
Тому що це фіксує всі винятки. Навряд чи ваша програма може відновитись із будь-якого з них.
Вам слід поводитися лише з винятками, з яких ви знаєте, як відновитись. Якщо ви не передбачаєте певного винятку, не обробляйте це, гучно вибивайте (запишіть деталі в журнал), тоді діагностуйте журнали та виправляйте код.
Ковтання винятків погано, не робіть цього.
Це специфічний випадок правила, що ви не повинні ловити жодне виняток, з яким ви не знаєте, як впоратися. Якщо ви не знаєте, як з цим впоратися, завжди краще дозволити деякій іншій частині системи вловлювати та обробляти її.
Я просто прочитав чудову публікацію про це на сайті honeybadger.io :
Чому не слід рятувати виняток
Проблема з врятуванням винятку полягає в тому, що він фактично рятує кожен виняток, який успадковується від винятку. Що таке .... всі вони!
Це проблема, оскільки є деякі винятки, які використовує Рубі всередині. Вони не мають нічого спільного з вашим додатком, і проковтування їх спричинить погані речі.
Ось кілька великих:
SignalException :: Перерва - Якщо ви врятуєте це, ви не можете вийти зі свого додатка, натиснувши control-c.
ScriptError :: SyntaxError - Поглинання помилок синтаксису означає, що такі речі, як ставки ("Забули щось), вийдуть з ладу.
NoMemoryError - Хочете знати, що відбувається, коли програма продовжує працювати після того, як вона використовує всю оперативну пам’ять? Я теж ні.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Я здогадуюсь, що ви насправді не хочете ковтати жоден із цих винятків на рівні системи. Ви хочете лише зафіксувати всі ваші помилки на рівні програми. Винятки спричинили ВАШ код.
На щастя, існує простий шлях до цього.