Day 10 — Stage I gate
Day 10 — Stage I gate
Stage I · ~3h (integration)
Goal: Ship a small, stdlib-only CLI that integrates the Stage I toolkit—modules, types, control flow, functions/defer, slices, maps, structs/methods, pointers where justified, and multi-package layout—with a README others can follow.
This is a gate, not a feature festival. A polished narrow tool beats a half-broken platform. Cut scope ruthlessly.
Why this day exists
Stage I topics only stick when they cohere in one artifact. Gates force:
- Design trade-offs under time
- Error handling end-to-end
- Package boundaries that tests can import
- README as part of the deliverable
If Day 10 works, you are ready for interfaces, real error wrapping, and modules depth in Stage II—not still fighting slice headers.
Theory 1 — What “good enough” means here
Must-haves
| Bar | Evidence |
|---|---|
| Multi-package module | cmd/... + at least one library package |
| Domain types | Structs + methods (not only maps of primitives) |
| Collections | Slices and/or maps used appropriately |
| Errors as values | No panic for expected bad input |
| Process UX | Non-zero exit on failure; usage on misuse |
| Docs | README: purpose, build, examples, exit codes |
| Build | go build ./... clean on Go 1.26 |
Explicit non-goals
- No web framework
- No external modules required (stdlib only)
- No concurrency requirement
- No database
- No perfect CLI parser library (
flagis enough)
Theory 3 — Suggested layout
day10/
go.mod
README.md
cmd/<app>/main.go
<domain>/
...
internal/
...
main stays thin
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(exitCode(err))
}
}Domain owns rules
Validation, aggregates, and pure logic live outside main so Stage II can put tests on them without refactoring everything.
Theory 4 — Exit code policy
| Situation | Code |
|---|---|
| Success | 0 |
| Domain / runtime error | 1 |
| Usage / args | 2 |
var ErrUsage = errors.New("usage")
func exitCode(err error) int {
if errors.Is(err, ErrUsage) {
return 2
}
return 1
}Theory 5 — README template
# <tool>
One paragraph: what it does.
## Requirements
- Go 1.26+
## Build
go build -o <tool> ./cmd/<tool>
## Usage
Examples with expected output.
## Exit codes
| Code | Meaning |
|------|---------|
| 0 | OK |
| 1 | Failure |
| 2 | Usage |
## Design notes
2–5 bullets: packages, storage, limitations.Worked examples bank
Example A — Flag + subcommand sketch
func run(args []string) error {
if len(args) < 1 {
return fmt.Errorf("%w: <command> [args]", ErrUsage)
}
switch args[0] {
case "list":
return cmdList()
case "add":
return cmdAdd(args[1:])
default:
return fmt.Errorf("%w: unknown command %q", ErrUsage, args[0])
}
}Example B — Persistence sketch (JSON)
func load(path string) (*Store, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return NewStore(), nil
}
return nil, err
}
var s Store
if err := json.Unmarshal(data, &s); err != nil {
return nil, err
}
s.ensure()
return &s, nil
}
func save(path string, s *Store) error {
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0o644)
}Example C — Smoke script
#!/usr/bin/env bash
set -euo pipefail
go build -o app ./cmd/app
./app list >/dev/null
./app add "demo"
./app list | grep -q demo
! ./app nope # expect failureLabs
Suggested workspace: ~/lab/90daysofx/go/day10
Lab 1 — Ship the gate project
mkdir -p ~/lab/90daysofx/go/day10
cd ~/lab/90daysofx/go/day10
go mod init example.com/day10Implement your chosen tool to the must-have bar.
Lab 2 — Bad input matrix
Manually run at least:
- No arguments → usage, exit 2
- Unknown subcommand → exit 2
- Domain error (missing id, empty name, …) → exit 1 with clear message
- Happy path → exit 0
Record commands and outputs in README or SMOKE.md.
Lab 3 — Self-review checklist
Without adding features, answer:
- Where could a package boundary improve tests?
- Any panic paths for normal errors?
- Any nil map writes?
- Slice ownership documented if you return internal slices?
- README sufficient for a teammate?
Lab 4 — Optional micro-test
One TestX in a library package that locks a pure function (parse, total, classify line). Stage II will expand testing culture.
Common gotchas
| Gotcha | Fix |
|---|---|
| Scope creep | Cut to core commands |
Logic trapped in main |
Extract packages mid-gate if needed |
| Ignoring exit codes | Define and test them |
| README afterthought | Write examples while building |
| Panic on bad input | Return error |
| “I’ll refactor later” forever | Gate requires shippable shape now |
Checkpoint
- Tool builds with Go 1.26
- Multi-package layout
- Structs + methods + maps/slices in play
- Bad input → non-zero exit
- README with build + examples
- Can explain slice header and map comma-ok from this codebase
- Ready to discuss interfaces without unfinished Stage I panic
Commit
git add .
git commit -m "day10: stage I gate mini CLI"Tomorrow
Stage II begins — Day 11 Interfaces: implicit satisfaction, small interfaces, any, and refactoring domain behavior behind interfaces without mock frameworks.