Minha pergunta é o que a maioria dos desenvolvedores prefere para tratamento de erros, exceções ou códigos de retorno de erro. Por favor, seja específico do idioma (ou família de idiomas) e por que você prefere um em vez do outro.
Estou perguntando isso por curiosidade. Pessoalmente, prefiro códigos de retorno de erro, pois são menos explosivos e não forçam o código do usuário a pagar a penalidade de desempenho de exceção se não quiserem.
atualização: obrigado por todas as respostas! Devo dizer que, embora eu não goste da imprevisibilidade do fluxo de código com exceções. A resposta sobre o código de retorno (e os identificadores de seu irmão mais velho) adiciona muito Noise ao código.
language-agnostic
exception
Robert Gould
fonte
fonte
Respostas:
Para algumas linguagens (por exemplo, C ++), o vazamento de recursos não deve ser uma razão
C ++ é baseado em RAII.
Se você tem um código que pode falhar, retornar ou lançar (ou seja, a maioria dos códigos normais), então você deve ter seu ponteiro dentro de um ponteiro inteligente (supondo que você tenha um bom motivo para não ter seu objeto criado na pilha).
Os códigos de retorno são mais detalhados
Eles são prolixos e tendem a se desenvolver em algo como:
No final, seu código é uma coleção de instruções identificadas (eu vi esse tipo de código no código de produção).
Este código pode muito bem ser traduzido em:
Que separa claramente o código do processamento de erros, o que pode ser uma coisa boa .
Códigos de retorno são mais frágeis
Se não for algum aviso obscuro de um compilador (veja o comentário de "phjr"), eles podem ser facilmente ignorados.
Com os exemplos acima, suponha que alguém se esquece de lidar com seu possível erro (isso acontece ...). O erro é ignorado quando "retornado" e possivelmente explodirá mais tarde (ou seja, um ponteiro NULL). O mesmo problema não acontecerá com exceção.
O erro não será ignorado. Às vezes, você quer que ele não exploda ... Portanto, você deve escolher com cuidado.
Códigos de retorno às vezes devem ser traduzidos
Digamos que temos as seguintes funções:
Qual é o tipo de retorno de doExperimenteToDoSomethingWithAllThisMess se uma de suas funções chamadas falhar?
Códigos de retorno não são uma solução universal
Os operadores não podem retornar um código de erro. Os construtores C ++ também não.
Códigos de retorno significam que você não pode encadear expressões
O corolário do ponto acima. E se eu quiser escrever:
Não posso, porque o valor de retorno já está em uso (e às vezes não pode ser alterado). Assim, o valor de retorno passa a ser o primeiro parâmetro, enviado como referência ... Ou não.
Exceções são digitadas
Você pode enviar classes diferentes para cada tipo de exceção. As exceções de recursos (ou seja, sem memória) devem ser leves, mas qualquer outra coisa pode ser tão pesada quanto necessário (eu gosto da exceção Java que me dá toda a pilha).
Cada captura pode então ser especializada.
Nunca use pegar (...) sem jogar de novo
Normalmente, você não deve ocultar um erro. Se você não relançar, no mínimo, registre o erro em um arquivo, abra uma caixa de mensagem, o que for ...
As exceções são ... NUKE
O problema com a exceção é que o uso excessivo deles irá produzir um código cheio de try / catch. Mas o problema está em outro lugar: quem tenta / captura seu código usando o contêiner STL? Ainda assim, esses contêineres podem enviar uma exceção.
Claro, em C ++, nunca deixe uma exceção sair de um destruidor.
As exceções são ... síncronas
Certifique-se de capturá-los antes que eles coloquem seu tópico de joelhos ou se propaguem dentro do loop de mensagens do Windows.
A solução poderia ser misturá-los?
Então eu acho que a solução é jogar quando algo não deveria acontecer. E quando algo puder acontecer, use um código de retorno ou um parâmetro para permitir que o usuário reaja a isso.
Portanto, a única questão é "o que é algo que não deveria acontecer?"
Depende do contrato de sua função. Se a função aceita um ponteiro, mas especifica que o ponteiro não deve ser NULL, então está ok para lançar uma exceção quando o usuário envia um ponteiro NULL (a questão é, em C ++, quando o autor da função não usou referências em vez disso de ponteiros, mas ...)
Outra solução seria mostrar o erro
Às vezes, seu problema é que você não quer erros. Usar exceções ou códigos de retorno de erro é legal, mas ... Você quer saber sobre isso.
No meu trabalho, usamos uma espécie de "Assert". Vai, dependendo dos valores de um arquivo de configuração, não importa as opções de depuração / liberação de compilação:
Tanto no desenvolvimento quanto no teste, isso permite que o usuário identifique o problema exatamente quando ele é detectado, e não depois (quando algum código se preocupa com o valor de retorno ou dentro de uma captura).
É fácil adicionar ao código legado. Por exemplo:
leva um tipo de código semelhante a:
(Eu tenho macros semelhantes que estão ativas apenas na depuração).
Note que na produção o arquivo de configuração não existe, então o cliente nunca vê o resultado desta macro ... Mas é fácil ativá-la quando necessário.
Conclusão
Ao codificar usando códigos de retorno, você está se preparando para o fracasso e espera que sua fortaleza de testes seja segura o suficiente.
Ao codificar usando exceção, você sabe que seu código pode falhar e geralmente coloca o contra-fogo na posição estratégica escolhida em seu código. Mas geralmente, seu código é mais sobre "o que deve fazer" do que "o que temo que aconteça".
Mas quando você codifica, você deve usar a melhor ferramenta à sua disposição e, às vezes, é "Nunca esconda um erro e mostre-o o mais rápido possível". A macro que falei acima segue essa filosofia.
fonte
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
melhor porque se eu precisar adicionar if / while / etc. para fins de execução normal, não quero que sejam misturadas com if / while / etc declarações adicionadas para propósitos excepcionais ... E como a regra real sobre o uso de exceções é lançar , não pegar , as declarações try / catch geralmente não são invasivas.Eu uso os dois na verdade.
Eu uso códigos de retorno se for um erro conhecido e possível. Se for um cenário que eu sei que pode e vai acontecer, então há um código que é enviado de volta.
As exceções são usadas apenas para coisas que NÃO estou esperando.
fonte
De acordo com o Capítulo 7 intitulado "Exceções" em Diretrizes de Design de Estrutura: Convenções, Expressões Idiomáticas e Padrões para Bibliotecas .NET Reutilizáveis , várias justificativas são fornecidas para explicar por que usar exceções em vez de valores de retorno é necessário para estruturas OO como C #.
Talvez este seja o motivo mais convincente (página 179):
"As exceções se integram bem com as linguagens orientadas a objetos. As linguagens orientadas a objetos tendem a impor restrições às assinaturas de membros que não são impostas por funções em linguagens não OO. Por exemplo, no caso de construtores, sobrecargas de operadores e propriedades, o desenvolvedor não tem escolha no valor de retorno. Por esse motivo, não é possível padronizar o relatório de erro baseado em valor de retorno para estruturas orientadas a objetos. Um método de relatório de erro, como exceções, que está fora da faixa da assinatura do método é a única opção. "
fonte
Minha preferência (em C ++ e Python) é usar exceções. Os recursos fornecidos pela linguagem tornam um processo bem definido para levantar, capturar e (se necessário) relançar exceções, tornando o modelo fácil de ver e usar. Conceitualmente, é mais limpo do que os códigos de retorno, pois exceções específicas podem ser definidas por seus nomes e têm informações adicionais que os acompanham. Com um código de retorno, você está limitado apenas ao valor do erro (a menos que queira definir um objeto ReturnStatus ou algo assim).
A menos que o código que você está escrevendo seja crítico em termos de tempo, a sobrecarga associada ao desenrolar da pilha não é significativa o suficiente para se preocupar.
fonte
As exceções devem ser retornadas apenas quando acontece algo que você não esperava.
O outro ponto de exceções, historicamente, é que os códigos de retorno são inerentemente proprietários, às vezes um 0 pode ser retornado de uma função C para indicar sucesso, às vezes -1, ou qualquer um deles para uma falha com 1 para um sucesso. Mesmo quando são enumeradas, as enumerações podem ser ambíguas.
As exceções também podem fornecer muito mais informações e, especificamente, definir bem 'Algo deu errado, aqui está, um rastreamento de pilha e algumas informações de suporte para o contexto'
Dito isto, um código de retorno bem enumerado pode ser útil para um conjunto conhecido de resultados, um simples 'aqui os resultados da função, e ele apenas funcionou dessa maneira'
fonte
Em Java, eu uso (na seguinte ordem):
Projeto por contrato (garantindo que as pré-condições sejam atendidas antes de tentar qualquer coisa que possa falhar). Isso captura a maioria das coisas e eu retorno um código de erro para isso.
Retornar códigos de erro durante o processamento do trabalho (e executar reversão, se necessário).
Exceções, mas são usadas apenas para coisas inesperadas.
fonte
assert
coisas de C, pois não detectará problemas no código de produção a menos que você execute asserções o tempo todo. Eu tendo a ver as asserções como uma forma de detectar problemas durante o desenvolvimento, mas apenas para coisas que serão ou não declaradas de forma consistente (como coisas com constantes, não coisas com variáveis ou qualquer outra coisa que pode mudar em tempo de execução).Os códigos de retorno falham no teste " Poço do Sucesso " para mim quase todas as vezes.
Com relação ao desempenho:
fonte
Um ótimo conselho que recebi do The Pragmatic Programmer foi algo como "seu programa deve ser capaz de executar todas as suas funções principais sem usar exceções".
fonte
Eu escrevi uma postagem no blog sobre isso há um tempo.
A sobrecarga de desempenho de lançar uma exceção não deve desempenhar nenhum papel em sua decisão. Afinal, se você estiver fazendo certo, uma exceção é excepcional .
fonte
Não gosto de códigos de retorno porque eles fazem com que o seguinte padrão se espalhe em todo o seu código
CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; }
Logo, uma chamada de método que consiste em 4 chamadas de função incha com 12 linhas de tratamento de erros. Algumas das quais nunca acontecerão. Casos de if e switch abundam.
As exceções são mais claras se você as usar bem ... para sinalizar eventos excepcionais .. após os quais o caminho de execução não pode continuar. Freqüentemente, são mais descritivos e informativos do que os códigos de erro.
Se você tiver vários estados após uma chamada de método que deve ser tratada de forma diferente (e não são casos excepcionais), use códigos de erro ou parâmetros de saída. Embora pessoalmente eu ache isso raro ..
Eu pesquisei um pouco sobre o contra-argumento da 'penalidade de desempenho' .. mais no mundo C ++ / COM, mas nas linguagens mais novas, acho que a diferença não é muito. Em qualquer caso, quando algo explode, as preocupações com o desempenho são relegadas para segundo plano :)
fonte
Com qualquer compilador decente ou exceções de ambiente de tempo de execução não incorrem em uma penalidade significativa. É mais ou menos como uma instrução GOTO que salta para o manipulador de exceções. Além disso, ter exceções capturadas por um ambiente de tempo de execução (como a JVM) ajuda a isolar e corrigir um bug com muito mais facilidade. Vou usar um NullPointerException em Java em vez de um segfault em C a qualquer dia.
fonte
finally
blocos e destruidores para objetos alocados na pilha.Tenho um conjunto simples de regras:
1) Use códigos de retorno para coisas às quais você espera que seu chamador imediato reaja.
2) Use exceções para erros que são mais amplos em escopo e podem ser razoavelmente esperados para serem tratados por algo muitos níveis acima do chamador, de modo que a percepção do erro não precise percorrer muitas camadas, tornando o código mais complexo.
Em Java, eu só usei exceções não verificadas, exceções verificadas acabam sendo apenas outra forma de código de retorno e, em minha experiência, a dualidade do que pode ser "retornado" por uma chamada de método geralmente é mais um obstáculo do que uma ajuda.
fonte
Eu uso exceções em python em circunstâncias excepcionais e não excepcionais.
Muitas vezes é bom poder usar uma Exceção para indicar que a "solicitação não pôde ser realizada", em vez de retornar um valor de Erro. Significa que você / sempre / sabe que o valor de retorno é do tipo correto, ao invés de arbitrariamente None ou NotFoundSingleton ou algo assim. Aqui está um bom exemplo de onde eu prefiro usar um manipulador de exceção em vez de um condicional no valor de retorno.
O efeito colateral é que quando um datastore.fetch (obj_id) é executado, você nunca precisa verificar se seu valor de retorno é None, você obtém esse erro imediatamente de graça. Isso vai contra o argumento, "seu programa deve ser capaz de executar todas as suas funções principais sem usar exceções".
Aqui está outro exemplo de onde as exceções são 'excepcionalmente' úteis, a fim de escrever código para lidar com o sistema de arquivos que não está sujeito a condições de corrida.
Uma chamada de sistema em vez de duas, sem condição de corrida. Este é um exemplo pobre porque obviamente isso irá falhar com um OSError em mais circunstâncias do que o diretório não existe, mas é uma solução 'boa o suficiente' para muitas situações rigidamente controladas.
fonte
Eu acredito que os códigos de retorno aumentam o ruído do código. Por exemplo, sempre odiei a aparência do código COM / ATL devido aos códigos de retorno. Deve haver uma verificação HRESULT para cada linha de código. Considero que o código de retorno de erro é uma das más decisões tomadas pelos arquitetos do COM. Isso torna difícil fazer o agrupamento lógico do código, portanto, a revisão do código se torna difícil.
Não tenho certeza sobre a comparação de desempenho quando há uma verificação explícita do código de retorno a cada linha.
fonte
Prefiro usar exceções para tratamento de erros e valores de retorno (ou parâmetros) como o resultado normal de uma função. Isso fornece um esquema de tratamento de erros fácil e consistente e, se feito corretamente, torna o código com uma aparência muito mais limpa.
fonte
Uma das grandes diferenças é que as exceções forçam você a lidar com um erro, enquanto os códigos de retorno de erro podem ficar desmarcados.
Códigos de retorno de erro, se usados intensamente, também podem causar códigos muito feios com muitos testes if semelhantes a este formulário:
Pessoalmente, prefiro usar exceções para erros que DEVEM ou DEVEM ser corrigidos pelo código de chamada, e só uso códigos de erro para "falhas esperadas" onde retornar algo é realmente válido e possível.
fonte
Há muitos motivos para preferir exceções ao código de retorno:
fonte
As exceções não são para tratamento de erros, IMO. As exceções são apenas isso; eventos excepcionais que você não esperava. Use com cautela, eu digo.
Códigos de erro podem estar OK, mas retornar 404 ou 200 de um método é ruim, IMO. Use enums (.Net) em vez disso, o que torna o código mais legível e fácil de usar para outros desenvolvedores. Além disso, você não precisa manter uma tabela de números e descrições.
Além disso; o padrão try-catch-finally é um antipadrão em meu livro. Try-finally pode ser bom, try-catch também pode ser bom, mas try-catch-finally nunca é bom. try-finally muitas vezes pode ser substituído por uma instrução "usando" (padrão IDispose), que é melhor IMO. E Try-catch onde você realmente pega uma exceção com a qual é capaz de lidar é bom, ou se você fizer isso:
Então, desde que você deixe a exceção continuar a borbulhar, está tudo bem. Outro exemplo é este:
Aqui, eu realmente lido com a exceção; o que eu faço fora do try-catch quando o método de atualização falha é outra história, mas acho que meu ponto foi feito. :)
Por que então tentar pegar finalmente é um anti-padrão? Aqui está o porquê:
O que acontece se o objeto db já foi fechado? Uma nova exceção é lançada e deve ser tratada! Isto é melhor:
Ou, se o objeto db não implementar IDisposable, faça o seguinte:
De qualquer maneira, são meus 2 centavos! :)
fonte
Eu só uso exceções, sem códigos de retorno. Estou falando sobre Java aqui.
A regra geral que sigo é se eu tenho um método chamado,
doFoo()
então segue que se ele não "faz foo", por assim dizer, então algo excepcional aconteceu e uma exceção deve ser lançada.fonte
Uma coisa que temo sobre as exceções é que lançar uma exceção bagunce o fluxo do código. Por exemplo, se você fizer
Ou pior ainda, se eu deletasse algo que não deveria, mas fosse jogado para pegar antes de fazer o resto da limpeza. Jogando colocar muito peso sobre o pobre usuário IMHO.
fonte
Minha regra geral no argumento de exceção vs. código de retorno:
fonte
Não acho os códigos de retorno menos feios do que as exceções. Com exceção, você tem o
try{} catch() {} finally {}
where, assim como os códigos de retorno que você temif(){}
. Eu costumava temer exceções pelas razões apresentadas no post; você não sabe se o ponteiro precisa ser apagado, o que você quer. Mas acho que você tem os mesmos problemas quando se trata de códigos de retorno. Você não sabe o estado dos parâmetros, a menos que conheça alguns detalhes sobre a função / método em questão.Independentemente disso, você deve lidar com o erro, se possível. Você pode tão facilmente permitir que uma exceção se propague para o nível superior quanto ignorar um código de retorno e deixar o programa falhar em seg.
Eu gosto da ideia de retornar um valor (enumeração?) Para resultados e uma exceção para um caso excepcional.
fonte
Para uma linguagem como Java, eu escolheria Exception porque o compilador dá erro de tempo de compilação se as exceções não forem tratadas. Isso força a função de chamada a tratar / lançar as exceções.
Para Python, estou mais em conflito. Não há compilador, portanto, é possível que o chamador não trate a exceção lançada pela função que leva às exceções de tempo de execução. Se você usar códigos de retorno, poderá ter um comportamento inesperado se não for tratado adequadamente e, se usar exceções, poderá obter exceções de tempo de execução.
fonte
Geralmente prefiro códigos de retorno porque permitem que o chamador decida se a falha é excepcional .
Essa abordagem é típica na linguagem Elixir.
As pessoas mencionaram que os códigos de retorno podem fazer com que você tenha muitas
if
instruções aninhadas , mas isso pode ser tratado com uma sintaxe melhor. No Elixir, awith
declaração nos permite separar facilmente uma série de valores de retorno do caminho feliz de quaisquer falhas.Elixir ainda tem funções que geram exceções. Voltando ao meu primeiro exemplo, eu poderia fazer qualquer um desses para gerar uma exceção se o arquivo não puder ser gravado.
Se eu, como chamador, souber que desejo gerar um erro se a gravação falhar, posso escolher chamar em
File.write!
vez deFile.write
. Ou posso escolher ligarFile.write
e lidar com cada um dos motivos possíveis para o fracasso de forma diferente.Claro que sempre é possível fazer
rescue
uma exceção, se quisermos. Mas, comparado a lidar com um valor de retorno informativo, parece estranho para mim. Se eu sei que uma chamada de função pode falhar ou mesmo deveria falhar, sua falha não é um caso excepcional.fonte