Ruby QuickRef di Ryan Davis dice (senza spiegazione):
Non salvare l'eccezione. MAI. o ti pugnalerò.
Perchè no? Qual è la cosa giusta da fare?
Ruby QuickRef di Ryan Davis dice (senza spiegazione):
Non salvare l'eccezione. MAI. o ti pugnalerò.
Perchè no? Qual è la cosa giusta da fare?
Risposte:
TL; DR : utilizzare StandardError
invece per la rilevazione generale delle eccezioni. Quando l'eccezione originale viene nuovamente sollevata (ad es. Durante il salvataggio per registrare solo l'eccezione), Exception
è probabile che il salvataggio sia corretto.
Exception
è la radice della gerarchia delle eccezioni di Ruby , così quando si rescue Exception
si salvataggio da tutto , tra cui sottoclassi, come SyntaxError
, LoadError
e Interrupt
.
Il salvataggio Interrupt
impedisce all'utente di utilizzare CTRLCper uscire dal programma.
Il salvataggio SignalException
impedisce al programma di rispondere correttamente ai segnali. Sarà ingiustificabile se non da kill -9
.
Salvare SyntaxError
significa questoeval
i fallimenti lo faranno silenziosamente.
Tutti questi possono essere indicati da eseguire questo programma, e cercando di CTRLCo kill
esso:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Il salvataggio da Exception
non è nemmeno il valore predefinito. fare
begin
# iceberg!
rescue
# lifeboats
end
non salva da Exception
, salva da StandardError
. Generalmente dovresti specificare qualcosa di più specifico del valore predefinito StandardError
, ma il salvataggio Exception
dall'ampliamento dell'ambito anziché restringerlo e può avere risultati catastrofici e rendere estremamente difficile la ricerca di bug.
Se hai una situazione in cui vuoi salvare StandardError
e hai bisogno di una variabile con l'eccezione, puoi usare questo modulo:
begin
# iceberg!
rescue => e
# lifeboats
end
che equivale a:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Uno dei pochi casi comuni Exception
in cui è opportuno eseguire il salvataggio è a scopo di registrazione / segnalazione, nel qual caso è necessario ripetere immediatamente l'eccezione:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
e poirescue *ADAPTER_ERRORS => e
La vera regola è: non buttare via le eccezioni. L'obiettività dell'autore della tua citazione è discutibile, come dimostra il fatto che termina con
o ti pugnalerò
Ovviamente, tieni presente che i segnali (per impostazione predefinita) generano eccezioni e che i processi di lunga durata vengono terminati attraverso un segnale, quindi catturare l'eccezione e non terminare con le eccezioni del segnale renderà il tuo programma molto difficile da interrompere. Quindi non farlo:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
No, davvero, non farlo. Non eseguirlo nemmeno per vedere se funziona.
Tuttavia, supponi di avere un server thread e desideri che tutte le eccezioni non:
thread.abort_on_exception = true
). Quindi questo è perfettamente accettabile nel thread di gestione della connessione:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
Quanto sopra risolve una variante del gestore delle eccezioni predefinito di Ruby, con il vantaggio che non uccide anche il tuo programma. Rails lo fa nel suo gestore di richieste.
Le eccezioni del segnale vengono sollevate nel thread principale. I thread in background non li avranno, quindi non ha senso cercare di catturarli lì.
Ciò è particolarmente utile in un ambiente di produzione, in cui non si desidera che il programma si interrompa semplicemente ogni volta che qualcosa va storto. Quindi è possibile prendere i dump dello stack nei registri e aggiungerli al codice per gestire l'eccezione specifica più in basso nella catena di chiamate e in modo più grazioso.
Nota anche che esiste un altro idioma di Ruby che ha più o meno lo stesso effetto:
a = do_something rescue "something else"
In questa linea, se do_something
solleva un'eccezione, viene catturato da Ruby, gettato via e a
assegnato "something else"
.
In generale, non farlo, tranne in casi speciali in cui sai che non devi preoccuparti. Un esempio:
debugger rescue nil
Il debugger
funzione è un modo piuttosto carino per impostare un punto di interruzione nel codice, ma se eseguito all'esterno di un debugger e Rails, genera un'eccezione. Ora teoricamente non dovresti lasciare il codice di debug in giro nel tuo programma (pff! Nessuno lo fa!) Ma potresti voler tenerlo lì per un po 'per qualche motivo, ma non eseguire continuamente il tuo debugger.
Nota:
Se hai eseguito il programma di qualcun altro che rileva le eccezioni del segnale e le ignora (dì il codice sopra) quindi:
pgrep ruby
o ps | grep ruby
, cercare il PID del programma offensivo, quindi eseguire kill -9 <PID>
. Se stai lavorando con il programma di qualcun altro che, per qualsiasi motivo, è disseminato di questi blocchi di eccezione-ignore, mettere questo in cima alla linea principale è un possibile cop-out:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Questo fa sì che il programma risponda ai normali segnali di terminazione terminando immediatamente, bypassando i gestori delle eccezioni, senza pulizia . Quindi potrebbe causare la perdita di dati o simili. Stai attento!
Se è necessario eseguire questa operazione:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
puoi effettivamente farlo:
begin
do_something
ensure
critical_cleanup
end
Nel secondo caso, critical cleanup
verrà chiamato ogni volta, indipendentemente dal fatto che venga generata un'eccezione.
kill -9
.
ensure
verrà eseguito indipendentemente dal fatto che vi sia o meno un'eccezione, mentre rescue
verrà eseguito solo se è stata sollevata un'eccezione.
Non farlo rescue Exception => e
(e non aumentare nuovamente l'eccezione), altrimenti potresti scappare da un ponte.
Diciamo che sei in macchina (con Ruby). Di recente hai installato un nuovo volante con il sistema di aggiornamento over-the-air (che utilizza eval
), ma non sapevi che uno dei programmatori ha incasinato la sintassi.
Sei su un ponte e ti rendi conto che stai andando un po 'verso la ringhiera, quindi giri a sinistra.
def turn_left
self.turn left:
end
oops! Questo è probabilmente Not Good ™, per fortuna, Ruby solleva a SyntaxError
.
L'auto dovrebbe fermarsi immediatamente - giusto?
No.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
Beep Beep
Avviso: Eccezione errore di sintassi rilevata.
Informazioni: errore registrato - processo continuo.
Si nota che qualcosa non va, e si sbattere sulle rotture di emergenza ( ^C
: Interrupt
)
Beep Beep
Avvertenza: eccezione di interruzione rilevata.
Informazioni: errore registrato - processo continuo.
Sì, non è stato di grande aiuto. Sei abbastanza vicino al parapetto, quindi metti la macchina nel parcheggio ( kill
ing:) SignalException
.
Beep Beep
Avviso: eccezione rilevata da SignalException.
Informazioni: errore registrato - processo continuo.
All'ultimo secondo, tiri fuori le chiavi ( kill -9
) e l'auto si ferma, sbatti in avanti sul volante (l'airbag non può gonfiarsi perché non hai fermato con grazia il programma - l'hai terminato) e il computer nella parte posteriore della tua auto sbatte contro il sedile di fronte. Una lattina piena di coca cola tra le carte. I generi alimentari nella parte posteriore sono schiacciati e la maggior parte sono ricoperti di tuorlo d'uovo e latte. L'auto necessita di seri interventi di riparazione e pulizia. (Perdita di dati)
Spero che tu abbia un'assicurazione (backup). Oh sì - perché l'airbag non si è gonfiato, probabilmente ti sei fatto male (essere licenziato, ecc.).
Ma aspetta! C'èDi Piùmotivi per cui potresti voler usare rescue Exception => e
!
Supponiamo che tu sia quella macchina e che tu voglia assicurarti che l'airbag si gonfi se la macchina sta superando il suo momento di arresto sicuro.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Ecco l'eccezione alla regola: puoi intercettare Exception
solo se riduci l'eccezione . Quindi, una regola migliore è quella di non ingoiare mai e di rievocare Exception
sempre l'errore.
Ma aggiungere il salvataggio è sia facile da dimenticare in una lingua come Ruby, sia mettere una dichiarazione di salvataggio prima di sollevare nuovamente un problema sembra un po 'non ASCIUTTO. E non vuoi dimenticare l' raise
affermazione. E se lo fai, buona fortuna cercando di trovare quell'errore.
Per fortuna, Ruby è fantastico, puoi semplicemente usare la ensure
parola chiave, che assicura che il codice venga eseguito. La ensure
parola chiave eseguirà il codice in ogni caso - se viene generata un'eccezione, in caso contrario, l'unica eccezione è se il mondo finisce (o altri eventi improbabili).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! E quel codice dovrebbe essere eseguito comunque. L'unico motivo da utilizzare rescue Exception => e
è se è necessario accedere all'eccezione o se si desidera eseguire solo il codice su un'eccezione. E ricorda di aumentare nuovamente l'errore. Ogni volta.
Nota: come sottolineato da @Niall, assicurarsi che funzioni sempre . Questo è utile perché a volte il tuo programma può mentirti e non generare eccezioni, anche quando si verificano problemi. Con attività critiche, come il gonfiaggio degli airbag, è necessario assicurarsi che ciò accada, qualunque cosa accada. Per questo motivo, controllare ogni volta che l'auto si ferma, se viene generata un'eccezione o meno, è una buona idea. Anche se gonfiare gli airbag è un compito un po 'insolito nella maggior parte dei contesti di programmazione, questo è in realtà abbastanza comune con la maggior parte delle attività di pulizia.
ensure
in alternativa a rescue Exception
è fuorviante - l'esempio implica che sono equivalenti, ma come affermato ensure
accadrà se c'è un'eccezione o no, quindi ora i tuoi airbag si gonfieranno perché hai superato i 5 miglia all'ora anche se nulla è andato storto.
Perché questo cattura tutte le eccezioni. È improbabile che il tuo programma possa recuperare da uno qualsiasi di essi.
Dovresti gestire solo le eccezioni da cui sai come recuperare. Se non prevedi un certo tipo di eccezione, non gestirla, arresta in modo anomalo (scrivi i dettagli nel log), quindi diagnostica i log e correggi il codice.
Ingoiare le eccezioni è male, non farlo.
Questo è un caso specifico della regola che non dovresti prendere alcuna eccezione che non sai come gestire. Se non sai come gestirlo, è sempre meglio lasciare che un'altra parte del sistema lo catturi e lo gestisca.
Ho appena letto un fantastico post sul blog su honeybadger.io :
Perché non dovresti salvare l'eccezione
Il problema con il salvataggio dell'eccezione è che in realtà salva ogni eccezione ereditata dall'eccezione. Che è .... tutti loro!
Questo è un problema perché ci sono alcune eccezioni utilizzate internamente da Ruby. Non hanno nulla a che fare con la tua app e la loro ingestione causerà cose brutte.
Ecco alcuni dei più grandi:
SignalException :: Interrupt: se lo salvi, non puoi uscire dall'app premendo control-c.
ScriptError :: SyntaxError - L'ingestione di errori di sintassi significa che cose come put ("Forgot qualcosa) falliranno silenziosamente.
NoMemoryError - Vuoi sapere cosa succede quando il tuo programma continua a funzionare dopo che utilizza tutta la RAM? Neanche io.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Immagino che tu non voglia davvero ingoiare nessuna di queste eccezioni a livello di sistema. Desideri solo rilevare tutti gli errori a livello di applicazione. Le eccezioni hanno causato il TUO codice.
Fortunatamente, c'è un modo semplice per farlo.