Reading Code That's Already Broken
Every exercise in this book has handed you a blank body and asked you to fill it. That is the smaller half of the job. Most of your working life is spent reading code that already exists, already compiles, and is quietly wrong — and the hardest bugs are the ones the compiler is perfectly happy with.
The two exercises here are a different shape. The code is written. It builds. go vet says nothing. A test fails, and your job is to work out why.
The compiler is not a correctness checker
Go's compiler is stricter than most: unused variables and unused imports are hard errors, not warnings. That strictness is real, and it trains a dangerous reflex — if it builds, it's probably fine.
It isn't. The compiler checks that your program is well-formed, not that it means what you intended. Two of Go's most common production bugs are invisible to it:
// Compiles. Vets clean. Returns nil even when parsing failed.
func Sum(values []string) (int, error) {
total := 0
var err error
for _, v := range values {
n, err := strconv.Atoi(v) // a NEW err, shadowing the outer one
if err != nil {
break
}
total += n
}
return total, err // the outer err — still nil
}
The := inside the loop declares a new err scoped to the loop body. The outer one, the one actually returned, is never assigned. Nothing warns you, because every variable is used.
Start from the failing test, not the code
The instinct when handed a bug is to read the function top to bottom looking for something that seems wrong. That scales badly — everything seems slightly wrong when you're hunting.
Read the failing assertion first. It tells you exactly what was expected and what happened. Then work backwards to the one line that could produce that difference. A test that says got 30, want error is telling you the error path never ran; a test that says the original slice changed is telling you two slices share memory. In both cases the message names the mechanism before you have read a single line of the implementation.
Reproduce before you fix
The temptation, once you have a theory, is to change the line and re-run. Resist it just long enough to be sure the test fails for the reason you think it does. A fix that makes a test pass for the wrong reason is worse than the bug: the bug was at least honest.
Try it
Two functions below, both broken. Neither has a syntax error and neither will be flagged by go vet — these are semantic bugs, the kind that reach production. Read the test failure, form a theory, then fix the implementation. Don't change the tests.
Practice
1. Fix the Aliasing Bug
⚑ Find the bugIntermediateHeadis supposed to return the firstnitems of a slice, leaving the caller free to do whatever they like with the result — including appending to it — without the original being affected.It compiles.
go vetis silent. The hidden test fails anyway, because appending to the returned slice writes straight into the original's memory.Find the bug and fix
Head. Don't change its signature.package main import "fmt" // Head returns the first n items of s. // // The caller owns the result: appending to it must never disturb s. func Head(s []string, n int) []string { if n > len(s) { n = len(s) } return s[:n] } func main() { original := []string{"a", "b", "c", "d"} head := Head(original, 2) head = append(head, "SURPRISE") fmt.Println(original) // should still be [a b c d] }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
2. Fix the Swallowed Error
⚑ Find the bugIntermediateSumparses every string and adds it up. On the first unparseable entry it should stop and return the parse error.It compiles.
go vetis silent. ButSumreturns anilerror even when the input is garbage, so callers happily use a wrong total.Find the bug and fix
Sum. Don't change its signature, and keep the behaviour of stopping at the first bad entry.package main import ( "fmt" "strconv" ) // Sum parses each value and returns the total. It stops at the first value // that isn't a number and returns that parse error. func Sum(values []string) (int, error) { total := 0 var err error for _, v := range values { n, err := strconv.Atoi(v) if err != nil { break } total += n } return total, err } func main() { fmt.Println(Sum([]string{"1", "2", "3"})) // 6 <nil> fmt.Println(Sum([]string{"1", "oops", "3"})) // should report an error }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.