Que processo você normalmente usa ao tentar depurar um problema / questão / bug com seu software? [fechadas]

15

A maioria das pessoas parece tratar a depuração como uma arte, e não como uma ciência. Para aqueles aqui que o tratam como ciência, e não como arte - que processo (s) você costuma usar quando se depara com um novo problema / bug / problema?

blueberryfields
fonte

Respostas:

13

Em termos muito gerais, o que faço é:

  1. Tente isolar o problema. Pense no que mudou quando o bug apareceu pela primeira vez. Onde você está trabalhando? Que parte do código você estava mudando? 99% dos meus erros são resolvidos dessa maneira. Geralmente é algo bobo.

  2. Se eu tiver um palpite sobre onde está o problema, dê uma boa olhada no código que parece ser a causa. Leia-o. Leia em voz alta mesmo. Pergunte-me: "O que estou tentando alcançar?". Para alguns tipos de problemas: poderia ter alguns efeitos colaterais ou ser afetado pelo código em algum outro lugar de uma maneira que eu não pensava?

  3. Tente de várias maneiras analisar o que está errado, onde e quando (veja abaixo).

  4. Se ainda não tenho idéia, verifico se uma versão mais antiga da minha fonte tem o mesmo problema, tento descobrir quando, no cronograma de desenvolvimento, o problema apareceu pela primeira vez. Para fazer isso, você precisa trabalhar com um bom sistema de controle de versão, como o git (o git tem um recurso chamado bisect exatamente para esse tipo de depuração).

  5. Se ainda não tem idéia, faça uma pausa .... na verdade, muitas vezes ajuda.

  6. Volte para a prancheta - revise como o seu programa deve funcionar e se isso realmente faz sentido.

Realmente depende do tipo de problema, mas assumindo que tenho uma ideia geral de onde o problema pode estar, então:

  • Se eu suspeitar que o problema esteja em alguma parte do código / alteração recente, tento primeiro remover / comentar / alterar ou o que for para fazer com que o bug desapareça, tornando o código mais simples e, em seguida, traga de volta o código problemático e faça uma boa olhada nisso.

  • Execute um depurador com pontos de interrupção (se possível) e veja como meus dados parecem tentar encontrar quando começam a funcionar mal, para ter uma idéia melhor de onde as coisas dão errado.

sinelaw
fonte
1
+1 por fazer uma pausa. Os problemas mais difíceis só ficam mais difíceis quando você está frustrado e na sua 6ª hora depurando-os. Saber quando fazer uma pausa é uma das habilidades de depuração mais úteis que obtive.
Brad Gardner
Resposta incrível. Eu não posso fazer melhor.
precisa saber é o seguinte
1
Muito parecido com a minha abordagem, mas você esqueceu a parte em que solicita a um colega que dê uma olhada rápida e eles instantaneamente percebem o erro de ortografia ... #
31711
1
Excelente resposta. Eu só quero acrescentar que um grama de prevenção vale um quilo de cura. Uma grande parte do meu processo de depuração é que, enquanto eu estou codificando, em primeiro lugar, faço apenas pequenas alterações incrementais e compilo, teste e confirmo localmente entre cada uma. Dessa forma, se um bug aparecer repentinamente, a lista provável de suspeitos é muito pequena e fácil de ver com um bzr qdiffcomando.
Karl Bielefeldt
8

Eu tento usar o desenvolvimento orientado a teste ( TDD ). Escrevo um teste que replica o bug e tento passar no teste. Às vezes, o ato de escrever o teste ajuda a encontrar o bug.

Isso me mantém fora do depurador a maior parte do tempo e fornece testes de regressão para evitar a reintrodução do bug.

Alguns links:

TrueWill
fonte
4
Eu acho que essa resposta é extremamente incompleta. Eu não entendo tantos votos.
Alex
1
Ele recebe muitos votos positivos porque inclui a sigla mágica: TDD.
Bjarke Freund-Hansen
@ Alex - eu adicionei alguns links. O "Encontre um erro, escreva um teste" tem um exemplo. Eu posso expandir isso, mas é realmente assim tão simples.
TrueWill
7

