Como escrever uma boa mensagem de exceção

96

Atualmente estou fazendo uma revisão de código e uma das coisas que estou percebendo é o número de exceções em que a mensagem de exceção parece apenas reiterar onde a exceção ocorreu. por exemplo,

throw new Exception("BulletListControl: CreateChildControls failed.");

Todos os três itens desta mensagem podem funcionar a partir do resto da exceção. Eu conheço a classe e o método do rastreamento de pilha e sei que ele falhou (porque eu tenho uma exceção).

Isso me fez pensar sobre qual mensagem eu coloquei em mensagens de exceção. Primeiro eu crio uma classe de exceção, se uma já não existir, pelo motivo geral (por exemplo, PropertyNotFoundException - o por que ), e quando eu a lanço a mensagem indica o que deu errado (por exemplo " Não é possível encontrar a propriedade 'IDontExist' no nó 1234 "- o o que ). A onde está o StackTrace . O quando pode acabar no log (se aplicável). O como é para o desenvolvedor trabalhar (e corrigir)

Você tem outras dicas para gerar exceções? Especificamente em relação à criação de novos tipos e à mensagem de exceção.

    
por Colin Mackay 23.12.2010 / 12:43
fonte

7 respostas

67

Vou direcionar minha resposta mais para o que vem depois de uma exceção: para que serve e como o software deve se comportar, o que os usuários devem fazer com a exceção? Uma ótima técnica que encontrei no início da minha carreira foi sempre reportar problemas e erros em 3 partes: contexto, problema & solução. O uso dessa diciplina altera enormemente o tratamento de erros e torna o software muito melhor para os operadores usarem.

Aqui estão alguns exemplos.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

Neste caso, o operador sabe exatamente o que fazer e a qual arquivo deve ser afetado. Eles também sabem que as alterações no pool de conexão não ocorreram e devem ser repetidas.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Eu escrevo sistemas no lado do servidor e meus operadores geralmente são especialistas em suporte de primeira linha. Eu escreveria as mensagens de forma diferente para softwares de desktop que têm um público diferente, mas incluem as mesmas informações.

Várias coisas maravilhosas acontecem se alguém usa essa técnica. O desenvolvedor de software geralmente está em melhor posição para saber como resolver os problemas em seu próprio código, de modo que codificar soluções dessa forma enquanto você escreve o código é de grande benefício para os usuários finais que estão em desvantagem ao encontrar soluções, pois o que exatamente o software estava fazendo. Qualquer pessoa que já tenha lido uma mensagem de erro da Oracle saberá o que quero dizer.

A segunda coisa maravilhosa que vem à mente é quando você está tentando descrever uma solução em sua exceção e está escrevendo "Check X e se A e B em C". Este é um sinal muito claro e óbvio de que sua exceção está sendo verificada no lugar errado. Você, o programador, tem a capacidade de comparar as coisas no código, então as instruções "if" devem ser executadas no código, por que envolver o usuário em algo que pode ser automatizado? As chances são de que é mais profundo no código e alguém fez a coisa preguiçosa e lançou IOException a partir de qualquer número de métodos e pegou erros potenciais de todos eles em um bloco de código de chamada que não pode descrever adequadamente o que deu errado, o que contexto específico é e como corrigi-lo. Isso o encoraja a escrever erros de granulação mais refinados, capturá-los e manipulá-los no lugar certo em seu código, para que você possa articular adequadamente as etapas que o operador deve seguir.

Em uma empresa, tínhamos operadores de primeira linha que conheciam o software muito bem e mantinham seu próprio "run book", que aumentava nosso relatório de erros e sugeria soluções. Para reconhecer isso, o software começou incluindo links wiki para o runbook em exceções, de modo que uma explicação básica estava disponível, assim como links para discussões e observações mais avançadas pelos operadores ao longo do tempo.

Se você já teve a disciplina para tentar esta técnica, torna-se muito mais óbvio o que você deve nomear suas exceções no código ao criar o seu próprio. O NonRecoverableConfigurationReadFailedException torna-se uma abreviação do que você está prestes a descrever mais detalhadamente para o operador. Eu gosto de ser detalhado e acho que será mais fácil para o próximo desenvolvedor que tocar meu código para interpretar.

    
por 23.12.2010 / 13:14
fonte
22

