Mecânico em frente a um carro amarelo com o capo aberto e o motor exposto

Resumo: Debug efetivo

Nem só de recursos novos vive um desenvolvedor.

Trabalhando como desenvolvedor boa parte de seu trabalho envolve manter sistemas e parte desta manutenção envolve a correção de bugs ou comportamentos não previstos.

Trabalhando como desenvolvedor boa parte de seu trabalho envolve manter sistemas e parte desta manutenção envolve a correção de bugs ou comportamentos não previstos.

Este é um resumo da palestra Debugging Effectively do Colin O’Dell sobre debug efetivo, se você consegue entender inglês sem legendas eu não tenho como recomendar o suficiente a palestra.

Mas nem todos nós temos tempo ou acesso, seja porque neste momento você não possa assistir o vídeo ou porque a lingua inglesa falada pode ser uma barreira, pensando nisso condensei as ideias principais da palestra neste breve resumo.

Introdução

Colin aborda a questão do debugging em 3 seções, sendo elas:

  1. A importância do processo de debugar
    Porque debugar é um processo e porque é tratar debugging como um processo.
  2. O processo de depuração
    Colin quebra o processo de depuração em 5 etapas coerentes, se você vai gravar algo deste resumo esta provavelmente é a parte mais importante.
  3. Ferramentas e técnicas
    Ele lista para ele quais são as ferramentas e técnicas mais importantes para realizar um debugging mais efetivo.

A importância do processo de debugar

Colin diz acreditar que debugar é a habilidade mais importante para um desenvolvedor, e embora ele mesmo considere essa afirmação um pouco forte ou pesada ele levantou alguns números que apoiam ele neste processo.

Citando o livro O Mítico Homem-Mês ele afirma que um desenvolvedor utiliza até 50% de seu tempo de trabalho debugando e testando código, 1/6 do seu tempo de fato desenvolvendo e 1/3 do seu tempo em outras atividades (planejamento, e-mails, chats, etc).

Ele também insiste na questão que debugging é um processo e que você deveria trata-lo como tal, que quando você tem que debugar algo você não pula de forma aleatória entre arquivos esperando que o problema se resolva, mas sim que você deve seguir um processo e isso segundo ele é a fundação do que ele considera Effective Debugging que nós iremos referenciar como Debug Efetivo de agora em diante.

Outro ponto positivo levantado por Colin é que quanto mais nós debugarmos mais experiência nós iremos ganhar com a base de código e com as ferramentas que usamos para isso e com isso você desenvolve uma intuição, não no sentido sobrenatural, mas sim no sentido lógico de entender e identificar possíveis causas com mais agilidade.


Colin faz uma reflexão que talvez muitos de nós não fazemos com a frequência que devíamos, computadores são dispositivos lógicos, bugs são defeitos de código, logo eles acontecem por alguma razão ele cita Nick Parlante em seu artigo Debugging Zen (WebArchive Link)

[…] O bug não está se movendo no seu código, tentando lhe enganar e fugir de você. Ele está parado em um lugar, fazendo a coisa errada da mesma forma toda a vez.

Fonte: https://cs.stanford.edu/people/nick/compdocs/Debugging.pdf (Pag. 1). Autor: Nick Parlante. Tradução própria.

Tendo isso em mente ele da um conselho sobre como abordar uma seção de debugging.

Sempre assuma que o problema está no seu código primeiro, antes de olhar para qualquer outra coisa, se você estiver ajudando outra pessoa a debugar assuma que o problema está no código dela, isso não quer dizer que você deve julgar a pessoa, mas na maioria dos casos os bugs estão dentro do código.

Sobre o processo

Na palestra Colin sugere usar uma abordagem sistemática para resolução de erros ou defeitos, sendo baseada em 5 passos:

  1. Obter informação.
  2. Replicar o erro.
  3. Identificar a causa.
  4. Arrumar o problema e retestar.
  5. Mitigar ocorrências futuras.

Obtendo informações.

Nesta etapa é importante obter informações sobre o problema, entre elas é importante obter informações como o Comportamento Esperado e o Comportamento Observado, Mensagens de erro, Stacktraces, Capturas de tela entre outros.

Estas informações nos ajudam a decidir se o problema é um problema ou um comportamento padrão do sistema, no caso das mensagens de erro elas nos ajudam a identificar onde no código o erro ocorreu, assim como o Stacktrace que pode nos dar uma visão do caminho percorrido pela aplicação para que o problema se manifestasse.

Outro item interessante são as capturas de tela, elas apresentam informações úteis, como URL, Sistema operacional e Navegador, mas acima de tudo, são muito simples de se obter, visto que qualquer usuário pode fazer uma.

Hora e data que o problema ocorreu e os logs (para procurar pelo problema nos logs).

A próxima etapa é replicar o erro.

Replicar o erro.

Uma coisa interessante informada pelo Colin é que os erros devem ser replicados com 100% de certeza, isso quer dizer, se você só consegue replicar o erro algumas vezes, por exemplo em metade de suas tentativas, quem garante que ao concertar o erro você conseguiu arrumar todos os cenários que ele ocorria, que de fato você resolveu a causa do erro? Claro, há alguns casos extremos, que envolvam alguma condição por exemplo caches, datas, dependências, mas em geral é importante replicar o erro com 100% de certeza.

