Clean code: production contracts · 3 MIN READ
Time and money need units and presence in their types
Separate durations, instants and exact amounts instead of passing ambiguous numbers.
An int64 does not tell the next engineer whether it represents milliseconds, Unix seconds, cents or token base units. A float64 cannot represent every decimal amount exactly. At API boundaries, define units, range, rounding and whether a missing field differs from zero. Inside the program, use types and constructors that make those contracts difficult to confuse.
clean-code-production · A MAP OF THE IDEA
- Parse with units
- Validate range/presence
- Keep exact representation
- Convert at boundary
Useful when
- Separate durations, instants and exact amounts instead of passing ambiguous numbers.
- Makes the owner and the failure boundary explicit.
Watch out
- Do not guess units from a JSON number or infer token decimals from a symbol.
- Run native service and failure tests in your own development environment.
Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.
The contract and the failure
An int64 does not tell the next engineer whether it represents milliseconds, Unix seconds, cents or token base units. A float64 cannot represent every decimal amount exactly. At API boundaries, define units, range, rounding and whether a missing field differs from zero. Inside the program, use types and constructors that make those contracts difficult to confuse.
Work through the example
Use time.Duration for elapsed budgets and time.Time for instants. Calendar schedules need a named location and explicit daylight-saving behavior. Go time values may contain a monotonic component for local elapsed measurements; serialization does not preserve that local clock reading. Use integer base units or a reviewed decimal package for money and validate scaling at the input boundary.
package main
import (
"fmt"
"math/big"
"time"
)
type Cents int64
func main() {
timeout := 250 * time.Millisecond
price := Cents(199)
raw, ok := new(big.Int).SetString("1000000000000000001", 10)
if !ok {
panic("invalid integer")
}
copied := new(big.Int).Set(raw)
copied.Add(copied, big.NewInt(1))
fmt.Println(timeout, price, raw.String(), copied.String())
}The duration is explicitly milliseconds, the amount is cents, and the large token amount is parsed without a floating-point round trip. big.Int operations mutate their receiver, so Set makes the independent copy before adding. The example demonstrates representation only; currency conversion, decimal formatting and overflow policy still need explicit rules.
Production decisions
Do not guess units from a JSON number or infer token decimals from a symbol. A pointer or optional type may distinguish omitted from zero. Check overflow before multiplication in fixed-width money types. Test rounding boundaries, negative amounts, maximum values, different decimal scales, a daylight-saving transition and serialization/restart of timestamps.
Explain it in an interview
Can a token amount with 18 decimals safely travel through float64 before big.Int? No. Parse exact integer base units or validated decimal text directly, and make scaling, range and rounding explicit. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.
TAKE THIS WITH YOU
Separate durations, instants and exact amounts instead of passing ambiguous numbers. No. Parse exact integer base units or validated decimal text directly, and make scaling, range and rounding explicit.
Check your understanding
Can a token amount with 18 decimals safely travel through float64 before big.Int?
Go to the source
Progress stays in this browser. No account needed.