Debugging Go Code with Visual Studio Code

Chris Ganga
👁️ 1,021 views
💬 comments

I recently wrote about My Top VSCode Tips and Tricks and among them was Debugging.

In this article, I'll cover how to debug your Go code, so it goes without saying that a prerequisite for this article is that you understand the basics of writing Go.

If you do not have experience writing Go, and would like to learn, here's a really short short list of the best resource to start your Go journey.

Table of Contents

    Setup

    You need have the following requirements met.

    1. You know a little about Go to write a simple program, or even a small http server.
    2. You have go installed in your machine and the environment variable $GOPATH is set. (It defaults to ~/go)
    3. The path $GOPATH/bin is exposed to your PATH.
    4. You have Visual Studio Code installed.
    5. You should have the VSCode-Go plugin installed.

    Once you have this installed, when open any .go files in VSCode, you will be prompted on the bottom right of the status bar to Install Analysis Tools, and you should click on that link to install the necessary Go packages for the plugin to work efficiently.

    We finally need to install a debugger for Go, called Delve. It's an open source project. To do this, there are detailed installation instructions for specific platforms. I'm using a Mac, so it's as straight forward as.

    brew install go-delve/delve/delve

    Examples

    We'll use two examples to debug our Go code.

    • A simple go program that generates a JSON.
    • We'll write a function, write the test and see how we debug tests in VS Code

    Example 1: Avengers

    Here's the source code for the first example. Create a file main.go.

    package main
    
    import (
        "encoding/json"
        "fmt"
        "log"
    )
    
    // Avenger represents a single hero
    type Avenger struct {
        RealName string `json:"real_name"`
        HeroName string `json:"hero_name"`
        Planet   string `json:"planet"`
        Alive    bool   `json:"alive"`
    }
    
    func (a *Avenger) isAlive() {
        a.Alive = true
    }
    
    func main() {
        avengers := []Avenger{
            {
                RealName: "Dr. Bruce Banner",
                HeroName: "Hulk",
                Planet:   "Midgard",
            },
            {
                RealName: "Tony Stark",
                HeroName: "Iron Man",
                Planet:   "Midgard",
            },
            {
                RealName: "Thor Odinson",
                HeroName: "Thor",
                Planet:   "Midgard",
            },
        }
    
        avengers[1].isAlive()
    
        jsonBytes, err := json.Marshal(avengers)
        if err != nil {
            log.Fatalln(err)
        }
        fmt.Println(string(jsonBytes))
    }

    Here we are just defining a struct Avenger, and then creating an array of avengers, changing the status of one of them to alive, then converting the results to JSON, and finally printing it to STDOUT.

    You can run the app with

    go run main.go
    
    [{"real_name":"Dr. Bruce Banner","hero_name":"Hulk","planet":"Midgard","alive":false},{"real_name":"Tony Stark","hero_name":"Iron Man","planet":"Midgard","alive":true},{"real_name":"Thor Odinson","hero_name":"Thor","planet":"Midgard","alive":false}]

    To get started with debugging, we need to create a configuration. Click on the Debug Icon on the left pane of Visual Studio Code. Next, click on the gear Icon to create a configuration.

    A configuration file is create under .vscode/launch.json with the contents shown above. Change the configurations program to point to the main.go file. In this instance, since we only have a main.go file, we can change it to our workspace root:

    "program": "${workspaceRoot}",

    We're set. Next we need to add a breakpoint, because that's what debugging is all about.

    Let's add a breakpoint on line 21 func main() by clicking to the left of the line number, we should see a red dot.

    Next, either press F5 or Click on the Launch button with a green play button on the Debug Section on the top left. It should open the Debug View.

    Click twice on the Step Over button on the Debug Toolbar.

    Once the debugger should have moved to line 40.

    The Debug Section on the left will give us the state of the current breakpoint position.

    We can see the state/value of the variables, at that particular time in the Variables section.

    We can also see the the call stack, and at the moment the running function is the main function, and line 40.

    You can continue Stepping Over, you'll see the value of avengers changes once we are past the line. Tony Start is Alive

    Pretty awesome right.

    Conditional Breakpoints

    VSCode breakpoints provide you with an option to edit breakpoints by giving them an expression, which most of the time is usually a boolean expression.

    For instance, on line 40 avengers[1].isAlive(), we could add a condition here that the breakpoint is only raised when the an expression we want evaluates to true. e.g avenger[1].Planet == "Earth"

    To do this, right click on the breakpoint and select edit breakpoint.

    If you did not have a breakpoint, you can still right click, and you will be told to add a Conditional Breakpoint

    Let's add the condition once we've selected any of the above. avenger[1].Planet == "Earth"

    Now if you launch the debugger, with F5, it will not stop at the breakpoint. The app will run fine, and we will see the results in the debug console.

    When however we edit the code to match what we expect. Change Tony Stark's planet to Earth

    // ....
    {
        RealName: "Tony Stark",
        HeroName: "Iron Man",
        Planet:   "Earth",
    },
    //...

    When we launch the debugger again with F5, now the Debug View opens, and the execution stops at the breakpoint. We can see the json is not displayed in the Debug Console

    Sweet!

    Debugging Tests

    Let's add a new function to our file, for simple addition arithmentic.

    func add(a, b int) int{
        return a+b
    }

    We then create a test file main_test.go in the same directory, with the following contents.

    package main
    
    import "testing"
    
    func Test_add(t *testing.T) {
        a, b, c := 1, 2, 3
        res := add(a, b)
        if res != c {
            t.Fail()
        }
    }

    The code just adds two numbers, and the test just calls the function.

    If you have VSCode-Go plugin installed however, you'll see additional options at the top of the test function. run test and debug test

    You can click on run test to run the test and see the results in the Output window.

    To debug the test however, maybe because we can't figure out something, all we need to do is add a breakpoint, like we did before, and click on debug test.

    Let's add a breakpoint on line 10 if res != c, and click on debug test.

    The Debug View is opened again and we can use the Debug Tool to step over and inspect the state on the variables section on the left.

    Recap

    Debugging is a critical part of developing software, and with tools such as Visual Studio Code, our lives can be made much easier.

    We've used a simple example here to explain the concepts, but feel free to add the debugger to any of your existing projects, and play around with it. You'll end up reducing your fmt.Println statements used to log to see the value/state of code at a give point during it's execution.

    Happy Coding.