Ruby QuickRef, de Ryan Davis, diz (sem explicação):
Não salve Exceção. SEMPRE. ou eu te apunhalarei.
Por que não? Qual é a coisa certa a fazer?
Ruby QuickRef, de Ryan Davis, diz (sem explicação):
Não salve Exceção. SEMPRE. ou eu te apunhalarei.
Por que não? Qual é a coisa certa a fazer?
Respostas:
TL; DR : use StandardError
para captura de exceção geral. Quando a exceção original é gerada novamente (por exemplo, ao resgatar para registrar apenas a exceção), o resgateException
provavelmente está correto.
Exception
é a raiz da hierarquia exceção de Ruby , então quando você rescue Exception
você salva de tudo , incluindo subclasses tais como SyntaxError
, LoadError
, e Interrupt
.
O resgate Interrupt
evita que o usuário use CTRLCpara sair do programa.
O resgate SignalException
impede que o programa responda corretamente aos sinais. Será inviável, exceto por kill -9
.
Resgatando SyntaxError
significa que os eval
que falharem o farão silenciosamente.
Tudo isso pode ser mostrado executando este programa e tentando CTRLCou kill
:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
O resgate de Exception
nem é o padrão. Fazendo
begin
# iceberg!
rescue
# lifeboats
end
não resgata Exception
, resgata de StandardError
. Você geralmente deve especificar algo mais específico do que o padrão StandardError
, mas resgatando a partir Exception
amplia escopo vez de estreitá-lo, e pode ter resultados catastróficos e tornar extremamente difícil a busca de bugs.
Se você tem uma situação em que deseja resgatar StandardError
e precisa de uma variável com a exceção, pode usar este formulário:
begin
# iceberg!
rescue => e
# lifeboats
end
que é equivalente a:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Um dos poucos casos comuns em que é prudente resgatar Exception
é para fins de registro / relatório; nesse caso, você deve imediatamente levantar novamente a exceção:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
em java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
e depoisrescue *ADAPTER_ERRORS => e
A regra real é: não jogue fora as exceções. A objetividade do autor da sua citação é questionável, como evidenciado pelo fato de que termina com
ou eu vou apunhalá-lo
Obviamente, esteja ciente de que os sinais (por padrão) geram exceções e, normalmente, os processos de execução longa são encerrados por meio de um sinal; portanto, capturar Exceção e não encerrar em exceções de sinal dificultará a interrupção do programa. Então não faça isso:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
Não, sério, não faça isso. Nem execute isso para ver se funciona.
No entanto, diga que você possui um servidor encadeado e deseja que todas as exceções não sejam:
thread.abort_on_exception = true
). Isso é perfeitamente aceitável no segmento de manipulação de conexões:
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
O exemplo acima funciona com uma variação do manipulador de exceção padrão do Ruby, com a vantagem de que ele também não mata o seu programa. O Rails faz isso em seu manipulador de solicitações.
Exceções de sinal são geradas no encadeamento principal. Os threads de plano de fundo não os receberão, portanto, não faz sentido tentar pegá-los lá.
Isso é particularmente útil em um ambiente de produção, no qual você não deseja que seu programa simplesmente pare sempre que algo der errado. Em seguida, você pode pegar os despejos de pilha em seus logs e adicionar ao seu código para lidar com exceções específicas mais adiante na cadeia de chamadas e de uma maneira mais elegante.
Observe também que há outro idioma Ruby que tem o mesmo efeito:
a = do_something rescue "something else"
Nesta linha, se houver do_something
uma exceção, ela é capturada por Ruby, jogada fora e a
é atribuída "something else"
.
Geralmente, não faça isso, exceto em casos especiais em que você sabe que não precisa se preocupar. Um exemplo:
debugger rescue nil
o debugger
função é uma maneira bastante agradável de definir um ponto de interrupção no seu código, mas se estiver executando fora de um depurador e do Rails, isso gera uma exceção. Agora, teoricamente, você não deve deixar o código de depuração no seu programa (pff! Ninguém faz isso!), Mas você pode mantê-lo por um tempo por algum motivo, mas não executar continuamente o seu depurador.
Nota:
Se você executou o programa de outra pessoa que captura exceções de sinal e as ignora (diga o código acima), então:
pgrep ruby
ou ps | grep ruby
procure o PID do seu programa incorreto e execute kill -9 <PID>
. Se você estiver trabalhando com o programa de outra pessoa que, por qualquer motivo, estiver repleto desses blocos de exceção de ignorar, colocar isso no topo da linha principal é uma possível cópia:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Isso faz com que o programa responda aos sinais normais de finalização, finalizando imediatamente, ignorando os manipuladores de exceção, sem limpeza . Portanto, pode causar perda de dados ou similar. Seja cuidadoso!
Se você precisar fazer isso:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
você pode realmente fazer isso:
begin
do_something
ensure
critical_cleanup
end
No segundo caso, critical cleanup
será chamado sempre, independentemente de uma exceção ser lançada ou não.
kill -9
.
ensure
será executado independentemente de haver uma exceção gerada ou não, enquanto o rescue
será executado apenas se uma exceção for gerada.
Não faça rescue Exception => e
(e não aumente a exceção) - ou você pode sair de uma ponte.
Digamos que você esteja em um carro (executando Ruby). Recentemente, você instalou um novo volante com o sistema de atualização sem fio (que usa eval
), mas não sabia que um dos programadores estragou a sintaxe.
Você está em uma ponte e percebe que está indo um pouco em direção ao corrimão, então vire à esquerda.
def turn_left
self.turn left:
end
oops! Provavelmente isso não é bom ™, felizmente, Ruby levanta a SyntaxError
.
O carro deve parar imediatamente - certo?
Não.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
bip Bip
Aviso: exceção SyntaxError detectada.
Info: Erro registrado - Processo contínuo.
Você percebe que algo está errado, e você pisar os freios de emergência ( ^C
: Interrupt
)
bip Bip
Aviso: exceção de interrupção detectada.
Info: Erro registrado - Processo contínuo.
Sim - isso não ajudou muito. Você está bem perto do trilho, então você estaciona o carro ( kill
ing:) SignalException
.
bip Bip
Aviso: Exceção detectada de SignalException.
Info: Erro registrado - Processo contínuo.
No último segundo, você puxa as teclas ( kill -9
) e o carro para, bate no volante (o airbag não pode ser inflado porque você não interrompeu graciosamente o programa - você o encerrou) e o computador na parte de trás do seu carro bate no banco em frente a ele. Uma lata pela metade de Coca-Cola derrama sobre os papéis. Os mantimentos na parte de trás são esmagados, e a maioria é coberta com gema de ovo e leite. O carro precisa de reparos e limpeza sérios. (Perda de dados)
Espero que você tenha seguro (Backups). Ah, sim - porque o airbag não inflou, você provavelmente está ferido (ser demitido, etc.).
Mas espere! HáMaisrazões pelas quais você pode querer usar rescue Exception => e
!
Digamos que você é o carro e deseja garantir que o airbag se esgote se o carro estiver excedendo seu momento de parada segura.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Aqui está a exceção à regra: Você pode capturar Exception
apenas se você aumentar novamente a exceção . Portanto, uma regra melhor é nunca engolir Exception
e sempre aumentar novamente o erro.
Mas é fácil esquecer o acréscimo de resgate em um idioma como Ruby, e colocar uma declaração de resgate logo antes de voltar a levantar um problema parece um pouco não-SECO. E você não quer esquecer a raise
afirmação. E se o fizer, boa sorte tentando encontrar esse erro.
Felizmente, Ruby é incrível, você pode simplesmente usar a ensure
palavra - chave, o que garante que o código seja executado. A ensure
palavra-chave executará o código, não importa o quê - se uma exceção for lançada, se não houver, a única exceção será se o mundo terminar (ou outros eventos improváveis).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Estrondo! E esse código deve ser executado de qualquer maneira. O único motivo que você deve usar rescue Exception => e
é se você precisa acessar a exceção ou se deseja que o código seja executado apenas em uma exceção. E lembre-se de aumentar novamente o erro. Toda vez.
Nota: Como o @Niall apontou, verifique sempre a execução. Isso é bom porque, às vezes, seu programa pode mentir para você e não gerar exceções, mesmo quando ocorrem problemas. Em tarefas críticas, como inflar airbags, você precisa garantir que isso aconteça, não importa o quê. Por isso, verificar sempre que o carro parar, se uma exceção é lançada ou não, é uma boa idéia. Embora inflar airbags seja uma tarefa pouco comum na maioria dos contextos de programação, isso é bastante comum na maioria das tarefas de limpeza.
ensure
alternativa rescue Exception
é enganosa - o exemplo implica que são equivalentes, mas, como afirmado ensure
, acontecerá se há uma exceção ou não, então agora seus airbags inflarão porque você ultrapassou os 5 km / h, mesmo que nada tenha dado errado.
Porque isso captura todas as exceções. É improvável que seu programa possa se recuperar de qualquer um deles.
Você deve lidar apenas com exceções das quais sabe se recuperar. Se você não antecipar um certo tipo de exceção, não lide com isso, bata alto (grave detalhes no log), faça o diagnóstico dos logs e corrija o código.
Engolir exceções é ruim, não faça isso.
Esse é um caso específico da regra que você não deve capturar nenhuma exceção que não sabe como lidar. Se você não sabe como lidar com isso, é sempre melhor deixar outra parte do sistema capturar e lidar com isso.
Acabei de ler um ótimo post sobre isso no honeybadger.io :
Por que você não deve resgatar a exceção
O problema com o resgate da exceção é que ele realmente resgata todas as exceções herdadas da exceção. Qual é .... todos eles!
Isso é um problema, porque existem algumas exceções usadas internamente pelo Ruby. Eles não têm nada a ver com seu aplicativo, e engoli-los fará com que coisas ruins aconteçam.
Aqui estão alguns dos grandes:
SignalException :: Interrupt - Se você resgatar isso, não poderá sair do aplicativo pressionando control-c.
ScriptError :: SyntaxError - Engolir erros de sintaxe significa que coisas como puts ("Esqueci alguma coisa) falharão silenciosamente.
NoMemoryError - Quer saber o que acontece quando o seu programa continua sendo executado depois que ele usa toda a RAM? Nem eu.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Suponho que você realmente não queira engolir nenhuma dessas exceções no nível do sistema. Você deseja capturar apenas todos os erros no nível do aplicativo. As exceções causaram SEU código.
Felizmente, há uma maneira fácil de fazer isso.