Em esta pergunta mais recente , afirmei que as exceções não deveriam conter nenhuma mensagem. Na minha opinião, o fato de que eles fazem é um equívoco enorme. O que estou propondo é que

The "message" of the exception is the (fully qualified) class name of the exception.

Uma exceção deve conter dentro de suas próprias variáveis de membro tantos detalhes quanto possível sobre o que aconteceu exatamente; por exemplo, um IndexOutOfRangeException deve conter o valor do índice que foi considerado inválido, bem como os valores superiores e inferiores válidos no momento em que a exceção foi lançada. Desta forma, usando o reflexo você pode ter uma mensagem construída automaticamente, que é assim: IndexOutOfRangeException: index = -1; min=0; max=5 e isso, junto com o rastreamento de pilha, deve ser toda a informação objetiva que você precisa para solucionar o problema. Formatá-lo em uma mensagem bonita como "índice -1 não estava entre 0 e 5" não adiciona nenhum valor.

Em seu exemplo específico, a classe NodePropertyNotFoundException conteria o nome da propriedade que não foi encontrada e uma referência ao nó que não continha a propriedade. Isto é importante: deve não conter o nome do nó; deve conter uma referência ao nó atual. Em seu caso particular, isso pode não ser necessário, mas é uma questão de princípio e uma maneira preferida de pensar: a principal preocupação ao construir uma exceção é que ela deve ser utilizável por código que possa capturá-la. Usabilidade por seres humanos é uma preocupação importante, mas apenas secundária.

Isso cuida da situação muito frustrante que você pode ter testemunhado em algum momento de sua carreira, em que você pode ter capturado uma exceção contendo informações vitais sobre o que aconteceu no texto da mensagem, mas não dentro de suas variáveis de membro. , então você teve que fazer uma análise de string do texto para descobrir o que aconteceu, esperando que o texto da mensagem permaneça o mesmo em versões futuras da camada subjacente, e rezando para que o texto da mensagem não esteja em alguma língua estrangeira quando seu programa é executado em outros países.

Naturalmente, como o nome da classe da exceção é a mensagem da exceção, (e as variáveis de membro da exceção são os detalhes específicos), isso significa que você precisa de muitas exceções diferentes para transmitir todos os diferentes mensagens, e isso é bom.

Agora, às vezes, quando escrevemos código, nos deparamos com uma situação errônea para a qual apenas queremos codificar rapidamente uma instrução throw e continuar escrevendo nosso código em vez de interromper o que estamos fazendo para criar uma nova classe de exceção para que possamos jogá-lo ali mesmo. Para esses casos, eu tenho uma classe GenericException que de fato aceita uma mensagem de string como um parâmetro de tempo de construção, mas o construtor dessa classe de exceção é adornado com um grande comentário brilhante e roxo FIXME XXX TODO afirmando que cada A instanciação dessa classe deve ser substituída por uma instanciação de alguma classe de exceção mais especializada antes que o sistema de software seja liberado, de preferência antes que o código seja confirmado.

    
por 14.04.2015 / 13:31
fonte
13

Como regra geral, uma exceção deve ajudar os desenvolvedores a identificar a causa , fornecendo informações úteis (valores esperados, valor real, possíveis causas / solução, etc.).

Novos tipos de exceção devem ser criados quando nenhum dos tipos internos fizer sentido . Um tipo específico permite que outros desenvolvedores capturem uma exceção específica e lidem com isso. Se o desenvolvedor souber como lidar com sua exceção, mas o tipo for Exception , ele não poderá manipulá-lo corretamente.

    
por 23.12.2010 / 13:55
fonte
4

No .NET, nunca throw new Exception("...") (como o autor da pergunta mostrou). A exceção é o tipo de exceção de raiz e não deve ser lançada diretamente. Em vez disso, lance um dos tipos de exceção .NET derivados ou crie sua própria exceção personalizada que deriva de Exception (ou outro tipo de exceção).

Por que não lançar exceção? Porque jogar Exception não faz nada para descrever sua exceção e força seu código de chamada a escrever código como catch(Exception ex) { ... } , o que geralmente não é uma coisa boa! : -).

    
por 10.10.2016 / 17:48
fonte
2

