Clean code in Go · 3 MIN READ
Refactoring: make the safe change small
Separate a behavior-preserving edit from a new requirement.
Separate a behavior-preserving edit from a new requirement. A safe refactor starts with the behavior that must stay stable, then changes one structure at a time. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Clean Go · A MAP OF THE IDEA
- Capture existing boundary behavior
- Extract one pure responsibility
- Run focused tests before the next change
Useful when
- A feature is hard to test through I/O
- A change can be divided into reviewable steps
Watch out
- A weak test suite can preserve an unnoticed bug
- Performance claims need representative measurements
Implementation & operation cost: Low to medium
The design decision
A safe refactor starts with the behavior that must stay stable, then changes one structure at a time. Extract a pure decision from I/O so tests can describe it cheaply. Keep a correctness change or optimization in a separate step when possible, with a focused reason for each.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"errors"
"fmt"
)
func ShippingCents(weight int) (int, error) {
if weight <= 0 {
return 0, errors.New("positive weight required")
}
if weight <= 1000 {
return 500, nil
}
return 900, nil
}
func main() {
for _, weight := range []int{0, 1000, 1001} {
cost, err := ShippingCents(weight)
fmt.Println(weight, cost, err)
}
}
// Output:
// 0 0 positive weight required
// 1000 500 <nil>
// 1001 900 <nil>
The boundary values 1000 and 1001 make the tariff transition visible. Moving this decision out of a handler lets the same cases protect a later structural edit. The zero case verifies that invalid input is rejected before a quote is returned.
Tradeoffs and the simpler alternative
Run go test and go vet for correctness, race tests for exercised concurrency, and benchmarks for a suspected performance issue. A shorter function is not automatically faster or clearer. Change a boundary only after its existing users and failure behavior are understood.
Separate a behavior change from a structural change
First capture the public behavior around the seam you will move, including failure and partial-result semantics. Rename or extract one responsibility without changing the result, then make the new feature in a separate step. Moving code and redesigning all errors at once makes a regression difficult to locate. Keep commits small enough to explain what behavior should be identical and what deliberately changed.
Migrate a boundary in reversible stages
Place an adapter around the old implementation, move callers one at a time and compare results for safe read-only operations. For writes, choose one authoritative path; calling old and new payment implementations in parallel would duplicate effects. Keep backward-compatible data until rollback no longer depends on it. A clean package tree is not a rollback plan: durable schemas, queued events and public clients need their own compatibility strategy.
Use deletion as the final proof of extraction
After callers use the new boundary, remove the old shared helper and inspect imports for hidden dependencies. Run focused unit and adapter tests and a small end-to-end path for the affected feature. Measure performance if the change introduces allocations, serialization or remote calls. Do not keep two permanent implementations just because both compile; unfinished migrations multiply maintenance cost and eventually let behavior diverge.
TAKE THIS WITH YOU
Changes structure while preserving an explicit contract. Run go test and go vet for correctness, race tests for exercised concurrency, and benchmarks for a suspected performance issue. A shorter function is not automatically faster or clearer. Change a boundary only after its existing users and failure behavior are understood.
Check your understanding
Which statement best describes Refactoring: make the safe change small?
Go to the source
Progress stays in this browser. No account needed.