Por que as exceções são consideradas melhores do que o teste de erro explícito? [duplicado]

43

Freqüentemente me deparei com posts acalorados nos quais o autor usa o argumento: "exceções versus verificação explícita de erros" para defender seu idioma preferido em algum outro idioma. O consenso geral parece ser que as linguagens que fazem uso de exceções são inerentemente melhores / mais limpas do que linguagens que dependem strongmente da verificação de erros através de chamadas explícitas de função.

O uso de exceções é considerado uma melhor prática de programação do que uma verificação explícita de erros e, em caso afirmativo, por quê?

    
por Richard Keller 25.09.2012 / 01:46
fonte

10 respostas

62

Na minha opinião, o maior argumento é a diferença no que acontece quando o programador comete um erro. Esquecer de lidar com um erro é um erro muito comum e fácil de fazer.

Se você retornar códigos de erro, é possível ignorar silenciosamente um erro. Por exemplo, se malloc falhar, ele retornará NULL e definirá o% globalerrno. Então o código correto deve fazer

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

Mas é muito fácil e conveniente escrever apenas:

void* myptr = malloc(1024);
doSomethingWith(myptr);

que inesperadamente passará NULL em seu outro procedimento e provavelmente descartará o errno que foi cuidadosamente definido. Não há nada visivelmente errado com o código para indicar que isso é possível.

Em uma linguagem que usa exceções, você escreveria

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

Neste exemplo (Java), o operador new retorna um objeto inicializado válido ou lança OutOfMemoryError . Se um programador deve lidar com isso, eles podem pegá-lo. No caso usual (e convenientemente, também o preguiçoso) em que é um erro fatal, a propagação da exceção encerra o programa de maneira relativamente limpa e explícita.

Essa é uma das razões pelas quais as exceções, quando usadas adequadamente, podem tornar a escrita mais clara e segura do código muito mais fácil. Este padrão se aplica a muitas, muitas coisas que podem dar errado, não apenas alocando memória.

    
por 25.09.2012 / 02:03
fonte
57

Embora a resposta de Steven forneça uma boa explicação, há outro ponto que considero bastante importante. Às vezes, quando você verifica um código de erro, não consegue lidar com o caso de falha imediatamente. Você tem que propagar o erro explicitamente através da pilha de chamadas. Quando você refatora uma função grande, talvez seja necessário adicionar todo o código clichê de verificação de erros à sua subfunção.

Com exceções, você só precisa cuidar do seu fluxo principal. Se uma parte do código lançar algum InvalidOperationError, você ainda poderá mover esse código para uma subfunção e a lógica de gerenciamento de erros será mantida.

Portanto, as exceções permitem refatorar mais rapidamente e evitar a placa da caldeira.

    
por 25.09.2012 / 03:09
fonte
17

Um ponto de vista de um ângulo diferente: O tratamento de erros tem tudo a ver com segurança. Um erro não verificado quebra todas as suposições e pré-condições nas quais o código a seguir foi baseado. Isso pode abrir muitos vetores de ataque externos: de um simples DoS sobre acesso não autorizado a dados, corrupção de dados e infiltração completa do sistema.

Claro, isso depende da aplicação específica, mas quando as suposições e condições prévias são quebradas, todas as apostas estão desativadas. Dentro de um software complexo, você simplesmente não pode mais dizer com certeza o que é possível a partir de então, e o que pode e não pode ser usado externamente.

Dado que, há uma observação fundamental: quando a segurança é tratada como um complemento opcional que pode ser anexado mais tarde, ela falha com mais freqüência. Funciona melhor quando já foi considerado nos primeiros estágios básicos de design e embutido desde o início.

Isso é o que você basicamente recebe com exceções: uma infraestrutura de tratamento de erros já incorporada que está ativa mesmo se você não se importa. Com testes explícitos, você tem que construir você mesmo. Você tem que construí-lo como um primeiro passo. Apenas começando a escrever funções que retornam códigos de erro, sem pensar no quadro geral até que você esteja nos estágios finais do desenvolvimento de seu aplicativo, é efetivamente um tratamento de erro adicional, fadado a falhar.