Existem várias definições para a palavra ciência, mas parece que você está se referindo ao que pode ser chamado com mais precisão de " método científico ". O método científico pode ser resumido como observação de alguns fenômenos (presumivelmente um erro ou comportamento inesperado do programa), formulação de hipóteses ou hipóteses para explicar o comportamento e o mais provável é experimentá-lo (escrever um teste que reproduza o problema de maneira confiável).

Os tipos de bugs (fenômenos) que podem ocorrer são praticamente infinitos e alguns não exigem necessariamente um processo bem definido. Por exemplo, às vezes você observa um erro e sabe instantaneamente o que o causou, simplesmente porque está familiarizado com o código. Outras vezes, você sabe que, com alguma entrada (ação, série de etapas etc.), ocorre um resultado incorreto (falha, saída incorreta etc.). Para esses casos, geralmente não requer muito pensamento "científico". Alguns pensamentos podem ajudar a reduzir o espaço de pesquisa, mas um método comum é simplesmente percorrer o código em um depurador e ver onde as coisas deram errado.

As situações, porém, que eu acho mais interessantes e possivelmente dignas de um processo científico, são onde você recebe algum resultado final e é solicitado que explique como isso aconteceu. Um exemplo óbvio disso é um despejo de memória. Você pode carregar o despejo de memória e observar o estado do sistema, e seu trabalho é explicar como ele chegou nesse estado. O dump de falha (ou núcleo) pode mostrar uma exceção, conflito, erro interno ou algum estado "indesejável", conforme definido pelo usuário (por exemplo, lentidão). Para essas situações, geralmente sigo etapas ao longo destas linhas:

  • Observação restrita : estude as informações diretamente ao redor do problema específico, se aplicável. As coisas óbvias aqui são a pilha de chamadas, as variáveis ​​locais, se você puder vê-las, as linhas de código que cercam o problema. Este tipo de estudo de localização específico nem sempre é aplicável. Por exemplo, estudar um sistema "lento" pode não ter uma localização inicial óbvia como essa, mas uma falha ou situação de erro interno provavelmente terá um ponto de interesse imediato e óbvio. Uma etapa específica aqui pode ser usar ferramentas como windbg (execute! Analy -v em um despejo de memória carregado e veja o que ele diz).

  • Observação ampla : estude outras partes do sistema. Examine o estado de todos os encadeamentos no sistema, verifique qualquer informação global (número de usuários / operações / itens, transações / processos / widgets ativos etc.), informações do sistema (SO) etc. Se o usuário forneceu detalhes externos , pense naqueles em conjunto com o que você observou. Por exemplo, se eles disserem que o problema ocorre toda terça-feira à tarde, pergunte-se o que isso poderia significar.

  • Hipotese: Esta é a parte realmente divertida (e não estou sendo ridicularizada por ser divertida). Geralmente, exige muito pensamento lógico ao contrário. Pode ser muito agradável pensar em como o sistema entrou no estado atual. Suspeito que essa seja a parte que muitas pessoas consideram uma arte. E suponho que seja, se o programador começar a jogar coisas aleatoriamente para ver o que fica. Mas com a experiência, esse pode ser um processo bastante bem definido. Se você pensa logicamente nesse ponto, geralmente é possível definir possíveis conjuntos de caminhos que levaram ao estado especificado. Eu sei que estamos no estado S5. Para que isso aconteça, S4a ou S4b precisou ocorrer e talvez S3 antes de S4a, etc. Mais frequentemente, não pode haver vários itens que podem levar a um determinado estado. Às vezes, pode ser útil anotar em um bloco de notas um diagrama simples de fluxo ou estado ou uma série de etapas relacionadas ao tempo. Os processos reais aqui variam muito, dependendo da situação, mas o pensamento sério (e o reexame nas etapas anteriores) nesse momento geralmente fornecem uma ou mais respostas plausíveis. Observe também que uma parte extremamente importante dessa etapa é eliminar coisas que são impossíveis. Remover o impossível pode ajudar a reduzir o espaço da solução (lembre-se do que Sherlock Holmes disse sobre o que resta depois de eliminar o impossível). Observe também que uma parte extremamente importante dessa etapa é eliminar coisas que são impossíveis. Remover o impossível pode ajudar a reduzir o espaço da solução (lembre-se do que Sherlock Holmes disse sobre o que resta depois de eliminar o impossível). Observe também que uma parte extremamente importante dessa etapa é eliminar coisas que são impossíveis. Remover o impossível pode ajudar a reduzir o espaço da solução (lembre-se do que Sherlock Holmes disse sobre o que resta depois de eliminar o impossível).

  • Experiência : Nesta etapa, tente reproduzir o problema com base nas hipóteses derivadas na etapa anterior. Se você pensou seriamente na etapa anterior, isso deve ser bem direto. Às vezes, "trapaceio" e modifico a base de código para ajudar em um determinado teste. Por exemplo, recentemente eu estava investigando um acidente que concluí que era de uma condição de corrida. Para verificar isso, basta colocar um Sleep (500) entre duas linhas de código para permitir que outro thread faça suas coisas ruins no momento "certo". Não sei se isso é permitido na ciência "real", mas é perfeitamente razoável no código que você possui.

