Como usar o debugger

Como usar o debugger

Doggie Daddy

Índice das lições


debugger é uma das ferramentas mais impressionantes do Smalltalk. É útil para fins variados. Todos ligados a examinar o comportamento na execução dos programas (no runtime).

O iniciante pode usá-lo para aprender como funciona a avaliação de expressões e rastrear a execução de métodos tomados como exemplo olhando a evolução de variáveis e sequência de chamadas de métodos.

No desenvolvimento de aplicações é útil para organizar a escrita e teste do código.

Nos sistemas em produção é útil para investigar problemas.

debugger é um dos "lugares" onde o smalltalker, como é conhecido o programador de Smalltalk, está na maior parte do tempo.

Aparentemente seria o System Browser esse lugar na visão dos iniciantes. Mas à medida que se vai aderindo ao conceito de sistema vivo (live programming) o programador se beneficia, estando no debugger, da existência de um contexto, com variáveis atribuídas com valores no run time que permitem desenvolver o código passo a passo fazendo experimentações e navegando pela hierarquia de chamadas provocando a execução das novas versões do métodos modificados durante a execução e sem que precise, na maioria das vezes, reiniciar o programa desde o início.

Essa característica de alterar o programa durante a execução é típica das linguagens de script que são executadas a partir do código fonte. Embora o Smalltalk seja compilado em bytecodes para que a máquina virtual (Virtual Machine) o execute ele é interpretado com uma granularidade que envolve a recompilação somente do método que foi modificado e não de todo o código fonte envolvido no programa.

No desenvolvimento Web o debugger continua ativo e útil na mesma proporção que ocorre no desenvolvimento desktop ou não-Web.

debugger

Vamos provocar o aparecimento da janela do debugger com um erro numa expressão aritmética avaliada no Playground.

Avalie a expressão com Do it, mas qualquer outra forma, como por exemplo Print itDo it and go e Inspect it, vão ter o mesmo efeito.

O efeito do erro é provocar o surgimento da janela do debugger .

Paineis

debugger mostrado acima é composto de três painéis que vamos apelidar como abaixo.

  • Stack
  • Source
  • Variables

Painel Stack

Neste painel é mostrada a pilha de chamadas dos métodos (Call Stack). Você pode navegar pela pilha voltando para examinar as chamadas anteriores e mover-se em qualquer direção.

Painel Source

Neste painel é mostrado o código fonte do método selecionado na pilha de chamadas. Logo após o erro o método selecionado é o último chamado e onde ocorreu o erro. Ele pode ser editado e salvo (Accept), o que causa a sua recompilação.

Painel Variables

Este painel mostra as variáveis no contexto do método selecionado. Permite inspecioná-las para examinar o estado da computação.

Você pode navegar selecionando as linhas da pilha de chamadas no painel Stack e também a partir de qualquer linha selecionada usar os botões no topo e no lado direito rotulados como ProceedRestartIntoOver e Through.

Esta navegação é um tanto análoga a voltar um filme que está assistindo para rever cenas passadas e avançar quadro a quadro. Mas a pilha de chamadas invoca outra analogia que é a das bonecas russas onde você, ao mergulhar no detalhes da execução, vai entrando e saindo dos métodos para se enfronhar com os detalhes e aprender mais sobre a implementação para fins auto-didáticos ou para corrigir problemas, como se abrisse "matrioskas" cada vez mais fundo.

Quando o sistema invoca o debugger em cada método mostrado durante a sua navegação ele vai realçando (highlighting) o trecho em que está a execução.

Vamos agora descrever o que faz cada botão.

Proceed

Este botão faz com que a execução continue. De imediato, após o erro, a continuação provoca o fechamento do debugger e a execução do código que está após o trecho onde está o erro prossegue. Se o erro afetar o código que é executado na continuação novos erros vão acionando novamente o debugger. Se não a execução prossegue normalmente até o final.

Abaixo acrescentamos mais uma expressão que usa o resultado da expressão anterior. Experimente prosseguir após o primeiro erro. Verá que a próxima expressão acionará novamente o debugger.

Restart

Este botão faz com que o ponto de execução volte para a primeira mensagem do método ou do bloco.

Vamos ver um exemplo.

Ao executar as expressões o debugger é acionado por causa do erro introduzido na linha 5.

Vamos selecionar a linha abaixo do topo do painel Stack.

Vemos que a mensagem onde ocorreu o erro está selecionada.

Acionando o botão Restart a execução volta para a primeira mensagem.

Caso o erro tenha ocorrido num bloco Restart faz a excução voltar para a primeira mensagem do bloco.

Vamos simular a existência de um bloco para ver que acontece ao acionar o botão Restart. Vamos envolver algumas expressões do exemplo anterior no interior de um bloco.

A execução para no erro dentro do bloco (Volte uma chamada na pilha).

Vamos agora acionar o botão Restart. Vemo que a mensagem selecionada como novo ponto de execução é a primeira do bloco.

Into

