The most important aspect of any module is not how it implements the facilities it provides, but the way in which it provides those facilities in the first place.
-- Damian Conway in Ten Essential Development Practices
Public APIs are forever -- (you have) one chance to get it right.
-- Joshua Bloch in How to Design a Good API and Why it Matters
Interfaces matter. The more public they are, the more they matter. Indeed, once a public API -- such as Conway's CPAN modules or Bloch's Java SPIs -- attracts widespread usage, changing it becomes practically impossible. By contrast, just about anything else can be easily fixed in a later release.
Making the task more daunting still, the dark art of interface and API design is certainly not an easy one to master, there being many disciplines in play: computer science, human factors (ergonomics), cognitive science, psychology, sociology, linguistics, usability, and so on.
I've spent the past few weeks researching this difficult topic and found very few references dedicated to this subject. Instead, many books devote a section or two to the art of interface design, or perhaps mention it in passing while analyzing a knotty design or coding matter.
This meditation reports the interface and API design references I've found useful and further presents some general interface design ideas and checklists in the hope that they may prove useful -- and that they might be improved upon by your insightful feedback.
Bloch's Seven Characteristics of a Good API
In his How to Design a Good API and Why it Matters keynote, Joshua Bloch proposed seven characteristics of a good API:
- 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.
Bloch also cautions that you need at least three different implementations of an API before you can be confident of its soundness.
Conway's Sufficiently Advanced Technologies (S.A.T)
In module design, interface is everything. Going one step beyond this dictum, Damian demonstrates and explains several practical applications of Clarke's Law ("Any sufficiently advanced technology is indistinguishable from magic") by presenting a series of useful modules whose interface is...nothing
-- Advertising of Damian Conway's S.A.T seminar
Like Bloch, Conway proposes seven API design tips, namely:
- Do one thing really well.
- Design by coding.
- Evolve by subtraction.
- Declarative trumps imperative.
- Preserve the metadata.
- Leverage the familiar.
- The best code is no code at all.
Some example S.A.T.-esque modules that achieve a lot with very little interface are: strict, diagnostics, Smart::Comments, Perl6::Say, Perl6::Slurp, IO::Prompt, Getopt::Clade, Getopt::Euclid, Tie::File, Win32::TieRegistry, IO::All.
- Evolve your module's interface by examining its clunky, awkward bits and eliminating them.
- To evolve by subtraction, ask: "what can be removed?", "what can be automated?", "what can be (contextually) inferred?".
- Make common usage the default; allow uncommon usage via optional attributes.
- Two of Perl's most endearing qualities are: it does the hard work for you; it doesn't get in your way. Make those the strengths of your module too. For example, a module with too many methods (aka a fat interface) gets in your way.
- The main idea behind all this is to support coding with no conscious effort.
Perl 6 Design Principles
The linguistic and cognitive principles behind the Perl 6 design are nicely laid out in Perl 6 and Parrot Essentials as follows:
- The waterbed theory of complexity. Languages tend to have similar overall complexity.
- The principle of simplicity. Prefer simple to complex. Beware of false simplicity.
- Huffman coding. Reserve the best shortcuts for commonly used features.
- The principle of adaptability. Design for change and growth.
- The principle of prominence. Mark prominence with stylistic devices.
- The principle of end weight. Place large complex elements at end of sentences.
- The principle of context. Use context to interpret meaning.
- The principle of DWIM. Use programmer's intuitions, don't fight them.
- The principle of reuse. Reuse the same structures in different contexts.
- The principle of distinction. Different things should look different. Distinction and reuse are in constant tension.
- Language cannot be separated from culture.
- The principle of freedom. TMTOWTDI.
- The principle of borrowing. Borrowing is common in natural and computer languages.
API Design Checklist
After all that, here's my attempt at an API design checklist:
- Make it easy to use correctly, hard to use incorrectly. To illustrate, Scott Meyers gives a cute example of a Date class with constructor Date(int month, int day, int year), making it easy to use incorrectly (i.e. to get the parameter order wrong). Another illustration is the PBP guideline: "Use a hash of named arguments for any subroutine that has more than three parameters", which works well because humans are better at remembering names than orderings.
- "Play test" your API from different perspectives: newbie user, expert user, maintenance programmer, support analyst, tester. In the early stages, imagine the perfect interface without worrying about implementation constraints. Design iteratively.
- Names matter. Choose names that are explanatory, consistent, regular.
- Have guiding principles and clear requirements.
- Define sound conceptual models and domain abstractions.
- Consider whether your domain functionality is best delivered as a library (e.g. DBI), an application framework (e.g. Catalyst), or a DSL (e.g. YACC (a translator) or make (an interpreter)).
- Hide implementation details. Reflect the user mental model, not the implementation model. Information hiding: Minimize the exposure of implementation details; provide stable interfaces to protect the remainder of the program from the details of the implementation (which are likely to change). Don't just provide full access to the data used in the implementation.
- Make easy things easy, hard things possible. Huffmanize.
- For non-public APIs, be sufficient, not complete. It is much easier to add a new feature than to remove a mis-feature. When in doubt, leave it out.
- When in doubt, or when the choice is arbitrary, follow the common standard practice or idiom.
- Consider the API from the perspectives of: usability, simplicity, declarativeness, expressiveness, regularity, learnability, extensibility, customizability, testability, supportability, portability, efficiency, scalability, maintainability, interoperability, robustness, type safety, thread-safety/reentrancy, exception-safety, security. Resolve any conflicts between perspectives based on requirements.
- Error handling. Document all errors in the user's dialect. Prefer throwing exceptions to returning special values. Prefer to find errors at compile time rather than run time (e.g. PBP p.182, Named Arguments: pass as a single hash ref not a list of name/value pairs, so that some argument errors are caught at compile time).
- Apply the principle of least astonishment. In particular, choose sensible (expected) and secure defaults.
- Look for ways to eliminate ungainly parts of the API (e.g. the need for repeated boilerplate code when using the API).
- Plan to evolve the API over time.
- Learn from prior art. In particular, avoid repeating interface design mistakes of the past.
Some Examples of Interface Mistakes
For some light relief from all those checklists, from the thousands of interface and API goofs that have been made over the years, I list here a few random ones I remember.
Perl 5's "string eval" and "block eval" is an example of violation of the "principle of distinction", aka "different things should look different" (this has been fixed in Perl 6, where block eval is now spelled try). This example illustrates the importance of choosing good names. Certainly, I've been shocked many times over the years at meeting experienced Perl programmers who were completely unaware that Perl supported exception handling and I feel that would not have happened had Perl sported a try keyword.
Lexical file handles are, for me, the most important feature introduced in Perl 5.6. Those evil old global file handles spawned a host of unfortunate idioms, such as:
In terms of a clear and simple interface to perform a simple task (set autoflush on a file handle), it doesn't get much worse than that, a compelling illustration of why keeping state in global variables is just plain evil. I won't dissect this horror further, other than to exhort you to replace it with:select((select($fh), $|=1));
use IO::Handle; # ... $fh->autoflush();
To round out this section, notice that the ANSI C strtok function has a shocker of an interface: it surprises by writing NULLs into the input string being tokenized; the first call has different semantics to subsequent calls (distinguished by special-case logic on the value of its first parameter); and it stores state between calls so that only one sequence of calls can be active at a time (bad enough in a single-threaded environment, but so intolerable in multi-threaded and signal handler (reentrant) environments that the POSIX threads committee invented a replacement strtok_r function).
The three greatest experts in the human side of interface design that I'm aware of are:
- Donald Norman (Cognitive Psychology). In particular, his classic The Design of Everyday Things is a must read.
- Jakob Nielsen (Web Usability). In particular, his classic Designing Web Usability: The Practice of Simplicity is a must read.
- Larry Wall (Linguistics). In particular, his classic Programming Perl is a must read.
From The Design of Everyday Things, Norman's seven principles of design are:
- Use both knowledge in the world and knowledge in the head.
- Simplify the structure of tasks.
- Make things visible: bridge the gulfs of Execution and Evaluation.
- Get the mappings right.
- Exploit the power of constraints, both natural and artificial.
- Design for error.
- When all else fails, standardize.
Some other principles derived from Norman and Nielsen's works are:
- Affordances. It's vital to ensure the perceived properties of an object match its actual properties. Norman gives a lovely example of a sliding door with a conventional door handle that looks like you should press it down to open, when in fact you must slide it.
- Things that are different should look different. Norman gives an amusing example of two similar switches on an airplane: one adjusted the wing flaps, the other lifted the wheels. As you might expect, there were a number of planes wrecked by pilots inadvertently lifting the wheels while taxiing down the runway.
- Provide a good conceptual model.
- Make things visible.
- Provide natural mappings. A classic example is the humble kitchen stove; with a natural mapping of burners to controls, no learning or remembering is required and there is no need for labels on the stove controls.
- Provide feedback.
- Leverage the power of constraints.
- Make it easy to remember.
- Prevent, detect and forgive errors.
- Make dangerous operations difficult to use. For example, child-safe gates to swimming pools, child-proof tops on bottles of dangerous chemicals.
- Usability before aesthetics.
- Go with the existing standard unless you have a significant improvement; especially go with the existing standard when the choice is arbitrary. For example, screw threads tighten clockwise, the hot water faucet is on the left, clocks turn clockwise (the backwards clock notwithstanding).
- Beware of creeping featurism.
- Make it explorable and discoverable.
- Make the computer invisible.
GUI Design Checklist
Here's a brief GUI design checklist. For more details, see the most excellent About Face 2.0 by Alan Cooper.
- Have clear objectives and guiding principles.
- Define personas; design to satisfy their goals.
- Adopt the user's perspective. Involve users in design. Perform usability tests. Give the user control. Make it configurable. Design iteratively.
- GUIs should reflect the user mental model, not the implementation model. Hide implementation details.
- Communicate actions to user. Provide feedback. Anticipate errors. Forgive errors. Offer warnings.
- Be clear and specific in what an option will achieve: for example, use verbs that indicate the action that will follow on a choice.
- Cater for both novice and expert. For novice: easy-to-learn, discoverable, tips, help. For expert: efficiency, flexibility, shortcuts, customizability. Optimize for intermediates.
- Fit the appearance and behavior of the UI to the environment/process/audience.
- Follow basic design principles: contrast (obviousness), repetition (consistency), alignment (appearance), proximity (grouping), balance (stability).
- Keep interfaces simple, natural, consistent, attractive. Try to limit to seven simultaneous concepts.
- Use real-world metaphors.
- Ask forgiveness, not permission. Make all actions reversible.
- Eliminate excise.
- Be polite; remember what the user entered last time.
- Avoid dialog boxes as much as possible; don't use them to report normalcy.
- Provide "wizards" for complex procedural tasks.
- How to Design a Good API and Why it Matters by Joshua Bloch
- The 7 Principles of API Design, Damian Conway talk (based on S.A.T)
- Quicktime movie of YAPC::Asia 2006 version of Damian's S.A.T talk
- Ruby RPA GoodApiDesign
- Perl 6 and Parrot Essentials. Chapter 3 details the cognitive and linguistic principles behind Perl 6.
- Perl Best Practices. Interface design tips are spread across a number of chapters. Chapter 17 (Modules) has an excellent "Design the module's interface first" tip, advising how to go about it (play testing and so on).
- Large-Scale C++ Software Design by John Lakos. Chapter 4 has excellent coverage of DFT (Design for Testability) being perhaps the first to point out the dangers of acyclic dependencies and the importance of "levelizing" large designs.
- The Practice of Programming by Kernighan and Pike. Chapter 4 (Interface) discusses how to design interfaces, though somewhat C/C++ specific.
- Design Patterns by Gamma et al. A number of these patterns are interface-related, for example: Adapter, Facade, Abstract Factory, and many others.
- The Art of Unix Programming by Eric Raymond. Chapter 11 (Interfaces) discusses User-Interface Design Patterns in the Unix environment.
- Interface Oriented Design by Ken Pugh. This new book will be released this month.
- Code Complete 2 by Steve McConnell
- The Design of Everyday Things by Donald Norman. Examines user interface design from the point of view of cognitive psychology.
- About Face 2.0 by Alan Cooper. Ideas and principles of GUI design. A nice summary of GUI design principles is given in Appendix A (Axioms) and Appendix B (Design Tips).
- The Humane Interface by Jef Raskin
- Designing Web Usability: The Practice of Simplicity by Jakob Nielsen
- Donald Norman
- Jakob Nielsen
- Alan Cooper
- Jef Raskin
- API wikipedia
- Human factors wikipedia
- Ergonomics wikipedia
- The Principle of Least Astonishment wikipedia
- Cognitive Psychology wikipedia
- Cognitive Science wikipedia
- Human-computer interaction wikipedia
- Affordance wikipedia
- Separation of concerns
- The Most Important Design Guideline? by Scott Meyers
- Building user interfaces for object-oriented systems by Allen Holub
- Why getter and setter methods are evil by Allen Holub
- API Design with Java by Bill Venners. This book is an unfinished work in progress.
- cat -v considered harmful by Rob Pike
- You and your APIs can NEVER go home by Alias
- The Rise of Worse is Better by Richard Gabriel
- Ugly Perl: A lesson in the importance of API design by David McLaughlin
- Ugly Perl: A Lesson in the Importance of API Design by chromatic
Some References Added Later
- The five basic principles of design
- gui design best practices (stack overflow)
- usability guidelines (stack overflow)
- Don't make me think book by Steve Krug
- User Interface Design for Programmers book by Joel Spolsky
Some Related Perl Monk Nodes
- Ingy's "Swiss Army Light Sabre" - or, "how do you design your APIs?" (discusses design of Ingy's IO::All module)
- Often Overlooked OO Programming Guidelines
- Re (tilly) 1: When do you function?
- Writing Solid CPAN Modules
- On Coding Standards and Code Reviews
- GUI Design/Organization - Recommended Practices?
- Re^4: Making 'all' the attributes read only by default (Moo) (interface)
Updated 10-jun: Minor wording improvements, expansion of "Conway's S.A.T" (thanks drbean), "Human Aspects", "GUI Design Checklist", and "References" sections. Updated 30-jan-2011: Added modernperl Ugly Perl reference.