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!