Este botão faz com que a execução vá para a primeira mensagem do método chamado pela mensagem atual (em realce ou highlighting). Podemos dizer que a execução "entra" (into) no método, mergulha nele. Outra metáfora é dizer que uma execução do tipo "caixa branca" (white box) em contraposição a uma execução do tipo "caixa preta" (black box) que caracteriza o próximo botão.

Over

Este botão faz com que a mensagem atual (em realce ou highlighting) seja enviada ao objeto e o ponto de execução vá para a próxima mensagem. A execução "passa por cima" (over) do método ou seja, executa todo o método. É uma execução do tipo "caixa preta" (black box) pois não se interessa em "ver" o que há dentro mas apenas provocar os seus efeitos.

Through

Este botão faz com que a execução vá para a primeira mensagem do bloco que é argumento da mensagem.


Se houver mais de um bloco como argumento a ordem em que serão executados usando o botão Through depende da lógica do método que foi acionado pela mensagem.

Usando os botões RestartInto e Over

Vamos usar a classe Account e seus métodos para construir um exemplo.

No script abaixo vemos que o saldo da conta ficou negativo. Se nossa especificação de como deve funcionar a nossa conta de banco não admite que haja saldo negativo devemos modificar nosso código para dar conta disso. Adotando uma programação defensiva vamos modificar o método withdraw: para tratar o caso em que não haja saldo suficiente na conta para a retirada.

Abra o System Browser, edite o método withdraw: e salve (Veja a lição auxilar Localizando um método).

Abaixo você pode ver como o método deve ficar após as alterações.

Executando de novo o script devemos obter o seguinte.

Nota: Nosso exemplo é um pouco artificial. Provocamos o erro apenas para que o script acionasse o debugger.

Selecione a linha que contém Do it no painel Stack.

Agora vamos acionar o botão Restart e avançar usando os botões Into e Over.

Primeiro vamos acionar botão Restart. A mensagem new é selecionada porque é a primeira do script.

Vamos acionar o botão Into para entrar no método ativado pela mensagem new.

O código acima do método new confirma o que descrevemos sobre ele criar um objeto (com basicNew) e chamar o método initialize do objeto.

Vamos usar Over para executar basicNew sem entrar no método e Into para entrar em initialize.

Abaixo você vê o nosso conhecido código do método initialize que criamos na classe Account para poder atribuir o valor de balance inicialmente como zero.

Continue a execução passo a passo pressionando o botão Over repetidas vezes até sair do método voltando ao contexto anterior e depois movendo o ponto de execução com Over para a mensagem seguinte account deposit: 450.

Vamos continuar usando Over até que a mensagem account withdraw: 600 esteja selecionada. Então usamos Into para entrar descendo mais um nível na hierarquia de chamadas para prosseguir no interior do método Account>>withdraw:.

Essa notação Account>>withdraw: segue a convenção nomeDaClasse>>seletor para indicar uma referência não ambígua a um método através da sua classe e do seu seletor.

No interior do método withdraw: a mensagem anAmount > balance é selecionada.

Experimente prosseguir usando uma combinação qualquer de pressionamentos dos botões OverInto e Proceed. Não se intimide se o erro ocorrer novamente e continue usando os botões sugeridos para ir até o final da execução do script.

Usando o botão Through

Antes de prosseguirmos vamos introduzir o submenu Debug it do menu de contexto. Ao selecionarmos um trecho de código e acionarmos o submenu Debug it o debugger é aberto com o ponto de execução na primeira expressão do código selecionado. Veja o exemplo a seguir.

Com a introdução do uso do submenu Debug it não precisamos mais usar o subterfúgio de provocar um erro para que o debugger seja acionado.

A primeira mensagem é selecionada e podemos prosseguir com os botões para a execução passo a passo que já conhecemos.

Use Over até obter a seleção da mensagem do: conforme abaixo:

Agora em vez de usar Over ou Into vamos usar Through para executar o código contido no bloco que é o argumento de do:.

A mensagem selecionada para execução sum + x é a primeira do bloco.

Para prosseguir você pode usar qualquer outro botão normalmente. Mas se quiser permanecer fazendo o debugging dentro do bloco pode repetir o acionamento do botão Through para continuar a execução até obter a próxima execução do bloco. Through quando não há bloco envolvido atua como o botão Over.

Com as mensagens ifTrue:ifFalse:whileTrue: e whileFalse: não precisamos usar Through porque o debugger faz isso por você.

Where is?

Este botão restabelece o realce (highlighting) que pode ter desaparecido quando você clica no editor. Dessa forma você volta a ver a seleção e em que trecho está a execução.

Experimente clicar em outro ponto do editor para ver desaparecer a seleção da mensagem onde está o ponto de execução. Depois use o botão Where is?.

Run to here

Este é um submenu do menu de contexto que é acionado com o botão direito. Antes de clicar com o botão direito você deve clicar com o botão esquerdo para colocar o cursor no ponto até onde quer que o código seja executado.

