How To Design A Good API and Why it Matters


  • Start with a 1-page spec and iterate on it heavily before starting to implement it.
  • Example code (arguably) deserves more attention than production code.
  • One chance to design an API well, because you’re pretty much stuck with it after release. It’s worth thinking about more rigorously.
  • Everyone is an API designer, even if you don’t ship customer-facing code. Inter-module boundaries are APIs too.
  • This seems geared towards API design at a language/core lib level, but there are still many good lessons applicable more widely.
  • A good API is easy to learn & memorize, and hard to misuse.
  • Fail fast, ideally at compile time. (Example: The Properties class accepts KV-pairs of any type even though only strings are supported; this is only made obvious when you attempt to save a Properties instance)
  • The conceptual weight of an API (number of concepts to learn about) far outweighs its bulk (number of classes/methods/etc)
  • Immutability is not always possible; design APIs that are as immutable as possible, encapsulating state tightly.
  • Naive usage of Serializable exposes all fields, including private ones.
  • APIs that return a string should also make portions of that string available separately (if applicable) so consumers don’t have to parse these strings manually. (example: printStackTrace was the only way to get stack traces initially).
  • An SPI is a special form of an API that provides pluggable behaviors (strategies?)
    • It’s almost impossible to go from a single instance of one of these pluggable behaviors to two.
    • It’s hard to go from two to three.
    • Write multiple plugins at the outset.
  • Classes are either explicitly designed for inheritance or are marked final; subclassing can break encapsulation.
  • It’s impossible to make everyone happy and still have a well-designed API.
  • Tradeoff between cohesiveness and supporting multiple stakeholders.
  • Avoid boilerplate: anything that can be done in the module should be.
  • Document every class/method/interface/parameter/etc. Read the code isn’t good enough; if the behavior of a class/method isn’t specified, the implementation implicitly becomes the spec, and it’s much harder to change as a result.
  • API design can impact performance (example: mutability, factories vs. constructors)
  • LSP: A extends B ← is every A a B? If in doubt, don’t inherit.