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.