[ Home | Library | Contents ]


[ Prev | Next ]



by Matt Slot

Designing a Good API (or Promises, Promises)

All software is built upon other people's work, whether it's a framework, the operating system, or right to the hardware. A well-designed foundation can speed the development process and improve the readability of code that relies on it. While the job of the application programmer involves mastering numerous libraries and stitching them together into a working product, the library programmer must strike a balance between functionality and elegance.

Applications should be written in small, functional chunks which communicate with each other (or with the foundation) through "application programmer interfaces". An API is both a promise to external developers (who will build on these services) and a goal for the library developer (who must deliver everything he promises).

Software that encapsulates specific operations into libraries and uses the provided APIs is much easier to design, improve, and maintain. The application programmer treats the internals as a black box, using the services he needs and ignoring the details. The library programmer can add new features under the hood (perhaps involving a signficant rewrite of the library), and post the updated binary when it is ready. And when bugs surface, responsibility will be easy to determine -- either they are inside or outside the box.

Designing an API for Others

As I mentioned, the first reason to write an API is to simplify the work of the application programmer. The less he has to worry about some small but involved piece of code, the more time he can spend in meetings or adding other features. It's not only about simplifying the design, but handing off technical issues (like portability or assembly-optimized loops) that need to be researched and solved.

All the symbols used in an API (constants, structures, functions) should be located in a single header file and library, and should be prefixed with an abbreviation based on the library they come from. This makes it easy to see a name within application code and know exactly where it comes from.

A function should have a very specific purpose (like reading a file or playing an animation), unless they are specifically written as part of a high-level interface (find, load, play, and release a multimedia object). Functions should also be named consistently, especially function pairs: ObjectCreate() should always have an ObjectDestroy(), DataPop() should follow DataPush().

An often overlooked part of designing an API assigning responsibility for memory allocation and deallocation: how and when should the caller dispose an object that was created by the library? This is difficult to predict without knowing which memory routines the object was allocated with (new, malloc), whether it is being reference-counted, or has internal structure.

For this reason, I strongly recommend dividing library data into 2 groups: parameter blocks of explicit data and opaque data structures. The data passed in a parameter block is always the responsibility of the caller, and an opaque object is always the responsibility of the library. When the library provides functions to create, manipulate, and examine an opaque object, it's much harder for the application to misuse it.

To be helpful, a library can also provide high-level routines that group many complicated operations into a single wrapper functions. This lets the application programmer decide how much control he wants over the operation, balancing ease of use (three lines of code) against writing specialized code that does almost the same thing (for power programmers).

A library writer should compile two implementations of his library: a debug version which performs perform parameter and state validation and debugging symbols, and a release version which is optimized for speed and size. This protects the library developer by catching common mistakes made by other developers, and lets everyone select the right build for either internal use or public release.

An intuitive and self-documenting API is a benefit to the author and his audience alike, because it's easier to learn and requires less maintenance.

Designing an API for Yourself

The other reason to write an API is that, once everyone has agreed upon it, you have a formal goal to meet. There shouldn't be any confusion about what you promised to do, because if it's not in the specification then it's not on your to-do list. You can then do whatever it takes to meet your promises, using whatever resources you need, because all of the code will be hidden behind the API and people will only see those functions you give them.

The first rule for designing an API is to keep it focused. A library that tries to do too much is difficult to write, learn, and maintain. The next goal is to keep it portable, by avoiding language-specific constructs and platform-specific extensions. I strongly recommend writing libraries in straight ANSI C.

Another virtue of libraries is the ability to run and test them outside a client application, which makes for easier (and more complete) testing. By writing a quick console application, you can isolate specific features and step through them in the debugger. It's also easier for the application writer to experiment with the library before integrating it into an existing code base.

Finally, and most importantly, a library should always detect errors in its operation and return an appropriate error code. Failure is always an option, especially when performing memory- or file-intensive tasks. This gives the application a chance to detect and respond to error codes (and developers who don't rigorously check for errors should be take out back and thrashed).

Also, after detecting an error but before returning it, the library should make every effort to return to a well-defined state. This makes it easier for the application to watch for errors, and perform the appropriate cleanup.

In the end, writing a library is both easier and more difficult than shipping a whole product. It's easier in that your audience are also developers who can give you good feedback and debugging advice, but it's difficult for the same reason -- developers are picky enough to want code that works right the first time and every time.

Designing an API is a delicate balance between features and workload, between promising too little and too much. They are a good way to divide the work of application development into manageable chunks, and to simplify software design through layers.

Matt Slot, Bitwise Operator


[ Prev | Home | Library | Contents | Next ]

Copyright ©1995-9 by Ambrosia Software, Inc. - All rights reserved