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

  1. Capture existing boundary behavior
  2. Extract one pure responsibility
  3. 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

01

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.

02

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.

go
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>
Read the code

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.

03

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.

04

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.

05

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.

06

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.