Tratamento de erros - Se um programa falhar nos erros ou ignorá-los silenciosamente

20

Estou escrevendo um pequeno programa simples para transmitir MIDI através de uma rede. Sei que o programa encontrará problemas de transmissão e / ou outras situações de exceção que não poderei prever.

Para o tratamento de exceções, vejo duas abordagens. Devo escrever o programa para que:

  • falha com um estrondo quando algo dá errado ou
  • deveria apenas ignorar o erro e continuar, à custa da integridade dos dados?

Qual abordagem um usuário esperaria razoavelmente?
Existe uma maneira melhor de lidar com exceções?

Além disso, minha decisão sobre o tratamento de exceções deve ser afetada pelo fato de eu estar ou não lidando com uma conexão de rede (por exemplo, algo em que posso razoavelmente esperar que ocorram problemas)?

Arlen Beiler
fonte
1
@ GlenH7 Pay It Forward ... :)
mosquito
Existe uma maneira de marcar com +1? ;) Oh, espere, eu sigo você. Certo, vai fazer :) #
22613 Arlen Beiler
2
"Qual abordagem um usuário esperaria razoavelmente?" Você tentou perguntar a um de seus usuários? Os testes de usabilidade no corredor podem ser notavelmente eficazes. Veja blog.openhallway.com/?p=146
Bryan Oakley
Eu não tenho quaisquer usuários :)
Arlen Beiler

Respostas:

34

Nunca, você nunca deve ignorar um erro que seu programa encontra. No mínimo, você deve registrá-lo em um arquivo ou em algum outro mecanismo para notificação. Não pode haver situações ocasionais em que você vai querer ignorar um erro, mas documentá-lo! Não escreva um catchbloco vazio sem comentários que expliquem por que está vazio.

Se o programa falhará ou não, depende muito do contexto. Se você conseguir lidar com o erro normalmente, vá em frente. Se for um erro inesperado, seu programa falhará. Isso é praticamente o básico do tratamento de exceções.

marco-fiset
fonte
18
Não é suficiente para garantir um -1, mas "nunca" é uma palavra forte, e há situações em que ignorar erros é o curso de ação correto. Por exemplo, software para decodificar transmissões de transmissão HDTV. Quando você encontra alguns erros, o que frequentemente ocorre, tudo o que você pode fazer é ignorá-lo e decodificar o que vem depois.
Whatsisname
1
@ whatsisname: true, mas você deve documentar claramente por que está ignorando. Resposta atualizada para considerar seu comentário.
marco-Fiset
1
@ marco-fiset Eu discordo. Falhando no programa inteiro é apenas a solução se for uma situação em que a reinicialização corrija o problema. Esse nem sempre é o caso; portanto, travar é muitas vezes inútil e o padrão é que o comportamento é estúpido. Por que você deseja suspender toda a operação devido a um erro localizado? Isso não faz sentido. A maioria dos aplicativos faz isso e, por algum motivo, os programadores estão completamente bem com isso.
MaiaVictor
@Dokkat: o ponto é não continuar com valores errados, o que dará uma solução errada (entrada e saída de lixo). Falhando faz corrigir o problema: ele força o usuário quer fornecer entradas diferentes ou pester o desenvolvedor a correção do seu programa;)
André Paramés
1
@ whatsisname Eu acho que você pode querer pensar na sua definição de "erro" nesse caso. Erros nos dados recebidos não são a mesma coisa que erros na estrutura e execução de um programa de software.
Joshin4colours 15/03/2013
18

Você nunca deve ignorar silenciosamente os erros, porque seu programa é desenvolvido com base em uma série de ações que dependem implicitamente de tudo o que aconteceu antes que elas dêem certo. Se algo der errado na etapa 3 e você tentar continuar na etapa 4, a etapa 4 começará com base em suposições inválidas, o que aumenta a probabilidade de que também acabe gerando um erro. (E se você ignorar isso também, a etapa 5 gera um erro e as coisas começam a bola de neve a partir daí.)

