Api Design 1 Bloch
Api Design 1 Bloch
17-214 1
Administrivia
17-214 2
Key concepts from last lecture
17-214 3
Outline
17-214 4
Characteristics of a Good API
Review
• Easy to learn
• Easy to use, even if you take away the documentation
• Hard to misuse
• Easy to read and maintain code that uses it
• Sufficiently powerful to satisfy requirements
• Easy to evolve
• Appropriate to audience
17-214 5
1. API Should Do One Thing and Do it Well
17-214 6
What not to do
public abstract class Calendar implements
Serializable, Cloneable, Comparable<Calendar>
The Calendar class is an abstract class that provides
methods for converting between a specific instant in time
and a set of calendar fields such
as YEAR, MONTH, DAY_OF_MONTH, HOUR, and so on,
and for manipulating the calendar fields, such as getting
the date of the next week. An instant in time can be
represented by a millisecond value that is an offset from
the Epoch, January 1, 1970 00:00:00.000 GMT
(Gregorian).
17-214 7
What not to do, continued
Like other locale-sensitive classes, Calendar provides a class
method, getInstance, for getting a generally useful object of this
type. Calendar's getInstance method returns a Calendar object whose calendar
fields have been initialized with the current date and time:
Calendar rightNow = Calendar.getInstance();
A Calendar object can produce all the calendar field values needed to implement
the date-time formatting for a particular language and calendar style (for
example, Japanese-Gregorian, Japanese-Traditional). Calendar defines the range
of values returned by certain calendar fields, as well as their meaning. For
example, the first month of the calendar system has value MONTH ==
JANUARY for all calendars. Other values are defined by the concrete subclass,
such as ERA. See individual field documentation and subclass documentation for
details.
• I have no clue!!!
– Combines every calendrical concept without addressing any
• Confusion, bugs, & pain caused by this class are immense
• Thankfully it’s obsolete as of Java 8; use java.time
• Inexplicably, it’s not deprecated, even as of Java 16!
• If you’re working on an API and you see a class
description that looks like this, run screaming!
17-214 9
2. API should be as small as possible but no smaller
“Everything should be made as simple as possible, but not simpler.” – Einstein
• API must satisfy its requirements
– Beyond that, more is not necessarily better
– But smaller APIs sometimes solve more problems
– Generalizing an API can make it smaller(!)
• When in doubt, leave it out
– Functionality, classes, methods, parameters, etc.
– You can always add, but you can never remove
• More precisely, you can always provide stronger guarantees but
you can never retract a promise.
• e.g., you can expose additional methods, types, or enum
constants; broaden parameter types; narrow return type
• Stronger guarantees in extendable types are problematic
17-214 10
Conceptual weight (a.k.a. conceptual surface area)
17-214 11
Example: generalizing an API can make it smaller
Subrange operations on Vector – legacy List implementation
17-214 12
Example: generalizing an API can make it smaller
Subrange operations on List
17-214 13
“Perfection is achieved not when there is nothing more to
add, but when there is nothing left to take away.”
― Antoine de Saint-Exupéry, Airman’s Odyssey, 1942
17-214 14
3. Don’t make users do anything library could do for them
APIs should exist to serve their users and not vice-versa
• Reduce need for boilerplate code
– Generally done via cut-and-paste
– Ugly, annoying, and error-prone
// W3C DOM code to write an XML document to a specified output stream.
static void writeDoc(Document doc, OutputStream out) throws IOException {
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
doc.getDoctype().getSystemId());
t.transform(new DOMSource(doc), new StreamResult(out));
} catch(TransformerException e) {
throw new AssertionError(e); // Can’t happen!
}
}
17-214 15
4. Make it easy to do what’s common, possible to do
what’s less so
• If it’s hard to do common tasks, users get upset
• For common use cases
– Don’t make users think about obscure issues - provide
reasonable defaults
– Don’t make users do multiple calls - provide a few
well-chosen convenience methods
– Don’t make user consult documentation
• For uncommon cases, it’s OK to make users work more
• Don’t worry too much about truly rare cases
– It’s OK if your API doesn’t handle them, at least initially
17-214 16
5. Implementation should not impact API
17-214 17
6. Be consistent
Within your API and across the platform
17-214 18
7. “Fail Fast” – prevent failure, or fail quickly,
predictably, and informatively
• Ideally, API should make misuse impossible
– Fail at compile time or sooner
• Misuse that’s statically detectable is second best
– Fail at build time, with proper tooling
• Misuse leading to prompt runtime failure is third best
– Fail when first erroneous call is made
– Method should succeed or have no effect (failure-atomicity)
• Misuse that can lie undetected is what nightmares
are made of
– Fail at an undetermined place and time in the future
17-214 19
Misuse that’s statically detectable (and fails
promptly at runtime if it eludes static analysis)
// The WRONG way to require one or more arguments!
static int min(int... args) {
if (args.length == 0)
throw new IllegalArgumentException("Need at least 1 arg");
int min = args[0];
for (int i = 1; i < args.length; i++)
if (args[i] < min)
min = args[i];
return min;
}
17-214 20
API that makes misuse impossible
// The right way to require one or more arguments
static int min(int firstArg, int... remainingArgs) {
int min = firstArg;
for (int arg : remainingArgs)
if (arg < min)
min = arg;
return min;
}
17-214 21
API that fails at an unknown time and place
Sweet dreams…
17-214 22
Outline
17-214 23
1. Minimize Mutability
17-214 24
2. Minimize accessibility of everything
17-214 25
3. Subclass only when an is-a relationship exists
17-214 26
4. Design & document for inheritance or else prohibit it
17-214 27
5. Don’t expose a new type that lacks meaningful
contractual refinements on an existing supertype
• Just use the existing type
• Reduces conceptual surface area
• Increases flexibility
• Resist the urge to expose type just because it’s there
17-214 28
Outline
17-214 29
1. Use appropriate parameter and return types
17-214 30
2. Provide programmatic access to all data
available in string form
• Otherwise, clients will be forced to parse strings
– Painful
– Error prone
– Worst of all, it turns string format into de facto API
17-214 31
3. Use consistent parameter ordering across methods
17-214 32
4. Avoid long parameter lists
• Three or fewer parameters is ideal
– More and users will have to refer to docs
• Long lists of identically typed params are very harmful
– Programmers transpose parameters by mistake
– Programs still compile and run, but misbehave!
• Techniques for shortening parameter lists
– Break up method
– Create helper class to hold several parameters
• Often they’re otherwise useful, e.g., Duration
– Use builder pattern
// Eleven (!) parameters including five consecutive ints
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam);
17-214 33
Arguably this was the Microsoft “house style”
This example code comes from the official documentation
https://docs.microsoft.com/en-us/windows/win32/procthread/creating-processes
17-214 34
5. Avoid return values that demand exceptional processing
17-214 35
6. Do not overspecify the behavior of methods
17-214 36
7. Overload with care
17-214 37
Outline
I. General principles(7)
II. Class design (5)
III. Method design (7)
IV. Exception design (4)
V. Documentation (2)
17-214 38
1. Throw exceptions to indicate exceptional conditions
Don’t force client to use them for control flow
17-214 39
2. Favor unchecked exceptions
17-214 40
3. Favor the reuse of existing exception types
Special case class design principle 5
17-214 41
4. Include failure-capture information in exceptions
17-214 42
Outline
17-214 43
1. API documentation is critical
• Documentation is specification
• Poor documentation risks loss of control over spec
• Stack overflow becomes the spec…
• And you’re forced to support incorrect uses forever
• Accelerates Hyrum’s Law:
With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.
17-214 44
2. Document religiously
17-214 45
API Design Summary
17-214 46