Sponsored Link •
This article suggests that good API designs happen when designers think of objects as machines, classes and interfaces as blueprints for those machines, and client programmers as users.
Objects are invisible machines that programmers use as tools. When you design an object, you design a machine for programmers. I feel thinking of objects as machines is helpful, because it encourages you to focus not only on functionality when you design objects, but also on usability.
The two main qualities I look for in any machine are functionality and usability. If I buy a new cyclometer for my bicycle, for example, I want that cyclometer to work well—to reliably keep accurate time, distance, and speed records. But I also want it to be easy to use. I want services I request often, such as switching the display between distance, time of day, and speed, to be quick and easy to access. I don't mind if less-used services, such as setting my tire size or the current time, are more difficult to access. But in no case do I want to invest more than a few minutes of my time pressing buttons or consulting the instruction booklet to access any functionality offered by my cyclometer.
Similarly, if I instantiate a class in an API, I want the resulting object to work well. I want the object to do what it promises to do, in an efficient manner, every time I ask it. But I also want the object to be easy to use. I want its interface to allow me, with no more than a few minutes searching through the API documentation, to figure out how to make the object perform the desired service.
People build machines to provide services to other people. By machine, I mean any kind of tool made by people for other people to use. Pencils, hammers, chairs, stamp dispensers, televisions, bicycles, buildings, and space shuttles are all machines in this sense. So are software applications such as word processors and accounting programs. And so are objects. Objects provide services to people who happen to be programmers.
Given that machines are created to provide services to people, every machine has a means by which people can use it, its interface. A hammer's interface is its handle. A stamp dispenser's interface is its buttons, knobs, and displays. A word processor's interface is the menus, toolbars, and dialog boxes of its graphical user interface (GUI).
An object's interface consists of some combination of constructors, fields (usually constants), and methods. These are the buttons, knobs, and displays that allow people to use the object. In the case of objects, the users are a particular kind of people, programmers, who are trained in the use of constructors, fields, and methods.
A class is not a machine. A class is a blueprint for a machine. The object is the machine. To create an object, you feed a class, the blueprint, to a computer. The computer creates the object from the blueprint automatically. Because objects exist only inside computers, people never use objects directly. Instead, people use objects indirectly by writing code. This is why object users are always programmers—to use an object, you must write code. Your code uses the object directly on your behalf.
To actually provide a service to people, a machine must have more than just an interface. It must have an implementation, a mechanism by which the machine actually provides the service. One of the fundamental notions of object oriented programming is a strong separation of interface and implementation. You can see this separation in many traditional machines as well.
One of the main reasons to separate interface and implementation is to hide complexity from the user. If you peel the back off a stamp dispenser, you'll find a nest of wires, circuit boards, chips, and motors. That is the implementation of the stamp dispenser. Normally, all that complexity is hidden from the user. The wires and circuit boards of the stamp dispenser are encased in a box that exposes only buttons, displays, a coin slot, and a change return lever. The complex implementation is hidden. A simpler interface is exposed.
An interface can be simpler than its implementation, because the interface can operate at a higher level of abstraction. If you insert 20 cents into a stamp dispenser and push the button underneath a 20 cent stamp, you expect a 20 cent stamp to come out. As a user, you don't need to understand how that will happen, only what will happen—that you'll get a 20 cent stamp. You don't need to know the details of all the activities inside the stamp dispenser. You need only think in far simpler terms of "a 20 cent stamp will come out."
Raising the level of abstraction in the interfaces of objects you design is an important way to help client programmers, your users, deal with the overall complexity of their systems. The interfaces say what will happen. The implementations say how. Programmers can think about the complex behavior of their systems in terms of its object parts and the interaction between them. The more that programmers can ignore implementation details of objects—the more the object interfaces abstract those details away—the more overall system complexity they can deal with.
The interface to any machine has two parts: shape and semantics. By shape, I mean the way in which the various elements of the interface present themselves to the user. By semantics, I mean the meaning of the various elements of the interface. It is not enough that users can see all the knobs and buttons in an interface: the shape. They also need to know what the knobs and buttons mean: the semantics. To use a machine effectively and safely, users need some understanding of the machine. They need to become familiar with both the shape of its human interface and its semantics. They need to build a mental model of what the machine does when they interact with the various elements of its shape.
One concern the designers of any machine should have is communicating semantics to users. Users need to learn what service the machine will provide when they push a button or turn a knob. They need to learn any rules surrounding the use of the buttons and knobs. If a user must flip a switch hidden underneath the steering wheel of her new car before she can turn the key and start the engine, she needs to know that. If the little red button next to the cigarette lighter in her new car causes the front passenger to be ejected out the sun roof, she need to know that too. Communicating semantics is not something to think about after you complete a design. It should be one of your main concerns while you design.
Three main ways to communicate semantics to users are shape, manuals, and training. The most important way to communicate the semantics of an interface is via the shape itself. To the extent possible, users should be able to intuit semantics from the look, feel, and labels of the interface elements. Semantics that are difficult to communicate via the shape, or dangerous or costly to learn by trial and error, can be explained in writing via a manual or in person via a training class. The reason that shape is the most important strategy for communicating semantics to users is because shape is the way most users figure out how to use most machines. Even if someone does read the manual or take a class at some point, they will often have forgotten what they learned the next time they use the machine. The shape is the one thing you know is in front of a user every time they use the machine, because the shape is how they use the machine.
An API's shape is the names of its types, fields and methods, and their modifiers and parameters. An API's semantics is the meanings of the pieces that make up the shape: the concepts represented by the types, what the methods do when you invoke them, what the values of fields mean, and so on. Why is it important to design types and methods that are cohesive, and to choose names that are pithy and descriptive? Because that's how you enable users to intuit the semantics of your API from its shape. At its heart, this aspect of design is about human to human communication. As the designer, you must transfer your understanding of the API's semantics to your users.
I believe that it is important when designing any machine, including objects, to think about the user. Your job as designer is to provide value for users—to give the user a machine that is effective, reliable, efficient, and easy to use.
Perhaps the most important goal for your machine is that it should effectively solve the user's actual problem at hand. If I need to pound a nail into wood, and you hand me a screw driver, I'm liable to either put my eye out trying to pound the nail with the handle of the screwdriver, or just not use your tool. If a user is getting their problem solved, they will put up with a lot of unreliability and poor usability. If you don't solve their problem, they won't use your machine. The first challenge of design, therefore, is in figuring out what the user really needs and providing that.
Reliability is also important. If stamp machine users insert coins and push the button next to an Elvis Presley stamp, they want an Elvis Presley stamp to come out. If sometimes when a user pushes the Elvis Presley button, a Tiny Tim stamp comes out instead, users will be frustrated. Users also care about performance. If pushing the button next to a Elvis Presley stamp does indeed always make an Elvis Presley stamp come out, but it takes 30 minutes for each Elvis Presley stamp to make its way out, users will be frustrated.
But in addition to providing the user with a machine that is effective, reliabile, and efficient, your users want you to provide a machine that is easy to learn and a joy to use. Users want to intuit semantics from shape. Users want supplemental documentation from which they can quickly get answers to their questions. Users want machines to be unsurprising, to fit with their previous experience with similar machines. Users want common tasks to be easy and obvious, rare tasks to be possible. Users want doing the right thing to be effortless, making mistakes to be difficult. Users want machines to be usable.
For many years now I have been observing the API design processes in which I've participated in an effort to gain insights into the ways in which people come up with good designs. And although the teams I've designed with have certainly spent time and energy worrying about effectiveness, reliability and efficiency, I have found that ultimately most design conversations are about usability. We ask, "Which types best communicate the concepts of this API? Which methods pay for their complexity by providing sufficiently valuable functionality or convenience for the user? Are tasks we expect users to commonly perform with our API easy to do via our interface? Are mistakes difficult to make? Can the public interface be made simpler, even if that makes the implementation more complex?" These are all usability questions.
I have found it useful to think of objects as machines, the classes and interfaces of APIs as blueprints for those machines, and client programmers as users. Good API designs happen when designers have a user-oriented focus and a philosophy that values both the functionality and the usability of the resulting API.
This article is taken from a design book that I've been working on—and not working on—for almost five years. The book has had at least six titles, including Better Java, Flexible Java, the Precise Object, Object Design, API Design, and it's current title: People-Oriented API Design. The project had been on indefinite hold for quite a while, but this morning awoke with a new vision for the book. So once again, at least for now, the project continues. From time to time, I plan to publish more articles on Artima.com based on material from this book project.
Table of contents with links to some material of People-Oriented API Design:
Interview with Ken Arnold Perfection and Simplicity:
Are Programmer's People? And if So, What to do About it., a post in Ken Arnold's weblog:
Donald Norman's book, The Design of Everyday Things, a excellent book about people-oriented design in general, is available on Amazon.com at: