Обработка ошибок ruby

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

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

Содержание

  1. Основное использование
  2. Пример обработки исключений в Ruby
  3. Другие блоки исключений
  4. Блокировка повтора
  5. Обеспечить блокировку
  6. Else блок
  7. Легкая обработка исключений
  8. Заключение

Основное использование

Большинство языков программирования реализуют обработку исключений с помощью блока try и catch. Однако, как и все остальное в Ruby, ключевые слова более информативны.

Мы можем выразить общий синтаксис, как показано ниже:

begin
raiseexception
# raise ecxeption
rescue exception
# rescue block
end

Мы заключаем блок обработки исключений в операторы начала и конца. Внутри этих операторов мы определяем блоки повышения и восстановления.

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

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

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

Пример обработки исключений в Ruby

Мы можем реализовать простой пример, чтобы проиллюстрировать, как работает обработка исключений в Ruby:

def err_me

begin

puts «Hi there!»

raise «string type»

rescue

puts «Never mind, I am fixed!»

end
end
err_me

В приведенном выше примере мы определяем функцию с блоком исключения.

Мы вручную вызываем исключение, которое прерывает выполнение программы и входит в блок восстановления. Это выполняет действия в блоке — в данном случае оператор put и завершает работу.

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

По умолчанию блок восстановления использует параметр StandardError. Однако в Ruby есть и другие типы ошибок, в том числе.

  1. SyntaxError
  2. IOError
  3. RegexpError
  4. ThreadError
  5. ZeroDivisionError
  6. NoMethodError
  7. IndexError
  8. NameError
  9. TypeError

Чтобы вызвать и обработать конкретный тип ошибки, мы можем передать ее блоку повышения в качестве параметра. Вот пример:

begin
raiseZeroDivisionError
rescue =>exception
puts exception.message
puts exception.backtrace.inspect
end

В приведенном выше примере мы вызываем ошибку ZeroDivisionError. Затем мы переходим к блоку восстановления, который печатает конкретный тип исключения и отслеживает источник.

Результат:

