Эта статья размещена перекрестно из блога Launch School. Ознакомьтесь с дополнительными статьями о программировании и обучении на основе навыков.

В этой статье я познакомлю вас с основами работы с исключениями в Ruby. Вероятно, вы уже сталкивались с исключениями в своих программах Ruby, но вы не можете полностью понять, откуда эти ошибки. Для начала мы обсудим, что такое исключение, а также различные типы исключений и их серьезность. Затем я представлю несколько основных методов обработки общих исключений, когда они возникают в вашем коде. Наконец, мы рассмотрим создание ваших собственных исключений и использование настраиваемых классов исключений.

Что такое исключение?

Исключение - это просто исключительное состояние в вашем коде. Это не обязательно плохо, но это способ Ruby сообщить вам, что ваш код ведет себя неожиданно. Если возникает исключение, и ваш код не обрабатывает исключение, ваша программа выйдет из строя, и Ruby выдаст сообщение, сообщающее, какой тип ошибки был обнаружен.

Ruby предоставляет иерархию встроенных классов для упрощения обработки исключений. Фактически, имена исключений, которые вы видите при сбое вашей программы, например TypeError, на самом деле являются именами классов. Класс на самом верху иерархии - это класс Exception. Exception имеет несколько подклассов, многие из которых имеют собственных потомков.

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

Ниже вы можете увидеть полную иерархию классов исключений Ruby.

Exception
  NoMemoryError
  ScriptError
    LoadError
    NotImplementedError
    SyntaxError
  SecurityError
  SignalException
    Interrupt
  StandardError
    ArgumentError
      UncaughtThrowError
    EncodingError
    FiberError
    IOError
      EOFError
    IndexError
      KeyError
      StopIteration
    LocalJumpError
    NameError
      NoMethodError
    RangeError
      FloatDomainError
    RegexpError
    RuntimeError
    SystemCallError
      Errno::*
    ThreadError
    TypeError
    ZeroDivisionError
  SystemExit
  SystemStackError
  fatal

Давайте кратко рассмотрим некоторые классы в этой иерархии и подумаем, когда вы можете с ними встретиться. Если вы какое-то время писали код Ruby, вполне вероятно, что вы уже видели некоторые из этих исключений, возникающих в ваших собственных программах.

  • Вы когда-нибудь нажимали ctrl-c для выхода из программы? Это фактически вызывает исключение через класс Interrupt.
  • SyntaxError, как следует из названия, будет выдан, когда Ruby попытается выполнить код, содержащий недопустимый синтаксис. Это, вероятно, покажется вам знакомым, если вы когда-либо по ошибке оставляли def или end вне определения метода.
  • SystemStackError возникает в случае переполнения стека. Возможно, вы видели это исключение, если выполняли в своей программе рекурсивный бесконечный цикл.
  • StandardError имеет много узнаваемых потомков. ArgumentError, TypeError, ZeroDivisionError и NoMethodError - все общие исключения, которые являются детьми или внуками класса StandardError.

Когда следует обрабатывать исключение?

Чаще всего ошибки, которые вы хотите обработать, являются потомками класса StandardError, который был представлен выше. Эти исключения могут быть вызваны множеством обстоятельств, включая неожиданный ввод пользователя, неправильное преобразование типов или деление на ноль. Как правило, обрабатывать эти исключения и продолжать выполнение программы относительно безопасно.

Почему бы просто не обработать все исключения? Это может быть очень опасно. Некоторые исключения более серьезны, чем другие; есть некоторые ошибки, которые мы должны допустить, чтобы наша программа вылетела из строя. Чтобы наша программа работала должным образом, необходимо устранить важные ошибки, такие как NoMemoryError, SyntaxError и LoadError. Обработка всех исключений может привести к маскированию критических ошибок и сделать отладку очень сложной задачей.

Чтобы сами не вызывать нежелательное поведение, важно умышленно и четко указать, какие исключения вы хотите обрабатывать и какие действия вы хотите предпринять при их обработке. Действие, которое вы решите предпринять при обработке исключения, будет зависеть от обстоятельств; Примеры включают регистрацию ошибки, отправку электронного письма администратору или отображение сообщения пользователю.

Как справиться с исключительным состоянием

Блок begin / rescue

Использование блока _21 _ / _ 22_ для обработки ошибок может предотвратить сбой вашей программы, если возникнет указанное вами исключение. Давайте посмотрим на простой пример.

В приведенном выше примере будет выполняться код из предложения rescue, а не выход из программы, если код в строке 2 вызывает TypeError. Если исключение не возникает, предложение rescue не будет выполняться вообще, и программа продолжит работу в обычном режиме. Вы можете видеть, что в строке 3 мы указали, какой тип исключения нужно спасать. Если тип исключения не указан, все StandardError исключения будут восстановлены и обработаны. Помните, что нельзя указывать Ruby на спасение Exception исключений классов. Это спасет все исключения в иерархии классов Exception и, как объяснялось ранее, очень опасно.

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

В качестве альтернативы, если вы хотите выполнить одно и то же действие для более чем одного типа исключения, вы можете использовать синтаксис в строке 3 ниже.

Объекты исключений и встроенные методы

Объекты-исключения - это обычные объекты Ruby, из которых мы можем получить полезную информацию. Ruby предоставляет встроенное поведение для этих объектов, которое вы можете использовать при обработке исключения или отладке. Взгляните на Exception документацию Ruby.

Итак, как нам использовать объект исключения?

