Поиск…


Основной блок обработки ошибок

Давайте сделаем функцию для деления двух чисел, это очень доверительно относится к ее вводу:

def divide(x, y)
  return x/y
end

Это будет отлично работать для большого количества ресурсов:

> puts divide(10, 2)
5

Но не все

> puts divide(10, 0)
ZeroDivisionError: divided by 0

> puts divide(10, 'a')
TypeError: String can't be coerced into Fixnum

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

def divide(x, y)
  begin
    return x/y
  rescue
    puts "There was an error"
    return nil
  end
end

> puts divide(10, 0)
There was an error

> puts divide(10, 'a')
There was an error

Сохранение ошибки

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

def divide(x, y)
  begin
    x/y
  rescue => e
    puts "There was a %s (%s)" % [e.class, e.message]
    puts e.backtrace
  end
end

> divide(10, 0)
There was a ZeroDivisionError (divided by 0)
       from (irb):10:in `/'
       from (irb):10
       from /Users/username/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'

> divide(10, 'a')
There was a TypeError (String can't be coerced into Fixnum)
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `eval'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `evaluate'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/context.rb:380:in `evaluate'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:489:in `block (2 levels) in eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:623:in `signal_status'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:486:in `block in eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `loop'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `catch'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:485:in `eval_input'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:395:in `block in start'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:394:in `catch'
/Users/username/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:394:in `start'
/Users/username/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'

Проверка различных ошибок

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

def divide(x, y)
  begin
    return x/y
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
    return nil
  rescue TypeError
    puts "Division only works on numbers!"
    return nil
  end
end

> divide(10, 0)
Don't divide by zero!

> divide(10, 'a')
Division only works on numbers!

Если вы хотите сохранить ошибку для использования в блоке rescue восстановления:

rescue ZeroDivisionError => e

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

def divide(x, y)
  begin
    return x/y
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
    return nil
  rescue TypeError
    puts "Division only works on numbers!"
    return nil
  rescue => e
    puts "Don't do that (%s)" % [e.class]
    return nil
  end
end

> divide(nil, 2)
Don't do that (NoMethodError)

В этом случае, пытаясь разделить nil на 2 не ZeroDivisionError или TypeError , поэтому он обрабатывается по умолчанию rescue пункта, который печатает сообщение , чтобы сообщить нам , что это был NoMethodError .

Повторная

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

def divide(x, y)
  begin
    puts "About to divide..."
    return x/y
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
    y = 1
    retry
  rescue TypeError
    puts "Division only works on numbers!"
    return nil
  rescue => e
    puts "Don't do that (%s)" % [e.class]
    return nil
  end
end

Если мы передадим параметры, которые, как нам известно, вызовут TypeError , будет выполняться предложение begin (помечено здесь, распечатав «О делении»), и ошибка поймана по-прежнему, и возвращается nil :

> divide(10, 'a')
About to divide...
Division only works on numbers!
 => nil

Но если мы передаем параметры , которые вызывают ZeroDivisionError , то begin условие выполняется, то ошибка поймана, делитель изменяется от 0 до 1, а затем retry Заставляет begin блок снова запустить (сверху), теперь с разные y . Во второй раз ошибки нет, и функция возвращает значение.

> divide(10, 0)
About to divide...     # First time, 10 ÷ 0
Don't divide by zero!
About to divide...     # Second time 10 ÷ 1
=> 10

Проверка отсутствия ошибки

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

def divide(x, y)
  begin
    z = x/y
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
  rescue TypeError
    puts "Division only works on numbers!"
    return nil
  rescue => e
    puts "Don't do that (%s)" % [e.class]
    return nil
  else
    puts "This code will run if there is no error."
    return z
  end
end

Предложение else не выполняется, если есть ошибка, которая передает управление одному из предложений о rescue :

> divide(10,0)
Don't divide by zero!
=> nil

Но если ошибка не возникает, выполняется условие else :

> divide(10,2)
This code will run if there is no error.
=> 5

Обратите внимание, что предложение else не будет выполнено, если вы вернетесь из предложения begin

def divide(x, y)
  begin
    z = x/y
    return z                 # Will keep the else clause from running!
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
  else
    puts "This code will run if there is no error."
    return z
  end
end

> divide(10,2)
=> 5

Код, который должен всегда запускаться

Используйте ensure положение , если есть код , который вы всегда хотите выполнить.

def divide(x, y)
  begin
    z = x/y
    return z
  rescue ZeroDivisionError
    puts "Don't divide by zero!"
  rescue TypeError
    puts "Division only works on numbers!"
    return nil
  rescue => e
    puts "Don't do that (%s)" % [e.class]
    return nil
  ensure
    puts "This code ALWAYS runs."
  end
end

Предложение ensure будет выполняться при возникновении ошибки:

> divide(10, 0)
Don't divide by zero!   # rescue clause
This code ALWAYS runs.   # ensure clause
=> nil

И когда нет ошибки:

> divide(10, 2)
This code ALWAYS runs.   # ensure clause
=> 5

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

Обратите внимание, что в отличие от предложения else предложение ensure выполняется до того, как предложение begin или rescue возвращает значение. Если условие ensure имеет return , который переопределит return значение любого другого предложения!



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow