Depuração para iniciantes absolutos

Sem falha, o código que escrevemos como desenvolvedores de software nem sempre faz o que esperamos. Às vezes, ele faz algo completamente diferente! Quando acontece o inesperado, a próxima tarefa é descobrir o porquê. Embora possamos ficar tentados a simplesmente ficar olhando nosso código por horas, é mais fácil e eficiente usar uma ferramenta de depuração ou um depurador.

Um depurador, infelizmente, não é algo que possa revelar magicamente todos os problemas ou "bugs" em nosso código. Depuração significa executar o código passo a passo em uma ferramenta de depuração, como o Visual Studio, para localizar o ponto exato em que você cometeu um erro de programação. Assim você entenderá quais correções são necessárias para fazer em seu código e as ferramentas de depuração geralmente permitem que você faça alterações temporárias para poder continuar executando o programa.

Usar um depurador com eficiência é também uma habilidade que leva tempo e prática, mas é definitivamente uma tarefa fundamental para todo desenvolvedor de software. Neste artigo, introduziremos os princípios fundamentais da depuração e forneceremos dicas para você começar.

Esclarecer o problema fazendo as perguntas certas a si mesmo

Isso ajuda a esclarecer o problema com o qual você se deparou antes de tentar corrigi-lo. Esperamos que você já se deparou com um problema em seu código, caso contrário, você não estaria aqui tentando descobrir como depurá-lo! Portanto, antes de iniciar a depuração, verifique se você identificou o problema que está tentando resolver:

  • O que você esperava que seu código fizesse?

  • Em vez disso, o que aconteceu?

    Se você se deparou com um erro (exceção) ao executar o aplicativo, isso pode ser algo bom! Uma exceção é um evento inesperado encontrado ao executar o código, geralmente um erro de algum tipo. Uma ferramenta de depuração pode levá-lo ao local exato em seu código em que a exceção ocorreu e pode ajudá-lo a investigar possíveis correções.

    Se outra coisa aconteceu, qual é o sintoma do problema? Você já suspeita onde esse problema ocorreu em seu código? Por exemplo, se o código exibir um texto, mas o texto estiver incorreto, você saberá que seus dados são inválidos ou que o código que define o texto de exibição tem algum tipo de bug. Percorrendo o código em um depurador, é possível examinar toda e cada alteração em suas variáveis para descobrir exatamente quando e como os valores incorretos são atribuídos.

Examinar suas suposições

Antes de investigar um bug ou um erro, pense nas suposições que fizeram você esperar determinado resultado. Suposições ocultas ou desconhecidas poderão atrapalhar a identificação de um problema mesmo quando você estiver olhando diretamente para a causa do problema em um depurador. Você pode ter uma longa lista de possíveis suposições! Veja algumas perguntas para fazer a si mesmo para desafiar suas suposições.

  • Você está usando a API certa (ou seja, o objeto, função, método ou propriedade certa)? Uma API que você está usando pode não fazer o que você acha que ela faz. (Depois de analisar a chamada à API no depurador, corrigi-la pode exigir uma consulta à documentação para ajudar a identificar a API correta.)

  • Você está usando uma API corretamente? Talvez você tenha usado a API certa, mas não a usou da maneira correta.

  • Seu código contém algum erro de digitação? Alguns erros de digitação, como um simples erro de ortografia de um nome de variável, podem ser difíceis de ver, principalmente ao trabalhar com linguagens que não exigem que variáveis sejam declaradas antes de serem usadas.

  • Você fez uma alteração em seu código e supôs que ela não está relacionada ao problema que você está vendo?

  • Você esperava que um objeto ou variável contivesse um determinado valor (ou um determinado tipo de valor) diferente do que realmente aconteceu?

  • Você conhece a intenção do código? Geralmente é mais difícil depurar o código de outra pessoa. Se não for seu código, poderá ser que você precise passar um tempo aprendendo exatamente o que o código faz antes de poder depurá-lo efetivamente.

    Dica

    Ao escrever o código, comece aos poucos e com um código que funcione! (Um bom código de exemplo é útil nesse momento). Às vezes, é mais fácil corrigir um conjunto de código grande ou complicado começando com um pequeno trecho de código que demonstra a tarefa principal que você está tentando alcançar. Em seguida, será possível modificar ou adicionar código de forma incremental, testando se há erros em cada ponto.

