Иерархия ошибок в Ruby, а также какие исключения нужно бросать, а какие - ловить

August 27, 2011, revised August 30, 2011 Ruby исключения Airbrake

Исключения – единственный механизм обработки ошибок в 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.

Из всех правил есть исключения, и из правил про исключения – тоже. Главное не противоречить логике и не забывать их документировать .

Buy Me a Coffee at ko-fi.com