Ruby Exceptions

Выполнение и исключение (execution and the exception) всегда идут вместе. Например ваша программа открывает несуществующий файл, и понятно, что ни чего (хорошего) у нее не получится, это ошибка, ошибка которая должна быть учтена, возникновение которой предусмотрено, обработано, тут и возникают исключения, которые используются для обработки различных типов ошибок, ошибок могущих возникнуть во время выполнения программы, и принятия соответствующих мер вместо полной остановки программы, и в результате чего удается избежать возвра­та кодов ошибок, запутанной логики их анализа, а код, который обнаружива­ет ошибку, можно отделить от кода, который ее обрабатывает.

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

begin

х = Math.sqrt(y/z)

# ...

rescue ArgumentError

puts "Ошибка при извлечении квадратного корня."

rescue ZeroDivisionError

puts "Попытка деления на нуль."

end

или так:

begin

х = Math.sqrt(y/z)

# ..•

rescue => err

puts err

end

Здесь в переменной err хранится объект-исключение; при выводе ее на печать объект будет преобразован в осмысленную символьную строку.

Синтаксис:

begin

# -

rescue OneTypeOfException

# -

rescue AnotherTypeOfException

# -

else

# Other exceptions

ensure

# Always will be executed

end

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

Для каждого rescue clause в блоке begin, Ruby по очереди сравнивает возникшее исключение с каждым из параметров. Сопоставление будет успешным, если исключение, указанное в предложении rescue, совпадает с типом текущего исключения или является суперклассом этого исключения. В случае, если исключение не соответствует ни одному из указанных типов ошибок, мы можем использовать else clause (предложение) после всех предложений восстановления.

Пример:

#!/usr/bin/ruby

begin

file = open("/unexistant_file")

if file

puts "File opened successfully"

end

rescue

file = STDIN

end

print file, "==", STDIN, "\n"

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

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

Using retry Statement (Использование оператора повтора):

Вы можете зафиксировать исключение с помощью rescue block , а затем использовать оператор retry для выполнения begin block с начала.

Синтаксис:

begin

# Exceptions raised by this code will

# be caught by the following rescue clause

rescue

# This block will capture all types of exceptions

retry # This will move control to the beginning of begin

end

Пример:

#!/usr/bin/ruby

begin

file = open("/unexistant_file")

if file

puts "File opened successfully"

end

rescue

fname = "existant_file"

retry

end

Ниже приводится последовательность действий:

  • Исключение при открытии.

  • Отправиляется to rescue. fname было переназначено.

  • При повторной попытке перейти к началу начала.

  • На этот раз файл открывается успешно.

  • Продолжается основной процесс.

ПРИМЕЧАНИЕ: Обратите внимание, что если файл с замещенным именем не существует, этот пример кода повторяется бесконечно.

Следует быть осторожным , если вы используете повтор (retry Statement) для процесса исключения.

Using raise Statement (Использование оператора,инструкции 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 'I am before the raise.'

raise 'An error has occurred.'

puts 'I am after the raise.'

rescue

puts 'I am rescued.'

end

puts 'I am after the begin block.'

Это даст следующий результат:

I am before the raise.

I am rescued.

I am after the begin block.

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

#!/usr/bin/ruby

begin

raise 'A test exception.'

rescue Exception => e

puts e.message

puts e.backtrace.inspect

end

Это даст следующий результат:

A test exception.

["main.rb:4"]

Using ensure Statement (Использование оператора ensure):

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

Синтаксис

begin

#.. process

#..raise exception

rescue

#.. handle error

ensure

#.. finally ensure execution

#.. This will always execute.

end

Пример:

begin

raise 'A test exception.'

rescue Exception => e

puts e.message

puts e.backtrace.inspect

ensure

puts "Ensuring execution"

end

Это даст следующий результат:

A test exception.

["main.rb:4"]

Ensuring execution

Using else Statement (Использование оператора else):

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

Синтаксис

begin

#.. process

#..raise exception

rescue

# .. handle error

else

#.. executes if there is no exception

ensure

#.. finally ensure execution

#.. This will always execute.

end

Пример:

begin

# raise 'A test exception.'

puts "I'm not raising exception"

rescue Exception => e

puts e.message

puts e.backtrace.inspect

else

puts "Congratulations-- no errors!"

ensure

puts "Ensuring execution"

end

Это даст следующий результат:

I'm not raising exception

Congratulations-- no errors!

Ensuring execution

Сообщение об ошибке может быть записано с помощью $! variable.

Catch and Throw

Хотя "механизм исключения" raise и rescure отлично подходит для прекращения выполнения, когда что-то идет не так, иногда лучше иметь возможность выити из какой-то глубоко вложенной конструкции во время нормальной обработки. Вот здесь и пригодятся catch & throw.

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

Синтаксис:

throw :lablename

#.. this will not be executed

catch :lablename do

#.. matching catch will be executed after a throw is encountered.

end

OR

throw :lablename condition

#.. this will not be executed

catch :lablename do

#.. matching catch will be executed after a throw is encountered.

end

Пример

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

def promptAndGet(prompt)

print prompt

res = readline.chomp

throw :quitRequested if res == "!"

return res

end

catch :quitRequested do

name = promptAndGet("Name: ")

age = promptAndGet("Age: ")

sex = promptAndGet("Sex: ")

# ..

# process information

end

promptAndGet("Name:")

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

Name: Ruby on Rails

Age: 3

Sex: !

Name:Just Ruby

Class 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

# Write out the data ...

rescue

# Something went wrong!

raise FileSaveError.new($!)

end

end

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