Um padrão comum para localizar um bug segue este script:
- Observe a estranheza, por exemplo, nenhuma saída ou um programa suspenso.
- Localize a mensagem relevante na saída do log ou do programa, por exemplo, "Não foi possível encontrar o Foo". (O seguinte é relevante apenas se este for o caminho usado para localizar o bug. Se um rastreamento de pilha ou outras informações de depuração estiverem prontamente disponíveis, isso é outra história.)
- Localize o código onde a mensagem é impressa.
- Depure o código entre o primeiro lugar em que o Foo entra (ou deve entrar) na imagem e onde a mensagem é impressa.
Essa terceira etapa é onde o processo de depuração geralmente é interrompido porque há muitos locais no código em que "Não foi possível encontrar o Foo" (ou uma seqüência de modelo Could not find {name}
) é impressa. De fato, várias vezes um erro de ortografia me ajudou a encontrar o local real muito mais rápido do que eu faria - tornou a mensagem única em todo o sistema e, muitas vezes, em todo o mundo, resultando em um mecanismo de pesquisa relevante atingido imediatamente.
A conclusão óbvia disso é que devemos usar IDs de mensagem globalmente exclusivos no código, codificando-o como parte da sequência de mensagens e possivelmente verificando se há apenas uma ocorrência de cada ID na base de código. Em termos de manutenção, o que essa comunidade acha que são os prós e os contras mais importantes dessa abordagem, e como você implementaria isso ou garantiria que sua implementação nunca seja necessária (assumindo que o software sempre terá bugs)?
Respostas:
No geral, essa é uma estratégia válida e valiosa. Aqui estão alguns pensamentos.
Essa estratégia também é conhecida como "telemetria", no sentido de que, quando todas essas informações são combinadas, elas ajudam a "triangular" o rastreamento da execução e permitem que um solucionador de problemas compreenda o que o usuário / aplicativo está tentando realizar e o que realmente aconteceu. .
Alguns dados essenciais que devem ser coletados (que todos sabemos) são:
Muitas vezes, as abordagens tradicionais de registro ficam aquém, devido à falha em rastrear uma mensagem de log de baixo nível até o comando de nível mais alto que a aciona. Um rastreamento de pilha captura apenas os nomes das funções superiores que ajudaram a manipular o comando de nível mais alto, não os detalhes (dados) que às vezes são necessários para caracterizar esse comando.
Normalmente, o software não foi escrito para implementar esse tipo de requisitos de rastreabilidade. Isso dificulta a correlação da mensagem de baixo nível com o comando de alto nível. O problema é particularmente pior em sistemas livremente multiencadeados, onde muitas solicitações e respostas podem se sobrepor e o processamento pode ser transferido para um encadeamento diferente do que o encadeamento original de recebimento de solicitações.
Assim, para obter o máximo valor da telemetria, serão necessárias alterações na arquitetura geral do software. A maioria das interfaces e chamadas de função precisarão ser modificadas para aceitar e propagar um argumento "rastreador".
Até as funções utilitárias precisarão adicionar um argumento "rastreador", para que, se falhar, a mensagem de log permita correlacionar-se com um determinado comando de alto nível.
Outra falha que dificultará o rastreamento de telemetria é a falta de referências a objetos (ponteiros nulos ou referências). Quando faltam alguns dados cruciais, pode ser impossível relatar algo útil para a falha.
Em termos de escrita das mensagens de log:
fonte
Imagine que você tenha uma função de utilidade trivial usada em centenas de lugares no seu código:
Se fizéssemos o que você sugere, poderíamos escrever
Um erro que poderia ocorrer é se a entrada fosse zero; isso resultaria em uma exceção de divisão por zero.
Então, digamos que você veja 27349262 em sua saída ou em seus logs. Onde você procura o código que passou o valor zero? Lembre-se de que a função - com seu ID exclusivo - é usada em centenas de lugares. Então, enquanto você sabe que ocorreu a divisão por zero, não tem idéia de quem
0
é.Parece-me que se você vai se incomodar em registrar os IDs de mensagem, também pode registrar o rastreamento de pilha.
Se a verbosidade do rastreamento da pilha é o que o incomoda, você não precisa despejá-lo como uma string da maneira que o tempo de execução o fornece. Você pode personalizá-lo. Por exemplo, se você quiser que um rastreio de pilha abreviado vá apenas para
n
níveis, escreva algo assim (se usar c #):E use-o assim:
Saída:
Talvez mais fácil do que manter identificações de mensagens e mais flexível.
Roubar meu código do DotNetFiddle
fonte
O SAP NetWeaver faz isso há décadas.
Ele provou ser uma ferramenta valiosa na solução de erros no gigantesco código gigantesco que é o típico sistema SAP ERP.
As mensagens de erro são gerenciadas em um repositório central onde cada mensagem é identificada por sua classe e número de mensagem.
Quando você deseja enviar uma mensagem de erro, apenas declara classe, número, gravidade e variáveis específicas da mensagem. A representação de texto da mensagem é criada em tempo de execução. Você geralmente vê a classe e o número da mensagem em qualquer contexto em que as mensagens sejam exibidas. Isso tem vários efeitos interessantes:
Você pode encontrar automaticamente qualquer linha de código na base de código ABAP que crie uma mensagem de erro específica.
Você pode definir pontos de interrupção do depurador dinâmico que são acionados quando uma mensagem de erro específica é gerada.
Você pode procurar erros nos artigos da base de conhecimento SAP e obter resultados de pesquisa mais relevantes do que se procurar "Não foi possível encontrar o Foo".
As representações de texto das mensagens são traduzíveis. Portanto, incentivando o uso de mensagens em vez de cadeias, você também obtém os recursos do i18n.
Um exemplo de pop-up de erro com o número da mensagem:
Procurando esse erro no repositório de erros:
Encontre-o na base de código:
No entanto, existem desvantagens. Como você pode ver, essas linhas de código não são mais auto-documentadas. Ao ler o código-fonte e ver uma
MESSAGE
declaração como a da captura de tela acima, você só pode inferir do contexto o que realmente significa. Além disso, às vezes as pessoas implementam manipuladores de erro personalizados que recebem a classe e o número da mensagem em tempo de execução. Nesse caso, o erro não pode ser encontrado automaticamente ou não pode ser encontrado no local em que o erro realmente ocorreu. A solução alternativa para o primeiro problema é criar o hábito de sempre adicionar um comentário no código fonte, informando ao leitor o significado da mensagem. O segundo é resolvido adicionando algum código morto para garantir que a pesquisa automática de mensagens funcione. Exemplo:Mas existem algumas situações em que isso não é possível. Existem, por exemplo, algumas ferramentas de modelagem de processos de negócios baseadas na interface do usuário nas quais você pode configurar as mensagens de erro para aparecer quando as regras de negócios forem violadas. A implementação dessas ferramentas é totalmente orientada a dados; portanto, esses erros não aparecerão na lista de onde são usados. Isso significa que confiar demais na lista de utilizações ao tentar encontrar a causa de um erro pode ser um problema.
fonte
O problema dessa abordagem é que ela leva a um registro cada vez mais detalhado. 99,9999% dos quais você nunca verá.
Em vez disso, recomendo capturar o estado no início do seu processo e o sucesso / falha do processo.
Isso permite que você reproduza o bug localmente, percorrendo o código e limitando seu registro a dois locais por processo. por exemplo.
Agora eu posso usar exatamente o mesmo estado na minha máquina de desenvolvimento para reproduzir o erro, percorrendo o código no meu depurador e escrevendo um novo teste de unidade para confirmar a correção.
Além disso, se necessário, posso evitar mais registros registrando apenas falhas de registro ou mantendo o estado em outro local (banco de dados? Fila de mensagens?)
Obviamente, precisamos ter cuidado extra ao registrar dados confidenciais. Portanto, isso funciona particularmente bem se sua solução estiver usando filas de mensagens ou o padrão de armazenamento de eventos. Como o log precisa apenas dizer "Mensagem xyz com falha"
fonte
Eu sugeriria que o registro não é o caminho a seguir, mas que essa circunstância é considerada excepcional (bloqueia o programa) e uma exceção deve ser lançada. Digamos que seu código era:
Parece que você chamando o código não está configurado para lidar com o fato de o Foo não existir e você poderia ser:
E isso retornará um rastreamento de pilha junto com a exceção que pode ser usada para ajudar na depuração.
Como alternativa, se esperamos que o Foo possa ser nulo quando recuperado e isso estiver correto, precisamos corrigir os sites de chamada:
O fato de o seu software travar ou agir 'estranhamente' em circunstâncias inesperadas me parece errado - se você precisa de um Foo e não consegue lidar com a ausência dele, é melhor travar do que tentar seguir um caminho que pode corromper seu sistema.
fonte
As bibliotecas de log adequadas fornecem mecanismos de extensão; portanto, se você quiser saber o método de origem de uma mensagem de log, elas poderão fazer isso imediatamente. Isso tem um impacto na execução, pois o processo requer a geração de um rastreamento de pilha e a sua passagem até que você esteja fora da biblioteca de criação de log.
Dito isso, realmente depende do que você deseja que seu ID faça por você:
Todas essas coisas podem ser feitas imediatamente com o software de registro adequado (ou seja, não
Console.WriteLine()
ouDebug.WriteLine()
).Pessoalmente, o mais importante é a capacidade de reconstruir caminhos de execução. É isso que ferramentas como o Zipkin são projetadas para realizar. Um ID para rastrear o comportamento de uma ação do usuário em todo o sistema. Ao colocar seus logs em um mecanismo de pesquisa central, você pode não apenas encontrar as ações mais longas, mas também chamar os logs que se aplicam a essa ação (como a pilha ELK ).
IDs opacos que mudam a cada mensagem não são muito úteis. Um ID consistente usado para rastrear o comportamento através de um conjunto inteiro de microsserviços ... imensamente útil.
fonte