Ruby Language
Cattura le eccezioni con Begin / Rescue
Ricerca…
Un blocco di gestione degli errori di base
Facciamo una funzione per dividere due numeri, questo è molto fiducioso sul suo input:
def divide(x, y)
return x/y
end
Questo funzionerà bene per molti input:
> puts divide(10, 2)
5
Ma non tutto
> puts divide(10, 0)
ZeroDivisionError: divided by 0
> puts divide(10, 'a')
TypeError: String can't be coerced into Fixnum
Possiamo riscrivere la funzione avvolgendo l'operazione di divisione rischiosa in un begin... end blocco begin... end per verificare gli errori, e utilizzare una clausola di rescue per emettere un messaggio e restituire nil se c'è un problema.
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
Salvataggio dell'errore
È possibile salvare l'errore se si desidera utilizzarlo nella clausola di 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>'
Controllo di diversi errori
Se vuoi fare cose diverse in base al tipo di errore, usa più clausole di rescue , ognuna con un tipo di errore diverso come argomento.
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!
Se si desidera salvare l'errore per l'utilizzo nel blocco di rescue :
rescue ZeroDivisionError => e
Utilizzare una clausola di rescue senza argomento per rilevare errori di un tipo non specificato in un'altra clausola di 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)
In questo caso, provare a dividere nil per 2 non è un ZeroDivisionError o un TypeError , quindi viene gestito dalla clausola di rescue predefinita, che stampa un messaggio per farci sapere che si trattava di un NoMethodError .
Nuovo tentativo
In una clausola di rescue , è possibile utilizzare retry di eseguire nuovamente la clausola begin , presumibilmente dopo aver modificato la circostanza che ha causato l'errore.
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
Se passiamo i parametri che sappiamo causeranno un errore TypeError , viene eseguita la clausola begin (contrassegnata qui stampando "About to divide") e l'errore viene rilevato come prima e viene restituito nil :
> divide(10, 'a')
About to divide...
Division only works on numbers!
=> nil
Ma se passiamo i parametri che causano un ZeroDivisionError , la clausola begin viene eseguita, l'errore viene rilevato, il divisore modificato da 0 a 1, quindi retry fa begin blocco ZeroDivisionError (dall'alto), ora con un diverso y . La seconda volta non ci sono errori e la funzione restituisce un valore.
> divide(10, 0)
About to divide... # First time, 10 ÷ 0
Don't divide by zero!
About to divide... # Second time 10 ÷ 1
=> 10
Verifica se non è stato sollevato alcun errore
È possibile utilizzare una clausola else per il codice che verrà eseguito se non viene generato alcun errore.
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
La clausola else non viene eseguita se c'è un errore che trasferisce il controllo a una delle clausole di rescue :
> divide(10,0)
Don't divide by zero!
=> nil
Ma se non viene segnalato alcun errore, viene eseguita la clausola else :
> divide(10,2)
This code will run if there is no error.
=> 5
Si noti che la clausola else non verrà eseguita se si torna dalla clausola 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
Codice che dovrebbe sempre funzionare
Utilizzare una clausola di ensure se esiste un codice che si desidera eseguire sempre.
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
La clausola di ensure verrà eseguita quando si verifica un errore:
> divide(10, 0)
Don't divide by zero! # rescue clause
This code ALWAYS runs. # ensure clause
=> nil
E quando non ci sono errori:
> divide(10, 2)
This code ALWAYS runs. # ensure clause
=> 5
La clausola di verifica è utile quando si desidera assicurarsi, ad esempio, che i file siano chiusi.
Si noti che, a differenza della clausola else , la clausola di ensure viene eseguita prima che la clausola begin o rescue restituisca un valore. Se la clausola di ensure ha un return che sostituirà il valore di return di qualsiasi altra clausola!