Março de 2016

Volume 31 Número 3

Visual Studio - Aprimoramentos de depuração no Visual Studio 2015

Por Andrew Hall | Março de 2016

Apesar de nos esforçarmos muito para escrever um código que funcione corretamente logo na primeira vez, a realidade é que nós, como desenvolvedores, gastamos muito tempo depurando. O Visual Studio 2015 trouxe novas funcionalidades e aprimoramentos com o objetivo de ajudar os desenvolvedores a identificar problemas logo no início do ciclo de desenvolvimento, além de melhorar a capacidade de encontrar e corrigir bugs. Neste artigo, analisarei os aprimoramentos adicionados ao Visual Studio 2015 para desenvolvedores em .NET e C++.

Vou começar com dois aprimoramentos de usabilidade e com a adição de ferramentas de desempenho que funcionam durante a depuração em Microsoft .NET Framework e C++. Depois, vou me aprofundar em um conjunto de aprimoramentos específicos ao desenvolvimento em .NET e terminarei com novidades para os desenvolvedores em C++.

Configuração de Pontos de Interrupção

Pontos de interrupção são um recurso fundamental do depurador, e há bastante chance de você ter usado esse recurso da última vez que disparou o depurador. Há momentos em que o uso de pontos de interrupção condicionais pode ajudar a encontrar rapidamente a causa de um bug. No Visual Studio 2015 é mais fácil aproveitar a vantagem dos pontos de interrupção condicionais introduzindo uma janela de configurações de ponto de interrupção não modal dentro do contexto de seu código. Também permite que você combine facilmente configurações diferentes dentro da mesma janela.

Vamos revisar as opções de configuração de pontos de interrupção oferecidas pelo Visual Studio 2015:

  • As expressões condicionais são interrompidas somente quando certas condições são atendidas. Isso é o equivalente lógico de adicionar uma instrução if ao código e colocar o ponto de interrupção dentro do if, de modo que seja ativado apenas quando as condições forem verdadeiras. As expressões condicionais são úteis quando o código é chamado várias vezes, mas o bug ocorre apenas com uma entrada específica. Seria muito chato ter que verificar manualmente os valores e depois retomar a depuração.
  • As contagens de ocorrência são interrompidas apenas quando o ponto de interrupção atinge uma certa quantidade de vezes. Essas situações são úteis quando o código é chamado várias vezes, e você sabe exatamente quando ele falha, ou tem uma ideia geral de que “ele falha pelo menos após” um certo número de iterações.
  • Os filtros são interrompidos quando o ponto de interrupção é atingido em um computador, processo ou thread específicos. Os pontos de interrupção de filtro são úteis para a depuração de códigos em execução em paralelo quando você deseja apenas parar em uma única instância.
  • Registrar uma mensagem (chamada de TracePoints) imprime uma mensagem na janela de saída e é capaz de retomar automaticamente a execução. TracePoints são úteis para registro em log temporários quando você precisa rastrear algo e não quer precisar interromper e rastrear cada valor manualmente.

Para ilustrar o valor dos pontos de interrupção condicionais, considere o exemplo exibido na Figura 1, no qual os aplicativos são recuperados de acordo com o tipo dentro de um loop. O código está funcionando para a maioria das cadeias de entrada, falhando somente quando a entrada é “desktop.” Uma opção é configurar um ponto de interrupção e, depois, inspecionar o valor de appType sempre que ele ocorrer até que seja “desktop”, para que eu possa começar a percorrer o aplicativo e ver o que não está funcionando. No entanto, será muito mais rápido criar um ponto de interrupção com uma expressão condicional que interrompa apenas quando appType for igual a “desktop”, como mostra a Figura 1.

Janela de configurações do ponto de interrupção com uma expressão condicional
Figura 1 - Janela de configurações do ponto de interrupção com uma expressão condicional

Os pontos de interrupção com uma expressão condicional reduzem as etapas manuais necessárias para colocar o aplicativo no estado correto para depuração.

Configurações de exceção

