/ The Blog of Jinho Ko / Computer science / Programming

·

4 min read

How To Design A Good API and Why it Matters

By Jinho Ko

How To Design A Good API and Why it Matters

Why is API design important?

  • for customers
    • Company’s greatest assets : customers invest heavily on buy/write/learning APIs
    • Can be company’s liabilities : aBad API leads to stream of support calls
  • for you
    • If you are programmer => you are API design
      • good code is moduled, API connects between modules
      • so good coder is a good API designer
    • modules become reusable
  • language design == API design

Good API

  • Easy to learn, use w.o. docs
  • Hard to misuse
  • “Sufficiently” Powerful (not just powerful, but powerful enough)
  • Easy to extend
  • Appropriate to audience (who’s using?)

I. Process of API Design

Gather Requirements

  • Gather! but with skepticism
    • only gather the ‘goal’, not the ‘method’
    • you decide the optimal method
  • Extract true requirements : from use-cases
  • Better if you even draw bigger picture than the given requirements

Start with small steps : short 1 page draft

  • repeat : get multiple feedbacks and modify
  • flesh it out as you gain confidence
    • this necessarily involves virtual coding

Write to your API early and often

  • virtually start using your API even implementing it!
    • saves you from wasting time
  • the code lives on examples and unit tests

Writing to SPI is even more important

  • SPI : Service Provider Interface
    • plug-in interface enabling multiple impls

Maintain Realistic Expectations

  • most API designs are over-constrained
    • you cannot please everyone, but stick to unified, strong rules
  • expect users to make mistakes -> and evolve them

II. General Principles

Should do one thing and do it well

  • functionality should be easy to explain
    • provide good names!
    • good names drive good designs (design functions, member variables, …)
    • make it portable

Should be as small as possible but no smaller

  • When in doubt, leave it out (very important)
    • better add design later, but impossible to deprecate
  • Conceptual weight is more important
    • e.g. TreeSet, HashSet, ConcurrentSet, … , but no one cares cuz they share same concepts

Implementation should not impact API

  • impl details should strictly be hidden to users
    • users cannot see nor change
    • to do so, be aware of what is an impl detail
      • e.g.1. don’t specify hash functions
      • e.g.2. all tuning parameters (do these need to be exposed???)
  • do not impl details leak into API
    • e.g. once api is serializable, all private fields go public

Minimize accessibility

  • information hiding
    • set members private as possible
    • public classes should have no public fields
  • remove dependencies between modules

Names matter - API is a language

  • Names should be self-explanatory
  • Be consistent - same word means same thing
    • e.g. remove vs delete?
  • Be regular - symmetry
    • e.g. addKey() <> deleteKey()

Documentation matters

  • Reuse is something that is far easier to say than to do. Doing it requires both good design and very good documentation. Even when we see good design, which is still infrequently, we won’t see the components reused without good documentation.

Document religiously!, but no one’s doing it.

  • Document every class / interface / method / … and exception
    • class : what an instance represents
    • method : pre/post conditions, side-effects
    • parameter : units, form, ownership
    • variables : mutable or not?

Consider performance consequence of API design decisions

  • (according to effective Java) Premature optimization is evil.
  • But you should never ignore it
    • e.g. unnecessarily making mutable
  • do not warp API to gain performance
    • good design usually coincides with good performance

API must coexist peacefully with platform

  • Do what is customary for the platform
    • Fit in the standard naming conventions
    • Avoid obsolete parameters
  • DO NOT transliterate APIs. If doing so, they broke eventually.
    • C++ != JAVA, the classes do not have to be same.

III. Class Design

Minimize Mutability

  • Classes should be immutable unless there’s a good reason to be mutable
  • If it must be mutable, keep state-space small, well defined

Subclass only where it makes sense

  • subclass implies substitutability
    • only ‘is-a’ relationship
    • otherwise, use composition
  • Bad : Properties extends Hashtable / Good : Set extends Collection

Design and document for inheritance or else prohibit

  • Inheritance violates encapsulation
    • When superclass modified, it modifies subclass

IV. Method Design

Don’t make the client do anything module could do

  • reduce need for boilerplate code

Don’t Violate the principle of least astonishment

  • API user should not be surprised by its behavior

Fail fast - Report Error as soon as possible

  • Compile-time is the best : static typing, generics
  • At runtime, method should be failure-atomic
    • if fails, make it did nothing!

Provide Programmatic access to all data available in string form

  • e.g. getFileName(), ..
  • user should not parse debugstring

Overload with care

  • avoid ambiguous overloading
    • to be conservative, no two with same #args
  • Often better with different name
  • If you must provide overloading, ensure same behavior for same arguments.
    • e.g. TreeSet(Collection c), TreeSet(SortedSet s), … ignores/respects ordering
      • what if SortedSet is casted into Collection?

Use appropriate parameter and return types

  • interface > classes for input
  • specify input param type
  • don’t use string if better type exists

V. Exception Design

Use consistent parameter ordering accross methods

  • important if param types identical
    • e.g. <string.h> : strcpy(dest,src) / bcopy(src, dst)

Avoid long parameter lists

  • if long, 1) break up method or 2) create helper class to hold parameters

Avoid return values that demand exceptional processing

  • user should not handle exceptions that are not specified
    • e.g. null values?

Throw exceptions to indicate exceptional conditions

Favor unchecked exceptions over checked exceptions

Include failure-capture information in exceptions

last modified June 2, 2024
comments powered by Disqus

© Copyright 2024. Jinho Ko. All rights reserved. Last updated: June 02, 2024.