ROSE Compiler Framework/Good API Design
Appearance
Google: "How to Design a Good API and Why it Matters" by Joshua Bloch
TODO: convert from Markdown
Characteristics of a Good API
[edit | edit source]- Easy to learn
- Easy to use, even without documentation
- Hard to misuse
- Easy to read and maintain code that uses it
- Sufficiently powerful to satisfy requirements
- Easy to extend
- Appropriate to audience
The Process of API Design
[edit | edit source]- Gather true requirements in the form of use-cases
- Start with a short 1-page specification
- Agility trumps completeness
- Collect a lot of feedback
- Use your API early and often
- [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development)
[T]he repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards.
- Doubles as examples/tutorials and unit tests
- Maintain realistic expectations
- You won't be able to please everyone... aim to displease everyone equally
- Expect to evolve API; mistakes happen; real-world usage is necessary
General Principles
[edit | edit source]- When in doubt, leave it out. You can always add, but you can never remove.
- Just because you can doesn't mean you should
- [Power-to-weight ratio](http://en.wikipedia.org/wiki/Power-to-weight_ratio)
> [A] measurement of actual performance [power / weight]
- Don't give users a gun to shoot themselves with
- Information hiding: minimize the accessibility of everything
Documentation Matters
[edit | edit source]- Class: what an instance represents
* Method: contract between method and calling client (preconditions, postconditions, and side-effects) * Parameter: indicate units, form, ownership
Pre- and Post- Conditions
- The precondition statement indicates what must be true before the function is called.
- The postcondition statement indicates what will be true when the function finishes its work.
/// \post <return_value>.empty() == false
API vs. Implementation
[edit | edit source]Implementation details should not impact the API. Don't let implementation details "leak" into the API.
Performance
- Design for usability, refactor for performance
- Do not warp the API to gain performance
- Effects of API design decisions on performance are real and permanent:
- Component.getSize() returns Dimension
- Dimension is mutable
- Each getSize call must allocate Dimension
- Causes millions of needless object allocations
"Harmonize"
[edit | edit source]- API must coexist peacefully with platform
- Do what is customary (standard)
- Avoid obsolete parameter and return types
- Mimic patterns in core APIs and language
- Take advantage of API-friendly features: generics, varargs, enums, default arguments
- Don't make the client do anything the module could do
- Reduce need for boilerplate code
- Don't violate the [Principle of Least Astonishment](http://en.wikipedia.org/wiki/Principle_of_least_astonishment)
> The design should match the user's experience, expectations, and mental models...aims to exploit users' pre-existing knowledge as a way to minimize the learning curve
- Provide programmatic access to all data available in string form => no client string parsing necessary
- Overload with care: ambiguous overloadings
Names Matter
[edit | edit source]- Largely self-explanatory (avoid cryptic abbreviations)
- Be consistent (e.g. same word means same thing)
- Strive for symmetry
- Should read like [prose](http://en.wikipedia.org/wiki/Prose)
> [T]he most typical form of language, applying ordinary grammatical structure and natural flow of speech rather than rhythmic structure (as in traditional poetry).
if (car.speed() > 2 * SPEED_LIMIT) generateAlert("Watch out for cops!");
Input Parameters
[edit | edit source]- interface types over classes: flexibility, performance
- most specific possible type: moves error from runtime to compile time
- use double (64 bits) rather than float (32 bits): precision loss is real, performance loss negligible
- consistent ordering:
#include <string.h> char *strcpy (char *dest, char *src); void bcopy (void *src, void *dst, int n); // bad!
- short parameter lists: 3 or fewer; more and users will have to refer to docs; identically typed params harmful
- Two techniques for shortening: 1) break up method, 2) create helper class to hold parameters
Return Values
[edit | edit source]- Avoid values that demand exceptional processing
> For example, return a `zero-length array` or `empty collection`, not `null`
Exceptions
[edit | edit source]- don't force client to use exceptions for control flow
- don't fail silently
- favor unchecked exceptions
- include failure-capture diagnostic information
- Fail fast: report errors ASAP
- Compile time: static typing, generics
- Run time: error on first bad method invocation (should be failure-atomic)