api-library-design
Installation
SKILL.md
API & Library Design
Core principle: friendly, consistent, discoverable
A public API should be usable by someone reading only the function signatures and a few examples — without needing to read the implementation. Consistency across the whole library matters more than any single function being clever.
Naming conventions
- Be consistent across the entire package: if one constructor is
New(...), don't have another beCreate(...)orNewFoo(...)unless there's a real reason for the difference (e.g., multiple constructors for different configurations —NewWithConfig,NewFromFile). - Match Go stdlib naming instincts:
Newfor constructors,Must*for a variant that panics instead of returning an error (used sparingly, and only when the panic is genuinely appropriate — e.g. package-level setup that can't reasonably continue),With*for functional options. - Avoid stutter:
kyu.Queue, notkyu.KyuQueue. Package name + type name should read naturally together at the call site (kyu.New(), notkyu.NewKyu()). - Prefer verbs for functions/methods and nouns for types —
queue.Enqueue(job), notqueue.Job(job). - Name things for what they do, not how they're implemented.
RateLimiter.Allow()notRateLimiter.CheckTokenBucket()— the token bucket is an implementation detail, not part of the contract.
Exposing configuration
- Default to the functional options pattern for anything with more than 2-3 optional configuration values — it keeps the zero-config case simple (
kyu.New()just works) while letting advanced users opt into more control (kyu.New(kyu.WithRetries(3), kyu.WithBackoff(...))). - Every option should have a sane default that works for the common case without the caller needing to know it exists. A library that requires reading the docs before it does anything useful has failed the "friendly" bar.
- Don't expose internal configuration that callers can't meaningfully act on — if a config value only matters for your own testing/debugging, keep it unexported.
- Struct-based config (a
Configstruct passed toNew) is a reasonable alternative to functional options when the config is fixed and unlikely to grow — pick whichever fits the library's actual shape, but be consistent within one library.