Исполнение и исключение всегда работают вместе. Если вы открываете файл, которого нет, то вы не справились с этой ситуацией должным образом, ваша программа считается некорректной.
Программа останавливается, если возникает исключение. Таким образом, исключения используются для обработки различных типов ошибок, которые могут возникать во время выполнения программы, и необходимо предпринимать соответствующие действия, а не полностью останавливать программу.
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>
Вы можете захватить исключение, используя 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
Ниже приводится поток процесса:
Вы можете использовать заявление 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 делает именно это. обеспечивается после последнего предложения 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 присутствует, оно выполняется после предложений 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
Это приведет к следующему результату:
Я не поднимаю исключение Поздравляем - ошибок нет! Обеспечение выполнения
Сообщение “Поднятая ошибка” можно записать с помощью переменной $!.
В то время как механизм исключения и 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
Стандартные классы и модули Ruby вызывают исключения. Все классы исключений образуют иерархию с классом Exception в верхней части. Следующий уровень содержит семь разных типов:
На этом уровне есть еще одно исключение, 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, по причине того, что конкретное исключение вызвало сбой записи данных.