Dica: Caso queira sair de um método em que entrou sem querer coloque o cursor no final e acione o Run to here.

Breakpoint

Breakpoint é um ponto onde o programador quer que o programa faça uma pausa para que possa examinar o estado da computação para detectar um problema e/ou entender um código de programa.

Mensagens de breakpoint

Breakpoints podem ser estabelecidos através de mensagens. Quando alcançadas as mensagens de breakpoint causam um pausa no programa. Abaixo falamos de cada uma delas.

halt

Todos os objetos respondem à mensagem halt. Então em qualquer lugar você pode escrever self halt ou anObject halt enviando a mensagem halt para o objeto corrente (self) ou para outro objeto qualquer. Isto vale também para as próximas mensagens de breakpoint.

Após a pausa causada pelo halt você pode prosseguir usando qualquer um dos botões de execução passo a passo.

halt:

Esta mensagem tem o mesmo efeito que a mensagem halt mas adicionalmente mostra uma texto no topo da janela do debugger.

O uso do halt: serve para quando queremos adicionar alguma informação do contexto de execução.

haltIf:

Este é um breakpoint que pausa o programa somente quando uma condição é satisfeita (true) ao se avaliar o bloco que é seu argumento.

haltIfNil

A mensagem haltIfNil deve ser enviada a uma variável ou resultado de uma expressão. Veja o exemplo abaixo.

Quando a mensagem haltIfNil encontra uma variável ou resultado de uma expressão que não referencia nenhum objeto (nil) o programa é pausado.

haltOnCount:

Cada vez que haltOnCount: é executado um contador, inicialmente com o valor zero, é incrementado de 1. Quando este contador atinge o valor do número inteiro que é o seu argumento acontece a pausa do programa.

haltFromCount:

haltFromCount: é similar a haltOnCount: mas continua pausando o programa a partir do momento em que atinge a contagem de chamadas da mensagem.

haltOnce

haltOnce só pausa o programa na primeira vez em que for alcançado.

Breakpoints no System Browser

Breakpoints também podem ser definidos através da interface do System Browser.

Colocar breakpoints usando mensagens de breakpoint como foi mostrado acima permite um ajuste mais fino sobre onde deseja que ocorra a pausa do programa. Mas em algun casos indicar o método onde se quer qua haja uma pausa no início do mesmo ou numa determinada linha é suficiente.

Criando um breakpoint no método

Vamos localizar o método Account>>deposit (Veja Localizando um método) e criar um breakpoint nele.

Breakpoint incondicional

Selecione o método e o submenu Add breakpoint.

Um ícone indicando a existência do breakpoint aparece na lateral do editor de código. Este breakpoint tem o mesmo efeito que a mensagem halt. É um breakpoint incondicional. Funciona como se você tivesse colocado uma mensagem self halt logo antes da primeira mensagem do método.

Breakpoint once

Este breakpoint é criado da mesma forma que o anterior mas usando o submenu Add breakpoint once. Equivale a inserir self haltOnce no início do método.

Breakpoint condicional

Este breakpoint é criado da mesma forma que o anterior mas usando o submenu Add breakpoint condition.... Equivale a inserir self haltIf: aBlockWithCondition no início do método. Antes de inserir o breakpoint é apresentado um diálogo que permite que seja inserida uma expressão booleana que representa a condição.

Criando um breakpoint numa linha do método

Você também pode criar um breakpoint usando o menu de contexto após clicar na lateral esquerda do editor que está mostrando o código do método.

Pode ser no método como um todo, que é o que fizemos acima, se você clicar na lateral na direção da primeira linha onde está a assinatura do método.

Para localizar o breakpoint numa linha basta clicar na lateral na direção da linha desejada e acionar o menu de contexto para escolher o tipo de breakpoint desejado.

Inspecionando as variáveis

O painel que apelidamos Variables mostra os nomes e valores das variáveis em cada contexto selecionado na pilha de chamadas. São mostrados self, variáveis locais, variáveis de instância, thisContext* e o stack top **.

thisContext representa o contexto atual de execução.
** stack top representa o resultado da última expressão avaliada.

No nosso exemplo abaixo temos que self referencia um objeto que é instância de Account que foi o receptor da mensagem deposit: 500anAmount é uma variável local que também é o argumento da mensagem que acionou o método. balance é uma variável de instância.

Modificando o código no próprio debugger

Vamos modificar withdraw: usando essa estratégia de executar o programa até um determinado ponto (onde provocamos uma pausa usando um breakpoint). Nesse ponto onde o programa aguarda uma ação modificamos o código do método e recompilamos com Accept. Automaticamente o ponto de execução vai para o início do método como se tivéssemos acionado o botão Restart. Daí em diante você pode continuar a execução como desejar.

A pausa poderia ter sido provocada por um erro. Então navegariamos até o ponto em que achamos que devemos modificar o código para resolver o problema e continuamos a execução após a recompilação do método onde introduzimos a correção.

Encerrando

Encerre salvando a imagem.


Report Page