Debugando aplicações em python

Dizendo adeus ao print(“Passou por aqui”)

Guilherme Salustiano
Turing Talks
6 min readNov 21, 2021

--

Apresentação

Olá, gente! Sejam muito bem-vindos a mais um turing talks.

Entradas de usuários inválidas, aquele edge case que você não pensou, aquela função que você achou que tratava aquele caso mas lançou uma exceção, existem várias possibilidades e coisas medonhas que podem dar errado durante a execução de uma aplicação.

Nesse momento quem nunca saiu colocando print(“Passei por aqui”), às vezes naquele loop gigante que você passa um tempo rodando o terminal até achar algo estranho.

O print será nosso eterno companheiro, entretanto com o desenvolvimento de um maior ferramental ficou cada vez mais fácil utilizar de outras alternativas, mais rápidas, escaláveis e manuteníveis.

O que vamos debugar por aqui

Trouxe um problema que tive que debugar na última semana, pois o código não funcionou de primeiro, era mais ou menos assim:

Primeira dica: escreva testes

Testes são códigos que testam outros códigos. Substituindo o trabalho braçal de a cada alteração ter que testar todos os componentes do sistema relacionados (e frequentemente esquecendo alguma coisa). Eles nos ajudam a garantir o funcionamento do sistema, simplificando o processo de debug e facilitando alterações futuras no código.

Há dois (algumas pessoas consideram três) principais testes: testes unitários — feitos para testar pequenas funções, com vários casos, e testes de integração, criados para testar a composição entre as funções.

Há diversas bibliotecas que fazem isso em python, por se tratar de um projeto pequeno e apenas testes unitários estou usando o pytest.

Ao rodar no terminal temos:

Testes passando, um sentimento de paz chega junto, e vamos seguindo.

Batalhar contra letras na frente do computador o dia todo é uma constante luta, e nem sempre ganhamos a primeira batalha, vamos olhar para esse caso introduzindo um bug:

Nesse caso esquecemos de usar o separador passado como argumento e estamos sempre usando o ‘.’, rodando agora o teste teremos:

BOOOOM! Explosões vermelhas no terminal, mas pelo menos graças aos testes conseguimos ter uma noção de quais funções e em quais casos estão apresentando erros, e, com pouca investigação, conseguimos nos isolar de onde o erro surge. Sabendo disso, vamos adentrar ainda mais ao código:

Segunda dica: Use debuggers!

O Python Debugger é um módulo padrão do python que assim comoo debugger de outras linguagens permite a você rodar seu código interativamente, passo a passo, ou definir breakpoints, pontos que o debugger começa a rodar passo a passo.

Podemos rodá-lo diretamente num arquivo com `python -m pdb <script>.py` ou a partir do shell do python, que será mais útil para debuggar a partir de uma função:

Dentro do pdb, temos alguns comando que podemos usar para interagir com o código, os que eu mais uso são:

Você também pode chamar o pdb direto do pytest com:

Ou ainda usando a função `breakpoint()` adicionada no python 3.7

Utilizando o pdb com VSCode

Felizmente hoje em dia há diversos editores e ferramentas construídas em cima do pdb que tornam a vida no desenvolvedor muito mais feliz.

No caso do VSCode, para rodar um script em debug podemos ir em Executar -> Iniciar a depuração -> Python File.

Entretanto, como a minha função não está diretamente chamada, ou precisaria adiciona-lá com

>> diff main <<

Porém, conforme o número de testes vão crescendo, isso se torna trabalhoso, portanto o VSCode fornece melhores ferramentas para gerenciar os testes, basta ativa-las em `python.testing`, no meu caso bastou definir ”python.testing.pytestEnabled”: true.

Nessa janela podemos visualizar e rodar todos os testes, ou de arquivos ou funções específicas. Encher essa lista com testes verdes é um orgulho, e, mesmo quando estão vermelho, podemos roda-los em debug para descobrir o problema

Vamos começar adicionando um ponto de interrupção no teste, basta clicar no espaço antes do número em um círculo vermelho que vai aparecer:

Então podemos finalmente clicar para debugar aquela função e veremos que o editor destaca aquela linha, a qual muda ligeiramente de aparência: que já, já eu entro em mais detalhes.

O que importa para nós agora é na parte superior, visto os diferentes plays e setinhas, que são respectivamente _continue_, _next_, _step_, _out_, os mesmos do pdb.

A principal diferença é que o _continue_ vai rodar até um novo brakpoint ou uma exceção não capturada, o _next_ vai para a próxima linha, passando por cima de chamada de funções, enquanto o _step_ entra dentro das funções.

Clicando no _step_ ou teclando F11, Voilà, estamos agora dentro da função.

Clicando 5 vezes no _next_ (F10) podemos observar que a janela de variáveis, do canto superior esquerdo, começa a mudar. Ela representa todos os valores que estão no escopo daquela linha que está sendo analisada e assim já podemos começar a notas certas estranhezas entre o que era esperado e o que acontece.

Logo abaixo dela temos a janela de _valores assistidos_: que permite ver como expressões, usando as variáveis disponíveis, se comportam. Muito útil para aquela expressão booleana que vai ser crucial para entrar naquele if que não está executando

Similarmente, podemos querer executar códigos python mais complexos, para isso, na parte inferior, junto ao terminal, temos o console de depuração. Ele é um shell do python, mas que tem acesso a todas as variáveis que estão no escopo da linha analisada, permitindo importar e chamar funções, fazendo alterações e testando diferentes chamadas

Na esquerda, abaixo da janela de _valores assistidos_, temos a pilha de chamadas. Quando uma função é chamada, o código pula para outro arquivo e precisa saber para onde voltar, por isso esse endereço de volta fica armazenado nesta pilha: e com o debbuger nós temos acesso a ela.

Ao clicar, conseguimos navegar e visualizar as variáveis no momento que a chamada foi feita, ou a chamada da função que chamou a função foi feita: útil nesse caso por exemplo de uma função recursiva.

E, por último, temos uma lista com todas as paradas, os breakpoints selecionados e a opção de também parar para analisar outros tipos de exceção

Terceira dica: Logging

Mesmo assim, o print acaba sendo muito útil para exemplificar um pouco mais do comportamento interno da aplicação, que pode servir tanto para entender melhor o que o programa faz quanto descobrir causas de problemas para simular localmente a aplicar as práticas já discutidas.

Para esse tipo de problema, existem bibliotecas de logging, que, diferentemente do print, permitem a você definir níveis de verbosidade, horário, personalizar a formação…

No python temos o módulo `logging` que faz parte da biblioteca padrão e seu uso é muito simples

Que resultará em:

Conclusão

Analisar o estado da sua aplicação é muito importante para entendê-la, consertá-la e modificá-la. Temos diferente ferramentas que podemos usar para alcançar esse propósito e espero ter conseguido apresentar novas formas de resolver esse antigo problema.

Assim chegamos ao fim de mais um Turing Talks. Não se esqueça de nos acompanhar em nossas redes sociais! No Facebook, Linkedin, Instagram e, claro, nossos posts do Medium! Também temos um servidor no Discord , no qual divulgamos e discutimos assuntos de Inteligência Artificial.

Um abraço e até a próxima!

--

--