Não que um sistema interno ajude de alguma forma. A maioria dos programadores não sabe como lidar com erros. É muito complexo na maior parte do tempo.

  • Existem tantos erros que podem acontecer, e cada erro requer seu próprio manuseio e suas próprias ações e reações.

  • Até o mesmo erro pode exigir ações diferentes, com base no contexto. Que tal um arquivo não encontrado ou uma falta de memória?

    • FNF - Uma biblioteca de terceiros necessária para o seu aplicativo ser executado? Terminar.
    • FNF - O arquivo de configuração de inicialização do seu aplicativo? Comece com as configurações padrão.
    • FNF - Um arquivo de dados em um compartilhamento de rede que o usuário deseja que seu aplicativo abra? Arquivos que desaparecem podem acontecer a qualquer momento, mesmo em microssegundos entre Exists () e Open (). Não é nem mesmo uma "exceção" no significado literal da palavra.
    • OOM - Para uma alocação de 2 GB? Nenhuma surpresa.
    • OOM - Para uma alocação de 100 bytes? Você está com sérios problemas.
  • O tratamento de erros vaza por todas as suas camadas de abstração cuidadosamente separadas. Um erro no nível mais baixo pode requerer notificação ao usuário com uma mensagem GUI. Pode exigir uma decisão do usuário sobre o que fazer agora. Pode precisar de log. Pode necessitar de operações de recuperação noutra parte, e. banco de dados aberto ou conexões de rede. Etc.

  • E o estado? Quando você chama um método em um objeto que invoca alterações de estado e gera um erro:

    • O objeto está em um estado inconsistente e precisa de uma reconstrução?
    • O estado do objeto é consistente, mas (parcialmente) alterado e precisa de uma reversão adicional ou de uma reconstrução?
    • A reversão é feita dentro do objeto e permanece inalterada para o chamador?
    • Onde é mesmo sensato fazer o que?

Mostre-me apenas um livro de aprendizado onde o tratamento de erros é rigorosamente projetado desde o início e, consequentemente, usado em todos os exemplos, sem ser deixado de lado por brevidade e legibilidade e como exercício para o leitor. Se isto é aplicável a partir de um ponto de vista educacional, é uma outra questão, mas não é surpresa que o tratamento de erros seja frequentemente um segundo ou terceiro pensamento quando deveria ser o primeiro.

    
por 25.09.2012 / 10:05
fonte
14

Eu gostaria de pensar em exceções como ...

Eles permitem que você escreva código da maneira que naturalmente tende a ser escrito

Ao escrever códigos, há várias coisas que o cérebro humano precisa para fazer malabarismos:

  • Qual é a lógica que estou tentando expressar aqui?
  • Como faço isso na linguagem XXXX?
  • Como esse código se encaixa no restante do aplicativo?
  • Por que continuo recebendo esses erros de construção?
  • Estou esquecendo as condições de contorno?
  • Este código é legível e pode ser mantido o suficiente?
  • O que acontece se algo nesse código falhar?
  • Estou produzindo código em estilo consistente e use as convenções de codificação corretas?

Cada uma dessas balas requer uma certa concentração e a concentração é um recurso limitado. É por isso que as pessoas que são novas em um projeto geralmente esquecem as coisas na parte inferior da lista. Eles não são pessoas ruins, mas porque muitas coisas podem ser novas para eles (linguagem, projeto, erros estranhos, etc ...), eles simplesmente não têm a capacidade de lidar com outras coisas.

Eu fiz minha parte de revisões de código e essa tendência é muito aparente. Uma pessoa com menos experiência esquecerá mais coisas. Essas coisas incluem estilo e muitas vezes manipulação de erros. Quando as pessoas estão tendo dificuldade em fazer com que o código funcione, o tratamento de erros tende a ficar no fundo de suas mentes. Às vezes eles voltam e adicionam algumas declarações if aqui e ali, mas, com certeza, eles vão esquecer um monte de coisas.

O tratamento de exceções permite que você escreva códigos onde a carne da sua lógica é escrita como se não houvesse erros. Você faz uma chamada de função e a próxima linha pode simplesmente assumir que a linha anterior foi bem-sucedida. Isso tem um bônus adicional de produzir código que é muito mais fácil de ler. Você já viu uma referência de API com exemplos de código que continham um comentário, "// Erro ao manipular omitido para maior clareza"? Se eles removerem o tratamento de erros na documentação para facilitar a leitura do exemplo, não seria ótimo se você pudesse fazer a mesma coisa no código de produção e facilitar a leitura? Isso é exatamente o que as exceções permitem que você faça.

Agora pelo menos uma pessoa lendo este post dirá: "pffttt noob não sabe codificar. Eu nunca esqueço o tratamento de erros." a) Bom para você, mas mais importante b) como eu disse acima, a codificação requer que seu cérebro se concentre e só há muito cérebro para percorrer; que se aplica a todos. Se o seu código é fácil de ler, isso significa menos concentração. Se você pode colocar try / catch em torno de um grande pedaço e, em seguida, simplesmente codificar a lógica do aplicativo, isso significa menos concentração. Agora você pode usar essa capacidade extra para coisas mais importantes, como fazer o trabalho real muito mais rápido.

    
por 25.09.2012 / 05:40
fonte
6

