Accidental removal of side-effect imports

This particular issue is something I’ve come across in Go code shipped to staging/a semi-stable environment, and it’s unfortunately one that is difficult to catch in code review if you’re not looking out for it. I’ll cover how the issue happens and show a couple of ways I’ve learned to prevent the issue from happening.

First, the overview. Go has the ability to import a package purely for the package’s side-effects (i.e. you don’t actually use a symbol from the package in your code, just the side-effects).

As an example let’s look at the expvar package in std lib. Importing the package, as a side-effect, registers a HTTP handler at “/debug/vars” that prints the state of published vars. In addition, it publishes by default a couple of exported vars named memstats and cmdline.

How the issue arises #

I’ll stick to the std lib in this example, so the example may be a bit simple, but I think it will get the general idea across.

Imagine you want to track the number of visits to your site, so you add code like the following. At this point you’d expect

to be visible at “/debug/vars”.

package main

import (
    "expvar"
    "net/http"
)

var visits = expvar.NewInt("visits")

func main() {
    http.HandleFunc("/", handler)
    ...
}

func handler(w http.ResponseWriter, r *http.Request) {
    visits.Add(1)
    ...
}

In the future, you decide to stop counting visits, so you delete the lines:

- var visits = expvar.NewInt("visits")

-   visits.Add(1)

Your editor then automatically adjusts imports — removing theexpvar import. If you’re used to goimports adjusting your imports you might miss to consciously register that you’ve lost the expvar import.

This, in turn, means you’ve (potentially unbeknownst) lost the side-effect HTTP handler at “/debug/vars”, thememstats var, and the cmdline var.

Preventing the issue #

A couple of ways I’ve learned to prevent this.

  1. When you import a package for its side-effects as well as for normal use, make sure that you explicitly import it twice. This way, the _ side-effect import sticks around even when the regular import is removed.

    import (
        ...
        "expvar"
        _ "expvar"
        ...
    )
    
  2. As an added measure add code like this to your package. I learned this from a former teammate.

    var (
        // NOTE: this exists to prevent accidental removal of 
        // package imports that are required for side effects
        _ = expvar.Do
    )
    

    This line would fail to compile, making you consciously aware that you’ve lost the expvar import.

 
1
Kudos
 
1
Kudos

Now read this

Parallel tests in Go

Go surprises me with how simple it makes things. Today, it was parallel testing. In other languages I’ve used, it may require third-party packages, require complicated syntax, or may just not be possible. In Go, it’s as simple as: import... Continue →