Por que muitas mensagens de exceção não contêm detalhes úteis?

213

Parece haver uma certa concordância de que mensagens de exceção devem contém detalhes úteis .

Por que muitas exceções comuns dos componentes do sistema não contêm detalhes úteis?

Alguns exemplos:

  • .NET List acesso ao índice ArgumentOutOfRangeException não não informa o valor do índice que foi testado e era inválido, nem me informa o intervalo permitido.
  • Basicamente, todas as mensagens de exceção da biblioteca padrão do MSVC C ++ são totalmente inúteis (na mesma linha acima).
  • Exceções do Oracle no .NET, dizendo a você (parafraseada) "TABELA OU VISUALIZAÇÃO não encontrada", mas não qual uma.

Então, para mim, parece que, na maior parte, as mensagens de exceção não contêm detalhes suficientes para serem úteis. As minhas expectativas estão fora de sintonia? Estou usando exceções erradas que eu mesmo percebo isso? Ou talvez minha impressão esteja errada: a maioria das exceções do realmente fornece detalhes úteis?

    
por Martin Ba 13.04.2015 / 16:21
fonte

9 respostas

202

Exceções não contêm detalhes úteis porque o conceito de exceções ainda não amadureceu o suficiente dentro da disciplina de engenharia de software, então muitos programadores não as compreendem completamente e, portanto, não as tratam adequadamente.

Sim, IndexOutOfRangeException deve conter o índice preciso que estava fora do intervalo, bem como o intervalo válido no momento em que foi lançado, e é desprezível em nome dos criadores do tempo de execução do .NET isso não acontece. Sim, a exceção table or view not found da Oracle deve conter o nome da tabela ou exibição que não foi encontrada e, novamente, o fato de não ser desprezível em nome de quem é responsável por isso.

Em grande parte, a confusão decorre da ideia original equivocada de que as exceções devem conter mensagens legíveis, o que, por sua vez, resulta de uma falta de compreensão do que são as exceções, portanto, é um ciclo vicioso.

Como as pessoas acham que a exceção deve conter uma mensagem legível, elas acreditam que qualquer informação transportada pela exceção também deve ser formatada na mensagem legível por humanos, e então elas estão entediadas para escrever todas as informações. código de construção de mensagens legível, ou eles têm medo de que isso possa estar divulgando uma quantidade desaconselhável de informações para qualquer olhar curioso que possa ver a mensagem. (Os problemas de segurança mencionados por outras respostas.)

Mas a verdade é que eles não devem se preocupar com isso porque a exceção não deve conter uma mensagem legível. Exceções são coisas que apenas os programadores devem ver e / ou lidar. Se houver a necessidade de apresentar informações de falha a um usuário, isso deve ser feito em um nível muito alto, de maneira sofisticada, e no idioma do usuário, o que, estatisticamente falando, é pouco provável que seja inglês.

Então, para nós programadores, a "mensagem" da exceção é o nome da classe da exceção , e qualquer outra informação pertinente à exceção deve ser copiada em (final / readonly) variáveis de membro do objeto de exceção. De preferência, cada pequena parte concebível. Desta forma, nenhuma mensagem precisa (ou deve) ser gerada e, portanto, nenhum olhar pode vê-la.

Para resolver a preocupação expressa por Thomas Owens em um comentário abaixo:

Sim, claro, em algum nível, você irá criar uma mensagem de log referente à exceção. Mas você já vê o problema com o que está dizendo: por um lado, uma mensagem de log de exceção sem um rastreio de pilha é inútil, mas, por outro lado, você não quer que o usuário veja todo o rastreamento de pilha de exceção. Novamente, nosso problema aqui é que nossa perspectiva é distorcida pelas práticas tradicionais. Os arquivos de log têm sido tradicionalmente escritos em texto simples, o que pode ter sido bom enquanto nossa disciplina estava em sua infância, mas talvez não mais: se houver uma preocupação de segurança, o arquivo de log deve ser binário e / ou criptografado.

Seja texto binário ou simples, o arquivo de log deve ser considerado como um fluxo no qual o aplicativo serializa informações de depuração. Esse fluxo seria apenas para os olhos dos programadores, e a tarefa de gerar informações de depuração para uma exceção deve ser tão simples quanto serializar a exceção no fluxo de log de depuração. Desta forma, observando o log, você verá o nome da classe de exceção (que, como já afirmei, é para todos os propósitos práticos "a mensagem"), cada uma das variáveis de membro de exceção que descrevem tudo o que é pertinente. e-prático-para-incluir-em-um-log e todo o rastreamento de pilha. Note como a formatação de uma mensagem de exceção legível é claramente ausente deste processo.

