Tell us what to do, dammit! Why text-based protocols hurt your design

Can have it all: flexibility with simplicity

By Ran Arad  |  February 4th, 2008  |  Filed under SDKs

API design is tricky. You have to constantly answer two opposite questions:

  1. How can I let the user do anything he would like to do?
  2. How can I make things simple to use?

Flexibility causes complexity, simplicity causes rigidness, right?

Wrong.

Take the void pointer for example. The void pointer can point to anything, so it’s flexible, and it is very simple as well. The same can be said for Java interfaces or C++ container classes. Or take to following API pair:

SetParam(
    IN  enumParamType 	t,
    IN 	DWORD           wVal,
    IN 	void *          pVal);
GetParam(
    IN    enumParamType t,
    INOUT DWORD *       wVal,
    OUT   void *        pVal);

The “SetParam” function takes a parameter type, and interprets the parameter wVal and pVal according to it: it may use only wVal for integer or enumerated parameters, it may cast and use only pVal for structure or pointer parameters, and it may use both for buffer or string parameters, where wVal indicates the size of pVal. The “GetParam” function is very similar, it either sets wVal, pVal, or both, using wVal to indicate the available space and setting it to the space used.

This function pair is very flexible, it can handle anything you want. It is also simple; just call these functions for all your parameter needs. So what’s the cost? The cost is clarity. It is the same with the void pointer: it’s flexible, it’s simple, but it’s never clear to the user exactly what’s in it. Java interfaces obscure the implementing class, and container classes inject a lot of type checking into the code just to see what the real object is. So we have, in fact, three questions:

  1. How can I let the user do anything he would like to do?
  2. How can I make things simple to use?And another question -
  3. How can I write functions with clear usage?

Even writing two sets of APIs, one simple and one flexible, does not solve this problem: the user may not be sure which API he should use and when. It seems to me like you can have two out of three with a little effort, but having all three is more of an art than a craft. Here are a 8 useful tips I gathered over the years:

1. Split the API into levels
The “Transaction” object may have a simple API, and the “Message” object has the complex API, and the user may work on the higher, “Transaction Level”, or may go down to the lower “Message” level for some nuts-and-bolts work, and he’ll always know which set of APIs he’s now supposed to work with.

2. Use complex names for the simple APIs and simple names for the flexible APIs
For instance, if you have callInit(), callSetRemote(), callDial(), and one function to simplify the process, name it callInitSetRemoteAndDial(), so that the user will have a clear understanding of which APIs were simplified.

3. Use the same naming conventions on all objects
For example: construct, destruct, start, stop, get, set, etc. The APIs do not get any simpler, but the user will have an easier time getting used to working with your APIs.

4. Insert validity checks into the code
When using APIs which are both simple and flexible, do a lot of checks and tests on the input, and print friendly messages if a mistake is detected. Again, the API does not get any clearer, just a little less frustrating.

5. Mark your generic APIs
Use some keyword like “Generic” or “Ext” to mark APIs that do the same as the APIs without that keyword, only in a more flexible, generic or extended way. Make that keyword well known.

6. Always try to have “default values”
For instance, NULL for pointers and -1 for integers, that when passed will mean some sort of default choice, or no choice at all. That way, if the user does not know what to pass in a certain parameter, he can use the default value, and later go back and change it to a value better suiting his needs.

7. Hint on the data’s content
There is a way to use at “unknown data” type while still hinting at the contents: making empty structures as type markers, and using them each time this type of “unknown data” is referenced:

typedef struct prot_user_data_t{
    void * unknown;
} prot_user_data;

The same can be done with empty interfaces in Java.

8. Use structures instead of parameter lists
The main difference between structures and parameter lists is that structures can be extended without changing the API prototype:

typedef struct fooParams_t{
    int one;
    int two;
    int bUseExtendedParameters1;
    char * three;
    int * four;
    int bUseExtendedParameters2;
} fooParams;

Users will have to set the final “use extended parameters” member to zero for forwards compatibility, but that’s a small price to pay for code clarity.



1 Comment
Add your own   

  • 1. Memo to API Service Providers | Code of Contacthttp://blog.radvision.com/codeofcontact/2008/04/09/gabe-wachob-memo-to-api-service-providers/  |  April 10th, 2008 at 11:25 am

    […] Wachob provides tips for API developers. In contrast to my low-level approach, Wachob looks at API design from a holistic perspective. Although he speaks on API design for web […]

Leave a Comment

Required

Required, hidden

:) :-S (H) :cry: 8-| :@ (!) :-D (?) :$ 8-) :-( :-) ;-)

Notify me of followup comments via e-mail

Trackback this post  |  Subscribe to the comments via RSS Feed


Subscribe

Subscribe via RSS
Subscribe via email:

Interactive Video Platform