by Matt Slot
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.
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.
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