As coisas que você quer estar procurando para "adicionar" à exceção são aqueles elementos de dados que não são inerentes à exceção ou ao rastreamento de pilha. Se essas são parte da "mensagem" ou precisam ser anexadas quando registradas, é uma questão interessante.

Como você já observou, a exceção lhe diz o que, o stacktrace provavelmente diz onde, mas o "porquê" pode estar mais envolvido (deve ser, seria de se esperar) do que apenas olhar para uma linha ou duas e dizendo "doh! Claro". Isso é ainda mais verdadeiro quando se registra erros no código de produção - muitas vezes fui mordido por dados ruins que foram encontrados em um sistema ativo que não existe em nossos sistemas de teste. Algo simples como saber qual é o ID do registro no banco de dados que está causando (ou contribuindo) para o erro pode economizar quantidades significativas de tempo.

Então ... listado ou, para .NET, adicionado à coleta de dados de exceções registradas (c.f. @Plip!):

  • Parâmetros (isso pode ser um pouco interessante - não é possível adicionar à coleta de dados se ela não for serializada e, às vezes, um único parâmetro pode ser surpreendentemente complexo)
  • Os dados adicionais retornados pelo ADO.NET ou Linq para SQL ou similar (isso também pode ser um pouco interessante!).
  • Qualquer outra coisa que possa não ser aparente.

Algumas coisas, é claro, você não saberá que precisa até não tê-las em seu relatório de erros inicial / log. Algumas coisas que você não percebe que pode conseguir até achar que precisa delas.

    
por 23.12.2010 / 16:55
fonte
0

O que são exceções para ?

(1) Dizer ao usuário que algo deu errado?

Este deve ser um último recurso, porque o seu código deve interceder e mostrar-lhes algo "melhor" que uma exceção.

A mensagem "erro" deve indicar de forma clara e sucinta o que deu errado e o que, se for o caso, o usuário pode fazer para recuperar da condição de erro.

por exemplo. "Por favor, não pressione este botão novamente"

(2) Dizer a um desenvolvedor quando deu errado?

Esse é o tipo de coisa que você registra em um arquivo para análise subseqüente.
O Stack Trace irá informar ao desenvolvedor onde o código quebrou; a mensagem deve, novamente, indicar o que deu errado.

(3) Contar um manipulador de exceção (ou seja, código) que algo deu errado?

O Tipo da Exceção decidirá qual manipulador de exceções poderá examiná-lo e as propriedades definidas no objeto Exceção permitirão ao manipulador lidar com ele.

A mensagem da exceção é totalmente irrelevante .

    
por 11.10.2016 / 13:14
fonte
-3

Não crie novos tipos se puder ajudar. Eles podem causar confusão, complexidade e levar a mais código para manutenção. Eles podem fazer com que seu código tenha que ser estendido. Projetar uma hierarquia de exceções exige muito e testar. Não é um pensamento posterior. Geralmente, é melhor usar a hierarquia de exceções de linguagem incorporada.

O conteúdo da mensagem de exceção depende do destinatário da mensagem - portanto, você deve se colocar no lugar dela.

Um engenheiro de suporte precisará identificar a origem do erro o mais rápido possível. Inclua uma pequena string descritiva e todos os dados que possam ajudar a solucionar o problema. Sempre inclua o rastreamento de pilha - se você puder - essa será a única fonte verdadeira de informações.

Apresentar erros aos usuários gerais do sistema depende do tipo de erro: se o usuário puder corrigir o problema, fornecendo informações diferentes, por exemplo, será necessária uma mensagem descritiva concisa. Se o usuário não puder corrigir o problema, é melhor declarar que ocorreu um erro e registrar / enviar um erro para suporte (usando as diretrizes acima).

Além disso, não faça um grande "HALT ERROR!" ícone. É um erro - não é o fim do mundo.

Então, em resumo: pense nos atores e nos casos de uso do seu sistema. Coloque-se nos sapatos do usuário. Seja útil. Seja legal. Pense nisso na frente do design do seu sistema. Da perspectiva de seus usuários - esses casos de exceção e como o sistema os manipula, são tão importantes quanto os casos normais em seu sistema.

    
por 23.12.2010 / 13:26
fonte

Tags