Skip to main content
  1. Blog/
  2.  

Deadcode - unreachable or just unused?

·649 words·4 mins

Related Posts

Failure

·900 words·5 mins

The Go dev team posted a new article titled Finding unreachable functions with deadcode on the go.dev blog this morning where they announce the availability of a new tool named deadcode that performs static analysis of your code and reports if any of the functions are unreachable.

If this sounds familiar it may be because you’ve seen either gopls report that a part of your function is, unreachable or golangci-lint report that a function is unused.

These tools are really useful in highlighting code that you don’t have to waste time refactoring unused code, or updating other unused functions that call some other code you just refactored. If you’re debugging a problem and can’t figure out why the real-world code execution doesn’t match your expectations, you could write a quick test that mocks the calls to the function you expect to called and use one of these tools see if the function is even being called at all.

Most importantly, the warnings from these tools should motivate you to evaluate whether the unused or unreachable code should be kept at all.

Update: Seeing a function reporting as unreachable or unused doesn’t always mean the function should be removed though, the warning may be telling you there is a fault in some conditional logic preventing the function from being called, or that exported function you added to your package for future use should have a test added to avoid regressions.

The example in the article, however, isn’t the best.

package main

import "fmt"

func main() {
    var g Greeter
    g = Helloer{}
    g.Greet()
}

type Greeter interface{ Greet() }

type Helloer struct{}
type Goodbyer struct{}

var _ Greeter = Helloer{}  // Helloer  implements Greeter
var _ Greeter = Goodbyer{} // Goodbyer implements Greeter

func (Helloer) Greet()  { hello() }
func (Goodbyer) Greet() { goodbye() }

func hello()   { fmt.Println("hello") }
func goodbye() { fmt.Println("goodbye") }

Okay, the example isn’t the problem - the paragraph after it is where the problem is:

It’s clear from its output that this program executes the hello function but not the goodbye function. What’s less clear at a glance is that the goodbye function can never be called.

“Can never be called” makes it sound that attempting to call it would result in errors or failure, but that isn’t the case.

After writing that quick snippet on The Go Playground, I wanted to see what deadcode would say when the updated code was checked. Setting up a quick module to test it got me thinking about other scenarios where the tool could report false-positives. What if the unreachable function was in a package? What if the code being checked is just a package? So I turned the module in to a git repo, added a few scenarios to as separate branches, and rounded it off with a Makefile that swaps between the branches and checks the code with deadcode.

git clone https://github.com/jsnfwlr/go-deadcode.git ./godeadcode
cd ./godeadcode
make
make articleplus
make imported
make importedplus
make package

Running through the Makefile commands shows that when deadcode is run against it the code that adds a call to the goodbye function, it isn’t reported as dead any more, as you would expect. It’s also promising that the results are the same whether the functions are in the main package or another package imported into the main package - functions that aren’t called are reported as dead, but if you add a call to them, they aren’t.

And finally, it the code you’re checking is just a package with no main package to call it, deadcode will just report an error: deadcode: no main packages.

The deadcode command is great, but the output message needs to be changed. The functions it reports as unreachable are reachable but unused, so swapping from unreachable to unused in cases like the example from the announcement article would improve the clarity of the warning.