O problema é que, à medida que os erros se acumulam, você eventualmente cometerá um erro tão grande que não poderá ignorá-lo, porque consistirá em algo que está sendo dado ao usuário e que algo estará completamente errado. Então você tem usuários reclamando de que seu programa não está funcionando corretamente e você precisa corrigi-lo. E se a parte "fornecer algo ao usuário" estiver na etapa 28 e você não tiver idéia de que o erro original que está causando toda essa bagunça estava na etapa 3, porque você ignorou o erro na etapa 3, terá uma muito tempo depurando o problema!

Por outro lado, se esse erro na etapa 3 fizer tudo explodir na cara do usuário e gerar um erro dizendo SOMETHING WENT BADLY WRONG IN STEP 3!(ou seu equivalente técnico, um rastreamento de pilha), o resultado será o mesmo - o usuário reclamando de você o programa não está funcionando corretamente - mas desta vez você sabe exatamente por onde começar a procurar quando for corrigi-lo .

EDIT: Em resposta aos comentários, se algo der errado que você antecipou e sabe como lidar, isso é diferente. Por exemplo, no caso de receber uma mensagem incorreta, isso não é um erro de programa; isso é "o usuário forneceu informações incorretas que falharam na validação". A resposta apropriada é informar ao usuário que ele está fornecendo informações inválidas, e é isso que parece que você está fazendo. Não há necessidade de travar e gerar um rastreamento de pilha em um caso como esse.

Mason Wheeler
fonte
Que tal retornar à última posição boa conhecida ou, no caso de um loop de recebimento, simplesmente largar essa mensagem e passar para a próxima.
Arlen Beiler 14/03/2013
@Arlen: Mesmo isso pode ser uma má idéia. Os erros ocorrem porque aconteceu algo que você não esperava quando estava codificando . Isso significa que todas as suas suposições foram eliminadas. Essa "última posição válida" pode não ser mais boa se você estivesse no meio da modificação de alguma estrutura de dados e agora ela estiver em um estado inconsistente, por exemplo.
Mason Wheeler
E se eu estiver esperando, como mensagens corrompidas que impedem a desserialização. Em alguns casos, serializei a exceção e a enviei de volta. Lá, veja minha edição da pergunta.
Arlen Beiler 14/03/2013
@Arlen: Se você o antecipou e tem certeza de que sabe quais são os efeitos do erro e que eles estão contidos corretamente, então isso é diferente. Parece-me que você está lidando com o erro e respondendo adequadamente, sem ignorá-lo. Eu estava falando sobre ignorar erros inesperados , que é o que parecia que você estava perguntando.
Mason Wheeler
16

Existem outras opções entre "explodir" e "ignorar".

Se o erro for previsível e evitável, altere seu design ou refatore seu código para evitá-lo.

Se o erro for previsível, mas não evitável, mas você souber o que fazer quando isso acontecer, identifique o erro e lide com a situação. Mas tenha cuidado para evitar o uso de exceções como controle de fluxo. E você pode registrar um aviso neste momento e talvez notificar o usuário se houver alguma ação que ele possa tomar para evitar essa situação no futuro.

Se o erro for previsível, inevitável e, quando isso acontecer, não haverá nada que garanta a integridade dos dados, será necessário registrar o erro e retornar a um estado seguro (o que, como já foi dito, pode significar falha).

Se o erro não é algo que você previu, então você realmente não pode ter certeza de que pode voltar a um estado seguro; portanto, é melhor apenas registrar e travar.

Como regra geral, não pegue nenhuma exceção sobre a qual você não possa fazer nada, a menos que planeje registrá-la e reproduzi-la novamente. E nos raros casos em que um try-catch-ignore é inevitável, adicione pelo menos um comentário no bloco catch para explicar o porquê.

Consulte o excelente artigo sobre tratamento de exceções de Eric Lippert para obter mais sugestões sobre como categorizar e manipular exceções.