Vantagens da exceção lançando sobre o código de erro que retorna:

  • O valor de retorno de uma função pode ser usado para o que foi projetado para fazer : retornar o resultado da chamada de função, não um código que represente sucesso ou uma das muitas falhas dentro da função. Isso cria um código mais limpo e elegante; menos parâmetros de saída / referência, atribuições são feitas para coisas que você realmente se importa e não para códigos de status descartáveis, etc.
  • As exceções são orientadas a objetos, portanto, os tipos de exceção têm um significado conceitual reutilizável . O que significa um código de retorno de -2? Você tem que procurar na documentação (o que é melhor que seja bom ou você é forçado a rastrear código, e se você não tem código fonte, você está realmente ferrando). O que significa um NullPointerException? Que você tentou chamar um método em uma variável que não faz referência a uma instância. Além disso, as exceções encapsulam dados, para que eles possam dizer exatamente como e onde seu programa errou em meios facilmente legíveis por humanos. Um código de retorno só pode informar o método errado.
  • Por padrão, os códigos de retorno são ignorados; exceções não são . Se você não armazenar e verificar o código de retorno, seu programa continua, corrompendo os dados, estragando os ponteiros e, em geral, se transformando em lixo. Se você não pegar uma exceção, ela será lançada no sistema operacional que finaliza seu programa. "Falha rápida".
  • As exceções são mais facilmente padronizadas . Como as exceções criam mais código de autodocumentação e, como a maioria de nós não grava todos os nossos programas 100% do zero, os tipos de exceção que abrangem uma ampla variedade de casos gerais já estão disponíveis. A solução mais barata é a que você já tem, portanto, esses tipos de exceção internos são usados em situações semelhantes àquelas para as quais foram originalmente criados. Agora, um InvalidOperationException significa que você tentou fazer algo inconsistente com o estado atual de um objeto (exatamente o que foi detalhado na mensagem de erro), independentemente de qual função de qual biblioteca de terceiros você estava tentando chamar.

    Compare isso com os códigos de retorno; para um método, -1 pode ser um "erro de ponteiro nulo", enquanto -2 pode ser um "erro de operação inválido". No próximo método, a condição "operação inválida" foi verificada primeiro e, assim, o erro recebeu o código de retorno -1, enquanto o "ponteiro nulo" recebeu -2. Um número é um número e você é livre para criar qualquer padrão para atribuir números a erros, assim como todos os outros, levando a um monte de padrões concorrentes.

  • As exceções permitem que você seja mais otimista . Em vez de verificar pessimisticamente tudo o que poderia dar errado com uma instrução antes de executar a instrução, você pode simplesmente tentar executar a instrução e capturar qualquer exceções que foram geradas por ele. Se você puder resolver a causa do erro e tentar novamente, ótimo, se não, bem; você provavelmente não poderia ter feito nada sobre isso se você soubesse de antemão.

    O tratamento de erros baseado em exceções cria um código mais eficiente, presumindo que funcionará até não funcionar. Declarações de guarda que retornam o tempo do processador de custo inicial; eles devem, portanto, ser usados principalmente quando os benefícios da verificação antecipada superam os custos; Se você puder determinar em alguns relógios que a entrada nunca produzirá uma saída válida, mas levará alguns segundos para que o corpo principal da função chegue à mesma conclusão, então, por todos os meios, coloque uma cláusula de guarda. No entanto, se houver uma dúzia de coisas que poderiam dar errado, nenhuma delas é particularmente provável e a maioria exige execução dispendiosa, o "caminho feliz" da execução normal do programa será várias vezes mais rápido se você simplesmente tentar / capturar. p>

por 25.09.2012 / 17:55
fonte
5

A diferença fundamental para mim é ler versus escrever:

A manipulação de código de erro tende a confundir a intenção : o código baseado em exceção geralmente é mais fácil de ler, pois a origem se concentra nas coisas que devem acontecer, e não em < em> poder .

OTOH, Um monte de código baseado em exceção é mais difícil de escrever, porque para uma análise de correção você tem que discutir detalhes "não relacionados", como ordem de construção / destruição, objetos parcialmente construídos, coleta de lixo etc.

Quão difícil? Isso é difícil.

A geração de exceções significativas pode sobrecarregar a fonte também. Tenho bases de código em que praticamente todas as linhas de código são seguidas por duas ou três linhas criando uma exceção alfabetizada.

