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

Исключения — единственный механизм обработки ошибок в 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.
Из всех правил есть исключения, и из правил про исключения — тоже. Главное не противоречить логике и не забывать их документировать .

«Поэтому raise без параметра перехватывает» – Может имелось введу rescue? Иначе я не понимаю что raise может перехватывать, оно ведь бросает ошибку, а не перехватывает.
Да, конечно. Спасибо.
Леонид, спасибо. Простые истины :)
Спасибо за статью!
Прошу объяснить:
Место пользовательским исключениям в Ruby – класс RuntimeError. Если бросать ошибки класса Exception, то вышестоящие обработчики могут их не понять, и пропустить дальше.
Почему «могут их не понять»?
Ну например:
Наследовать Exception вместо RuntimeError — все равно, что вызывать пожарников, даже если всего-навсего задымилась урна.