John M Gant
fonte
1
A melhor resposta até agora, na minha opinião.
thursdaysgeek
6

Estes são os meus pontos de vista sobre a questão:

Um bom princípio de partida é falhar rapidamente. Especificamente, você nunca deve escrever um código de manipulação de erros para qualquer falha cuja causa não seja conhecida.

Depois de aplicar esse princípio, você pode adicionar o código de recuperação para condições de erro específicas encontradas. Você também pode introduzir vários "estados seguros" para retornar. A interrupção de um programa é quase sempre segura, mas às vezes você pode querer retornar a outro bom estado conhecido. Um exemplo é como um sistema operacional moderno lida com um programa incorreto. Ele apenas desliga o programa, não todo o sistema operacional.

Ao falhar rápida e lentamente, cobrindo condições de erro cada vez mais específicas, você nunca compromete a integridade dos dados e avança constantemente para um programa mais estável.

Erros de deglutição, ou seja, tentar planejar erros para os quais você não conhece a causa exata e, portanto, não possui uma estratégia de recuperação específica, apenas leva a uma quantidade crescente de códigos de pular e contornar erros no seu programa. Como não se pode confiar que os dados anteriores foram processados ​​corretamente, você começará a verificações dispersas de dados incorretos ou ausentes. Sua complexidade ciclomática ficará fora de controle e você terminará com uma grande bola de lama.

Se você está ciente ou não dos casos de falha é menos importante. Mas se você estiver lidando, por exemplo, com uma conexão de rede para a qual conhece uma certa quantidade de estados de erro, adie a adição de tratamento de erros até também adicionar código de recuperação. Isso está de acordo com os princípios descritos acima.

Alexander Torstling
fonte
6

Você nunca deve ignorar silenciosamente os erros. E, especialmente, não à custa da integridade dos dados .

O programa está tentando fazer alguma coisa. Se falhar, você deve encarar o fato e fazer algo a respeito. O que essa coisa será depende de muitas coisas.

No final, o usuário solicitou que o programa fizesse alguma coisa e o programa deveria dizer a eles que não teve êxito. Há muitas maneiras de como fazer isso. Pode parar imediatamente, pode até reverter as etapas já concluídas ou, por outro lado, pode continuar e concluir todas as etapas possíveis e depois informar ao usuário que essas etapas foram bem-sucedidas e as demais falharam.

O caminho que você escolher depende da proximidade entre as etapas e se é provável que o erro ocorra novamente em todas as etapas futuras, o que, por sua vez, pode depender do erro exato. Se for necessária uma integridade forte dos dados, você precisará reverter para o último estado consistente. Se você está apenas copiando vários arquivos, pode pular alguns e dizer ao usuário no final que esses arquivos não puderam ser copiados. Você não deve pular arquivos silenciosamente e não dizer nada ao usuário.

Edição de anúncio, a única diferença que faz é que você deve tentar repetir várias vezes antes de desistir e dizer ao usuário que não funcionou, pois é provável que a rede tenha erros transitórios que não ocorrerão novamente se você tentar novamente.

Jan Hudec
fonte
Você realmente quis colocar um ponto de interrogação no final da primeira linha?
a CVn 15/03
@ MichaelKjörling: Não. Erro de copiar e colar (copiou a formulação da pergunta e incluiu o ponto de interrogação por engano).
Jan Hudec
4

Há uma classe de casos em que ignorar erros é a coisa certa a fazer: quando não há nada que possa ser feito sobre a situação e quando resultados ruins e possivelmente incorretos são melhores que nenhum resultado.

O caso da decodificação do fluxo HDMI para fins de exibição é esse. Se o fluxo é ruim, é ruim, gritar sobre isso não o corrigirá magicamente. Você faz o possível para exibi-lo e deixa o espectador decidir se é tolerável ou não.

Loren Pechtel
fonte
1

Não acredito que um programa silenciosamente ignore ou cause estragos sempre que ocorrer um problema.