Синтаксис в приведенном выше коде спасает любой TypeError и сохраняет объект исключения в e. Некоторые полезные методы экземпляра, предоставляемые Ruby, - это Exception#message и Exception#backtrace, которые возвращают сообщение об ошибке и обратную трассировку, связанную с исключением, соответственно. Давайте посмотрим на пример этого в блоке _35 _ / _ 36_.

Приведенный выше код спасет любой тип StandardError исключения (включая всех его потомков) и выведет сообщение, связанное с объектом исключения. Такой код может быть полезен при отладке и необходимости сузить тип или причину ошибки. Вы всегда можете уточнить, какой тип исключения обрабатывать, но не забывайте никогда не спасать класс Exception.

Напомним, что объекты исключений - это просто обычные объекты Ruby, а различные типы исключений, такие как ArgumentError и NoMethodError, на самом деле являются именами классов. Следовательно, мы можем даже вызвать Object#class для объекта исключения, чтобы вернуть его имя класса.

гарантировать

Вы также можете включить предложение ensure в свой блок _43 _ / _ 44_ после последнего предложения rescue. Эта ветвь будет выполняться всегда, независимо от того, возникло ли исключение. Итак, когда это полезно? Простой пример - управление ресурсами; приведенный ниже код демонстрирует работу с файлом. Независимо от того, возникло ли исключение при работе с файлом, этот код гарантирует, что он всегда будет закрыт.

Если в блоке _47 _ / _ 48_ есть несколько предложений rescue, предложение ensure служит единой точкой выхода для блока и позволяет вам разместить весь код очистки в одном месте, как показано в приведенном выше коде.

При использовании ensure важно помнить, что очень важно, чтобы этот код сам не создавал исключения. Если код в предложении ensure вызывает исключение, любое исключение, возникшее ранее при выполнении блока _52 _ / _ 53_, будет замаскировано, и отладка может стать очень сложной.

повторить попытку

Я кратко представлю retry, но вряд ли вы будете часто его использовать. Использование retry в вашем блоке _56 _ / _ 57_ перенаправляет вашу программу обратно к оператору begin. Это позволяет вашей программе сделать еще одну попытку выполнить код, вызвавший исключение. Вы можете найти retry полезным, например, при подключении к удаленному серверу. Помните, что если ваш код постоянно дает сбой, вы рискуете попасть в бесконечный цикл. Чтобы избежать этого, рекомендуется установить ограничение на количество раз, которое вы хотите, чтобы retry выполнялось. retry должен вызываться в блоке rescue, как показано ниже в строке 8. Использование retry в другом месте вызовет SyntaxError.

Вызов исключений вручную

До сих пор в этой статье обсуждалось, как обрабатывать исключения, вызванные Ruby. В предыдущих примерах кода у нас не было контроля над тем, когда вызывать исключение или какой тип ошибки использовать; все решено за нас. Обработка исключения - это реакция на уже созданное исключение.

А теперь давайте переключимся и исследуем, как можно усилить контроль при работе с исключениями в программе. На самом деле Ruby дает вам возможность самостоятельно создавать исключения вручную, вызывая Kernel # raise. Это позволяет вам выбрать, какой тип исключения вызывать, и даже установить собственное сообщение об ошибке. Если вы не укажете, какой тип исключения вызывать, Ruby по умолчанию будет использовать RuntimeError (подкласс StandardError). Есть несколько различных вариантов синтаксиса, которые вы можете использовать при работе с raise.

Ниже приведен дополнительный вариант синтаксиса.

В следующем примере тип исключения по умолчанию будет RuntimeError, потому что ничего другого не указано. Указано сообщение об ошибке ”invalid age”.

Важно понимать, что исключения, которые вы вызываете вручную в своей программе, можно обрабатывать так же, как исключения, которые Ruby вызывает автоматически.

Выше мы поместили метод validate_age в блок _71 _ / _ 72_. Если в метод передан недопустимый возраст, будет выдано RuntimeError с сообщением об ошибке ”invalid age” и будет выполнено предложение rescue нашего блока _76 _ / _ 77_.

Вызов настраиваемых исключений

Помимо предоставления множества встроенных классов исключений, Ruby позволяет нам гибко создавать собственные классы исключений.

Обратите внимание, что наш пользовательский класс исключения ValidateAgeError является подклассом существующего исключения. Это означает, что ValidateAgeError имеет доступ ко всем встроенным функциям поведения объекта исключения, которые предоставляет Ruby, включая Exception#message и Exception#backtrace. Как обсуждалось ранее в этой статье, вам всегда следует избегать маскирования исключений из самого класса Exception и других классов исключений системного уровня. Скрытие этих исключений опасно и устранит очень серьезные проблемы в вашей программе - не делайте этого. Чаще всего вы захотите унаследовать от StandardError.

При использовании настраиваемого класса исключений вы можете указать конкретную ошибку, с которой столкнулась ваша программа, присвоив классу очень информативное имя. Это может помочь в отладке. Давайте изменим наш предыдущий пример кода и воспользуемся нашим более наглядным настраиваемым классом исключений.

Как показано в приведенном выше примере, вы можете создавать и обрабатывать настраиваемые исключения точно так же, как любое встроенное исключение, предоставляемое Ruby.

В заключении

Я надеюсь, что эта статья заставила вас почувствовать себя уверенно и узнать, как работать с исключениями в ваших программах Ruby. Некоторые из описанных здесь методов обработки ошибок могут использоваться вами очень редко, в то время как другие могут быть полезны чаще. Не стесняйтесь возвращаться к этой статье или документации для освежения информации, когда вам это нужно.