Ao questionar suas suposições, você pode reduzir o tempo que leva para encontrar um problema em seu código. Você também pode reduzir o tempo necessário para corrigir um problema.

Percorrer seu código no modo de depuração para encontrar onde o problema ocorreu

Quando normalmente você executa um aplicativo, você vê erros e resultados incorretos somente depois que o código foi executado. Um programa também pode ser encerrado inesperadamente sem informar o porquê.

Ao executar um aplicativo dentro do depurador, também chamado de modo de depuração, o depurador monitora ativamente tudo que está acontecendo conforme o programa é executado. Isso também permite pausar o aplicativo a qualquer momento para examinar seu estado e percorrer o código linha por linha para observar todos os detalhes à medida que acontecem.

No Visual Studio, você entra no modo de depuração usando F5 (ou o comando de menu Depurar>Iniciar Depuração ou o botão Iniciar DepuraçãoIcon showing Start Debugging button. na Barra de Ferramentas Depurar). Se ocorrerem exceções, o Auxiliar de Exceção do Visual Studio levará você ao ponto exato em que a exceção ocorreu e fornecerá outras informações úteis. Para obter mais informações de como tratar exceções no código, confira Técnicas e ferramentas de depuração.

Se você não se deparou com uma exceção, então provavelmente tem uma boa ideia do local para procurar o problema em seu código. É nessa etapa que você usa os pontos de interrupção com o depurador para ter uma oportunidade de examinar o código mais cuidadosamente. Pontos de interrupção são o recurso mais básico e essencial da depuração confiável. Um ponto de interrupção indica o local em que o Visual Studio deve pausar o código em execução para você poder examinar os valores das variáveis ou o comportamento da memória ou a sequência na qual o código é executado.

No Visual Studio, é possível definir rapidamente um ponto de interrupção clicando na margem esquerda ao lado de uma linha de código. Ou coloque o cursor em uma linha e pressione F9.

Para ajudar a ilustrar esses conceitos, nós o levamos por meio de um código de exemplo que já tem vários bugs. Estamos usando o C#, mas os recursos de depuração são aplicáveis ao Visual Basic, C++, JavaScript, Python e a outras linguagens com suporte. O código de exemplo do Visual Basic também é fornecido, mas as capturas de tela estão em C#.

Criar um aplicativo de exemplo (com alguns bugs)