Você sabe, por ser um desenvolvedor, que exceções podem ocorrer durante a execução de seu aplicativo. Em muitos casos, você precisa estar atento à possibilidade de que algo pode dar errado acrescentando instruções try/catch. Por exemplo, quando um aplicativo recupera informações por meio de uma chamada de rede, essa chamada pode lançar uma exceção se o usuário não tiver uma conexão de rede funcional ou se o servidor não estiver respondendo. Nesse caso, a solicitação de rede precisa ser colocada dentro de uma instrução try, e se uma exceção ocorrer, o aplicativo deverá mostrar para o usuário uma mensagem de erro apropriada. Se a solicitação falhar quando a expectativa é de que ela funcione (graças a uma formatação incorreta da URL no código, por exemplo), talvez você fique tentado a procurar no código um lugar para estabelecer um ponto de interrupção ou a remover a instrução try/catch, para que o depurador interrompa na exceção sem tratamento.

No entanto, a abordagem mais eficiente é configurar o depurador para interromper quando a exceção for lançada, usando a caixa de diálogo Configurações de Exceção; isso permite que você configure o depurador para interromper quando todas as exceções, ou apenas exceções de um tipo específico, forem lançadas. Recebemos comentários com as versões anteriores do Visual Studio de que a caixa de diálogo Configurações de Exceção demorava muito para abrir e tinha uma funcionalidade de pesquisa ruim. Portanto, no Visual Studio 2015, a caixa de diálogo antiga de Configurações de Exceção foi substituída por uma nova e moderna janela de Configurações de Exceção, que abre instantaneamente e oferece um recurso de pesquisa rápido e consistente que você espera, como mostra a Figura 2.

A janela Configurações de Exceção do Visual Studio 2015 com uma pesquisa por todos os tipos de exceção que contêm “Web”
Figura 2 - A janela Configurações de Exceção do Visual Studio 2015 com uma pesquisa por todos os tipos de exceção que contêm “Web”

Ferramentas de desempenho durante a depuração

Cada vez mais, seus usuários finais esperam softwares rápidos e responsivos e expor os usuários a interfaces de usuário lentas e com controles giratórios pode afetar negativamente a satisfação e a retenção desse usuário. O tempo é precioso e quando os usuários podem escolher entre aplicativos com funcionalidades semelhantes, eles escolhem aquele com a melhor experiência de uso.

No entanto, ao escrever um software, normalmente você adia um ajuste de desempenho proativo e segue as práticas recomendadas, esperando que o aplicativo seja rápido o suficiente. O principal motivo para isso é a demora e a dificuldade de medir o desempenho, e pode ser ainda mais difícil encontrar formas de aprimorá-lo. Para ajudar com isso, no Visual Studio 2015, a equipe de Diagnóstico do Visual Studio integrou um conjunto de ferramentas de desempenho diretamente no depurador, chamadas PerfTips, e a janela de Ferramentas de Diagnóstico. Essas ferramentas ajudam você a conhecer o desempenho de seu código como parte da depuração diária, de modo que você possa detetar os problemas logo no início e tomar decisões bem-informadas, permitindo que você compile seu aplicativo pensando no desempenho desde o início.

Uma forma fácil de entender o desempenho de seu aplicativo é executá-lo usando o depurador para ter uma ideia de quanto tempo cada linha de código demora para ser executada. Infelizmente, isso não é muito científico, pois depende da capacidade de perceber diferenças. E é difícil perceber a diferença entre uma operação que demora 25 ms versus uma operação que demora 75 ms. Como alternativa, você pode modificar o código para adicionar temporizadores que capturam informações precisas, mas isso apresenta a inconveniência de ter de alterar o código.

As PerfTips resolvem isso mostrando a você quanto demorou para o código ser executado, colocando o tempo em milissegundos na extremidade direita da linha atual após a interrupção do depurador, como mostra a Figura 3. As PerfTips mostram o tempo que demorou para o aplicativo executar entre dois estados de interrupção no depurador. Isso significa que elas trabalham ao percorrer o código e ao encontrar pontos de interrupção.

PerfTip mostrando o tempo decorrido até chegar a uma chamada de função
Figura 3 - PerfTip mostrando o tempo decorrido até chegar a uma chamada de função