Se você conseguir reproduzi-lo, é provável que esteja quase pronto (tudo o que resta é o simples passo de corrigi-lo ... mas isso é por mais um dia). Certifique-se de verificar o novo teste no sistema de teste de regressão. E devo salientar que pretendi que a afirmação anterior sobre consertar que fosse simples ser irônica. Encontrar uma solução e implementá-la pode exigir um trabalho extenso. É minha opinião que a correção de um bug não faz parte do processo de depuração, mas é um desenvolvimento. E se a correção estiver envolvida, ela deverá exigir uma certa quantidade de design e revisão.

Mark Wilkins
fonte
A maioria dos erros que eu vi não era reproduzível de maneira confiável e, para o subconjunto que era, a maioria ainda exigia um trabalho significativo de depuração após a reprodução, antes que qualquer trabalho para corrigi-los pudesse começar. Mesmo que, em vez de dizer "tenha sucesso em reproduzi-lo", você diga "tenha sucesso em restringir um teste de unidade que claramente exercita o bug", eu diria que o trabalho de depuração não terminou. Para mim, a depuração termina quando eu tenho uma correção, posso provar que corrige o problema e tenho provas confiáveis ​​de que minha correção é o que realmente corrige as coisas.
blueberryfields
Concordo que pode ser bastante trabalhoso corrigi-lo. Na verdade, eu estava usando sarcasmo nas minhas palavras "passo simples para consertá-lo", mas isso não ocorre muito bem no tipo.
4

Tente reduzir o caso de teste. Quando é pequeno o suficiente, geralmente é mais fácil localizar o código correspondente que está causando o problema.

É provável que um novo check-in esteja causando o problema e a compilação diária anterior foi boa. Nesse caso, o seu log de alterações do controle de origem deve ajudá-lo a decidir quem capturar.

Além disso, se você estiver no C / C ++, considere executar o valgrind ou o purify para isolar problemas relacionados à memória.

Fanatic23
fonte
2

