Em coisas de alto nível, exceções; em coisas de baixo nível, códigos de erro.
O comportamento padrão de uma exceção é desenrolar a pilha e parar o programa, se estou escrevendo um script e procuro uma chave que não está em um dicionário, provavelmente é um erro e quero que o programa pare e deixe-me sabe tudo sobre isso.
Se, entretanto, estou escrevendo um trecho de código cujo comportamento devo conhecer em todas as situações possíveis, então quero códigos de erro. Caso contrário, preciso saber cada exceção que pode ser lançada por cada linha em minha função para saber o que ela fará (leia A exceção que aterrou uma companhia aérea para ter uma ideia de como isso é complicado). É entediante e difícil escrever um código que reaja adequadamente a todas as situações (incluindo as infelizes), mas isso porque escrever código sem erros é tedioso e difícil, não porque você está passando códigos de erro.
Tanto Raymond Chen quanto Joel apresentaram alguns argumentos eloqüentes contra o uso de exceções para tudo.
nodiscard
atributo que fornecerá um aviso do compilador se o valor de retorno de uma função não for armazenado. Ajuda um pouco a detectar a verificação de código de erro esquecido. Exemplo: godbolt.org/g/6i6E0BEu normalmente prefiro exceções, porque elas têm mais informações contextuais e podem transmitir (quando usadas corretamente) o erro para o programador de uma maneira mais clara.
Por outro lado, os códigos de erro são mais leves do que as exceções, mas são mais difíceis de manter. A verificação de erros pode ser omitida inadvertidamente. Os códigos de erro são mais difíceis de manter porque você precisa manter um catálogo com todos os códigos de erro e, em seguida, ativar o resultado para ver qual erro foi gerado. Os intervalos de erro podem ajudar aqui, porque se a única coisa em que estamos interessados é se estamos na presença de um erro ou não, é mais simples verificar (por exemplo, um código de erro HRESULT maior ou igual a 0 é sucesso e menos que zero é falha). Eles podem ser omitidos inadvertidamente porque não há nenhuma força programática de que o desenvolvedor irá verificar os códigos de erro. Por outro lado, você não pode ignorar exceções.
Para resumir, prefiro exceções aos códigos de erro em quase todas as situações.
fonte
Eu prefiro exceções porque
fonte
goto
Os códigos de erro podem ser ignorados (e geralmente são!) Pelos chamadores de suas funções. As exceções pelo menos os forçam a lidar com o erro de alguma forma. Mesmo que a versão deles para lidar com isso seja ter um manipulador de captura vazio (suspiro).
fonte
Exceções sobre códigos de erro, sem dúvida. Você obtém muitos dos mesmos benefícios das exceções que os códigos de erro, mas também muito mais, sem as deficiências dos códigos de erro. A única crítica às exceções é que é um pouco mais indireto; mas nos dias de hoje, essa sobrecarga deve ser considerada insignificante para quase todos os aplicativos.
Aqui estão alguns artigos discutindo, comparando e contrastando as duas técnicas:
Há alguns bons links que podem fornecer mais leituras.
fonte
Eu nunca misturaria os dois modelos ... é muito difícil converter de um para o outro conforme você se move de uma parte da pilha que está usando códigos de erro para uma parte superior que está usando exceções.
As exceções são para "qualquer coisa que interrompa ou iniba o método ou sub-rotina de fazer o que você pediu" ... NÃO para passar mensagens de volta sobre irregularidades ou circunstâncias incomuns, ou o estado do sistema, etc. Use valores de retorno ou ref (ou fora) parâmetros para isso.
As exceções permitem que métodos sejam escritos (e utilizados) com semânticas que dependem da função do método, ou seja, um método que retorna um objeto Employee ou List of Employees pode ser digitado para fazer exatamente isso e você pode utilizá-lo chamando.
Com códigos de erro, todos os métodos retornam um código de erro, então, para aqueles que precisam retornar algo a ser usado pelo código de chamada, você deve passar uma variável de referência a ser preenchida com esses dados e testar o valor de retorno para o código de erro, e lidar com ele, em cada chamada de função ou método.
Se você codificar de forma que cada método faça uma e apenas uma coisa simples, deverá lançar uma exceção sempre que o método não conseguir cumprir o objetivo desejado. As exceções são muito mais ricas e fáceis de usar dessa forma do que os códigos de erro. Seu código é muito mais limpo - O fluxo padrão do caminho do código "normal" pode ser dedicado estritamente ao caso em que o método É capaz de realizar o que você deseja ... E então o código para limpar ou lidar com o Circunstâncias "excepcionais" quando algo de ruim acontece que impede o método de ser concluído com êxito podem ser isoladas do código normal. Além disso, se você não pode lidar com a exceção onde ela ocorreu e deve passá-la para cima na pilha para uma IU (ou pior, através da conexão de um componente de camada intermediária para uma IU), então, com o modelo de exceção,
fonte
No passado, entrei no campo do código de erro (fazia muita programação C). Mas agora eu vi a luz.
Sim, as exceções são um pouco onerosas para o sistema. Mas eles simplificam o código, reduzindo o número de erros (e WTF's).
Portanto, use a exceção, mas com sabedoria. E eles serão seus amigos.
Como uma nota rodapé. Aprendi a documentar qual exceção pode ser lançada por qual método. Infelizmente, isso não é exigido pela maioria dos idiomas. Mas aumenta a chance de lidar com as exceções certas no nível certo.
fonte
Pode haver algumas situações em que usar exceções de uma maneira limpa, clara e correta seja complicado, mas na grande maioria das vezes as exceções são a escolha óbvia. O maior benefício que o tratamento de exceções tem sobre os códigos de erro é que muda o fluxo de execução, o que é importante por dois motivos.
Quando ocorre uma exceção, o aplicativo não está mais seguindo seu caminho de execução 'normal'. A primeira razão pela qual isso é tão importante é que, a menos que o autor do código saia bem e verdadeiramente de seu caminho para ser ruim, o programa irá parar e não continuará fazendo coisas imprevisíveis. Se um código de erro não for verificado e as ações apropriadas não forem tomadas em resposta a um código de erro ruim, o programa continuará fazendo o que está fazendo e quem sabe qual será o resultado dessa ação. Existem muitas situações em que fazer com que o programa faça "qualquer coisa" pode acabar saindo muito caro. Considere um programa que recupera informações de desempenho de vários instrumentos financeiros que uma empresa vende e entrega essas informações a corretores / atacadistas. Se algo der errado e o programa continuar, ele poderia enviar dados de desempenho errôneos aos corretores e atacadistas. Não sei de mais ninguém, mas não quero ser aquele sentado no escritório de um VP explicando por que meu código fez com que a empresa recebesse multas regulatórias no valor de 7 dígitos. Entregar uma mensagem de erro aos clientes é geralmente preferível a entregar dados errados que podem parecer 'reais', e a última situação é muito mais fácil de enfrentar com uma abordagem muito menos agressiva, como códigos de erro.
A segunda razão pela qual gosto de exceções e sua quebra da execução normal é que isso torna muito, muito mais fácil manter a lógica de 'coisas normais estão acontecendo' separada da lógica de 'algo deu errado'. Para mim, este:
... é preferível a isto:
Existem outras pequenas coisas sobre as exceções que são boas também. Ter um monte de lógica condicional para controlar se algum dos métodos sendo chamados em uma função teve um código de erro retornado e retornar esse código de erro mais acima é um monte de boiler plate. Na verdade, é um monte de boiler plate que pode dar errado. Tenho muito mais fé no sistema de exceção da maioria das línguas do que em um ninho de ratos de declarações if-else-if-else que Fred escreveu "recém-saído da faculdade", e tenho coisas muito melhores para fazer com o meu tempo do que o código revisando o dito ninho de rato.
fonte
Você deve usar ambos. A questão é decidir quando usar cada um .
Existem alguns cenários em que as exceções são a escolha óbvia :
Em algumas situações você não pode fazer nada com o código de erro , e você só precisa tratá-lo em um nível superior na pilha de chamadas , geralmente apenas registrar o erro, exibir algo para o usuário ou fechar o programa. Nesses casos, os códigos de erro exigiriam que você borbulhasse os códigos de erro manualmente nível por nível, o que é obviamente muito mais fácil de fazer com exceções. A questão é que isso é para situações inesperadas e intratáveis .
Ainda assim, sobre a situação 1 (onde algo inesperado e impossível de manusear acontece, você simplesmente não quer registrar), as exceções podem ser úteis porque você pode adicionar informações contextuais . Por exemplo, se eu obtiver uma SqlException em meus auxiliares de dados de nível inferior, vou querer capturar esse erro no nível inferior (onde conheço o comando SQL que causou o erro) para que possa capturar essas informações e relançar com informações adicionais . Observe a palavra mágica aqui: relançar e não engolir . A primeira regra de tratamento de exceções: não engula exceções . Além disso, observe que minha captura interna não precisa registrar nada porque a captura externa terá o rastreamento de pilha inteiro e pode registrá-lo.
Em algumas situações, você tem uma sequência de comandos, e se algum deles falhar, você deve limpar / descartar recursos (*), seja esta uma situação irrecuperável (que deve ser lançada) ou uma situação recuperável (neste caso, você pode lidar localmente ou no código do chamador, mas você não precisa de exceções). Obviamente, é muito mais fácil colocar todos esses comandos em uma única tentativa, em vez de testar os códigos de erro após cada método e limpar / descartar no bloco finally. Por favor, note que se você quiser que o erro borbulhe (que é provavelmente o que você quer), você nem precisa detectá-lo - você apenas usa o finally para limpar / descartar - você só deve usar catch / retrow se quiser para adicionar informações contextuais (consulte o marcador 2).
Um exemplo seria uma sequência de instruções SQL dentro de um bloco de transação. Novamente, esta também é uma situação "intratável", mesmo se você decidir pegá-la cedo (trate-a localmente em vez de borbulhar até o topo), ainda é uma situação fatal em que o melhor resultado é abortar tudo ou pelo menos abortar um grande parte do processo.
(*) É como o
on error goto
que usávamos no antigo Visual BasicEm construtores, você só pode lançar exceções.
Dito isso, em todas as outras situações em que você está retornando alguma informação sobre a qual o chamador PODE / DEVE fazer alguma coisa , usar códigos de retorno é provavelmente uma alternativa melhor. Isso inclui todos os "erros" esperados , porque provavelmente eles devem ser tratados pelo chamador imediato, e dificilmente precisarão subir muitos níveis na pilha.
É claro que sempre é possível tratar os erros esperados como exceções e capturar imediatamente um nível acima, e também é possível incluir cada linha de código em um try catch e executar ações para cada erro possível. IMO, este é um design ruim, não apenas porque é muito mais prolixo, mas especialmente porque as possíveis exceções que podem ser lançadas não são óbvias sem a leitura do código-fonte - e exceções podem ser lançadas de qualquer método profundo, criando gotos invisíveis . Eles quebram a estrutura do código criando vários pontos de saída invisíveis que tornam o código difícil de ler e inspecionar. Em outras palavras, você nunca deve usar exceções como controle de fluxo, porque isso seria difícil para outras pessoas entenderem e manterem. Pode ficar ainda mais difícil entender todos os fluxos de código possíveis para teste.
Novamente: para uma limpeza / descarte corretos, você pode usar try-finally sem pegar nada .
A crítica mais popular sobre os códigos de retorno é que "alguém pode ignorar os códigos de erro, mas no mesmo sentido, alguém também pode engolir exceções. O tratamento incorreto de exceções é fácil em ambos os métodos. Mas escrever um bom programa baseado em código de erro ainda é muito mais fácil do que escrever um programa baseado em exceções . E se alguém por qualquer motivo decidir ignorar todos os erros (os antigos
on error resume next
), você pode fazer isso facilmente com códigos de retorno e não pode fazer isso sem um monte de boilerplate try-catchs.A segunda crítica mais popular sobre os códigos de retorno é que "é difícil aparecer" - mas isso é porque as pessoas não entendem que as exceções são para situações não recuperáveis, enquanto os códigos de erro não.
Decidir entre exceções e códigos de erro é uma área cinzenta. É até possível que você precise obter um código de erro de algum método de negócios reutilizável e, em seguida, decida agrupá-lo em uma exceção (possivelmente adicionando informações) e deixá-lo borbulhar. Mas é um erro de design presumir que TODOS os erros devem ser lançados como exceções.
Resumindo:
Gosto de usar exceções quando tenho uma situação inesperada, em que não há muito o que fazer e geralmente queremos abortar um grande bloco de código ou até mesmo toda a operação ou programa. Isso é como o antigo "em erro, goto".
Gosto de usar códigos de retorno quando espero situações em que o código do chamador pode / deve realizar alguma ação. Isso inclui a maioria dos métodos de negócios, APIs, validações e assim por diante.
Essa diferença entre exceções e códigos de erro é um dos princípios de design da linguagem GO, que usa "pânico" para situações inesperadas fatais, enquanto as situações normais esperadas são retornadas como erros.
Já sobre o GO, ele também permite múltiplos valores de retorno , o que é algo que ajuda muito no uso de códigos de retorno, já que você pode retornar simultaneamente um erro e algo mais. Em C # / Java, podemos conseguir isso sem parâmetros, Tuplas ou (meu favorito) Genéricos, que combinados com enums podem fornecer códigos de erro claros para o chamador:
Se eu adicionar um novo retorno possível em meu método, posso até verificar todos os chamadores se eles estão cobrindo esse novo valor em uma instrução switch, por exemplo. Você realmente não pode fazer isso com exceções. Quando você usa códigos de retorno, geralmente sabe com antecedência todos os erros possíveis e os testa. Com exceções, você geralmente não sabe o que pode acontecer. Encapsular enums dentro de exceções (em vez de genéricos) é uma alternativa (desde que esteja claro o tipo de exceções que cada método lançará), mas IMO ainda é um projeto ruim.
fonte
Meu raciocínio seria se você estiver escrevendo um driver de baixo nível que realmente precisa de desempenho, use códigos de erro. Mas se você estiver usando esse código em um aplicativo de nível superior e ele puder lidar com um pouco de sobrecarga, envolva esse código com uma interface que verifica esses códigos de erro e levanta exceções.
Em todos os outros casos, as exceções são provavelmente o caminho a percorrer.
fonte
Posso estar sentado em cima do muro aqui, mas ...
Em Python, o uso de exceções é uma prática padrão e estou muito feliz em definir minhas próprias exceções. Em C, você não tem exceções.
Em C ++ (pelo menos no STL), as exceções são normalmente lançadas apenas para erros verdadeiramente excepcionais (virtualmente nunca os vejo). Não vejo razão para fazer algo diferente em meu próprio código. Sim, é fácil ignorar os valores de retorno, mas C ++ também não o força a capturar exceções. Acho que você só precisa adquirir o hábito de fazer isso.
O código base em que trabalho é principalmente C ++ e usamos códigos de erro em quase todos os lugares, mas há um módulo que levanta exceções para qualquer erro, incluindo os muito comuns, e todo o código que usa esse módulo é horrível. Mas isso pode ser apenas porque misturamos exceções e códigos de erro. O código que usa consistentemente códigos de erro é muito mais fácil de trabalhar. Se nosso código usasse exceções consistentemente, talvez não fosse tão ruim. Misturar os dois não parece funcionar tão bem.
fonte
Como trabalho com C ++ e tenho RAII para torná-los seguros para uso, uso exceções quase que exclusivamente. Ele puxa o tratamento de erros do fluxo normal do programa e torna a intenção mais clara.
Deixo exceções para circunstâncias excepcionais. Se estou esperando que um certo erro aconteça com frequência, verificarei se a operação será bem-sucedida antes de executá-la ou chamo uma versão da função que usa códigos de erro (como
TryParse()
)fonte
As assinaturas do método devem comunicar a você o que o método faz. Algo como long errorCode = getErrorCode (); pode estar bem, mas long errorCode = fetchRecord (); é confuso.
fonte
Minha abordagem é que podemos usar ambos, ou seja, códigos de exceções e erros ao mesmo tempo.
Estou acostumado a definir vários tipos de Exceptions (ex: DataValidationException ou ProcessInterruptExcepion) e dentro de cada exceção definir uma descrição mais detalhada de cada problema.
Um exemplo simples em Java:
Dessa forma, eu uso exceções para definir erros de categoria e códigos de erro para definir informações mais detalhadas sobre o problema.
fonte
throw new DataValidationException("The input is too small")
? Uma das vantagens das exceções é permitir informações detalhadas.As exceções são para circunstâncias excepcionais - isto é, quando não fazem parte do fluxo normal do código.
É bastante legítimo misturar exceções e códigos de erro, onde códigos de erro representam o status de algo, ao invés de um erro na execução do código em si (por exemplo, verificar o código de retorno de um processo filho).
Mas quando ocorre uma circunstância excepcional, acredito que as exceções são o modelo mais expressivo.
Há casos em que você pode preferir, ou deve, usar códigos de erro no lugar de Exceções, e eles já foram adequadamente cobertos (além de outras restrições óbvias, como suporte do compilador).
Mas indo na outra direção, usar Exceptions permite que você crie abstrações de nível ainda mais alto para o tratamento de erros, o que pode tornar seu código ainda mais expressivo e natural. Eu recomendo fortemente a leitura deste excelente, embora subestimado, artigo do especialista em C ++ Andrei Alexandrescu sobre o que ele chama de "Enforcements": http://www.ddj.com/cpp/184403864 . Embora seja um artigo C ++, os princípios são geralmente aplicáveis, e traduzi o conceito de reforços para C # com bastante sucesso.
fonte
Em primeiro lugar, concordo com a resposta de Tom de que, para itens de alto nível, use exceções, e para itens de baixo nível, use códigos de erro, contanto que não seja Arquitetura Orientada a Serviços (SOA).
Em SOA, onde os métodos podem ser chamados em máquinas diferentes, as exceções não podem ser transmitidas, em vez disso, usamos respostas de sucesso / falha com uma estrutura como a abaixo (C #):
E use assim:
Quando eles são usados consistentemente em suas respostas de serviço, ele cria um padrão muito bom de tratamento de sucessos / falhas no aplicativo. Isso permite um tratamento mais fácil de erros em chamadas assíncronas nos serviços, bem como entre os serviços.
fonte
Eu preferiria as exceções para todos os casos de erro, exceto quando uma falha é um resultado esperado sem erros de uma função que retorna um tipo de dados primitivo. Por exemplo, encontrar o índice de uma substring dentro de uma string maior normalmente retornaria -1 se não fosse encontrado, em vez de gerar uma NotFoundException.
Retornar ponteiros inválidos que podem ser desreferenciados (por exemplo, causando NullPointerException em Java) não é aceitável.
Usar vários códigos de erro numéricos diferentes (-1, -2) como valores de retorno para a mesma função geralmente é um estilo incorreto, pois os clientes podem fazer uma verificação "== -1" em vez de "<0".
Uma coisa a ter em mente aqui é a evolução das APIs ao longo do tempo. Uma boa API permite alterar e estender o comportamento de falha de várias maneiras sem interromper os clientes. Por exemplo, se um identificador de erro do cliente for verificado para 4 casos de erro, e você adicionar um quinto valor de erro à sua função, o manipulador do cliente pode não testar isso e quebrar. Se você lançar exceções, isso geralmente tornará mais fácil para os clientes migrarem para uma versão mais recente de uma biblioteca.
Outra coisa a se considerar é, ao trabalhar em equipe, traçar uma linha clara para que todos os desenvolvedores tomem essa decisão. Por exemplo, "Exceções para coisas de alto nível, códigos de erro para coisas de baixo nível" é muito subjetivo.
Em qualquer caso, onde mais de um tipo trivial de erro é possível, o código-fonte nunca deve usar o literal numérico para retornar um código de erro ou para tratá-lo (retornar -7, se x == -7 ...), mas sempre uma constante nomeada (retornar NO_SUCH_FOO, se x == NO_SUCH_FOO).
fonte
Se você trabalha em um grande projeto, não pode usar apenas exceções ou apenas códigos de erro. Em casos diferentes, você deve usar abordagens diferentes.
Por exemplo, você decide usar apenas exceções. Mas depois de decidir usar o processamento assíncrono de eventos. É uma má ideia usar exceções para tratamento de erros nessas situações. Mas usar códigos de erro em todos os lugares do aplicativo é entediante.
Portanto, minha opinião é que é normal usar exceções e códigos de erro simultaneamente.
fonte
Para a maioria dos aplicativos, as exceções são melhores. A exceção é quando o software precisa se comunicar com outros dispositivos. O domínio em que trabalho é o de controles industriais. Aqui, os códigos de erro são preferidos e esperados. Portanto, minha resposta é que depende da situação.
fonte
Acho que também depende se você realmente precisa de informações como rastreamento de pilha do resultado. Se sim, você definitivamente vai para Exception, que fornece objeto cheio de informações sobre o problema. No entanto, se você está interessado apenas no resultado e não se importa por que esse resultado, vá para o código de erro.
Por exemplo, quando você está processando um arquivo e enfrenta IOException, o cliente pode se interessar em saber de onde isso foi disparado, em abrir o arquivo ou analisar o arquivo, etc. Portanto, é melhor retornar IOException ou sua subclasse específica. Porém, cenário como você tem método de login e quer saber se foi bem sucedido ou não, ou você apenas retorna booleano ou para mostrar a mensagem correta, retorna o código de erro. Aqui, o Cliente não está interessado em saber qual parte da lógica causou aquele código de erro. Ele só sabe se sua credencial é inválida ou se a conta está bloqueada etc.
Outro caso de uso em que posso pensar é quando os dados trafegam na rede. Seu método remoto pode retornar apenas o código de erro em vez de Exceção para minimizar a transferência de dados.
fonte
Minha regra geral é:
fonte
Os códigos de erro também não funcionam quando o seu método retorna qualquer coisa diferente de um valor numérico ...
fonte