Vamos examinar um exemplo rápido de como uma PerfTip pode ajudar você a conhecer o tempo de execução de um código. Você tem um aplicativo que carrega arquivos do disco quando o usuário clica em um botão e, em seguida, os processa adequadamente. A expectativa é de que demorará apenas alguns milissegundos para carregar os arquivos do disco. No entanto, com as PerfTips, você pode ver que demora um tempo consideravelmente maior. Com base nessas informações, você pode modificar o design do aplicativo para que ele não dependa do carregamento de todos os arquivos quando o usuário clicar no botão. E isso pode ser feito logo no início do ciclo de desenvolvimento e antes que a mudança fiquei muito cara. Para saber mais sobre as PerfTips, visite aka.ms/perftips.

A janela Ferramentas de Diagnóstico mostra um histórico de uso da CPU e da Memória pelo aplicativo e todos os eventos de interrupção do depurador (pontos de interrupção, etapas, exceções e interromper tudo). A janela oferece três guias: Eventos, Uso da Memória e Uso da CPU. A guia Eventos mostra um histórico de todos os eventos de interrupção do depurador, ou seja, ela contém um registro completo de todos os valores de PerfTips. Além disso, na versão Enterprise do Visual Studio 2015, ela contém todos os eventos de IntelliTrace (abordados mais adiante neste artigo). A Figura 4 mostra a guia Eventos no Visual Studio 2015 Enterprise. Além disso, ao contar com a atualização de informações da CPU e da memória durante sua sessão de depuração, você pode ver as características da CPU e da memória de seções específicas do código. Por exemplo, você pode chegar a uma chamada de método e observar a mudança no gráfico ao medir o impacto desse método específico.

Janela Ferramentas de Diagnóstico com a guia Eventos selecionada
Figura 4 - Janela Ferramentas de Diagnóstico com a guia Eventos selecionada

O gráfico de memória permite que você veja o uso total da memória de seu aplicativo e localize tendências de uso da memória por parte do aplicativo. Por exemplo, o gráfico pode mostrar uma tendência constante de elevação que indicaria uma perda de memória por parte do aplicativo, com a possibilidade de uma eventual falha. Começarei analisando como isso funciona para o .NET Framework, e depois cobrirei a experiência para C++.

Ao depurar o .NET Framework, o gráfico mostra quando ocorrem as Coletas de Lixo (GCs - Garbage Collections), além da memória total; isso ajuda a perceber situações nas quais o uso geral da memória por parte do aplicativo está em um nível aceitável, mas o desempenho do aplicativo possa estar sofrendo devido a frequentes GCs (normalmente causados pela alocação excessiva de objetos de curta duração). A guia Uso da Memória permite que você tire instantâneos dos objetos na memória a qualquer momento por meio do botão Tirar Instantâneo. Ela também permite a comparação de dois instantâneos diferentes, o que representa a maneira mais fácil de identificar uma perda de memória. Você tira um instantâneo, continua a usar o aplicativo durante um período de uma maneira neutra com relação à memória, e então tira um segundo instantâneo. A Figura 5 mostra a ferramenta de memória com dois instantâneos de .NET.

A guia Uso da Memória da janela Ferramentas de Diagnóstico com dois instantâneos
Figura 5 - Guia Uso da Memória da janela Ferramentas de Diagnóstico com dois instantâneos

Quando você clica em um instantâneo, uma segunda janela chamada de Modo de exibição de heap é aberta mostrando os detalhes de quais objetos estão na memória, incluindo o número total e o volume de memória usado. A metade inferior do Modo de exibição de heap mostra o que está fazendo referência aos objetos, impedindo que sejam coletados como lixo (chamados de Caminhos para Raiz), além dos outros tipos do tipo selecionado que estão fazendo referência na guia Tipos Referenciados. A Figura 6 mostra o Modo de exibição de heap com as diferenças entre os dois instantâneos.

O Modo de exibição de instantâneo de heap mostra as diferenças entre dois instantâneos .NET
Figura 6 - O Modo de exibição de instantâneo de heap mostra as diferenças entre dois instantâneos .NET