P.S.

Mais algumas reflexões sobre esse assunto podem ser encontradas nesta resposta: Como escrever uma boa mensagem de exceção

P.P.S.

Parece que muitas pessoas ficaram incomodadas com a minha sugestão sobre arquivos de log binários, então alterei a resposta mais uma vez para deixar ainda mais claro que o que estou sugerindo aqui é não que o arquivo de log deve ser binário, mas que o arquivo de log pode ser binário, se necessário.

    
por 13.04.2015 / 18:13
fonte
45

Why is it that many common exceptions from system components do not contain useful details?

Na minha experiência, há vários motivos pelos quais as exceções não contêm informações úteis. Espero que esses tipos de motivos também se apliquem aos componentes do sistema - mas não sei ao certo.

  • As pessoas com foco em segurança veem as exceções como uma fonte de vazamento de informações (por exemplo ). Como o comportamento padrão das exceções é exibir essas informações para o usuário, os programadores às vezes erram por precaução.
  • Em C ++, eu ouvi argumentos contra a alocação de memória em blocos catch (onde pelo menos parte do contexto faz boas mensagens). Essa alocação é difícil de gerenciar e, pior, pode causar uma exceção de falta de memória, muitas vezes travando seu aplicativo ou vazando memória. É difícil formatar as exceções sem alocar memória, e essa prática pode ter migrado entre idiomas como os programadores fazem.
  • Eles não sabem. Quer dizer, existem cenários onde o código tem não idéia do que deu errado. Se o código não sabe - não pode te dizer.
  • Eu trabalhei em lugares onde a preocupação com a localização impedia colocar strings em inglês no sistema - mesmo para exceções que só seriam lidas pela equipe de suporte que fala inglês.
  • Alguns lugares, eu vi exceções usadas mais como afirma. Eles estão lá para fornecer uma mensagem clara e alta durante o desenvolvimento de que algo não foi feito, ou uma mudança foi feita em um lugar, mas não em outra. Estas são muitas vezes únicas o suficiente para que uma boa mensagem seja um esforço duplicado ou simplesmente confuso.
  • As pessoas são preguiçosas, programadoras mais do que a maioria. Nós gastamos muito menos tempo no caminho excepcional do que o caminho feliz, e isso é um efeito colateral.

Are my expectations out of line? Am I using Exceptions wrong that I even notice this?

Mais ou menos? Quero dizer, as exceções devem ter mensagens legais, mas também são exceções . Você deve gastar seu tempo projetando código para evitar condições excepcionais, ou escrever código para lidar com condições excepcionais (ignorando a mensagem), não usá-las como uma espécie de mecanismo de feedback interativo ao codificar. É inevitável usá-los para fins de depuração, mas na maioria das situações isso deve ser mantido ao mínimo. O fato de você perceber esse problema me preocupa que você não esteja fazendo um bom trabalho em impedi-los.

    
por 13.04.2015 / 17:54
fonte
12

Eu não tenho um excesso de experiência em C #, ou C ++ especificamente, mas posso dizer-lhe isto - exceções escritas pelo desenvolvedor 9 em 10 vezes são mais úteis do que qualquer exceção genérica que você irá encontrar, ponto final.

Idealmente, sim, uma exceção genérica indicará exatamente por que o erro ocorreu e você poderá corrigi-lo com facilidade, mas, realisticamente, em aplicativos grandes com várias classes que podem lançar uma ampla variedade de tipos diferentes de exceções ou o tipo de exceções same , é sempre mais valioso gravar sua própria saída para retorno de erro do que depender da mensagem padrão.

É assim que deve ser, porque, como muitas pessoas apontaram, alguns aplicativos não querem lançar uma mensagem de erro que eles não querem que o usuário veja, por motivos de segurança ou para evitar confundi-los.

Em vez disso, você deve prever em seu design quais tipos de erros podem ser lançados em seu aplicativo (e sempre haverá erros) e gravar mensagens de erro que ajudem a identificar o problema.

Isso nem sempre ajuda você, pois nem sempre é possível prever qual mensagem de erro será útil, mas é o primeiro passo para entender melhor sua própria aplicação a longo prazo.

    
por 13.04.2015 / 19:13
fonte
7

A questão é especificamente perguntar por que tantas exceções lançadas por "componentes do sistema" (também conhecidas como classes de biblioteca padrão) não contêm detalhes úteis.