O que faço com o software interno que escrevo para minha empresa ...

Depende do erro, digamos que se é uma função crítica que está inserindo dados no MySQL, ele precisa informar ao usuário que falhou. O manipulador de erros deve tentar coletar o máximo de informações e fornecer ao usuário uma idéia de como corrigir o erro para que eles possam salvar os dados. Também gosto de fornecer uma maneira silenciosa de nos enviar as informações que eles tentam salvar, para que, se piorar, possamos inseri-la manualmente após a correção do bug.

Se não for uma função crítica, algo que pode errar e não afetar o resultado final do que eles estão tentando alcançar, talvez eu não mostre uma mensagem de erro, mas envie um email que o insira automaticamente em nosso software de rastreamento de bugs ou um grupo de distribuição de e-mail que alerta todos os programadores da empresa para que tomemos conhecimento do erro, mesmo que o usuário não esteja. Isso nos permite corrigir o back-end enquanto no front-end ninguém sabe o que está acontecendo.

Uma das maiores coisas que tento evitar é travar o programa após o erro - não conseguir recuperar. Eu sempre tento dar ao usuário a opção de continuar sem fechar o aplicativo.

Acredito que se ninguém souber sobre o bug - ele nunca será corrigido. Também acredito firmemente no tratamento de erros que permite que o aplicativo continue funcionando depois que um bug for descoberto.

Se o erro estiver relacionado à rede - por que não fazer com que as funções executem um teste simples de comunicação de rede antes de executar a função para evitar o erro em primeiro lugar? Em seguida, basta alertar o usuário que uma conexão não está disponível, verifique sua internet etc. etc. e tente novamente?

Jeff
fonte
1
Pessoalmente, faço muita verificação antes de executar funções críticas para tentar limitar os erros que não compreendo completamente e não posso fornecer um tratamento de erro útil que retorne informações detalhadas. Não funciona 100% do tempo, mas não me lembro da última vez que tive um bug que me perdi por horas.
Jeff
1

Minha própria estratégia é distinguir entre erros de codificação (bugs) e erros de tempo de execução e, tanto quanto possível, dificultar a criação de erros de codificação.

Os erros precisam ser corrigidos o mais rápido possível, para que uma abordagem de Design por contrato seja apropriada. No C ++, gosto de verificar todas as minhas pré-condições (entradas) com asserções na parte superior da função para detectar o bug o mais rápido possível e facilitar a conexão de um depurador e a correção do bug. Se o desenvolvedor ou o testador optar por tentar continuar executando o programa, qualquer perda de integridade dos dados se tornará problema deles.

E encontre maneiras de evitar o bug em primeiro lugar. Ser rigoroso com a correção constante e escolher tipos de dados apropriados para os dados que eles conterão são duas maneiras de dificultar a criação de bugs. O Fail-Fast também é bom fora do código crítico de segurança que precisa de uma maneira de se recuperar.

Para erros de tempo de execução que podem ocorrer com código sem erros, como falhas de comunicação em rede ou serial ou arquivos ausentes ou corrompidos:

  1. Registre o erro.
  2. (Opcional) Tente tentar silenciosamente ou se recuperar da operação.
  3. Se a operação continuar falhando ou for irrecuperável, relate o erro visivelmente ao usuário. Então, como acima, o usuário pode decidir o que fazer. Lembre-se do princípio de menor espanto , porque uma perda de integridade dos dados surpreende o usuário, a menos que você os avise com antecedência.
snips-n-caracóis
fonte
0

Falhar é a opção certa quando você tem motivos para pensar que o estado geral do programa é instável e algo ruim pode acontecer se você deixar que ele seja executado a partir de agora. Um pouco "ignorá-lo" (ou seja, como outros já apontaram, registrando-o em algum lugar ou exibindo uma mensagem de erro para o usuário e continuando) está ok quando você sabe que, com certeza, a operação atual não pode ser executada, mas o programa pode continue correndo.

Fabien
fonte