Api Design 2 Principles
Api Design 2 Principles
17-480/780 1
Administrivia
17-480/780 2
Key concepts from Tuesday…
17-480/780 3
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-480/780 4
Outline
17-480/780 5
1. API Should Do One Thing and Do it Well
17-480/780 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-480/780 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!!!
• The confusion, bugs, and pain caused by this class are
incalculable
• Thankfully it’s obsolete as of Java 8; use java.time
• Inexplicably, it’s not deprecated, even as of Java 10
• If you working on an API and you see a class description
that looks like this, run screaming!
17-480/780 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
17-480/780 10
Conceptual weight (a.k.a. conceptual surface area)
17-480/780 11
Example: generalizing an API can make it smaller
Subrange operations on Vector – legacy List implementation
17-480/780 12
Example: generalizing an API can make it smaller
Subrange operations on List
17-480/780 13
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
import org.w3c.dom.*;
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
17-480/780 14
4. Make it easy to do what’s common and
preferable, possible to do what’s less-common
• People tend to take the easy way out
– Make sure it’s what they want and should do
• If it’s hard to do what they want, they’ll get upset
– They’ll have to go to the documentation
• If it’s easier to do something wrong, dangerous, or
expensive, that’s exactly what users will do
• Don’t worry too much about truly rare stuff
– It’s OK if your API doesn’t handle it, at least
in the first release
17-480/780 15
5. Monitor complexity constantly
• Train yourself to run “complexity meter” in the background
• When it starts climbing, look for ways to simplify
Complexity
17-480/780 16
6. Implementation should not impact API
17-480/780 17
7. APIs should coexist peacefully with platform
• Do what is customary
– Obey standard naming conventions
– Avoid obsolete parameter and return types
– Mimic patterns in core APIs and language
• Take advantage of API-friendly features
– varargs, enums, iterables, try-with-resources,
default methods, etc.
• Don’t Transliterate APIs
– Common in the early days (Corba, JGL, etc.)
– Still happens
17-480/780 18
8. Consider the performance consequences of API
design decisions
• Bad API decisions can limit performance forever
– Making type inappropriately mutable (or immutable!)
– Providing public constructor instead of static factory
– Using implementation type instead of interface
• But do not warp API to gain performance
– Underlying performance issue will get fixed,
but headaches will be with you forever
– Good design usually coincides with good performance
17-480/780 19
Outline
17-480/780 20
1. 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-480/780 21
2. Minimize Mutability
17-480/780 22
3. Minimize accessibility of everything
17-480/780 23
4. Subclass only when an is-a relationship exists
17-480/780 24
5. Design & document for inheritance or else prohibit it
17-480/780 25
Outline
17-480/780 26
1. “Fail Fast” – prevent failure, or fail quickly,
predictably, and informatively
• API should make it impossible to do what’s wrong
– 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 be failure-atomic
• Misuse that can lie undetected is what nightmares
are made of
– Fail at an undetermined place and time in the future
17-480/780 27
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-480/780 28
API that makes it impossible to do what’s wrong
// 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-480/780 29
API that fails at an unknown time and place
Sweet dreams…
17-480/780 30
2. Handle boundary conditions (edge cases,
corner cases) gracefully
• Client should not have to write extra code
– These cases should just work
• e.g., Return zero-length arrays, collections; not null
package java.awt.image;
public interface BufferedImageOp {
// Returns the rendering hints for this operation,
// or null if no hints have been set.
public RenderingHints getRenderingHints();
}
17-480/780 31
3. Use appropriate parameter and return types
• Favor interface types over classes for input
– Provides flexibility, performance
• Use most specific reasonable input parameter type
– Moves error from runtime to compile time
• Don't use String if a better type exists
– Strings are cumbersome, error-prone, and slow
• Don't use floating point for monetary values
– Binary floating point causes inexact results!
• Use double (64 bits) rather than float (32 bits)
– Unless you know (via benchmarking) that you need the
performance, and you can tolerate the low precision
17-480/780 32
4. Use consistent parameter ordering across methods
17-480/780 33
5. 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, 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 four 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-480/780 34
6. Avoid return values that demand exceptional processing
package java.awt.image;
17-480/780 35
7. Do not overspecify the behavior of methods
17-480/780 36
8. 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
• Java got this wrong for exception stack traces…
– But StackTraceElement[] getStackTrace() added in Java 4
– At the same time as we enhanced stack trace string format
– A few users complained at the time, but quickly got over it
17-480/780 37
9. Overload with care
17-480/780 38
Outline
• General principles
• Class design (8)
• Method design (5)
• Exception design (9)
• Documentation (4)
17-480/780 39
1. Throw exceptions to indicate exceptional conditions
17-480/780 40
2. Favor unchecked exceptions
17-480/780 41
3. Favor the reuse of existing exception types
17-480/780 42
4. Include failure-capture information in exceptions
17-480/780 43
Outline
• General principles
• Class design
• Method design
• Exception design
• Documentation
17-480/780 44
API documentation is critical
17-480/780 45
Document religiously
17-480/780 46
API Design Summary
17-480/780 47