A ferramenta de memória de C++ rastreia as alocações e desalocações de memória a fim de saber o que há na memória a qualquer ponto específico. Para ver os dados, use o botão Tirar Instantâneo para criar um registro das informações de alocação nesse momento. Também é possível comparar os instantâneos para ver o que mudou na memória entre os dois, facilitando bastante a localização de perdas de memória nos caminhos do código dos quais você espera que sejam completamente liberados na memória. Quando você seleciona um instantâneo para exibição, a Exibição de heap mostra uma lista dos tipos junto com seus tamanhos, e no caso da comparação entre dois instantâneos, as diferenças entre esses números. Quando você vir um tipo cujo consumo de memória deseja entender melhor, escolha ver as instâncias desse tipo. A exibição de instâncias mostra o tamanho de cada instância, há quanto tempo ela está ativa na memória e a pilha de chamadas que alocou a memória. A Figura 7 mostra a exibição de instâncias.

Modo de exibição de heap da memória em C++ mostrando o modo de exibição de instâncias com a alocação da pilha de chamadas
Figura 7 - Modo de exibição de heap da memória em C++ mostrando o modo de exibição de instâncias com a alocação da pilha de chamadas

O gráfico da CPU mostra o consumo de CPU do aplicativo como uma porcentagem de todos os núcleos no computador. Isso significa que é útil para identificação de operações que causam picos desnecessários no consumo de CPU, e pode ser útil para identificação de operações que não estão aproveitando totalmente as vantagens da CPU. Considere um exemplo de processamento de uma grande quantidade de dados em que cada registro pode ser processado de forma independente. Ao depurar a operação, você percebe que o gráfico da CPU em um computador com quatro núcleos está um pouco abaixo de 25 por cento. Isso indica que há uma oportunidade de paralelizar o processamento de dados em todos os núcleos do computador, a fim de obter um desempenho melhor do aplicativo.

No Visual Studio 2015 Update 1, a equipe de Diagnósticos do Visual Studio levou isso a um nível superior e adicionou à guia Uso da CPU um criador de perfil de CPU integrado ao depurador, mostrando um detalhamento das funções no aplicativo que estão usando a CPU, como mostra a Figura 8. Por exemplo, há um código que valida se um endereço de e-mail inserido pelo usuário está em um formato válido usando uma expressão regular. Quando um endereço de e-mail válido é inserido, o código é executado extremamente rápido; mas, se um endereço de e-mail com formato impróprio for inserido, a PerfTip mostrará que pode demorar até dois segundos para determinar que o endereço não é válido. Ao analisar a janela Ferramentas de Diagnóstico, é possível ver que houve um pico na CPU durante esse período. Depois, observando a árvore de chamadas na guia Uso da CPU, é possível ver que a correspondência de expressão regular é o que está consumindo a CPU, o que também pode ser visto na Figura 8. Acontece que as expressões regulares em C# têm uma desvantagem: Se elas não conseguem conciliar instruções complexas, o custo de processamento de toda a cadeia de caracteres pode ser alto. Com as PerfTips e a ferramenta de uso da CPU na janela Ferramentas de Diagnóstico é possível determinar rapidamente que o desempenho do aplicativo não será aceitável em todos os casos com o uso da expressão regular. Assim, você pode modificar seu código para usar algumas operações de cadeia de caracteres padrão que geram um desempenho muito melhor quando há a inserção de dados inválidos. Esse poderia ter sido um bug muito difícil de corrigir mais tarde, especialmente se chegasse até a produção. Felizmente, as ferramentas integradas ao depurador permitiram a mudança no design durante o desenvolvimento para assegurar um desempenho consistente. Para saber mais sobre a janela Ferramentas de Diagnóstico, acesse aka.ms/diagtoolswindow.

Guia Uso da CPU mostrando o consumo de CPU de expressões regulares correspondentes
Figura 8 - Guia Uso da CPU mostrando o consumo de CPU de expressões regulares correspondentes

Em seguida, vamos analisar alguns aprimoramentos específicos feitos à depuração em .NET.

Suporte a Lambda nas janelas Inspeção e Imediata

Expressões Lambda (como LINQ) são uma forma incrivelmente poderosa e comum de lidar rapidamente com coleções de dados. Elas permitem operações complexas com uma única linha de código. Normalmente, você sente vontade de testar mudanças nas expressões nas janelas do depurador ou usar o LINQ para consultar uma coleção em vez de expandi-la manualmente no depurador. Por exemplo, considere uma situação na qual seu aplicativo está consultando uma coleção de itens e não retorna qualquer resultado. Você tem certeza de que há itens que correspondem aos critérios pretendidos, então começa a consultar a coleção para extrair os elementos distintos na lista. Os resultados confirmam que há elementos correspondentes aos critérios pretendidos, mas parece haver uma incompatibilidade com a capitalização da cadeia de caracteres; ainda assim, você não se importa se a capitalização não coincide exatamente. Sua hipótese é de que você precisa modificar a consulta para ignorar a capitalização durante a comparação da cadeia de caracteres. A maneira mais fácil de testar essa hipótese é digitar a nova consulta na janela Inspeção ou Imediata e verificar se os resultados esperados retornam, como mostra a Figura 9.