Identificar a causa.

Uma vez que conseguimos reproduzir o erro a próxima etapa é definir a causa, neste ponto Colin tem boas dicas como:

  • Seja metódico
  • Não assuma nada (Não assuma que determinada classe ou conjunto de código está correto, não assuma que só porque algo sempre funcionou ele não é a causa do problema, não assuma que só porque o código é seu ele está correto, etc).
  • E quando encontrar o problema Entenda o problema, porque ele faz o que faz? o que causa ele?

De fato no inicio da palestra ele fala para sempre assumir que o problema está no seu código, e apenas caso você não consiga provar que está no seu código começar a verificar os códigos de terceiros.

Arrume o problema e reteste.

Aqui novamente temos um grupo de passos e dicas,

  • Após aplicar a correção tente replicar o erro novamente para ver se ele ocorre.
  • Evite o problema XY
    • O problema XY é um fenômeno que ocorre da seguinte forma:
      Você tem um problema X.
      Você não consegue arrumar o problema X e assume que a solução Y pode arrumar o problema.
      Você faz a solução Y.

      O problema aqui é que você não entendeu a causa do problema e portanto sua solução Y pode não resolver de fato o problema X.
  • Sem soluções de contorno temporárias.
    • Elas adicionam débito técnico.
    • Podem introduzir outros problemas.
    • Quase sempre nunca são substituídas por soluções verdadeiras (que de fato resolvem o problema) (O famoso temporário-permanente).
      • Aqui ele dá um exemplo muito bom, um cliente informou que havia um erro e você corrigiu com uma solução temporária, o cliente nunca irá lhe pagar para resolver o problema em definitivo porque na visão deles eles já te pagaram para resolver o problema.

Mitigar ocorrências futuras.

Por último neste processo é importante garantir que o problema não volte a acontecer, para isso ele recomenda:

  • Adicione testes automátizados (para evitar regressão).
  • Compartilhe seu conhecimento (Para evitar que outras pessoas causem este tipo de erros).
    • Coloque na documentação do projeto.
    • Faça um artigo contando como você resolveu o problema.
    • Responda no stack overflow (coloque a solução que você implementou e o proque ela funciona (Não coloque apenas Resolvida)).
  • Caso esteja em uma biblioteca externa submeta um patch para o repositório para evitar que o erro continue para outras pessoas (também evita que o erro continue em versões futuras).

Benefícios a Longo Prazo.

Ele também menciona benefícios em debug, como ganhar experiência sobre o ambiente e sistema, aprender como o sistema funciona, aumenta sua confiança.

Ferramentas

Bem, você comprou a palavra do Colin e acredita que debugar é um processo, o que você precisa ou o que pode lhe ajudar a debugar de forma mais efetiva.

No vídeo ele menciona ferramentas, vale a pena verificar elas no vídeo.

IDE (Ambiente de desenvolvimento integrado)

Caso você não conheça o termo IDEs eles normalmente se referem a ambientes de desenvolvimento integrado, você pode pensar em um programa que inclui um editor, ferramentas para analisar o código, interagir com bancos de dados, sistemas de controle de versão entre outros.

Exemplos de IDEs famosas são o Eclipse e o IntelliJ IDEA para quem trabalha com Java, Visual Studio para amantes do C# ou ainda PHPStorm ou Zend Studio para desenvolvedores PHP.

Para Colin uma IDE deve ter no mínimo

  • Syntax Highlighting (Ajuda a notar que você esqueceu ;, parenteses, etc), 
  • Auto-complete (como IntelliSense), 
  • Rápida navegação de código (Ajuda a encontrar utilizações e definições).
  • Debugger

Mais recentemente no entanto, com o advento de CPUs mais potentes e por consequência mais poder de processamento você pode adicionar:

  • Plugins para análise estática.
    Eles são uma mão na roda e vão te ajudar a detectar variáveis que não foram utilizadas, trechos de código que não estão acessíveis no fluxo do programa (se por exemplo uma condição nunca poder ser verdadeira ou um return for lançado antes de chegar a execução).
  • Integração com sistemas de controle de versão.
    Convenhamos, hoje em dia dificilmente você irá trabalhar em um projeto que não está versionado, por que não aproveitar sua IDE para ajudar na gestão do controle de versão de seu código?
  • Integração com suites de testes.
    A maior parte das grandes IDEs já possuem suporte as frameworks de testes mais famosos de suas linguagens, aproveite, parte do principio do TDD é que testes devem ser rápidos e constantes, e se sua IDE se integra a suites de testes você terá menos motivos para deixar de testar.

Debugger interativo

Um debugger interativo lhe permite pausar a execução do código em tempo de execução ele:

  • Pode adicionar pontos de parada.
  • Pode adicionar pontos de parada condicionais.
  • Pode visualizar e explorar a execução linha a linha.
  • Visualizar as variáveis e seus valores.
  • Explorar a pilha de chamadas.

