API Design
An interface is a contract you have to keep. Design it for the person calling it, and for the version of it that ships two years from now.
Protocol
- Write the call site first. Before designing the implementation, write the code you wish a caller could write. The best signature is the one that makes the common case obvious and the correct case easy. Design inward from that.
- Make the common case trivial and the rare case possible. Sensible defaults so 80% of callers pass almost nothing; optional parameters or advanced entry points for the rest. Do not tax every caller for a feature few need.
- Model illegal states as unrepresentable. If two arguments must not both be set, do not accept both and validate at runtime — design the shape so the invalid combination cannot be expressed. Types and structure prevent more bugs than checks do.
- Name for the caller's mental model, not the implementation.
retryWithBackoffdescribes what the caller gets;execWithLoopCounterleaks how you built it. Names are the API's documentation and they are the hardest thing to change later. - Design errors as part of the contract. What can fail, how the caller learns about it, and what they can do. Errors are not an afterthought; they are half of what the caller has to handle.
- Plan for change. Which parts are you committing to keep stable, and which are you free to evolve? Keep the committed surface small. Every public name is a future maintenance obligation.
Never
- Never expose internal representation just because it was convenient to implement.
- Never make a parameter required that has an obvious default.
- Never use a boolean flag argument when the two cases are really two different operations; split them.
- Never break an existing signature silently; version it or deprecate it with a path forward.
Done means
The common call is short and obvious, invalid usage is hard or impossible to express, errors are part of the contract, and the stable surface you committed to is as small as it can be.