Infelizmente, em versões do Visual Studio anteriores à de 2015, digitar uma expressão Lambda na janela do depurador resultaria em uma mensagem de erro. Portanto, para resolver essa solicitação de recursos, houve a adição do suporte ao uso de expressões Lambda nas janelas do depurador.

Janela imediata com duas expressões Lambda avaliadas
Figura 9 - Janela imediata com duas expressões Lambda avaliadas

Aprimoramentos em Editar e Continuar de .NET

Um recurso de produtividade de depuração favorito no Visual Studio é Editar e Continuar. O recurso Editar e Continuar permite que você altere o código enquanto ele está parado no depurador, aplique a edição sem precisar interromper a depuração, recompile e execute o aplicativo no mesmo local a fim de verificar se a alteração corrigiu o problema. No entanto, uma das maiores frustrações ao usar Editar e Continuar é fazer a edição, tentar retomar a execução e ver uma mensagem de que não foi possível aplicar a edição feita durante a depuração. Isso estava se tornando um problema mais comum à medida que a estrutura continuava a adicionar novos recursos de linguagem que não recebiam suporte de Editar e Continuar (expressões Lambda e métodos assíncronos, por exemplo).

Para melhorar isso, a Microsoft acrescentou o suporte para vários tipos de edição que antes não recebiam suporte, aumentando consideravelmente a quantidade de vezes que as edições podem ser aplicadas durante a depuração. Entre os aprimoramentos estão a capacidade de modificar expressões Lambda, editar métodos anônimos e assíncronos, trabalhar com tipos dinâmicos e modificar recursos de C# 6.0 (como interpolação de cadeias de caracteres e operações condicionais nulas). Para obter uma lista completa das edições com suporte, visite aka.ms/dotnetenc. Se você fizer uma edição e receber uma mensagem de erro de que não foi possível aplicá-la, verifique a Lista de Erros à medida que o compilador acrescenta informações sobre o motivo de a edição não poder ser compilada usando Editar e Continuar.

Entre outros aprimoramentos em Editar e Continuar estão o suporte para aplicativos que usam CoreCLR x86 e x64, ou seja, é possível usar esse recurso ao depurar aplicativos para Windows Phone no emulador, e o suporte para depuração remota.

Nova experiência IntelliTrace

O IntelliTrace fornece informações históricas sobre seu aplicativo para ajudar a eliminar a suposição durante a depuração na edição Enterprise, e a levar você com mais rapidez até a parte relevante de seu código, com menos sessões de depuração. O IntelliTrace recebeu um conjunto abrangente de aprimoramentos com o objetivo de facilitar bastante seu uso. Entre os aprimoramentos estão uma linha do tempo que mostra quando um evento ocorreu, a capacidade de ver os eventos em tempo real, o suporte para TracePoints e integração à janela Ferramentas de Diagnóstico.

A linha do tempo permite que você entenda quando os eventos ocorreram e perceba grupos de eventos que podem ter relação. Os eventos aparecem ao vivo na guia Eventos, enquanto em versões anteriores era necessário entrar em um estado de interrupção no depurador para ver os eventos coletados pelo IntelliTrace. A integração de TracePoint permite que você crie eventos personalizados do IntelliTrace usando a funcionalidade de depuração padrão. Por fim, a integração com a janela Ferramentas de Diagnóstico coloca os eventos IntelliTrace em contexto com informações sobre o desempenho, permitindo que você use essas informações para entender a causa dos problemas de desempenho e de memória por meio da correlação das informações em uma linha do tempo comum.

