Иерархия ошибок в Ruby, а также какие исключения нужно бросать, а какие - ловить
August 27, 2011 , revised August 30, 2011 in Ruby on Rails
Исключения – единственный механизм обработки ошибок в Ruby. Это одновременно и очень мощный, и опасный инструмент.
Поэтому важно правильно пользоваться исключениями, и в первую очередь – понимать, какие классы исключений нужно бросать, и какие – ловить.
Почему ловить Exception плохо?
Exception – базовый класс всей классовой иерархии исключений. (Чтобы ее посмотреть, можно выполнить скрипт, расположенный в статье Ruby’s Exception hierarchy). Это значит, что вообще все ошибки в Ruby наследуют класс Exception.
Например, при синтаксической ошибке бросается SyntaxError
. Наследник Exception
. А при выходе из программы - SystemExit
(очень изящный подход, по-моему):
begin
exit
rescue Exception
puts $!.inspect # #<SystemExit: exit>
end
puts 'And it was the Nth time I died' # все еще работает!
Таким образом, перехват Exception грозит неожиданными сбоями в работе скрипта.
Поэтому rescue
без параметра перехватывает не Exception
, а StandardError
, пропуская критические ошибки.
Почему бросать Exception плохо?
Место пользовательским исключениям в Ruby - класс RuntimeError
. Если бросать ошибки класса Exception
, то вышестоящие обработчики могут их не понять, и
пропустить дальше.
Плохой тон.
Хорошо: ловить только ожидаемые исключения
Перехватывай только те классы исключений, которые ты можешь обработать, либо те, которые можешь с уверенностью проигнорировать.
Всегда лучше пропустить лишнее исключение, чем не заметить неправильно перехваченную ошибку.
Хорошо: использовать короткую строковую форму raise
Лаконичный код – правильный код: raise "message"
равносильно raise RuntimeError.new("message")
. Именно такую запись лучше использовать на скорую руку.
Напомню, что RuntimeError
– наиболее подходящее место для пользовательских ошибок.
begin
raise "I'm Batman!"
rescue
puts $!.inspect # #<RuntimeError: I'm Batman!>
end
Хорошо: создавать свои классы исключений
С помощью своих классов исключений ты даешь возможность скрипту-потребителю ловить только ожидаемые исключения.
Не вижу противопоказаний для того, чтоб создавать новый класс на каждую отдельную ошибку, если, конечно, не лень.
Лучше всего создавать свою под-иерархию классов:
RuntimeError
FooLibrary::Error
FooLibrary::BarError
FooLibrary::BazError
Исключения из исключений?
–Hoptoad– Airbrake ловит все исключения, чтобы сообщать даже о синтаксических ошибках.
На случай апокалипсиса можно бросить и class EndOfWorldError < Exception
.
Из всех правил есть исключения, и из правил про исключения – тоже. Главное не противоречить логике и не забывать их документировать .
Понравился пост? Купи мне кофе