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

  1. Parse with units
  2. Validate range/presence
  3. Keep exact representation
  4. 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.

01

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.

02

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.

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

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.

03

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.

04

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.