Quando você vê um problema no aplicativo, normalmente forma uma hipótese sobre onde começar a investigar, coloca um ponto de interrupção e executa o cenário novamente. Se você descobre que o problema não está nesse local, precisa formar uma nova hipótese sobre como obter o local correto no depurador e começar o processo novamente. O objetivo do IntelliTrace é melhorar esse fluxo de trabalho descartando a necessidade de executar novamente o cenário.

Considere o exemplo dado anteriormente neste artigo, no qual uma chamada de rede falha inesperadamente. Sem o IntelliTrace, você precisa ver a falha na primeira vez, permitir que o depurador interrompa quando a exceção for lançada e executar o cenário novamente. Com o IntelliTrace, quando você vir a falha, basta analisar a guia Eventos da janela Ferramentas de Diagnóstico. A exceção aparece como um evento; selecione-a e clique em Ativar Depuração Histórica. Em seguida, você é levado de volta no tempo até o local do código onde a exceção ocorreu, as janelas Locais e Autos mostram as informações sobre a exceção, e a janela Pilha de Chamadas é preenchida com a pilha de chamadas onde a exceção ocorreu, como mostra a Figura 10.

Visual Studio no Modo de Depuração Histórica no local onde uma exceção foi lançada
Figura 10 - Visual Studio no Modo de Depuração Histórica no local onde uma exceção foi lançada

Por fim, vamos analisar alguns dos aprimoramentos mais importantes feitos para depuração em C++ no Visual Studio 2015.

Editar e Continuar em C++

Conforme mencionado antes, Editar e Continuar é um recurso de produtividade que permite a modificação de seu código enquanto ele está parado no depurador, e a aplicação das edições ao retomar a execução, sem a necessidade de interromper a depuração para recompilar o aplicativo modificado. Em versões anteriores, Editar e Continuar em C++ apresentava limitações importantes. Primeiro, oferecia suporte apenas a aplicativos x86. Segundo, a ativação de Editar e Continuar resultava no uso do depurador de C++ do Visual Studio 2010 por parte do Visual Studio, que não contava com novas funcionalidades, como o suporte para visualizações de dados Natvis (confira aka.ms/natvis). No Visual Studio 2015, essas duas falhas foram solucionadas. Editar e Continuar está habilitado por padrão para projetos em C++, tanto para aplicativos x86 quanto para x64, e pode funcionar até mesmo ao anexar a processos e depuração remota.

Suporte para Android e iOS

À medida que o mundo assume cada vez mais uma mentalidade móvel, muitas organizações descobrem a necessidade de criar aplicativos móveis. Com a diversificação de plataformas, C++ é uma das poucas tecnologias que pode ser usada em qualquer dispositivo e sistema operacional. Muitas organizações de grande porte estão usando o código C++ compartilhado para lógicas de negócios comuns que desejam reutilizar em um amplo espectro de ofertas. Para facilitar isso, o Visual Studio 2015 oferece ferramentas que permitem aos desenvolvedores móveis direcionar para Android e iOS diretamente do Visual Studio. Isso inclui a experiência de depuração conhecida do Visual Studio usada pelos desenvolvedores para o trabalho diário em C++ no Windows.

Conclusão

A equipe de Diagnósticos do Visual Studio está muito empolgada com a enorme gama de funcionalidades que foi adicionada à experiência de depuração no Visual Studio 2015. Tudo o que foi abordado neste artigo, com exceção do IntelliTrace, está disponível na edição Community do Visual Studio 2015.

Você pode continuar acompanhando o progresso da equipe e os próximos aprimoramentos no blog da equipe em aka.ms/diagnosticsblog. Experimente os recursos discutidos aqui e envie seus comentários à equipe, dizendo como ela pode continuar o aprimoramento do Visual Studio para atender às suas necessidades de depuração. Você pode deixar comentários e perguntas nas postagens do blog ou enviá-los diretamente pelo Visual Studio. Basta clicar no ícone Enviar Comentários na parte superior direita do IDE.


Andrew Hall* é o líder de gerenciamento de programas da equipe de Diagnósticos do Visual Studio que cria as principais experiências de depuração, criação de perfil e IntelliTrace, além do Emulador de Android para Visual Studio. Durante os anos, ele trabalhou diretamente nas ferramentas de depuração, criação de perfil e análise de código no Visual Studio.*

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Angelos Petropoulos, Dan Taylor, Kasey Uhlenhuth e Adam Welch