Alguns debuggers também permitem a execução de determinadas expressões, suponha que você queira testar se o resultado de uma condicional seria verdadeiro ou falso se fosse escrito de outra forma.

Debuggers mais modernos permitem adicionar variáveis para assistir (watch), dessa forma elas ficam em evidência e é possível acompanhar a mudança de estado delas durante a execução da sessão de debugging.

Técnicas:

No vídeo ele fala sobre algumas técnicas que ajudam este processo.

Rastrear no sentido inverso

Essa técnica é usada quando você conhece a localização do erro, neste caso você tem uma mensagem de erro específica ou uma exceção, algo que mencione ou de uma dica de onde o erro pode estar sendo causado.

Uma vez munido desta informação você pode usar o debugger interativo de sua IDE para verificar o estado da aplicação quando o erro ocorre e desta forma identificar o contexto e o motivo no e pelo qual o erro ocorre, permitindo assim identificar a causa para aplicar uma solução definitiva.

Rastrear no sentido normal

Nem sempre temos uma mensagem de erro ou uma pista do que pode ter acontecido, nestes casos outra técnica seria tentar as etapas de reprodução.

Execute o debugger a partir do inicio das etapas conhecidas por causar o erro até que ele ocorra, durante esta execução verifique como a aplicação muda de estado e eventualmente você deve ser capaz de encontrar o erro.

Esta técnica é normalmente mais ineficiente que a anterior, mas funciona quando não se há uma pista de onde o erro ocorre.

Dividir e conquistar

Outra forma de abordar o problema é definir fronteiras dentro do código, você deve ter um sistema com vários módulos, desta forma se você está tendo um erro com pagamento é possível que o erro esteja no módulo de pagamento e não no módulo de login por exemplo.

Você pode definir estas fronteiras usando breakpoints, se um determinado breakpoint não for tocado durante a execução você sabe que o erro ocorre na parte anterior.

A partir disso é possível focar seus esforços nesta seção.

Use ferramentas

Nem sempre podemos contar com um debugger interativo, neste caso há algumas ferramentas que podem ser empregadas em alguns casos.

  • Dump de variáveis na tela ou em arquivos.
    Comece a soltar output do valor de variáveis para arquivos ou na tela para verificar o valor do estado da aplicação naquele momento de execução e verifique se está de acordo com o esperado.
  • Inspetores Web / CURL
    Sistemas Web devem dar a URL que foi solicitada, junto com o código de resposta e o tempo de execução. O CURL permite enviar e testar requisições HTTP.
  • Consoles
    Utilize ferramentas que permitam ajustar configurações ou executar testes no código.
  • Profilers
    Ajuda a identificar problemas de performance, se algo está consumindo muitos recursos, se há loops, etc.
  • Git Bisect
    Verifique as mudanças no código submetido ao controle de versão, permite fazer uma “busca” no estilo arvore binária para identificar quando o erro foi introduzido e desta forma identificar que código pode estar causando o erro, pode ser associado com testes automatizados para tornar a busca mais rápida.
  • Strace
    Mostra as chamadas feitas pelo PHP e ajuda a entender porque algo está processando e não exibe mensagens de erro ou sucesso.

Obtenha ajuda

Nem sempre você vai conseguir resolver o Bug usando apenas as etapas anteriores, neste caso algumas coisas podem lhe ajudar.

  • Leia a documentação do projeto, talvez haja algo que você não saiba e possa lhe ajudar.
  • Verifique a lista de issues (problemas) do projeto, pode ser que o erro já tenha sido reportado, resolvido ou que possua uma solução de contorno conhecida.
  • Verifique o StackOverflow, raramente nossos problemas são únicos.
  • Pergunte a colegas de trabalho ou amigos da área, tem algum expert neste sistema ou linguagem? Algum Senior? Eles podem lhe ajudar não custa tentar.
  • Rubber Ducking
    Esta técnica é interessante, tem esse nome pelo emprego de patos de borracha, mas consiste em falar em voz alta o problema para um objeto inanimado (ex. um pato de borracha), isso pode lhe ajudar a ouvir o problema por uma outra perspectiva e notar algo que tenha passado despercebido.

Faça uma pausa

Se nada disso funcionou faça uma pausa, pode parecer contra intuitivo, mas há alguns benefícios em fazer isso:

  • Ao dar uma pausa você volta para o problema com um novo ponto de vista.
  • Ajuda a esquecer suposições equivocadas que você poderia ter sobre o problema.
  • Ajuda você a recuperar energia para voltar a trabalhar.

Concluindo

Espero que este resumo tenha lhe ajudado a compreender os benefícios de usar uma abordagem prática e sistemática a suas seções de debug e que você consiga tirar proveito do processo, ferramentas e técnicas apresentadas pelo Colin em sua jornada.

TLDR – Coisas para se tirar do vídeo segundo o palestrante.

  1. Computadores não são aleatórios, nem bugs.
  2. Seja persistente, isso se paga (se você não resolver o problema ao menos você terá mais conhecimento sobre o sistema).
  3. Não tenha medo de mergulhar no problema.
  4. Não assuma coisas, nem tenha conceitos dados por garantido.

1 Comment

Deixe uma resposta