A parte mais difícil da depuração é isolar o problema, principalmente quando o problema é enterrado em várias camadas. Na faculdade, estudei gravação de música e, curiosamente, havia uma aula de Studio Electronics que se aplica diretamente aqui. Vou usar a depuração de um ambiente de estúdio como uma ilustração do processo sistemático de depuração.

  1. Teste seus medidores. Usando um tom de teste em uma tensão calibrada conhecida, o medidor deve ler "U" (ganho de unidade). Tradução: se suas ferramentas estão quebradas, você não pode usá-las para descobrir o que mais está errado.
  2. Teste cada estágio do componente / ganho trabalhando de trás para frente. Usando o mesmo tom de teste aplicado à entrada do palco, não deve haver alteração na saída do palco. Tradução: isolando cada objeto da saída para trás, estamos construindo confiança em nosso código até encontrarmos o local em que ele está bagunçando. Se você precisar de algumas camadas para sinalizar o problema, precisará saber que as camadas intermediárias não estão contribuindo para isso.

O código de depuração realmente não é tão diferente. A depuração é muito mais fácil quando o código está lançando uma exceção. Você pode rastrear para trás a partir do rastreamento de pilha dessa exceção e definir pontos de interrupção nas posições principais. Geralmente, logo após você definir uma variável, ou na linha que chama o método que gera a exceção. Você pode achar que um ou mais valores não estão corretos. Se não estiver certo (um nulo quando não deveria existir ou o valor estiver fora do intervalo), é um processo de descobrir por que não está certo. Os pontos de interrupção em um IDE são equivalentes aos pontos de teste eletrônicos (projetados para a sonda de um medidor para verificar o circuito).

Agora, depois de passar por essa parte difícil de descobrir onde está meu problema real, escreverei alguns testes de unidade para verificar isso no futuro.

Berin Loritsch
fonte
2

Com os erros desagradáveis ​​que luto para rastrear no final da tarde, minha estratégia mais eficaz é me levantar e me afastar por alguns minutos. Geralmente, novas idéias sobre possíveis fontes de erro começam a aparecer após apenas 30 segundos.

Jay
fonte
2

Para uma abordagem mais prática:

  1. Se o bug estiver relacionado a uma exceção não tratada - observe o rastreamento da pilha. Referência nula, índice fora dos limites, etc. e suas próprias exceções definidas são as mais comuns. Você pode atribuir esse bug a um desenvolvedor júnior, provavelmente é fácil e um bom aprendizado.

  2. Se isso não acontecer em todas as máquinas, provavelmente é uma forma de condição de corrida / problema de segmentação. Eles são super divertidos de rastrear, coloque seu programador sênior entediado nele. Muitos registros, bons conhecimentos e boas ferramentas fazem isso.

  3. Outra grande classe de erros é quando a equipe de teste ou o (s) cliente (s) não gostam de um comportamento específico. Por exemplo, eles não gostam que você decida exibir IDs de usuário ou, ao pesquisar, não é preenchido automaticamente. Esses são erros genuínos, considere ter um melhor gerenciamento de produtos e desenvolvedores com uma visão mais ampla. O desenvolvedor deve levar um tempo relativamente curto para "consertar" isso se ele construir o sistema com a expansão em mente.

  4. 80% de todos os outros erros são resolvidos com um bom sistema de registro e coleta de informações suficientes para resolvê-los. Use rastreamento integrado com vários níveis de sistemas de registro complexos, como o Log4Net / Log4J

  5. bugs de desempenho são uma categoria própria, a regra golder aqui é "meça primeiro, corrija depois!", e você ficaria surpreso ao ver quantos desenvolvedores adivinham onde está o problema e vão direto para corrigi-lo apenas para ver depois, uma simples diminuição de 3-4% no tempo de resposta.

Bogdan Gavril MSFT
fonte
Se eu pudesse marcar cada um desses 5 com +1, eu o faria!
precisa saber é o seguinte
1

Eu tenho duas abordagens de fluxo:

  1. Divida o problema em partes menores e, em seguida, conquiste cada uma das partes menores seguindo o Divide and ConquerParadigma.
  2. Sempre que tenho dúvidas sobre quaisquer valores, apenas imprimo os valores das variáveis ​​para ver exatamente o que está entrando e saindo da variável.

Essas abordagens me ajudaram na maioria das vezes.

Rachel
fonte