- Blog/
Deadcode - unreachable or just unused?
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.