Para transferir exceções significativas para o nível superior, elas geralmente precisam ser reempacotadas. Quando clico em "células rebicker", uma mensagem de erro como "Violação de compartilhamento em% tmp% \ foo \ file536821" não é muito mais útil do que "Erro 77" .

Eu realmente não tenho uma preferência, ambas as opções são igualmente feias. O que é pior: qual é o melhor parece depender muito do projeto, de maneiras difíceis de prever. Na minha experiência, a interação de hardware de baixo nível é mais suave com códigos de erro (podem ser os clientes ...), o acesso a dados de baixo nível é melhor com exceções.

    
por 25.09.2012 / 08:51
fonte
3

Vamos colocar os problemas de implementação de muitos idiomas de lado. As exceções dos benefícios são que elas podem ser centralizadas para lidar com todos os tipos (em teoria) de estados excepcionais do programa. O problema que eles enfrentam é que excepcionais coisas acontecem e dificilmente podem ser previstas. O que é ótimo (em teoria) com exceções é que você está seguro contanto que você:

  • Escreva código seguro de exceção para o mais alto grau possível
  • Capture os estados excepcionais por meio de exceções
  • Lide com as exceções de acordo
por 25.09.2012 / 02:50
fonte
2

O código baseado em exceção move o tratamento de erros para fora do fluxo principal do programa. Em vez disso, o tratamento de erros é movido para o destruidor de objetos ou para a construção de captura.

A maior vantagem e também a maior desvantagem, com exceção, é que não força você a pensar sobre o tratamento de erros. Com exceção, você freqüentemente escreveria código simples e deixaria o destruidor fazer o seu trabalho, e esqueceria aquele pequeno cenário que o destruidor não manipula e precisa ser feito manualmente (o que é claramente documentado e, portanto, é totalmente sua culpa). para esquecer). O mesmo cenário pode acontecer com o código de erro, mas ao escrever código com código de erro, você é forçado a verificar o manual / documentação / fonte o tempo todo e é menos provável que você esqueça essa pequena condição.

Sim, isso significa que as exceções podem dificultar a gravação do código correto.

Escrever um código incorreto com código de erro é fácil, escrever um código bom com código de erro é difícil. Escrever um código decente na exceção é fácil (porque qualquer erro encerra o programa ou é capturado por manipuladores de nível muito alto que poderiam avisar da desgraça iminente, em vez de serem passados silenciosamente), escrever código bom com exceção é realmente difícil (porque você está lidando com os erros de um braço do local onde o erro acontece).

    
por 25.09.2012 / 11:03
fonte
1
  • Fácil esquecer os códigos de saída
  • Código duplicado. Verificar o código de erro e retornar o código de erro quando houver um erro causaria a manipulação de muitos códigos duplicados e muito a alteração, caso o tipo de retorno seja alterado. Além disso, o que fazer quando diferentes tipos podem causar um erro?
  • Exceções são executadas fora do caminho do código, o que significa que não há trabalho verificando o valor de retorno. Mas há trabalho no binário em algum lugar quando o código de exceção é executado.
  • É fácil ver quais funções lidam com o tipo de erro. Você não precisa procurar muito código, basta percorrer a captura.
  • Mais detalhes (em determinados idiomas). Ele incorporou informações como qual linha, função, etc. Colocar isso em uma classe de erro seria tedioso. Provavelmente você escreveria apenas o nome da função e, esperamos, o tipo ou a causa do erro.
  • Depuradores entendem que as exceções são erros e podem pausar quando acertar uma enquanto você estiver depurando. Ferramentas ajuda:)
por 25.09.2012 / 03:25
fonte
-1

Se ambos são implementados perfeitamente as diferenças não são muito, no entanto, geralmente nada é perfeito. Se o tratamento de alguns erros estiver faltando, no caso de exceções, o programa pára com um erro visível, enquanto o programa com o teste de erros continua sendo executado com um comportamento indefinido.

Se você desenvolver um programa e tiver um erro que não percebeu, o que você preferiria ter?

  1. O programa trava então você começa a procurar e encontrar o bug (ou o programa trava nos beta testers, ou talvez no usuário para que eles possam enviar um relatório de bug) (manipulação de exceção)
  2. O programa continua em execução, enquanto isso silenciosamente corrompe algumas variáveis sem causar efeitos visíveis em seu computador. No entanto, ele excluirá todos os dados dos usuários quando eles forem executados, sem que eles percebam quando isso acontece. ( teste de erros )
por 25.09.2012 / 10:13
fonte