Gofix

gofix is not a test suite

I ran gofix over an older utility and was rewarded with a clean build. Then its first real input produced different output. That was a useful correction to my optimism.

My migration loop is now deliberately boring:

copy or commit the old tree
run gofix
inspect the diff
format and build
run representative inputs

The surprise is how convincing syntactically correct output can be. gofix understands Go source and can perform precise, repeatable rewrites for known language and library changes. It cannot infer every assumption my programme made about the old operation.

That boundary follows from the mechanism. A fix rule recognizes a source pattern and substitutes the new form. It can use syntax and type context; it does not possess the history behind my data or know which edge cases matter to users.

I prefer the tool over hand-editing twenty copies of the same call. Mechanical work should be mechanical. I also prefer one rule or related group at a time, because a small diff tells me which migration changed behaviour.

The caveat is code generation. Fixing generated output while leaving the generator untouched guarantees an encore. Tests and examples may need migration too, and they are often the best record of old semantics.

In my utility the changed behaviour was easy to repair once a failing input exposed it. gofix had done its job. I had briefly promoted it to quality assurance director without discussing the salary.

The Go 1 preview and gofix

The Go 1 plan changed how I looked at a compiler error today. Until now, a broken build after updating was mostly the price of following a young language. With a compatibility target in sight, the churn starts to look like a migration with a destination.

I tried gofix on a copy of a small package before changing anything by hand. The useful workflow is pleasantly conservative:

$ gofix -r rule package.go
$ hg diff package.go

The available rules and invocation depend on the snapshot, so I check the tool’s help rather than preserving that command as scripture. The second command matters more: automated rewriting should produce a diff I can understand.

My surprise was that the mechanical part was the easy part. Renaming an API or adjusting a call shape can often be recognized from syntax and type information. Deciding whether the programme still means the same thing requires a person and tests. A clean compile is evidence, not absolution.

gofix works by parsing Go source rather than treating it as undifferentiated text. That lets a rule identify a particular construct and rewrite it while preserving valid Go structure. It can then format the result. This is much safer than a global search-and-replace that cannot distinguish a package name from a comment or an unrelated identifier.

The Go 1 preview is important because it proposes a stable base for source code and core packages. It does not mean every implementation detail freezes, nor does it make the current preview the final release. There is still work between a compatibility promise and a toolchain one can call 1.0 without crossing one’s fingers behind one’s back.

I prefer making these migrations promptly and in small commits. Old code plus several skipped snapshots turns a sequence of obvious edits into software archaeology. I run gofix, inspect every hunk, format, build, and exercise the behaviour that changed.

The caveat is generated source and unusual build arrangements. A rewrite may need to happen at the generator, not merely in its output. Local APIs with names resembling standard ones also deserve attention.

Tools cannot remove change, but they can make change reviewable. That seems a suitably Go-like ambition: less ceremony, still no magic.