Infelizmente, a maioria dos desenvolvedores não escreve os componentes principais em bibliotecas padrão, nem documentos de design detalhados ou outras justificativas de design são necessariamente tornadas públicas. Em outras palavras, podemos nunca saber com certeza.

No entanto, existem dois pontos-chave para ter em mente o motivo pelo qual informações detalhadas sobre exceções podem não ser desejáveis ou importantes:

  1. Uma exceção pode ser usada pelo código de chamada de qualquer maneira: uma biblioteca padrão não pode colocar restrições sobre como a exceção é usada. Especificamente, pode ser exibido para o usuário. Considere um índice de matriz fora dos limites: isso pode fornecer informações úteis para um invasor. O designer de idiomas não tem ideia de como um aplicativo usará a exceção gerada ou mesmo o tipo de aplicativo (por exemplo, aplicativo da Web ou desktop), portanto, deixar de fora as informações pode ser mais seguro do ponto de vista da segurança.

  2. Exceções não devem ser exibidas para o usuário. Em vez disso, exiba uma mensagem de erro amigável e registre a exceção em um local que o invasor não tenha acesso (se aplicável). Depois que o erro é identificado, um desenvolvedor deve depurar o código, inspecionando quadros de pilha e caminhos lógicos. Neste ponto, o desenvolvedor tem mais informações do que uma exceção poderia esperar ter.

por 13.04.2015 / 17:41
fonte
4
Primeiro, deixe-me explodir uma bolha dizendo que, mesmo que a mensagem de diagnóstico esteja carregada com informações que levem você à linha de código e ao subcomando exatos em 4 segundos, é provável que os usuários nunca a anotem ou transmitam o pessoal de apoio e você será informado "Bem, disse algo sobre uma violação ... Eu não sei que parecia complicado!"

Tenho escrito software e suportado os resultados de outros softwares por mais de 30 anos e, pessoalmente, a qualidade atual de uma mensagem de exceção não tem praticamente nada a ver com segurança, independentemente de como os resultados finais se encaixaram. seu modelo do universo, muito mais a ver com o fato de tantas pessoas em nossa indústria serem originalmente autodidatas, e elas nunca incluíram lições em comunicações. Talvez se nós forçássemos todos os novos codificadores em uma posição de manutenção por alguns anos, onde eles tivessem que lidar com o que estava errado, eles entenderiam a importância de pelo menos alguma forma de precisão.

Recentemente, em um aplicativo sendo reconstruído, foi tomada a decisão de que os códigos de retorno se encaixariam em um dos três grupos:

  • 0 a 9 seria sucesso através do sucesso com adicionais em formação.
  • 10 a 99 seria não fatal (recuperável) erros e
  • 101 a 255 seriam erros fatais.

(100 foi por algum motivo deixado de fora)

Em qualquer fluxo de trabalho específico, nosso pensamento foi reutilizado ou código genérico usaria retornos genéricos (> 199) para erros fatais, deixando-nos com 100 erros fatais possíveis para um fluxo de trabalho. Com dados ligeiramente diferenciados na mensagem, erros como arquivos não encontrados podem usar o mesmo código e se diferenciar com as mensagens.

Quando o código voltou dos contratantes, você não acreditaria em nossa surpresa quando praticamente TODOS OS ERROS FATAIS ÚNICOS eram código de retorno 101.

Tudo o que considerei, acho que a resposta à sua pergunta é que as mensagens são tão insignificantes, porque quando originalmente criadas, elas eram espaços reservados para os quais ninguém retornava. Eventualmente, as pessoas descobriram como consertar os problemas não por causa das mensagens, mas por revelá-las.