Agora, você criará um aplicativo com alguns bugs.

  1. Você precisa ter o Visual Studio instalado e a carga de trabalho de desenvolvimento para desktop com .NET instalada.

    Se você ainda não tiver instalado o Visual Studio, acesse a página Downloads do Visual Studio para instalá-lo gratuitamente.

    Se você precisar instalar a carga de trabalho, mas já tiver o Visual Studio, selecione Ferramentas>Obter ferramentas e recursos. O Instalador do Visual Studio é iniciado. Escolha a carga de trabalho Desenvolvimento de área de trabalho do .NET e, em seguida, selecione Modificar.

  2. Abra o Visual Studio.

    Na tela Iniciar, selecione Criar um novo projeto. Digite console na caixa de pesquisa, selecione C# ou Visual Basic como a linguagem e escolha Aplicativo de Console para .NET. Escolha Próxima. Digite um nome de projeto como ConsoleApp_FirstApp e selecione Avançar.

    Escolha a estrutura de destino recomendada ou o .NET 8 e escolha Criar.

    Caso não veja o modelo de projeto Aplicativo de Console para o .NET, acesse Ferramentas>Obter Ferramentas e Recursos e o Instalador do Visual Studio será aberto. Escolha a carga de trabalho Desenvolvimento de área de trabalho do .NET e, em seguida, selecione Modificar.

    O Visual Studio criará o projeto de console, que aparecerá no Gerenciador de Soluções no painel à direita.

  3. No Program.cs (ou Program.vb), substitua todo o código padrão pelo código a seguir. (Selecione a guia de linguagem correta primeiro: C# ou Visual Basic).

    using System;
    using System.Collections.Generic;
    
    namespace ConsoleApp_FirstApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Welcome to Galaxy News!");
                IterateThroughList();
                Console.ReadKey();
            }
    
            private static void IterateThroughList()
            {
                var theGalaxies = new List<Galaxy>
            {
                new Galaxy() { Name="Tadpole", MegaLightYears=400, GalaxyType=new GType('S')},
                new Galaxy() { Name="Pinwheel", MegaLightYears=25, GalaxyType=new GType('S')},
                new Galaxy() { Name="Cartwheel", MegaLightYears=500, GalaxyType=new GType('L')},
                new Galaxy() { Name="Small Magellanic Cloud", MegaLightYears=.2, GalaxyType=new GType('I')},
                new Galaxy() { Name="Andromeda", MegaLightYears=3, GalaxyType=new GType('S')},
                new Galaxy() { Name="Maffei 1", MegaLightYears=11, GalaxyType=new GType('E')}
            };
    
                foreach (Galaxy theGalaxy in theGalaxies)
                {
                    Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
                }
    
                // Expected Output:
                //  Tadpole  400,  Spiral
                //  Pinwheel  25,  Spiral
                //  Cartwheel, 500,  Lenticular
                //  Small Magellanic Cloud .2,  Irregular
                //  Andromeda  3,  Spiral
                //  Maffei 1,  11,  Elliptical
            }
        }
    
        public class Galaxy
        {
            public string Name { get; set; }
    
            public double MegaLightYears { get; set; }
            public object GalaxyType { get; set; }
    
        }
    
        public class GType
        {
            public GType(char type)
            {
                switch(type)
                {
                    case 'S':
                        MyGType = Type.Spiral;
                        break;
                    case 'E':
                        MyGType = Type.Elliptical;
                        break;
                    case 'l':
                        MyGType = Type.Irregular;
                        break;
                    case 'L':
                        MyGType = Type.Lenticular;
                        break;
                    default:
                        break;
                }
            }
            public object MyGType { get; set; }
            private enum Type { Spiral, Elliptical, Irregular, Lenticular}
        }
    }
    

    Nossa intenção para esse código é exibir o nome da galáxia, a distância até ela e o tipo de galáxia. Tudo isso em uma lista. Para depurar, é importante entender a intenção do código. Veja o formato para uma linha da lista que desejamos mostrar na saída:

    nome da galáxia, distância, tipo de galáxia.

Executar o aplicativo

Pressione F5 ou o botão Iniciar depuraçãoIcon showing Start Debugging button. na Barra de ferramentas de Depuração, localizada em cima do editor de código.

O aplicativo é iniciado e não há exceções mostradas pelo depurador. No entanto, a saída que você vê na janela do console é não o que você espera. Veja a saída esperada:

Tadpole  400,  Spiral
Pinwheel  25,  Spiral
Cartwheel, 500,  Lenticular
Small Magellanic Cloud .2,  Irregular
Andromeda  3,  Spiral
Maffei 1,  Elliptical

Mas, em vez disso, você verá esta saída:

Tadpole  400,  ConsoleApp_FirstApp.GType
Pinwheel  25,  ConsoleApp_FirstApp.GType
Cartwheel, 500,  ConsoleApp_FirstApp.GType
Small Magellanic Cloud .2,  ConsoleApp_FirstApp.GType
Andromeda  3,  ConsoleApp_FirstApp.GType
Maffei 1, 11,  ConsoleApp_FirstApp.GType

Examinando a saída e o nosso código, sabemos que GType é o nome da classe que armazena o tipo de galáxia. Estamos tentando mostrar o tipo de galáxia real (como "Espiral"), não o nome da classe!

