A maneira clássica de programar é com try ... catch
. Quando é apropriado usar try
sem catch
?
No Python, o seguinte parece legal e pode fazer sentido:
try:
#do work
finally:
#do something unconditional
No entanto, o código não fez catch
nada. Da mesma forma, pode-se pensar em Java que seria o seguinte:
try {
//for example try to get a database connection
}
finally {
//closeConnection(connection)
}
Parece bom e, de repente, não preciso me preocupar com tipos de exceção etc. Se essa é uma boa prática, quando é uma boa prática? Como alternativa, quais são as razões pelas quais isso não é uma boa prática ou não é legal? (Eu não compilei a fonte. Estou perguntando, pois pode ser um erro de sintaxe para Java. Verifiquei se o Python certamente compila.)
Um problema relacionado com o qual me deparei é o seguinte: continuo escrevendo a função / método, no final do qual deve retornar algo. No entanto, pode estar em um local que não deve ser alcançado e deve ser um ponto de retorno. Portanto, mesmo que eu lide com as exceções acima, ainda estou retornando NULL
ou uma string vazia em algum momento do código que não deve ser alcançado, geralmente o final do método / função. Eu sempre consegui reestruturar o código para que ele não precise return NULL
, pois isso parece absolutamente menos do que uma boa prática.
fonte
try/catch
não é "a maneira clássica de programar". É a maneira clássica de programar em C ++ , porque o C ++ não possui uma construção try / finalmente adequada, o que significa que você precisa implementar mudanças de estado reversíveis garantidas usando hacks feios que envolvem RAII. Porém, linguagens OO decentes não têm esse problema, porque fornecem try / finalmente. É usado para uma finalidade muito diferente da tentativa / captura.Respostas:
Depende se você pode lidar com as exceções que podem ser levantadas neste momento ou não.
Se você pode lidar com as exceções localmente, deve fazê-lo e é melhor lidar com o erro o mais próximo possível de onde é gerado.
Se você não puder manipulá-los localmente, basta ter um
try / finally
bloco perfeitamente razoável - supondo que exista algum código que você precise executar, independentemente de o método ter êxito ou não. Por exemplo (a partir do comentário de Neil ), abrir um fluxo e depois passar esse fluxo para um método interno a ser carregado é um excelente exemplo de quando você precisariatry { } finally { }
, usando a cláusula finally para garantir que o fluxo seja fechado independentemente do sucesso ou falha na leitura.No entanto, você ainda precisará de um manipulador de exceção em algum lugar do seu código - a menos que queira que seu aplicativo trave completamente, é claro. Depende da arquitetura do seu aplicativo exatamente onde está o manipulador.
fonte
try { } finally { }
, aproveitando a cláusula finalmente para garantir que o fluxo seja finalmente fechado, independentemente do sucesso / falha.O
finally
bloco é usado para o código que deve sempre ser executado, independentemente de uma condição de erro (exceção) ocorrer ou não.O código no
finally
bloco é executado após a conclusão dotry
bloco e, se uma exceção detectada ocorrer, após a conclusão docatch
bloco correspondente . É sempre executado, mesmo se uma exceção não capturada ocorreu no blocotry
oucatch
.O
finally
bloco é normalmente usado para fechar arquivos, conexões de rede etc. que foram abertos notry
bloco. O motivo é que a conexão de arquivo ou rede deve ser fechada, se a operação usando esse arquivo ou conexão de rede foi bem-sucedida ou se falhou.Deve-se tomar cuidado no
finally
bloco para garantir que ele próprio não gere uma exceção. Por exemplo, verifique duplamente todas as variáveisnull
, etc.fonte
try-finally
pode ser substituída por umawith
declaração.try/finally
construção. C #using
, Pythonwith
, etc.using
etry-finally
, como oDispose
método não será chamado pelousing
bloco se ocorrer uma exceção noIDisposable
construtor do objeto.try-finally
permite executar código mesmo que o construtor do objeto gere uma exceção.Um exemplo em que try ... finalmente sem uma cláusula catch é apropriado (e mais idiomático ) em Java é o uso do Lock no pacote de bloqueios de utilitários simultâneos.
fonte
l.lock()
dentro de tentar?try{ l.lock(); }finally{l.unlock();}
try
neste trecho é destinado a envolver o acesso a recursos, por poluindo-o com algo relacionado a issol.lock()
falhar, ofinally
bloco ainda será executado sel.lock()
estiver dentro dotry
bloco. Se você fizer como o gnat sugere, ofinally
bloco só será executado quando sabemos que o bloqueio foi adquirido.Em um nível básico
catch
efinally
resolva dois problemas relacionados, mas diferentes:catch
é usado para lidar com um problema relatado pelo código que você chamoufinally
é usado para limpar dados / recursos que o código atual criou / modificou, não importa se ocorreu ou não um problemaPortanto, ambos estão relacionados de alguma forma a problemas (exceções), mas isso é tudo o que eles têm em comum.
Uma diferença importante é que o
finally
bloco deve estar no mesmo método em que os recursos foram criados (para evitar vazamentos de recursos) e não pode ser colocado em um nível diferente na pilha de chamadas.O
catch
porém é uma questão diferente: o lugar correto para isso depende de onde você pode realmente lidar com a exceção. Não adianta capturar uma exceção em um lugar onde você não pode fazer nada a respeito; portanto, às vezes é melhor simplesmente deixá-la cair.fonte
finally
declaração try (estática ou dinamicamente) anexa ... e ainda ser 100% à prova de vazamentos.@yfeldblum tem a resposta correta: tente-finalmente sem uma instrução catch geralmente deve ser substituído por uma construção de linguagem apropriada.
Em C ++, ele está usando RAII e construtores / destruidores; em Python é uma
with
afirmação; e em C #, é umausing
declaração.Elas são quase sempre mais elegantes porque o código de inicialização e finalização está em um local (o objeto abstraído) e não em dois locais.
fonte
Em muitos idiomas, uma
finally
declaração também é executada após a declaração de retorno. Isso significa que você pode fazer algo como:O que libera os recursos, independentemente de como o método foi finalizado com uma exceção ou uma declaração de retorno regular.
Se isso é bom ou ruim, isso está em debate, mas
try {} finally {}
nem sempre se limita ao tratamento de exceções.fonte
Eu poderia invocar a ira dos Pythonistas (não sei, porque não uso muito o Python) ou programadores de outras linguagens com esta resposta, mas, na minha opinião, a maioria das funções não deveria ter um
catch
bloco, idealmente falando. Para mostrar o porquê, deixe-me contrastar isso com a propagação manual do código de erro do tipo que eu tive que fazer ao trabalhar com o Turbo C no final dos anos 80 e início dos anos 90.Então, digamos que temos uma função para carregar uma imagem ou algo parecido em resposta a um usuário selecionar um arquivo de imagem para carregar, e isso está escrito em C e assembly:
Omiti algumas funções de baixo nível, mas podemos ver que identifiquei diferentes categorias de funções, codificadas por cores, com base nas responsabilidades que eles têm com relação ao tratamento de erros.
Ponto de falha e recuperação
Agora, nunca foi difícil escrever as categorias de funções que chamo de "ponto possível de falhas" (aquelas que
throw
, por exemplo) e as funções "recuperação e relatório de erros" (aquelas quecatch
, por exemplo).Essas funções eram sempre trivial para escrever corretamente antes de manipulação de exceção estava disponível desde uma função que pode ser executado em uma falha externa, como deixar de alocar a memória, só pode retornar um
NULL
ou0
ou-1
ou definir um código de erro global ou algo nesse sentido. E a recuperação / relatório de erros sempre foi fácil, uma vez que você percorreu a pilha de chamadas até um ponto em que fazia sentido recuperar e relatar falhas, basta pegar o código de erro e / ou a mensagem e relatá-lo ao usuário. E, naturalmente, uma função na folha desta hierarquia que nunca pode falhar, não importa como seja alterada no futuro (Convert Pixel
) é simplesmente simples de escrever corretamente (pelo menos no que diz respeito ao tratamento de erros).Propagação de erros
No entanto, as funções tediosas propensas ao erro humano eram os propagadores de erros , aqueles que não corriam diretamente para a falha, mas denominavam funções que poderiam falhar em algum lugar mais profundo da hierarquia. Nesse ponto,
Allocate Scanline
talvez seja necessário lidar com uma falhamalloc
e retornar um erro paraConvert Scanlines
, em seguida,Convert Scanlines
teria que verificar esse erro e passá-lo paraDecompress Image
, thenDecompress Image->Parse Image
e andParse Image->Load Image
, eLoad Image
para o comando do usuário no qual o erro foi finalmente relatado .É aqui que muitos humanos cometem erros, já que é preciso apenas um propagador de erro para não verificar e transmitir o erro para que toda a hierarquia de funções seja derrubada quando se trata de lidar adequadamente com o erro.
Além disso, se códigos de erro são retornados por funções, perdemos praticamente a capacidade de, digamos, 90% de nossa base de código, retornar valores de interesse pelo sucesso, pois muitas funções precisariam reservar seu valor de retorno para retornar um código de erro em falha .
Reduzindo o erro humano: códigos de erro globais
Então, como podemos reduzir a possibilidade de erro humano? Aqui eu posso até invocar a ira de alguns programadores em C, mas uma melhoria imediata na minha opinião é usar códigos de erro globais , como o OpenGL com
glGetError
. Isso pelo menos libera as funções para retornar valores significativos de interesse pelo sucesso. Existem maneiras de tornar esse thread seguro e eficiente, onde o código de erro está localizado em um thread.Também existem alguns casos em que uma função pode ter um erro, mas é relativamente inofensivo continuar um pouco mais antes de retornar prematuramente, como resultado da descoberta de um erro anterior. Isso permite que isso aconteça sem ter que verificar se há erros em 90% das chamadas de função feitas em todas as funções, para que ainda possa permitir o tratamento adequado de erros sem ser tão meticuloso.
Reduzindo o erro humano: tratamento de exceções
No entanto, a solução acima ainda exige muitas funções para lidar com o aspecto do fluxo de controle da propagação manual de erros, mesmo que isso possa ter reduzido o número de linhas do
if error happened, return error
tipo de código manual . Ele não o eliminaria completamente, pois muitas vezes ainda haveria pelo menos um local verificando um erro e retornando para quase todas as funções de propagação de erro. Então é aí que o tratamento de exceções entra em cena para salvar o dia (mais ou menos).Mas o valor do tratamento de exceções aqui é liberar a necessidade de lidar com o aspecto do fluxo de controle da propagação manual de erros. Isso significa que seu valor está associado à capacidade de evitar a necessidade de escrever uma carga de
catch
blocos em toda a sua base de código. No diagrama acima, o único local que deve ter umcatch
bloco éLoad Image User Command
onde o erro é relatado. Idealmente, nada mais deveria tercatch
alguma coisa, porque, do contrário, está começando a ser tão entediante e propenso a erros quanto o tratamento de códigos de erro.Portanto, se você me perguntar, se você tem uma base de código que realmente se beneficia com o tratamento de exceções de uma maneira elegante, ela deve ter o número mínimo de
catch
blocos (no mínimo, não quero dizer zero, mas mais como um para cada código único operação do usuário final que pode falhar e, possivelmente, menos ainda, se todas as operações de usuário high-end forem chamadas por meio de um sistema de comando central).Limpeza de Recursos
No entanto, o tratamento de exceções apenas resolve a necessidade de evitar lidar manualmente com os aspectos do fluxo de controle da propagação de erros em caminhos excepcionais separados dos fluxos normais de execução. Muitas vezes, uma função que serve como um propagador de erros, mesmo que faça isso automaticamente agora com o EH, ainda pode adquirir alguns recursos que precisa destruir. Por exemplo, essa função pode abrir um arquivo temporário que precisa ser fechado antes de retornar da função, independentemente do que seja, ou bloquear um mutex necessário para desbloquear, independentemente do que seja.
Para isso, posso invocar a ira de muitos programadores de todos os tipos de linguagens, mas acho que a abordagem C ++ para isso é ideal. A linguagem introduz destruidores que são invocados de maneira determinística no instante em que um objeto sai do escopo. Por esse motivo, o código C ++ que, digamos, bloqueia um mutex por meio de um objeto mutex com escopo definido com um destruidor, não precisa desbloqueá-lo manualmente, pois ele será desbloqueado automaticamente assim que o objeto sair do escopo, aconteça o que acontecer (mesmo se houver uma exceção). encontrado). Portanto, não há realmente nenhuma necessidade de código C ++ bem escrito para lidar com a limpeza de recursos locais.
Em idiomas que não possuem destruidores, eles podem precisar usar um
finally
bloco para limpar manualmente os recursos locais. Dito isso, ainda é melhor ter que desarrumar seu código com propagação manual de erros, desde que você não tenhacatch
exceções em todo o lugar.Reversão de efeitos colaterais externos
Esse é o problema conceitual mais difícil de resolver. Se alguma função, seja um propagador de erros ou um ponto de falha, causar efeitos colaterais externos, será necessário reverter ou "desfazer" esses efeitos colaterais para retornar o sistema a um estado como se a operação nunca tivesse ocorrido, em vez de um " semi-válido "onde a operação foi bem-sucedida. Não conheço linguagens que facilitem muito esse problema conceitual, exceto linguagens que simplesmente reduzem a necessidade de muitas funções causarem efeitos colaterais externos em primeiro lugar, como linguagens funcionais que giram em torno da imutabilidade e estruturas de dados persistentes.
Aqui
finally
está, sem dúvida, uma das soluções mais elegantes disponíveis para o problema nas linguagens que envolvem mutabilidade e efeitos colaterais, porque esse tipo de lógica geralmente é muito específico para uma função específica e não se encaixa tão bem no conceito de "limpeza de recursos" " E eu recomendo usarfinally
liberalmente nesses casos para garantir que sua função reverta os efeitos colaterais em idiomas que a suportam, independentemente de você precisar ou não de umcatch
bloco (e, novamente, se você me perguntar, o código bem escrito deve ter o número mínimo decatch
blocos e todos oscatch
blocos devem estar em locais onde faz mais sentido, como no diagrama acima, emLoad Image User Command
).Linguagem dos Sonhos
No entanto, a IMO
finally
está próxima do ideal para a reversão de efeitos colaterais, mas não exatamente. Precisamos introduzir umaboolean
variável para reverter efetivamente os efeitos colaterais no caso de uma saída prematura (de uma exceção lançada ou não), da seguinte forma:Se eu pudesse projetar uma linguagem, minha maneira de sonho de resolver esse problema seria assim para automatizar o código acima:
... com destruidores para automatizar a limpeza dos recursos locais, tornando-o necessário
transaction
,rollback
ecatch
(embora eu ainda queira adicionarfinally
, digamos, trabalhar com recursos C que não se limpam). No entanto,finally
com umaboolean
variável é a coisa mais próxima de tornar isso simples que eu descobri até agora sem a linguagem dos meus sonhos. A segunda solução mais direta que encontrei para isso são os protetores de escopo em linguagens como C ++ e D, mas sempre achei protetores de escopo um pouco desajeitados conceitualmente, pois desfocam a idéia de "limpeza de recursos" e "reversão de efeitos colaterais". Na minha opinião, essas são idéias muito distintas para serem abordadas de maneira diferente.Meu pequeno sonho de uma linguagem também giraria em torno de imutabilidade e estruturas de dados persistentes para facilitar muito, embora não seja necessário, escrever funções eficientes que não precisam copiar profundamente estruturas de dados maciças em sua totalidade, mesmo que a função cause Sem efeitos colaterais.
Conclusão
Enfim, com minhas divagações de lado, acho que seu
try/finally
código para fechar o soquete é ótimo e ótimo, considerando que o Python não tem o equivalente em C ++ de destruidores, e pessoalmente acho que você deve usá-lo liberalmente em locais que precisam reverter os efeitos colaterais e minimize o número de lugares onde você precisacatch
para lugares onde faz mais sentido.fonte
Capturar erros / exceção e manipulá-los de maneira organizada é altamente recomendado, mesmo que não seja obrigatório.
A razão pela qual digo isso é porque acredito que todo desenvolvedor deve conhecer e lidar com o comportamento de seu aplicativo, caso contrário ele não concluiu seu trabalho de maneira adequada. Não há situação em que um bloco try-finalmente substitua o bloco try-catch-finally.
Vou dar um exemplo simples: suponha que você tenha escrito o código para fazer upload de arquivos no servidor sem capturar exceções. Agora, se por algum motivo o upload falhar, o cliente nunca saberá o que deu errado. Mas, se você capturou a exceção, pode exibir uma mensagem de erro clara explicando o que deu errado e como o usuário pode remediá-la.
Regra de ouro: sempre pegue uma exceção, porque adivinhar leva tempo
fonte
catch
cláusula sempre deve estar presente sempre que houver atry
. Contribuidores mais experientes, inclusive eu, acreditam que este é um péssimo conselho. Seu raciocínio é falho. O método que contém otry
não é o único local possível em que a exceção pode ser capturada. Geralmente é mais simples e melhor permitir capturar e relatar exceções do nível mais alto, em vez de duplicar cláusulas de captura em todo o código.