File-Split Refactoring: Pre-commit Hooks and Test Breakage#
Problem#
When splitting internal/validator/opnsense.go (1,061 lines) into domain-specific files, two non-obvious issues occurred:
- Pre-commit hooks silently rearranged helpers. After creating the new files and trimming the orchestrator, the pre-commit formatter moved domain-specific helpers back into
opnsense.go, creating a different layout than intended. - Changing an unexported function signature broke tests. Optimizing
validateInterfaceto accept a pre-computedmap[string]struct{}instead of*schema.Interfacesbroke two test call sites that called the unexported function directly.
Root Cause#
-
Hook rearrangement:
gofumptand linters run viapre-commitcan reformat and reorder code. When helpers are moved to new files but the orchestrator file is also modified, the formatter may consolidate related declarations back into the larger file based on its own heuristics. -
Test coupling: Go test files in the same package can call unexported functions directly. When a file split changes a function's parameter type (even for a pure optimization like pre-computing a map), every test call site must be updated.
Solution#
For pre-commit hook interference#
After writing new files and trimming the orchestrator, always re-read the files to verify the hook didn't rearrange them:
# After the split, verify contents match intent
wc -l internal/validator/*.go # Check line counts
go build ./internal/validator/ # Verify no duplicates
For test breakage on signature changes#
Before changing any unexported function signature, grep for all call sites:
# Find all callers of the function across test files
grep -rn 'validateInterface(' internal/validator/*_test.go
Then update each call site. In this case, wrapping the argument:
// Before (old signature: accepts *schema.Interfaces)
errors := validateInterface(&tt.iface, "test", interfaces)
// After (new signature: accepts map[string]struct{})
errors := validateInterface(&tt.iface, "test", collectInterfaceNames(interfaces))
For pre-existing issues found during split#
CodeRabbit review caught 4 pre-existing issues in the moved code. Per project policy ("Zero tolerance for tech debt"), all were fixed in the same PR:
isValidIPcallednet.ParseIPtwice -- cached the result- GID/UID error messages said "positive integer" but allowed 0 -- fixed to "non-negative integer"
validateDhcpddocstring didn't start with function name -- fixed per Go conventionscollectInterfaceNamescalled per-interface in O(N^2) loop -- pre-computed once
Prevention#
- Always re-verify file contents after pre-commit hooks run during file-split refactors
- Grep
*_test.gofor call sites before changing any unexported function signature - Run
just ci-checkafter every file-split refactor -- it catches duplicate declarations, test failures, and lint issues in one pass - Treat code review findings on moved code as in-scope -- the "zero tech debt" policy means pre-existing issues in touched code get fixed now, not later