Перейти к основному содержимому

Иерархия исключений в Ruby

Разработчику Архитектору
Сначала — общая теория

Ошибка vs исключение — в Ruby перехватывают объекты через begin/rescue/ensure; фатальные сбои VM в обычный rescue не попадают.
Общая теория стека — Ошибки, исключения и отказоустойчивость.


О чём эта статья

Исключения в Ruby — объекты; перехват через begin / rescue / ensure, повторный выброс — raise. Иерархия Exception / StandardError и типичные классы ошибок.

Связь: управление.

Интерактивное демо — часть сценариев на Python (try / except); в Ruby — begin / rescue / ensure, но стек вызовов и раскрутка те же. Подробнее: ошибки и исключения.

Play ITЗагрузка интерактивного демо…

Теория

В Ruby перехватывают объекты-исключения через begin / rescue / ensure; ensure выполняется при любом исходе, как finally.

В общий rescue без типа попадают только наследники StandardError — не перехватывайте так SystemExit и SignalException.

Повторный сбой — raise.

Теория стека и крэшей — ошибки и исключения.


Корневой класс

  • Exception

Основные подклассы Exception

  1. SystemExit — вызывается при завершении программы (exit, Kernel#exit).

  2. SignalException — получение сигнала ОС (например, SIGINT при нажатии Ctrl+C).

    • Interrupt — частный случай SignalException для SIGINT.
  3. ScriptError — ошибки синтаксиса и загрузки:

    • LoadError
    • NotImplementedError
    • SyntaxError
  4. StandardErrorосновной родитель для большинства исключений, возникающих в пользовательском коде.

    • ArgumentError
      • UncaughtThrowError
    • EncodingError
      • CompatibilityError
    • FiberError
    • IOError
      • EOFError
    • IndexError
      • KeyError
      • StopIteration
    • LocalJumpError
    • NameError
      • NoMethodError
    • RangeError
      • FloatDomainError
    • RegexpError
    • RuntimeError
    • SecurityError
    • SystemCallError — базовый класс для системных ошибок (аналог Errno::*).
      • Подклассы создаются динамически — Errno::ENOENT, Errno::EACCES, Errno::EEXIST и т.д.
    • ThreadError
    • TypeError
    • ZeroDivisionError
  5. fatal — внутренний псевдокласс (на самом деле не используется напрямую); некоторые фатальные ошибки VM могут быть представлены как fatal, но они не перехватываются обычным rescue.


Особенности

  • rescue без аргументов перехватывает только StandardError и его подклассы.
    Пример:
begin
# ...
rescue => e
# то же, что rescue StandardError => e
end
  • Чтобы перехватить все исключения (включая SystemExit, SignalException), нужно явно указать Exception:
begin
# ...
rescue Exception => e
# крайне не рекомендуется в production
end
  • Системные ошибки (Errno::*) генерируются автоматически при ошибках системных вызовов (например, открытие несуществующего файла → Errno::ENOENT).

  • NoMethodError — одна из самых частых ошибок: вызов несуществующего метода.

  • KeyError — выбрасывается при использовании Hash#fetch с отсутствующим ключом без значения по умолчанию.

  • StopIteration — используется для завершения итераторов (внутренне в Enumerator).


Как получить список программно

Ruby позволяет инспектировать иерархию классов:

# Все подклассы Exception
def subclasses_of(klass)
klass.subclasses + klass.subclasses.flat_map { |k| subclasses_of(k) }
end

puts subclasses_of(Exception).map(&:name).sort

Примечание: метод subclasses доступен только в режиме отладки или при подключении соответствующих утилит; в общем случае можно использовать ObjectSpace.each_object(Class).


Рекомендации

  • Для пользовательских исключений создавайте подклассы StandardError:
class MyCustomError < StandardError; end
  • Не перехватывайте Exception без веской причины — это может помешать корректному завершению программы (например, игнорированию Ctrl+C).