Many-Step Sequences in Go

April 16, 2026
A detailed shot of hands placing dominoes in a row, symbolizing strategy and precision.
Photo by Ron Lach on Pexels

It has been reported that Chris Lesiw has been translating long-lived build scripts from Bash into Go and running into a familiar bugbear: the mega-function. Bash gives you linear steps and “set -e” to bail out on failure — simple, predictable. Move that into Go and you gain type safety and modularity, sure, but you also often end up with one massive function that does everything. The result? Tests that are brittle as glass and a developer’s patience wearing thin. Sound familiar?

The testing pinch

Lesiw lays out the problem plainly: when every step depends on the previous one, you can’t easily unit-test a single step without faking a lot of state. Early failures cascade and hide the real culprit. It’s maddening. You want pinpointable failures, not a stack of dominoes. Break the work into functions and you get better isolation, but the orchestrator still becomes the last gate — full-sequence tests are still required, and error-handling code balloons into noisy boilerplate.

State machines to the rescue — with caveats

He revisits Rob Pike’s lexical-scanning pattern: each state is a function that returns the next state, and a simple loop drives the machine. Nice! Branching hides inside states, the driver is tiny, and the tedium of per-step error checks evaporates — error handling becomes just another state. But Lesiw notes the rough edges: lexers work on local, short-lived state. For long-running steps that call networks or spawn processes, you need context awareness and more testability than the classic state-fn pattern gives.

Types, method values, and a pragmatic middle path

Lesiw’s itch leads to a practical nudge: if many top-level functions share the same parameter, promote that parameter into a type and make steps methods — method values become first-class step descriptors. Types become namespaces and the state container becomes the natural home for step logic. The upshot is a middle path: keep orchestration tidy, preserve per-step testability, and reduce boilerplate without returning to inscrutable monoliths. It’s part of a broader trend — move away from fragile shell scripts toward typed, testable pipelines — and it’s worth a look if your CI feels like playing whack-a-mole.

Sources: lesiw.dev, Lobsters