Depurar o aplicativo

  1. Com o aplicativo ainda em execução, insira um ponto de interrupção.

    Clique com o botão direito do mouse ao lado do método Console.WriteLine para mostrar o menu de contexto e selecione Ponto de interrupção>Inserir Ponto de interrupção no menu suspenso.

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
    }
    

    Quando você definir o ponto de interrupção, um ponto vermelho será exibido na margem esquerda.

    Como você vê um problema na saída, começa a depuração examinando o código anterior que define a saída no depurador.

  2. Selecione o botão ReiniciarIcon showing RestartApp button in Debug toolbar. na Barra de Ferramentas de Depuração (Ctrl + Shift + F5).

    O aplicativo fará uma pausa no ponto de interrupção que você definir. O realce amarelo indica o local em que o depurador está parado (a linha de código amarela ainda não foi executada).

  3. Passe o mouse sobre a variável GalaxyType à direita e, em seguida, à esquerda do ícone de chave inglesa, expanda theGalaxy.GalaxyType. Você verá que GalaxyType contém uma propriedade MyGType e que o valor da propriedade é definido como Spiral.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a menu open below the Galaxy GalaxyType property.

    "Espiral" é, na verdade, o valor correto que você estava aguardando para imprimir no console! Portanto, é um bom começo você poder acessar o valor neste código ao executar o aplicativo. Nesse cenário, estamos usando a API incorreta. Veremos se é possível corrigir isso durante a execução do código no depurador.

  4. No mesmo código, ainda durante a depuração, coloque o cursor no final de theGalaxy.GalaxyType e altere-o para theGalaxy.GalaxyType.MyGType. Embora seja possível fazer a alteração, o editor de códigos mostra um erro que indica que não é possível compilar esse código. (No Visual Basic, o erro não é mostrado e esta seção do código funciona.)

  5. Pressione F11 (Depurar>Intervir ou o botão Intervir na Barra de Ferramentas de Depuração) para executar a linha de código atual.

    F11 avança o depurador (e executa o código) uma instrução por vez. F10 (Passo a Passo) é um comando semelhante e ambos são úteis ao aprender a usar o depurador.

    A caixa de diálogo Editar e Continuar é exibida, indicando que as edições não podem ser compiladas.

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and a message box with the Edit option selected.

    Observação

    Para depurar o código de exemplo do Visual Basic, ignore as próximas etapas até receber a instrução para clicar no botão ReiniciarIcon showing Restart app button in Debug toolbar..

  6. Selecione Editar na caixa de mensagem Editar e Continuar. Você vê uma mensagem de erro agora na janela Lista de Erros. O erro indica que o 'object' não contém uma definição para MyGType.

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and an Error List window with two errors listed.

    Ainda que tenhamos definido cada galáxia com um objeto do tipo GType (que tem a propriedade MyGType), o depurador não reconhece o objeto theGalaxy como um objeto do tipo GType. O que está havendo? Você deseja examinar qualquer código que define o tipo de galáxia. Quando você fizer isso, verá que a classe GType definitivamente tem uma propriedade de MyGType, mas algo não está certo. A mensagem de erro sobre object acaba sendo a pista; para o interpretador de linguagem, o tipo parece ser um objeto do tipo object, em vez de um objeto do tipo GType.

  7. Examinando seu código com relação à definição do tipo de galáxia, você acha que a propriedade GalaxyType da classe Galaxy é especificada como object, em vez de GType.

    public object GalaxyType { get; set; }
    
  8. Altere o código anterior da seguinte maneira:

    public GType GalaxyType { get; set; }
    
  9. Selecione o botão ReiniciarIcon showing Restart app button in Debug toolbar. na Barra de Ferramentas de Depuração (Ctrl + Shift + F5) para recompilar o código e reiniciar.

    Agora, quando o depurador for pausado no Console.WriteLine, será possível focalizar theGalaxy.GalaxyType.MyGType e ver que o valor foi devidamente definido.

  10. Remova o ponto de interrupção clicando no círculo do ponto de interrupção na margem esquerda (ou clique com o botão direito do mouse e escolha Ponto de interrupção>Excluir ponto de interrupção) e, em seguida, pressione F5 para continuar.

    O aplicativo é executado e exibe a saída. Parece que ficou bom, mas você percebe uma coisa. Você esperava que a galáxia Pequena Nuvem de Magalhães fosse exibida como uma galáxia Irregular na saída do console, mas ela não aparece como nenhum tipo de galáxia.

    Tadpole  400,  Spiral
    Pinwheel  25,  Spiral
    Cartwheel, 500,  Lenticular
    Small Magellanic Cloud .2,
    Andromeda  3,  Spiral
    Maffei 1,  Elliptical
    
  11. Defina um ponto de interrupção nessa linha de código antes da instrução switch (antes da instrução Select no Visual Basic).

    public GType(char type)
    

    Esse código é onde o tipo de galáxia foi definido, portanto, queremos examiná-la mais de perto.

  12. Selecione o botão ReiniciarIcon showing Restart app button in Debug toolbar. na Barra de Ferramentas de Depuração (Ctrl + Shift + F5) para reiniciar.

    O depurador é pausado na linha de código em que você definiu o ponto de interrupção.

  13. Passe o mouse sobre a variável type. Você verá um valor de S (seguindo o código de caractere). Você está interessado em um valor de I, uma vez que você sabe que é um tipo de galáxia Irregular.

  14. Pressione F5 e passe o mouse sobre a variável type novamente. Repita essa etapa até que você veja um valor de I na variável type.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a window with the type variable value of 73 I.

  15. Agora, pressione F11 (Depurar>Intervir).

  16. Pressione F11 até chegar na linha de código da instrução switch para um valor de "I" (instrução Select para o Visual Basic). Aqui, você vê um claro problema decorrente de um erro de digitação. Você esperava que o código avançasse para o local em que ele define MyGType como um tipo de galáxia Irregular, mas, em vez disso, o depurador ignora esse código completamente e faz uma pausa na seção default da instrução switch (instrução Else no Visual Basic).

    Screenshot showing the typo error.

    Examinando o código, você vê um erro de digitação na instrução case 'l'. Ela deverá ser case 'I'.

  17. Selecione o código de case 'l' e substitua-o por case 'I'.

  18. Remova o ponto de interrupção e, em seguida, selecione o botão Reiniciar para reiniciar o aplicativo.

    Os bugs são corrigidos agora e você vê a Saída esperada!

    Pressione qualquer tecla para concluir o aplicativo.