Desde aquela época, as pessoas autodidatas simplesmente nunca tiveram um bom exemplo do que uma exceção deveria conter. Acrescente a isso que mais usuários não leiam as mensagens, muito menos tentem passá-lo para o suporte (vi mensagens de erro que foram cortadas e coladas pelo usuário que foram então editadas antes de serem enviadas com o comentário posterior que parecia muita informação e eu não poderia querer tudo, então eles removeram aleatoriamente um monte dela.

E vamos enfrentá-lo, com muitos (não todos, mas muitos) da próxima geração de codificadores, se é mais trabalho e não adiciona flash, apenas não vale a pena ...

Última observação: Se uma mensagem de erro incluir um erro / código de retorno, parece-me que em algum lugar dos módulos executados deve haver uma linha que leia algo como "se a condição retornar o valor de código" e a condição deve informar o motivo o código de retorno ocorreu. Isso parece uma abordagem lógica simples, mas, para minha vida, apenas tente fazer com que a Microsoft informe o que aconteceu quando uma atualização do Windows falhou no CÓDIGO 80241013 ou em algum outro identificador muito exclusivo. Meio triste, não é?

    
por 14.04.2015 / 08:52
fonte
3

Embora eu concorde que as exceções devem conter o máximo de informações possível, ou pelo menos menos genéricas. Na tabela não encontrada, o nome da tabela seria bom.

Mas você sabe mais sobre o que estava tentando fazer no local no código em que recebeu a exceção. Embora muitas vezes você realmente não possa fazer muito para corrigir a situação quando algo dá errado em uma biblioteca fora do seu controle, você pode adicionar informações muito mais úteis sobre em que situação algo deu errado.

No caso da tabela não encontrada, não será muito útil se você disser que a tabela que não pode ser encontrada se chama STUDENTS, porque você não tem essa tabela, e essa string não está em lugar nenhum do seu código.

Mas se você pegar a exceção e relançá-la com a instrução SQL que você estava tentando executar, você ficará melhor, já que você tentou inserir um registro com o campo de nome sendo Robert '); DROP TABLE STUDENTS; (Há sempre um xkcd!)

Assim, para combater as exceções menos informativas: tente-retraia-receba com mais informações específicas para o que você estava tentando fazer.

Eu provavelmente deveria acrescentar, para que isso seja mais uma resposta sobre o porquê da pergunta, que uma razão provável pela qual o foco dos criadores das bibliotecas não tem sido tornar as mensagens de exceção melhores, é que elas não sei porque algo foi tentado que falhou, que a lógica está no código de chamada.

    
por 14.04.2015 / 10:52
fonte
3

Para dar uma resposta ligeiramente diferente: O código incorreto provavelmente foi feito para a especificação:

  • A função recebe X e retorna Y
  • Se X for inválido, ative a exceção Z

Adicione a pressão para entregar exatamente à especificação (por medo de ser rejeitado em revisão / teste) em tempo mínimo e com o mínimo de barulho, então você tem a sua receita para uma exceção de biblioteca totalmente compatível e inútil.

    
por 14.04.2015 / 23:12
fonte
3

As exceções têm um custo específico de linguagem e implementação.

Por exemplo, as exceções do C ++ são necessárias para destruir todos os dados existentes entre o quadro de chamada de lançamento e o quadro de chamada de captura, e isso é caro. Portanto, os programadores não desejam usar muito as exceções.

No Ocaml, o lançamento de exceção é quase tão rápido quanto um C setjmp (seu custo não depende do número de quadros de chamada percorridos), portanto os desenvolvedores podem usá-lo muito (mesmo para não-excepcionais, muito comuns casos). Em contraste, as exceções do C ++ são pesadas o suficiente, então você provavelmente não as usará muito como faria no Ocaml.

Um exemplo típico é uma pesquisa ou exploração recursiva que pode ser "interrompida" em uma recursão bastante profunda (por exemplo, localizar uma folha em uma árvore ou uma função de unificação). Em alguns idiomas, é mais rápido (de forma mais idiomática) propagar essa condição para todos os chamadores. Em outros idiomas, lançar uma exceção é mais rápido.

Portanto, dependendo da linguagem (e dos hábitos dos desenvolvedores que a usam), uma exceção pode conter muitos detalhes úteis ou, pelo contrário, ser usada como um salto rápido não local e transportar apenas os dados muito úteis. / p>     

por 14.04.2015 / 09:03
fonte
-2

O que faz você pensar que o valor do índice ou o intervalo necessário ou o nome da tabela é um detalhe útil, para uma exceção?

As exceções não são um mecanismo de tratamento de erros; eles são um mecanismo recuperação .

O ponto de exceção é borbulhar até o nível de código que pode manipular a exceção. Onde quer que esse nível esteja, você tem as informações necessárias, ou não é relevante. Se houver informações que você precisa, mas não tem acesso imediato , você não está lidando com a exceção no nível apropriado.

A única vez que posso pensar em onde informação extra poderia ser útil é no nível superior absoluto da sua aplicação, onde você falha e emite um dump de erro; mas não é o trabalho da exceção acessar, compilar e armazenar essas informações.

Eu não estou dizendo que você pode simplesmente colocar "throw new Exception" em todos os lugares e chamá-lo de bom, é possível escrever más exceções. Mas incluir informações irrelevantes não é necessário para usá-las corretamente.

    
por 15.04.2015 / 13:13
fonte