$ ruby errhandling.rb
ZeroDivisionError
[«err-handling.rb:2:in `<main>’»]

Другие блоки исключений

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

Блокировка повтора

Блок повтора используется для повторного запуска блока восстановления после возникновения исключения. Вот пример:

begin
raise ZeroDivisionError
puts «I don’t run 😢»
rescue => exception
puts «#{exception.message} caused me to die ⚰️»
retry
end

Если мы запустим приведенный выше код, он распечатает сообщение внутри блока восстановления. Он встретит блок повтора, который переходит в блок восстановления.

Распространенный вариант использования блоков повтора

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

ВНИМАНИЕ: будьте осторожны при использовании блока повтора, поскольку он является обычным источником бесконечных циклов.

Обеспечить блокировку

Если вы программировали на другом языке, таком как Python, вы, вероятно, знакомы с блоком finally. Блок обеспечения в Ruby работает аналогично блоку finally в других языках программирования.

Блок обеспечения всегда запускается в конце кода. Независимо от того, правильно ли было обработано возникшее исключение или завершено выполнение программы, оно всегда запускается или выполняется.

Вот пример:

begin
raise ZeroDivisionError
puts «I don’t run 😢»
rescue => exception
puts «#{exception.message} caused me to die ⚰️»
ensure
puts «I will always run 🚀»
end

В этом случае приведенный выше код напечатает сообщение об исключении и, наконец, запустит блок обеспечения.

ZeroDivisionError caused me to die ⚰️

I will always run 🚀

Else блок

Если исключение не возникает, мы можем реализовать блок для выполнения действия с помощью оператора else.

Например:

begin
rescue => exception
puts «#{exception.message} caused me to die ⚰️»
else
puts «Trust me, I ran successfully 😀»
ensure
puts «& I will always run 🚀»
end

Блок else помещается между блоками rescue и protect. В приведенном выше примере вы заметите, что отсутствует блок повышения, что приводит к запуску блока else.

Вот пример вывода:

Поверьте мне, я успешно провел 😀

Легкая обработка исключений

Блоки повышения и восстановления — удобный способ выполнить действие при возникновении ошибки. Однако, поскольку обработка ошибок создает трассировку стека для облегчения отладки, это может легко стать проблемой в вашей программе. Вот тут-то и пригодятся блоки для ловли и броска.

Чтобы реализовать блок catch-throw, вы начинаете с определения метки с помощью ключевого слова catch. Как только Ruby встречает блок throw, который ссылается на блок catch, он останавливает выполнение и переходит к блоку catch.

Давайте использовать пример, чтобы проиллюстрировать эту концепцию. Рассмотрим беспорядочное вложение, показанное в приведенном ниже коде:

catch(:kill_me_now) do
langs = [«Python»«Ruby»«C++»«C#»]
foriinlangsdo
for index in 1..5
if index == 3
ifi == «C#»
puts «After throw, nothing will run!’»
throw(:kill_me_now)
puts «I am C#»
end
end
end
end
end
puts «Oh boy! That was a long one!»

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

Это немедленно прекратит выполнение и вернется на уровень оператора catch.

Вот пример вывода:

After throw, nothing will run!
Oh boy! That was a long one!

Заключение

В этом руководстве показано, как реализовать обработку ошибок в Ruby с помощью блоков повышения и восстановления.

Исключительные ситуации (exceptions) — это ошибки возникшие в вашем коде и которые также представлены в виде специальных объектов. Лично у меня самой популярной ошибкой, благодаря тому, что яне владею слепым набором, является NoMethodError или NameError:

Array.hello
#NoMethodError: undefined method `hello' for Array:Class
#from (irb):3
#from /home/vladimir/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

hello
#NameError: undefined local variable or method `hello' for main:Object
#from (irb):4
#from /home/vladimir/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

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

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal

Вам не обязательно создавать в вашем коде ошибку, вы можете принудительно вызвать исключительную ситуацию при помощи метода raise:

def my_method()
  raise "SomeError message ..."
end

my_method
#exceptions.rb:2:in `my_method': SomeError message ... (RuntimeError)
#from exceptions.rb:5:in `<main>'

Давайте разберем сообщение об ошибке. Оно содержит весьма полезную информацию, которая необходима вам для исправления ошибки: где находится ошибка (exceptions.rb:2:in `my_method’), сообщение описывающее ошибку (SomeError message …), тип ошибки (RuntimeError), и место где возникла ошибка (#from exceptions.rb:5:in `<main>’).

Обработка ошибок
Реальная польза от всех этих типов ошибок заключается в возможности их обработки. Обработка ошибок — это код, который выполняется только при условии возникновения ошибок. Код, ошибки в котором следует обрабатывать необходимо заключить в блок begin — end, а отлавливание ошибок следует производить при помощи ключевого слова rescue. Пример:

begin
  100 / 0
rescue
  puts "Fuck! Divider is zero!"
end
#=> Fuck! Divider is zero!

Код после rescue выполнится только после возникновения исключительной ситуации, любой исключительной ситуации! rescue может принимать параметр — тип исключительной ситуации для того, чтобы обрабатывать лишь один определенный тип ошибок, таким образом можно выполнять различный код для различных ошибок. Пример:

begin
  some_undefined_method_call
rescue NameError
  puts "Fuck! Undefined method!"
end
#=>Fuck! Undefined method!

Иногда бывает необходимость выполнить кусок кода независимо от того была ошибка или небыло. Для этого существует ensure. Пример:

begin
  some_undefined_method_call
rescue NameError
  p "Fuck! Undefined method!"
ensure
  p "RubyDev.ru"
end
#=>"Fuck! Undefined method!"
#=>"RubyDev.ru"

Выможете использовать обработчик ошибок rescue и ensure не только в контексте begin — end, но и в контексте любого блока кода, например в контексте метода или класса. Пример:

def hello(msg = "")
  raise "Empty message!" if msg == ""
  puts(msg)
rescue
  puts "Some Error!"
end

hello("Rubydev.ru") #Rubydev.ru
hello #Some Error!

«Кошэрная» обработка ошибок

Чтобы в обработчике ошибок иметь доступ к различной информации об ошибке, необходимо использовать следующий синтаксис:

def hello(msg = "")
  raise "Empty message!" if msg == ""
  puts(msg)
rescue RuntimeError => error
  puts error.inspect
end

hello #<RuntimeError: Empty message!>

Теперь в контексте обработчика ошибок мы имеем доступ к экземпляру ошибки, что дает нам возможность получить некоторые данные об ошибке:

def hello(msg = "")
  raise "Empty message!" if msg == ""
  puts(msg)
rescue RuntimeError => error
  puts error.message
  puts error.backtrace
end

hello
#=>Empty message!
#exceptions.rb:2:in `hello'
#exceptions.rb:9:in `<main>'

Создание собственных типов ошибок

Глядя на иерархию исключительных ситуаций можно увидить, что все исключительные ситуации происходят от класса Exception. Доказательство:

puts RuntimeError.superclass #StandardError
puts RuntimeError.superclass.superclass #Exception

Хотя все ошибки и происходят от класса Exception, вам следует использовать класс StandartError для наследования поскольку Exception слишком низкоуровневый класс, который обслуживает между всего прочего еще и ошибки окружения. Пример создания собственной ошибки:

class SomeError < StandardError
  def message
    "Some Error!"
  end
end

raise SomeError #exceptions.rb:7:in `<main>': Some Error! (SomeError)

Лучшая благодарнорность автору — ваши комментарии!

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

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

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

Синтаксис

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# другие исключения
ensure
# Всегда будет выполняться
end

Все от begin до rescue защищено. Если во время выполнения этого блока кода возникает исключение, управление передается блоку между rescue и end.

Для каждого предложения rescue в begin Ruby сравнивает поднятое исключение с каждым из параметров по очереди. Совпадение завершится успешно, если исключение, указанное в предложении rescue, совпадает с типом создаваемого исключения или является суперклассом этого исключения.

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

Пример

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "Файл успешно открыт "
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "n"

Это приведет к следующему результату. Вы можете видеть, что STDIN заменяется file, потому что произошла ошибка открытия.

#<IO:0xb7d16f84>==#<IO:0xb7d16f84>

Использование утверждения Retry

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

Синтаксис

begin
 #Исключения в этом коде
 #поймал следующий пункт rescue
rescue
   # Этот блок будет захватывать Все типы исключений
   retry  # Это переместит управление в начало <i>begin</i>
end

Пример

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "Файл успешно открыт"
   end
rescue
   fname = "existant_file"
   retry
end

Ниже приводится поток процесса:

  • Исключение произошло при открытии.
  • Вызвано rescue. fname было переназначено.
  • retry указал на начало begin.
  • Этот файл открывается успешно.
  • Продолжал необходимый процесс.

Примечание

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

Использование выражения raise

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

Синтаксис

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

Первая форма просто повторно вызывает текущее исключение (или RuntimeError, если нет текущего исключения). Используется в обработчиках исключений, которые должны перехватывать исключение, прежде чем передавать его.

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

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

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

Пример

#!/usr/bin/ruby

begin  
   puts 'Код перед raise.'  
   raise 'Произошла ошибка.'  
   puts 'Код после raise.'  
rescue  
   puts 'Код в rescued.'  
end  
puts 'Код после блока begin.'

Это приведет к следующему результату:

Код после raise.  
Код в rescued.  
Код после блока begin.  

Еще один пример, показывающий использование raise:

#!/usr/bin/ruby

begin  
   raise 'Тестовое исключение.'  
rescue Exception => e  
   puts e.message  
   puts e.backtrace.inspect  
end

Это приведет к следующему результату:

Тестовое исключение.
["main.rb:4"]

Использование инструкции ensure

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

Предложение ensure делает именно это. обеспечивается после последнего предложения rescue и содержит кусок кода, который всегда будет выполняться по завершении блока. Не имеет значения, нормально ли завершиться блок, если есть исключения raises и rescues, или если он завершается исключением, блок ensure будет запущен.

Синтаксис

begin 
   #.. процесс
   #..поднять исключение
rescue 
   #.. ошибка обработки 
ensure 
   #.. наконец, убедитесь в выполнении
   #.. Это всегда будет выполняться.
end

Пример

begin
   raise 'Тестовое исключение.'
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
ensure
   puts "Обеспечение выполнения"
end

Это приведет к следующему результату:

Тестовое исключение.
["main.rb:4"]
Обеспечение выполнения

Использование инструкции else

Если предложение else присутствует, оно выполняется после предложений rescue и до того, как оно будет выполнено.

Тело предложения else выполняется только в том случае, если в основной части кода не возникают исключения.

Синтаксис

begin 
   #.. процесс
   #.. вызывается исключение
rescue 
   # .. ошибка обработки
else
   #.. выполняется, если нет исключения
ensure 
   #.. наконец, убедитесь в выполнении
   #.. Это всегда будет выполняться.
end

Пример

begin
   # raise 'Тестовое исключение.'
   puts "Я не поднимаю исключение"
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
else
   puts "Поздравляем - ошибок нет!"
ensure
   puts "Обеспечение выполнения"
end

Это приведет к следующему результату:

Я не поднимаю исключение
Поздравляем - ошибок нет!
Обеспечение выполнения

Сообщение «Поднятая ошибка» можно записать с помощью переменной $!.

Catch и Throw

В то время как механизм исключения и rescue отлично подходит для отказа от выполнения, когда что-то идет не так, иногда бывает приятно выпрыгнуть из какой-то глубоко вложенной конструкции во время нормальной обработки. Это — то, где может пригодится catch и throw.

catch определяет блок, который помечен с данным именем (которое может быть символ или строка). Блок выполняется нормально до тех пор, пока не будет обнаружен throw.

Синтаксис

throw :lablename
#.. это не будет выполнено
catch :lablename do
#.. соответствующий catch будет выполняться после обнаружения throw.
end

OR

throw :lablename condition
#.. это не будет выполнено
catch :lablename do
#.. соответствующий catch будет выполняться после обнаружения throw.
end

Пример

В следующем примере используется throw для прекращения взаимодействия с пользователем, если ‘!’ набирается в ответ на любое приглашение.

def promptAndGet(prompt)
   print prompt
   res = readline.chomp
   throw :quitRequested if res == "!"
   return res
end

catch :quitRequested do
   name = promptAndGet("Имя: ")
   age = promptAndGet("Возраст: ")
   sex = promptAndGet("Пол: ")
   # ..
   # process information
end
promptAndGet("Имя:")

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

Имя: Ruby on Rails
Возраст: 18
Пол: !
Имя: AndreyEx

Класс Exception

Стандартные классы и модули Ruby вызывают исключения. Все классы исключений образуют иерархию с классом Exception в верхней части. Следующий уровень содержит семь разных типов:

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError
  • SystemExit

На этом уровне есть еще одно исключение, Fatal, но интерпретатор Ruby использует это только внутренне.

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

Давайте посмотрим на пример:

class FileSaveError < StandardError
   attr_reader :reason
   def initialize(reason)
      @reason = reason
   end
end

Теперь рассмотрим следующий пример, который будет использовать это исключение:

File.open(path, "w") do |file|
begin
   # Запишите данные ...
rescue
   # Что-то пошло не так!
   raise FileSaveError.new($!)
end
end

Важной линией здесь является поднять FileSaveError.new ($!). Мы вызываем raise, чтобы сигнализировать о том, что произошло исключение, передав ему новый экземпляр FileSaveError, по причине того, что конкретное исключение вызвало сбой записи данных.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Исключения

Исключение — это допустимая ошибка, возникающая в процессе выполнения программы, прерывающая выполнение до тех пор, пока не ислючение не будет обработано. Если исключение не обрабатывается, то выполнение программы прекращается.

Каждое исключение относится к классу Exception и его производным. Обычно название каждого подкласса, содержит полную информацию о причине возникновения исключения.

Для создания новых типов исключений обычно используются классы StandartError и RuntimeError.

Системные ошибки, имеющие стандартный цифровой код, также относятся к исключениям. Модуль Errno динамически связывает полученные от операционной системы цифровые коды с подклассами Exception. При этом для каждой ошибки создается собственный подкласс SystemCallError, на который ссылается константа в модуле Errno. Цифровой код ошибки может быть получен с помощью константы Errno (Errno::<ErrorKlass>::Errno).

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

  • Exception — базовый класс для всех исключений.

    • NoMemoryError — выделение памяти не может быть выполнено;

      ScriptError— базовый класс для ошибок интерпретации;
      + LoadError — файл не может быть загружен;
      NotImplemenetedError — метод не поддерживается системой;
      SyntaxError — ошибка в синтаксисе;

      SecuirityError — нарушение требований безопасности;

      SignalException — получение сигнала от системы;
      + Interrupt — сигнал прервать процесс выполнения (обычно Ctrl+C);

      SystemExit — завершение выполнения программы системой;

      SystemStackError — переполнение стека;

      StandardError — базовый класс для стандартных ошибок выполнения;
      + Math::DomainError — объекты не принадлежат области определения функции;
      ArgumentError — ошибка при передаче аргументов;

        _EncodingError_ - базовый класс для ошибок, связанных с кодировкой;
          + _Encoding::CompatibilityError_ - исходная кодировка не совместима с требуемой;  
            _Encoding::ConverterNotFoundError_ - требуемая кодировка не поддерживается;  
            _Encoding::InvalidByteSequenceError_ - текст содержит некорректные байты;  
            _Encoding::UndefinedConversionError_ - текст содержит неопределенные символы;
      
        _FiberError_ - ошибка при работе с управляемыми блоками;
      
        _IOError_ - возникновение ошибки при работе с потоками;
          + _EOFError_ - достигнут конец файла;
      
        _IndexError_ - индекс не найден;
          + _KeyError_ - ключ не найден;  
            _StopIteration_ - завершение итерации;
      
        _LocalJumpError_ - блок не может быть выполнен;
      
        _NameError_ - неизвестный идентификатор;
          + _NoMethodError_ - неизвестный метод;
      
        _RangeError_ - выход за границы диапазона;
          + _FloatDomainError_ - попытка преобразования констант для определения специальных чисел (NaN и т.д.);
      
        _RegexpError_ - ошибка в регулярном выражении;
      
        _RuntimeError_ - универсальный класс для ошибок выполнения;
      
        _SystemCallError_ - базовый класс для системных ошибок;
      
        _ThreadError_ - ошибка при работе с процессами;
      
        _TypeError_ - неправильный тип объекта. Данное исключение также возникает при объявлении наследования для существующего класса;
      
        _ZeroDivisionError_ - деление целого числа на ноль.
      

Методы

Exception

::exception( message = nil ) # -> exception

Используется для создания объекта. Для аргумента вызывается метод object.to_str.

::new( mesage = nil ) # -> exception

Используется для создания объекта.

.exception( message = nil ) # -> exception

Используется для получения нового экземпляра того же класс. Для аргумента вызывается метод .to_str.

.backtrace # -> array

Используется для получения данных о распространении исключения. Каждый элемент имеет вид:
"имя_файла:номер_строки: in 'идентификатор_метода'"
или
"имя_файла:номер_строки"

.set_backtrace(array) # -> array

Используется для изменения данных о распространении исключения.

.to_s # -> string
Синонимы: message

Сообщение об ошибке (или идентификатор класса).

.inspect # -> string Идентификатор класса.

LoadError [ruby 2.0]

.path # -> string

Метод используется для получения пути, по которому не был найден файл.

SignalException

::new(sig_name) # -> a_signal_exception

(sig_number, name = nil)

Метод используется для создания нового объекта. Название сигнала должно быть известно интерпретатору.

.signo # -> number

Метод используется для получения номера сигнала.

SystemExit

::new( status = 0 ) # -> exception

Используется для создания нового объекта.

.status # -> integer Статус завершения программы.

.success? # -> bool

Проверка удалось ли завершение программы.

Encoding::InvalidByteSequenceError

.destination_encoding # -> encoding Требуемая кодировка

.destination_encoding_name # -> string Название требуемой кодировки.

.source_encoding # -> encoding

Исходная кодировка. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.source_encoding_name # -> string

Название исходной кодировки. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.error_bytes # -> string

Байт из-за которого возникло исключение.

.incomplete_input? # -> bool

Проверка возникновения исключения из-за преждевременного завершения текста.

.readagain_bytes # -> string

Байт, обрабатываемый в момент возникновения исключения.

Encoding::UndefinedConversionError

.destination_encoding # -> encoding Требуемая кодировка

.destination_encoding_name # -> string Название требуемой кодировки.

.source_encoding # -> encoding

Исходная кодировка. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.source_encoding_name # -> string

Название исходной кодировки. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.error_char # -> string

Символ из-за которого возникла ошибка.

StopIteration

.result # -> object
Результат итерации.

LocalJumpError

.exit_value # -> object

Аргумент, передача которого привела к возникновению исключения.

.reason # -> symbol

Идентификатор инструкции, выполнение которой привело к возникновению исключения (:break, :redo, :retry, :next, :return, или :noreason).

NameError

::new( message, name = nil ) # -> exception

Используется для создания нового объекта.

.name # -> name

Идентификатор, использование которого привело к возникновению исключения.

NoMethodError

::new( message, name, *args ) # -> exception

Используется для создания нового объекта.

.args # -> object

Аргументы, переданные отсутствующему методу.

SystemCallError

::new( message, integer ) # -> exception

Используется для создания нового экземпляра класса из модуля Errno (если методу передан известный системе цифровой код ошибки) или класса SystemCallError.

.errno # -> integer Цифровой код ошибки.

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

Вызов исключения

Вызов исключения выполняется с помощью частного метода экземпляров из модуля Kernel.

.raise( message = nil ) # -> exception

( exc = RuntimeError, message = nil, pos = caller ) # -> exception
Синонимы: fail

Используется для повторного вызова последнего исключения или создания нового ( RuntimeError), если $! ссылается на nil.

В другом случае методу передаются любой объект, отвечающий на вызов метода .exception, сообщение об ошибке и текущая позиция выполнения программы.

Обработка исключений

Обработка событий выполняется с помощью предложения rescue, которое может использоваться только в теле предложений begin, def, class, или module.

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

Если исключения возникло в результате обработки другого исключения, то поиск обработчиков выполняется заново.

После обработки исключения выполнение программы не продолжается.

Полный синтаксис

  begin
    тело_предложения
  rescue
    тело_обработчика
  else
    code
  ensure
    code
  end
  • Тело обработчика выполняется после возникновения исключения в теле предложения. Переменная $! при этом ссылается на конкретный экземпляр исключения.

    Чтобы инициализировать локальную переменную используют инструкцию
    rescue => локальная_переменная.

  • По умолчанию обрабатываются экземпляры StandardError и его производных.

    Для ограничения типов обрабатываемых исключений используют инструкцию rescue class или rescue class => локальная_переменная. Несколько классов разделяются запятыми.

  • Инструкция else выполняется если исключений не получено. При этом исключения, возникшие в теле инструкции не обрабатываются.

  • Инструкция ensure выполняется после выполнения всего предложения. Результат ее выполнения не влияет на результат выполнения предложения (кроме случаев использования инструкций return, break и т.д)

Краткий синтаксис:

код rescue тело_обработчика

Если в коде будет вызвана ошибка, то выполняется тело обработчика. Обрабатываются только экземпляры StandardError и его производных.

Catch и Throw

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

.catch(name = nil) { |name| } # -> object

Используется для создания прерываемого фрагмента кода. Выполнение останавливается при вызове метода object.throw с тем же идентификатором. При вызове без аргументов новый случайный идентификатор передается блоку.

.throw( name, *args )

Используется для завершения выполнения блока, переданного методу object.catch с тем же идентификатором (иначе возникает исключение). Поиск блока выполняетcя вверх по иерархии области видимости. Дополнительные аргументы возвращаются методом object.catch.

 on
November 19, 2019

What is exception handling?

Software systems can be quite prone to error conditions. Systems that involve user interaction are more vulnerable to exceptions as they attract errors at multiple fronts. Errors can take many forms — syntactical errors, network errors, form input errors, invalid authentication errors etc. If not accounted for, these can affect user experience and can even manifest as security loopholes, enabling attackers to compromise the system.

Exception handling is used to prevent system failures and to save the user from lousy error messages.

In most cases, when an error condition occurs, the programming language forces the program to terminate and therefore program control is lost. Exception handling allows you (or the program) to not lose control and to be able to deal with what happens after the error has occurred. This gives you some room to make amends — to show affable error messages, to release some resources or to conveniently redirect the user to a relevant page. 

Exception handling helps you clear up the mess, without making it look like one.

It is the process of accommodating error conditions and responding to them programmatically, resulting in execution of a presumably alternate, but already planned sequence of code.

Exception handling in Ruby

In the vast majority of languages, exception handling is performed using “try, throw and catch” mechanisms. When the try block is encountered, the compiler becomes super aware in the lookout for an exception (thrown either manually, or by the language itself) and instructs the catch block to deal with it. Ruby, however decided to go with different names for their conventional exception handling system. 

I find the Ruby terminology for dealing with exceptions a lot more poetic. 

There is a beginning, followed by errors raised and eventually rescued. And like all good things, it comes to an end, unless you want to retry.

begin
    # raise exception here

rescue
    # raised exception handled here

retry
    # retrying the code in the begin block


ensure
    # Always executed

end

Exception handling in Ruby primarily consists of —

  • Begin — end block
  • Raise
  • Rescue
  • Retry
  • Ensure

Begin — end block, raise, rescue and ensure are analogous to the programming concepts of try, throw, catch and finally. Let’s try to understand them.

Note: Ruby also provides throw and catch functions for handling exceptions, which are lightweight counterparts of raise and rescue that do not build stack traces of the exceptions. Even though raise and rescue based mechanisms are more prevalent among Ruby developers, we’ll see how Ruby’s throw and catch mechanisms can help later in this post.

2.1 Begin — end block

The error prone part of your code comes in the begin block. The corresponding exception handling, through the raise and rescue mechanisms, also happens inside the begin block.

You can use begin blocks to sectionalize the different types of errors that are likely to show up. Differently erroneous codes can be put into different begin — end blocks and can accordingly be handled.

begin
    # code likely to give error comes in this block
    # raising and rescuing something
end

begin
    # raising and rescuing something else
end

Note: The body of a Ruby method can also act as begin-end block and thus does not require an explicit ‘begin’ call.

def raise_and_rescue
  # begin call not necessary 
  # raising and rescuing
end  

2.2 Raise

An exception can either be raised manually by the developer, using the raise command, or automatically by the language itself. Exceptions are raised automatically on incurring a syntactical error, like a variable not declared, a function not defined or an invalid math operation etc.

Based on your application, custom exception conditions can be catered to by manually raising errors. 

A very common use case would be a signup webpage that doesn’t submit the form until you have entered (and/or re-entered) a valid password. This sort of client-side proofing can save you from an unnecessary HTTP request to your server which might either affect database integrity or respond with an error message, which will need to be handled later. 

Let’s see how we can raise an exception in Ruby — 

begin  
    # if some condition is met or not met ->   
    raise "I am the error message!"
    # below lines are not executed    
end  

Here we are just raising an exception, not handling it (yet). This results in the program being terminated where the exception is raised, and as a result, the lines below are not executed. A language-generated error message is output as shown below — 

Ruby1.png

To prevent this, we can handle the exceptions in our own way, by using rescue blocks.

2.3 Rescue to the rescue

Fire.gif

Rescue blocks are like catch blocks that take control of the program after an exception has been raised. Raised exceptions can be followed by one or more rescue blocks to handle the exceptions based on their type. A generic rescue call looks like this — 

begin
    # if some condition is met or not met ->
    raise 'I am the error message!'

    puts 'I am not executed.'
rescue    
    puts 'Rescue to the rescue!'
end    

puts 'Amicably exiting ..'  

Once the exception is handled, execution resumes from after the begin block.

Ruby2.png

The rescue call defaults to a StandardError parameter. Rescue calls can however be made to handle specific exceptions. Some common Ruby errors (also common across other languages) are — 

  • IndexError
  • NameError
  • RangeError
  • TypeError
  • ZeroDivisionError
  • RuntimeError

Rescue blocks can be made to cater to certain types (classes) of exceptions.

In this way, raised exceptions can be followed by multiple rescue clauses, each responsible for handling a different type of exception. Let’s see how we can do this in code — 

begin  
  # error raised here 
rescue IndexError  
  # dealing in one way
rescue RangeError  
  # dealing in another way  
end 

Arguments can be used with the rescue clause to act as placeholders for the rescued exception — 

begin  
    raise 'I am the error message!'  
rescue StandardError => e  
    # inspecting the raised exception
    puts e.message

    # information about where the exception was raised  
    puts e.backtrace.inspect  
end 

Ruby3.png

The output tells how the exception was raised in line 2 of our code.

Specific types (classes) of exceptions can be raised even with custom error messages. For example —

begin
raise ZeroDivisionError, "Custom error message"
rescue ZeroDivisionError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
end 

Ruby4.png2.4 Else

Sometimes it’s possible that there was nothing to rescue from, that no exception was raised. The else clause can be used in this case as such — 

begin  
rescue StandardError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
else
    puts 'No exception to capture?'
end 

Ruby5.pngMagic variables

When exceptions are raised, Ruby assigns two variables — $! and $@ with information about the raised exception.

  • $! stores the exception message
  • $@ stores the backtrace
begin
raise ZeroDivisionError, "Custom error message"
rescue ZeroDivisionError => e
    # inspecting the raised exception
    puts $! # equivalent to e.message
    puts $@ # equivalent to e.backtrace.inspect
end 

Ruby6.png
2.5 Retry

Well, what if you don’t want to give up? You might want to keep trying till it works. 

undefined

This could be a common case when network connections are inconsistent. 

As I type this post on a Google document, at my home, on a poor internet connection, I can see the webpage continuously trying to keep me connected to the internet.

Till now we have seen how rescue blocks can be used to deal with what happens after an exception has occurred. The retry clause allows us to run the begin block again. This can come in handy after we have fixed what caused the exception to happen in the first place. 

Let’s see how retry can help, with a rather simple example. 

We know division of a number by zero is undefined. In the example below, we try to divide a by b (a / b). Initially the denominator, b is zero and therefore the division would raise a ZeroDivisionError. In the rescue block, we change the denominator to 1 and retry the whole thing.

a = 10
b = 0
begin
puts a/b
rescue ZeroDivisionError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
    b = 1
    retry
else
    puts 'No exception to capture?'
    puts 'Division was successful!'
end 

Ruby7.png

2.6 Ensure

Ensure is like the finally block that we have in other programming languages. It is there to always run at the end of each begin block. Regardless of any control sequence that the program ends up following, the ensure block is always executed at the end of it’s parent begin block. 

begin
    # if some condition is met or not met ->
    raise 'I am the error message!'
rescue    
    puts 'Rescue to the rescue!'
ensure
    puts 'Always executed at the end.'
end    

Ruby8.pngEnsure blocks are executed even if there isn’t a rescue block to handle a thrown exception. 

begin
    # if some condition is met or not met ->
    puts 'Begin!'
    raise 'I am the error message!'
ensure
    puts 'Executed even when there is no rescue clause'
end 

Ruby9.png

Note how in this case, even though the ensure block is executed at the end, the raised exception manifests as a runtime error after the ensure block.

Ensure blocks are generally used to free up any resources after an exception has been raised eg. closing database connections, closing file handlers etc. Without such blocks, the program abruptly terminates without clearing the resources.

Throw and catch in Ruby — lightweight alternatives

It’s not a good idea to exceptionally over-handle your code. Extensive raising and rescuing can take a toll on the system’s performance. 

Each time an exception is raised, a stack trace is built that helps in debugging the error. Too many of such handled exceptions can soon become a terrible bottleneck.

Turns out there is a lightweight alternative to the conventional approach that allows you to smoothly transfer program control out of complex nested blocks. Throw clauses and catch blocks in Ruby are linked through a common label name that both share.

throw :labelname
# ..
catch :labelname do
# ..
end

You can also use conditional throw calls as such — 

throw :labelname if a > b
# ..
catch :labelname do
# ..
end

Throw and catch in Ruby are quite different from conventional throw-catch mechanisms in other languages. In Ruby, they are used more for terminating execution quickly, and not as much for handling exceptions. 

It can come in handy when you are working with nested for loops and you want to get out of the computationally expensive mess when a condition is met, without having to use multiple break statements. Throw helps program control to move around swiftly to help programs execute faster, in a more efficient manner, compared to slow conventional begin/raise/rescue mechanisms that are more concerned about the raised exception, where it happened etc.

The way that throw and catch are used is that throw calls are made from inside catch blocks, transferring control from deep inside a nested construct, back to the level of the catch block in the code.

catch(:get_me_out) do
  # arbitrary nested for loop
  for a in 1..5 do
    for b in 1..5 do
      # arbitrary condition
      if a + b == 7
        # throwing to get out of the nested loops
        throw(:get_me_out)
      end
    end
  end
end

puts 'Good to go!'

Ruby10.png

A throw function can also be called with a custom message as an argument, which the catch block ends up returning after something is thrown.

thrown_msg = catch(:get_me_out) do

  # arbitrary nested for loop
  for a in 1..5 do
    for b in 1..5 do

      # arbitrary condition
      if a + b == 7

        # throwing to get out of the nested loops
        throw(:get_me_out, 'Inside this mess, get me out')
      end
    end
  end
end

puts thrown_msg
puts 'Good to go!'

Ruby11.png

Custom Exception class in Ruby

We can create our own Exception classes that cater to specific requirements in our projects. This can be done by inheriting from the provided StandardError class.

class MyCustomError < StandardError
end

raise MyCustomError

Ruby12.png

It’s good to have an error message to accompany the raised exception. We can do this by overriding the initialize method for our custom class with a default msg argument.

class MyCustomError < StandardError
  def initialize(msg="Custom error message")
    # to override the method
    super
  end
end

raise MyCustomError

Ruby13.png

Wrapping it up

wrap.gif

In this post, we looked at —

  • What is exception handling (in general)
  • Exception handling in Ruby —
    • Begin — end block
    • Raise
    • Rescue
    • Else
    • Retry
    • Ensure
  • Throw and catch in Ruby
  • Custom exception classes

All in all, we learnt how Ruby prefers a different terminology for dealing with exceptions. Except for the retry function, the begin / raise / rescue paradigm resonates with the conventional try / throw / catch setup used across most other programming languages. Throw and catch in Ruby however help in quickly getting yourself out of complex constructs. We also saw how we can create our application-specific custom Exception classes that inherit from the StandardError class.

Try ScoutAPM

Thanks for reading our blog. Now that you have a handle on handling exceptions, you need the best way to find them in your application.  Sign up here today for a free trial or contact us with any queries you might have  Want to show off Scout to your fellow developers? Click here for some free ScoutAPM swag.

Понравилась статья? Поделить с друзьями:
  • Обработка ошибок react native
  • Обработка ошибок 1с пример
  • Обработка ошибок python яндекс практикум
  • Обработка ошибки 404 spring
  • Обработка ошибок python selenium