Tem sido uma grande semana. No momento, estou revisando o rascunho final do meu próximo livro de depuração . Este é sempre um momento sóbrio e emocionante. Um momento em que de repente todos os meses de trabalho se tornam "reais".
Também gravei 9 vídeos para o youtube elevando o total de vídeos do curso para 29 (com muitos mais a caminho). Estou iniciando dois cursos gratuitos adicionais, um voltado para iniciantes absolutos .
Outro é sobre programação Java moderna, que lançarei em breve. Ambos serão gratuitos no (obrigatório "curtir, se inscrever e compartilhar").
No post desta semana, falaremos sobre breakpoints. Já faz muito tempo que o que queremos dizer quando dizemos ponto de interrupção é realmente apenas "ponto de interrupção de linha". Há muito mais sobre eles, como você pode ver abaixo.
Bem-vindo de volta à quarta parte da depuração no Scale, onde detonamos e anotamos nomes. Nomes de variáveis!
Nesta seção, discutimos os pontos de interrupção, que são as unidades de trabalho mais básicas durante a depuração. Mas há muito mais para eles do que apenas quebrar.
Falamos sobre o ponto de interrupção mais básico em nossa primeira parcela. Desta vez, vamos nos aprofundar um pouco mais em algumas das nuances menos conhecidas.
Começamos com pontos de interrupção condicionais. Pontos de interrupção condicionais nos permitem definir uma condição para um acerto de ponto de interrupção. Isso evita que o thread pare constantemente em um ponto de interrupção. Eu discuti isso antes com o objeto marcador myThread que criamos no vídeo anterior.
Nesse caso, só paramos se for diferente do thread atual. Isso significa que esse ponto de interrupção será atingido apenas se um thread diferente o invocar. É uma ótima maneira de detectar problemas relacionados a encadeamentos, como impasses ou corridas. Vale a pena repetir esse recurso, pois é um recurso muito importante.
Pontos de interrupção de método são bastante problemáticos.
Para relembrar, podemos colocar um ponto de interrupção de linha em qualquer linha do método e o ponto de interrupção será interrompido aí. Isso é tão onipresente que apenas o chamamos de “ponto de interrupção”.
Podemos alternar o ponto de interrupção de linha padrão com control-F8 ou, alternativamente, em um mac com Command-F8
.
Os pontos de interrupção do método param quando inserimos um método. Você pode pensar que isso é inútil. Por que não usar um ponto de interrupção de linha?
Você estaria certo. Pontos de interrupção de método são muito mais lentos e você não deve usá-los: para isso... Há um caso de uso para pontos de interrupção de método.
Observe que, como os pontos de interrupção do método são muito lentos, eles geralmente são emulados pelo IDE. Ele apenas usa pontos de interrupção de linha para simular pontos de interrupção de método. Isso é quase perfeito para nós como usuários do IDE, mas precisamos saber sobre isso porque, no passado, esse não era o caso.
Você ainda pode encontrar mensagens de usuários no StackOverflow reclamando sobre a lentidão dos pontos de interrupção do método.
Para ver o caso de uso dos pontos de interrupção do método, vamos para a janela de gerenciamento de pontos de interrupção. Digamos que queremos interromper todos os métodos que começam com as letras I e S em classes cujo nome começa com Prime.
Podemos fazer isso usando uma expressão como esta para parar em todos esses métodos baseados em um padrão. Isso pode parecer exagerado, mas na verdade é muito útil se você tiver uma classe base abstrata cujas subclasses sigam uma convenção de nomenclatura e tenham muitos métodos relacionados.
Você deseja rastrear tudo e pode fazer isso usando essa abordagem. Observe que você também pode usar tracepoints aqui e obter um registro muito profundo. Discutiremos os tracepoints em alguns minutos.
Pontos de controle de campo não são seu ponto de interrupção típico. Um watchpoint irá parar toda vez que o valor do campo for alterado ou toda vez que for lido. Essa é uma maneira incrivelmente legal de detectar um caso em que algum código modifica uma variável ou descobre como um valor de campo se propaga no código.
Observe que podemos ajustar se ele para nas operações de leitura, gravação ou ambas usando esta caixa de diálogo. Também podemos tornar o ponto de controle condicional, assim como fazemos com qualquer outro ponto de interrupção.
É chamado de watchpoint e não de breakpoint porque não é o ponto onde o código para. Ele para no ponto de acesso, não no próprio campo.
Os IDEs fornecem uma interface do usuário de gerenciamento para todos os pontos de interrupção. Podemos gerenciar os breakpoints que já temos e criar novos breakpoints no menu view breakpoints. Posso abri-lo por meio da opção de menu Exibir pontos de interrupção ou posso usar a combinação de teclas Shift-Control-F8
.
Observe que, em um mac, precisamos usar o comando em vez da tecla de controle.
Nesta caixa de diálogo, podemos desativar, excluir e editar pontos de interrupção. Você notará que temos muitas opções aqui que discutiremos em breve. Podemos adicionar pontos de interrupção a partir daqui. Existem várias opções interessantes, mas agora adicionarei apenas um ponto de interrupção de campo simples.
Podemos definir um ponto de interrupção para parar quando uma exceção é lançada. Mas é um pouco problemático. Eu tenho duas opções. Primeiro, posso capturar uma exceção específica pelo nome. Isso é útil se você souber antecipadamente a exceção que será lançada.
Mas não consigo pensar em muitos casos em que isso aconteceu e ainda não conhecia a linha em que a exceção é lançada.
O caso de uso mais valioso é capturar todas as exceções. A razão pela qual isso é útil é que às vezes posso não olhar para o console durante a depuração. Exceções podem ser registradas lá e eu posso perdê-las completamente.
Posso reiniciar a sessão de depuração e perder essas exceções. Mas se um ponto de interrupção parar repentinamente em uma exceção, é difícil perder isso. O problema é que a captura de todas as exceções é quebrada por padrão!
Infelizmente, isso é difícil de mostrar em um aplicativo principal principal simples, então mudei a demonstração para um aplicativo simples de inicialização por mola. O conteúdo do aplicativo não é importante para este caso. Vamos habilitar a captura de qualquer exceção e ver o que acontece…
Depois de habilitar a captura, tento continuar, mas instantaneamente atinge o ponto de interrupção repetidamente. O código pesquisa um WebService em segundo plano. Esse WebService está sem um cabeçalho HTTP, portanto, o código que analisa esse cabeçalho falha em um NumberFormatException.
Estamos presos no código que lança essa exceção que é válida, pois Java não forneceu outra maneira de analisar cabeçalhos numéricos quando esse código foi escrito.
Então o que nós podemos fazer? Isso efetivamente torna a parada em qualquer exceção inútil para quase todos os aplicativos do mundo real.
Vamos mover a janela um pouco e depois aumentar o zoom para ver o que estamos fazendo aqui.
Agora podemos definir um filtro de classe. Observe que eu prefixo ambos os filtros com um caractere de menos para transformar isso em um filtro de exclusão. Aqui, defino um filtro para todos os pacotes java e todos os pacotes sun. Isso significa que todas as exceções tratadas nesses pacotes não serão interrompidas.
Depois de pressionar OK, posso pressionar continuar e o aplicativo é executado sem interromper as exceções problemáticas. Outras exceções funcionarão normalmente e nos avisarão quando algo quebrar em nosso código!
É incrível que esse não seja o padrão do IDE. Sem ele, o recurso é praticamente inútil.
Tracepoints ou LogPoints são alguns dos tipos mais importantes de pontos de interrupção que temos. Podemos adicionar um tracepoint clicando com a tecla Shift pressionada na sarjeta. Isso abre uma caixa de diálogo de ponto de interrupção familiar, mas parece um pouco diferente. Em primeiro lugar, observe isto:
A opção de suspensão está desmarcada; observe que podemos converter qualquer ponto de interrupção em um ponto de rastreamento desmarcando a caixa de seleção suspender. Por padrão, um ponto de interrupção é interrompido. Ele interrompe o thread atual e o suspende para que possamos inspecionar sem pressa a pilha de aplicativos e ver o que está acontecendo.
Um tracepoint não suspende o thread atual. O aplicativo atinge o ponto de interrupção e continua em execução sem parar. Isso é bastante inovador, como você passa por cima?
Bem, você não. Em vez disso, podemos fazer várias outras coisas…
Podemos registrar as palavras “breakpoint hit” sempre que atingirmos o ponto de interrupção, mas isso não é muito útil, a menos que tenhamos apenas um tracepoint e queiramos apenas saber se atingimos esse ponto. Podemos imprimir um rastreamento de pilha toda vez que atingirmos o ponto de rastreamento, o que é realmente mais útil. Mas não muito.
É difícil ler muitos rastros em uma lista e seguir adiante. O que eu quero focar é outra coisa.
Observe que a avaliação e o log já continham o valor cnt
porque eu tinha o valor cnt
selecionado no IDE antes de clicar com a tecla Shift pressionada na calha.
Podemos escrever qualquer expressão que quisermos imprimir aqui. Posso invocar um método para escrever strings descritivas, etc. Isso é literalmente uma declaração de log padrão que posso adicionar dinamicamente ao código. Observe que ainda posso tornar esse ponto de rastreamento condicional, assim como qualquer ponto de interrupção condicional.
Isso significa que posso imprimir qualquer valor neste momento. Assim como qualquer logger. Esta é uma característica espetacular. Imagine os pontos de interrupção do método que discutimos anteriormente com esses pontos de rastreamento; podemos registrar instantaneamente em escala. Vamos pressionar OK.
E então execute este programa; podemos ver o log que adicionamos no tracepoint impresso no console como se o tivéssemos escrito no código!
Agrupar e nomear são cruciais quando aumentamos nossa depuração.
Podemos nomear um ponto de interrupção usando a descrição para indicar seu significado semântico. Isso é muito útil quando trabalhamos com muitos pontos de interrupção. Também podemos agrupá-los com base em arquivos, pacotes, etc.
Mas o legal é que podemos criar grupos personalizados para pontos de interrupção e desabilitar os pontos de interrupção no grupo com um botão. Isso é muito conveniente!'
Isso nos poupa de pressionar continuamente continuar ao tentar alcançar um estado e nos permite gerenciar muitos pontos de interrupção.
Mas o verdadeiro valor aqui está em escala. Digamos que você tenha uma sessão de depuração complexa com vários pontos de rastreamento em execução simultaneamente.
Você pode agrupar a sessão e desativar os pontos de interrupção enquanto alterna para uma ramificação diferente para depurar outra coisa. Então volte para onde você estava quando terminar.
Às vezes, um ponto de interrupção é atingido constantemente e precisamos apenas de uma pilha específica.
Podemos desativar um ponto de interrupção até que um ponto de interrupção diferente ou uma exceção seja atingido, ponto em que o ponto de interrupção será ativado automaticamente. Isso nos poupa a necessidade de continuar pressionando o tempo todo se quisermos apenas testar um caminho específico.
Isso também funciona com exceções e pontos de interrupção de exceção, e podemos decidir sobre o comportamento depois. Queremos desativá-lo novamente ou continuar como de costume?
Isso é muito útil para o caso de uma falha que só passa por um caminho específico. Posso adicionar um tracepoint ao primeiro método. Em seguida, desative o ponto de interrupção real que desejo nesse ponto de rastreamento.
Os filtros de instância nos permitem aceitar apenas um ponto de interrupção de uma instância de objeto específica.
Observe a instância do objeto atual marcada como “this” no relógio. Observe que tem o símbolo arroba seguido do número 656.
Este é o ID do objeto Java que é equivalente a um ponteiro em outras linguagens de programação. Em filtros de instância, podemos limitar o ponto de interrupção para que seja atingido apenas por um objeto específico.
Digamos que este ponto de interrupção de linha seja muito atingido por várias instâncias, mas eu só me importo com os resultados de uma instância de objeto específica e quero filtrar todo o ruído. Posso abrir a caixa de diálogo de detalhes avançados clicando aqui.
Preciso verificar a opção de filtro de instância e, em seguida, digitar o ID do objeto da instância que desejo filtrar. Isso significa que o ponto de interrupção não será interrompido para outros tipos de instância. Para aplicar essa alteração, pressiono concluído.
Neste ponto, podemos ver que o filtro de instância ainda está parando no ponto de interrupção esperado…
Portanto, a próxima etapa é alterar o filtro de instância para uma instância de objeto diferente. Estou inventando um número, pois isso é apenas para um teste.
Observe que, quando clico com o botão direito do mouse no ponto de interrupção, obtenho uma versão personalizada dessa caixa de diálogo porque temos um filtro de instância instalado. Este é um recurso realmente interessante que torna a interface do usuário muito mais fácil de trabalhar.
Agora que fizemos essa alteração, o ponto de interrupção não é mais interrompido.
Os filtros de classe não fazem sentido para um ponto de interrupção de linha típico. Os filtros de classe fazem sentido ao usar um ponto de controle de campo ou um ponto de interrupção de exceção.
Neste caso, eu tenho um campo público. Eu filtro o acesso ao campo para ignorar todos os acessos da classe principal principal. Se uma classe diferente acessar o campo, o ponto de interrupção será atingido.
Isso é muito útil, caso contrário, posso obter muitos acessos no ponto de controle da classe atual. Mas quero ver outros casos.
O filtro do chamador implementa um filtro baseado na assinatura do método de chamada. Para usá-lo, precisamos novamente ir ao menu avançado. Ele oferece suporte a curingas para que eu possa limitar o chamador para permitir apenas o método run.
Observe que ele usa a notação JVM para assinatura de método, que é um assunto bastante complexo que discutirei mais tarde. Eu tenho uma seção que cobre isso no meu livro de depuração.
O método continua parando no ponto de interrupção conforme o esperado porque, como podemos ver aqui, o método run está de fato no rastreamento de pilha. Portanto, o filtro se aplica corretamente e atinge o ponto de interrupção.
Podemos personalizar novamente o ponto de interrupção na interface do usuário de edição rápida para o filtro. Nesse caso, alterei o filtro para procurar um método chamado stop que não existe e, de fato, o breakpoint não é mais interrompido.
Este foi um longo vídeo, espero que você tenha achado educativo e abrangente. No próximo vídeo, falaremos sobre depuração de streams e coleções.
Se você tiver alguma dúvida, use a seção de comentários. Obrigado!