Ruby Language
Łapanie wyjątków przy pomocy Begin / Rescue
Szukaj…
Podstawowy blok obsługi błędów
Stwórzmy funkcję dzielącą dwie liczby, która jest bardzo ufna w odniesieniu do jej danych wejściowych:
def divide(x, y)
return x/y
end
Będzie to działać dobrze dla wielu danych wejściowych:
> puts divide(10, 2)
5
Ale nie wszystko
> puts divide(10, 0)
ZeroDivisionError: divided by 0
> puts divide(10, 'a')
TypeError: String can't be coerced into Fixnum
Możemy przepisać tę funkcję, zawijając ryzykowną operację podziału w bloku begin... end
, aby sprawdzić błędy, i użyć klauzuli rescue
, aby wyświetlić komunikat i zwrócić nil
jeśli wystąpi problem.
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
Zapisywanie błędu
Możesz zapisać błąd, jeśli chcesz go użyć w klauzuli 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>'
Sprawdzanie różnych błędów
Jeśli chcesz robić różne rzeczy w zależności od rodzaju błędu, użyj wielu klauzul rescue
, każda z innym typem błędu jako argumentem.
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!
Jeśli chcesz zapisać błąd do użycia w bloku rescue
:
rescue ZeroDivisionError => e
Użyj klauzuli rescue
bez argumentu, aby wyłapać błędy typu nieokreślonego w innej klauzuli 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)
W tym przypadku próba podzielenia nil
przez 2 nie jest ZeroDivisionError
ani TypeError
, więc jest obsługiwana przez domyślną klauzulę rescue
, która wypisuje komunikat informujący nas, że był to NoMethodError
.
Ponowna próba
W rescue
klauzuli, można użyć retry
aby uruchomić begin
ponownie klauzulę, przypuszczalnie po zmianie okoliczności, który spowodował błąd.
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
Jeśli mamy przekazać parametry, które znamy spowoduje TypeError
The begin
klauzula jest wykonywany (oznaczone tutaj drukując „O podzielenie”), a błąd zostanie złapany jak poprzednio, a nil
jest zwracana:
> divide(10, 'a')
About to divide...
Division only works on numbers!
=> nil
Ale jeśli mamy przekazać parametry, które spowodują ZeroDivisionError
The begin
klauzula jest wykonywany, błąd zostanie złapany, dzielnik zmieniło od 0 do 1, a następnie retry
powoduje, że begin
blok należy uruchomić ponownie (od góry), teraz z inny y
. Za drugim razem nie ma błędu, a funkcja zwraca wartość.
> divide(10, 0)
About to divide... # First time, 10 ÷ 0
Don't divide by zero!
About to divide... # Second time 10 ÷ 1
=> 10
Sprawdzanie, czy nie wystąpił błąd
Możesz użyć klauzuli else
dla kodu, który zostanie uruchomiony, jeśli nie zostanie zgłoszony błąd.
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
Klauzula else
nie jest uruchamiana, jeśli występuje błąd, który przenosi kontrolę do jednej z klauzul rescue
:
> divide(10,0)
Don't divide by zero!
=> nil
Ale jeśli nie zostanie zgłoszony błąd, klauzula else
wykonuje:
> divide(10,2)
This code will run if there is no error.
=> 5
Zauważ, że klauzula else
nie zostanie wykonana, jeśli wrócisz z klauzuli 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
Kod, który powinien zawsze działać
Użyj klauzuli ensure
, jeśli istnieje kod, który zawsze chcesz wykonać.
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
Klauzula ensure
zostanie wykonana, gdy wystąpi błąd:
> divide(10, 0)
Don't divide by zero! # rescue clause
This code ALWAYS runs. # ensure clause
=> nil
A kiedy nie ma błędu:
> divide(10, 2)
This code ALWAYS runs. # ensure clause
=> 5
Klauzula upewnij się, gdy na przykład chcesz się upewnić, że pliki są zamknięte.
Należy zauważyć, że w przeciwieństwie do klauzuli else
klauzula ensure
jest wykonywana, zanim klauzula begin
lub rescue
zwróci wartość. Jeżeli ensure
klauzuli ma return
, który zastąpi return
wartości każdej innej klauzuli!