Resumo

Quando você vir um problema, use o depurador e os comandos de etapa como F10 e F11 para localizar a região de código com o problema.

Observação

Se for difícil identificar a região do código em que o problema ocorre, defina um ponto de interrupção no código executado antes de o problema ocorrer e, em seguida, use os comandos de etapa até que você veja o manifesto do problema. Também é possível usar tracepoints para registrar as mensagens na janela de Saída. Examinando as mensagens registradas (e observando quais mensagens ainda não foram registradas!), geralmente é possível isolar a região do código com o problema. Talvez você precise repetir esse processo várias vezes para restringir esse conjunto.

Quando você encontrar a região de código com o problema, use o depurador para investigar. Para encontrar a causa de um problema, inspecione o código do problema durante a execução de seu aplicativo no depurador:

  • Inspecione variáveis e verifique se elas contêm o tipo de valores que devem conter. Se você encontrar um valor incorreto, descubra onde o valor incorreto foi definido (para localizar onde o valor foi definido, talvez precise reiniciar o depurador, examinar a pilha de chamadas ou ambos).

  • Verifique se o seu aplicativo está executando o código que você espera. (Por exemplo, no aplicativo de exemplo, esperávamos que o código para a instrução switch definisse o tipo de galáxia como Irregular, mas o aplicativo ignorou o código devido ao erro de digitação).

Dica

Use um depurador para ajudá-lo a encontrar bugs. Uma ferramenta de depuração poderá localizar bugs para você somente se conhecer a intenção do seu código. Uma ferramenta só poderá conhecer a intenção do seu código se você, o desenvolvedor, expressar essa intenção. Gravar testes de unidade é como se faz isso.

Próximas etapas

Neste artigo, você aprendeu alguns conceitos gerais de depuração. Em seguida, é possível começar a aprender mais sobre o depurador.