A stack of techical books

"There's No Silver Bullet"

We believe in methods that amplify learning, communication and collaborative teamwork. Methods that help us do our job better and faster.

In this section our experts share their insights and experience.

Articles

Reaktor Twitter

Basics of API Design

API design is one of the most important skills for a programmer

Every software component has an interface, regardless of whether the programming language has an explicit syntax for that concept. A good interface exposes only those parts of the implementation which are necessary for clients – everything else is hidden. Changes in the component’s internal implementation don’t require changes in other components. It can be used as a “black box” without knowing anything about the internals.

Choosing the level of abstraction is an art

Encapsulation and data hiding are universally known basic practices of object oriented programming. Unfortunately they are often discussed only in the context of a single class or module. Choosing what to expose and what to hide is usually trivial in those cases.

The task gets harder when you have a program with millions of lines of code and it has to be partitioned into modular components which communicate with each other through well defined interfaces. “Component” here means any semi-independent part of the program; it can consist of hundreds of classes or files.

It is difficult to find the correct level of abstraction. If too many details are exposed, it is impossible to learn and understand the whole. On the other hand, if the level of abstraction is too high, things can become too generic for practical use.

This problem is not specific to programming. The same issues are encountered in requirements gathering and documentation, and also in many topics outside the software industry, like education and communication. Choosing the right level of abstraction can only be learned by doing.

Although there are no shortcuts to mastering this skill, some rules of thumb can be useful. I will show some examples of them next.

Java is used in this article’s examples, but the same principles apply to any programming language and even to language-independent integration points between subsystems.

Prevent illegal states

It should be impossible to initialize a component to an illegal state. If that’s not feasible, at least it should be made as difficult as possible.

This is bad:

Component c = new Component();
// If we invoke some other method of c
// than initialize() here, we get an error,
// because the component is not yet initialized.
c.initialize(new Configuration());
// Only now the component can be used.

This is better:

Component c = new Component(new Configuration());
// Now component c can be used immediately.

The same principle applies to all operations, not only initialization. It should be impossible to move a component into an invalid state. A good API is as easy as possible to use correctly, and as hard as possible to use incorrectly.

Make operations atomic

The operations provided by a component should be atomic. Otherwise the client must remember to invoke methods in the correct order, and this makes it possible to invoke them in the wrong order.

This is bad:

// The client must remember to begin a transaction...
c.beginTransaction();
// Execute the actual operation, and...
c.doSomething();
// … also remember to commit the transaction.
c.commit();
// BTW, what happens if doSomething() fails for some reason?

This is better:

c.doInTransaction(new Callback() {
   @Override
   public void execute(Component c) {
       // Method doInTransaction() begins
       // and commits the transaction,
       // and handles recovery in error cases.
       c.doSomething();
   }
});

The same principle applies here as in the previous example: make it easy to do the right thing and hard to do the wrong thing.

Hide complexity

The public interface of a component must be as simple as possible, but not simpler. If you have to make a choice between a simple interface and a complex implementation, or a complex interface and a simple implementation, the former option should always win. A wristwatch is an extremely complex device, but still it is very easy to check the time from it. A software component should be like that: easy to use even if it is hard to implement.

There must be nothing unnecessary in an API. For example, the commonly used JavaBeans model where fields are exposed via get/set methods, is quite bad, because it bloats the interface with details which should be hidden.

Every API is a user interface

API design is a form of user interface design; every API is a user interface for the programmer who uses it. The same principles which apply to GUI design also apply to API design: the end result should be intuitive and accessible – easy to learn – and it should minimize the amount of unnecessary work for the user.

A bad programmer does not care about the quality of her code as long as it “works”. An average programmer tries to produce clean code, but she does not yet understand in which parts of the code some complexity is acceptable and where it is not. A good programmer can partition her code into public interfaces (which must be kept clean) and internal implementation (where complexity is sometimes unavoidable). She strives for simple and elegant code everywhere, but when ugliness is unavoidable, she knows where to hide it.

Ville Peurala closeup

Ville Peurala, Chief Architect

Ville Peurala has worked in software projects for over ten years. His experience is that the importance of API design quality grows exponentially when projects get bigger. Unnecessary complexity tends to spread and grow, and if it is not fought against all the time, it can slowly make the whole codebase unmaintainable.