How not to maintain an API

So I’ve been working on my graphing code for EDB.  I was eventually able create a Qt widget which natively  renders a graphviz graph layout. It actually works quite nicely, you can create an ordinary graphviz graph either in memory or from a file like usual.  The code can simply create a “GraphWidget” and the code will display the graph perfectly (there are some constructs which it doesn’t support, but the basics are there) with nice things such as zooming and rotating.

All of this works great, except for the fact that graphviz decided to change some of the structures used to represent the layed out graph.

The most annoying issue I found is that the older versions of the graphviz headers did not have any defines for versioning. This means that there is no good way to detect at compile time which version you are trying to compile against. Later one was introduced, which means that you can coarsely determine if you are compiling against a version before or after that macro was introduced. But this isn’t really good enough given than there have been several changes over time before and after this macro was introduced.

So rule #1: If you plan to change anything in your publicly installed headers, ever, have a version macro which is unique for each and every version of structures and API.

Of course, the fun doesn’t stop there, otherwise I wouldn’t have a whole lot to write about here. In addition to that, they decided to make the version macro they did create resolve to a string! This is great and all for displaying the version to the user. But is not easily comparable at compile time at all. The most creative solution I can think of is to have a configure script which will emit run a program  which includes the header and outputs the version if available, then finally the configure script will parse the result and produce a compile time comparable macro. This is some of what the autotools scripts do to produce things like config.h. But is less than fun to deal with.

Rule #2: Make the version macro numeric, and encode things in a way that x >= y always means that x is newer.

Something like this feels ideal.
#define VERSION 0x00010203 /* version 1.2.3 */
Here I am suggesting to use a 32-bit constant for the version, dividing on byte boundaries for trivial decoding and ease of human readability.  There is no need to stick to the tradition versioning schemes either. Lately, I’ve been preferring using the year and release number like this:
#define VERSION 0x00200901 /* version 2009.1 */
Both work equally well. But wait there is more nonsense! The graphviz people decided to use macros to access the members of the primary structures (node, edge, graph). This way they can change the implementation details of these structures but the code using these structures can remain the same. This was a good idea. Unfortunately, they didn’t do it for all of the structures. Only those three. But there are a whole bunch of structures representing text labels, coordinates and other fun stuff which didn’t use this system.

Rule #3: Be consistent with your API.

Finally, they used to have both floating point and non floating point versions of certain data members. At some point they decided to switch to only floating point. The problem is that some things changed type, but not the name. So if I wanted to store the value of one of these, I can’t conveniently. For example:
struct T { point *pos; };
became
struct T { pointf *pos; };
This makes code such as this unfortunately broken across versions.
point *p = x.pos;
If I had a usable version macro, I could make the type depend on the version. Even worse some things changed names and types. For example:
ND_coord_i /* returns "struct point " */
was replaced by:
ND_coord /* returns "struct pointf" */
The former no longer exists. At least I can test for the existence of the ND_coord macros to figure out which to use, but the net result is messier code.

Rule #4: Do everything possible to avoid changing datatypes of structure members and return types of functions. It makes storing the result ugly and convoluted.

One could argue that not all of the structures and functions are part of there public API. After all, it is pretty rare that someone implements there own rendering engine for something as big, complex as graphviz. But these structures are in the headers that every distribution installs into /usr/include/graphviz/.

Rule #5: If it is in a header which gets installed for the user, it is part of the API. If you have internal details you wish to hide use the PIMPL pattern.

So the end of the story is that I’ve got my Qt based graphing widget I sought after. But the deployment is less than fun. Because I distribute sources I need to test that it compiles on all sorts of distributions. All of this could have been so much simpler if they just planned ahead and had a version friendly API.

3 thoughts on “How not to maintain an API

  1. bingi

    hi i wanted to use your QHexView widget but after changing C to QVector and addres_t to uint32 as you said i can’t get my data displayed. Could you give me a step-by-step guide to opening the file and getting it displayed in the widget?

    thankx.

  2. Evan Teran Post author

    The QHexView widget has slowly gotten a bit more tied to the rest of the code base a bit. I’ll try to work up a standalone example for you.

Leave a Reply

Your email address will not be published. Required fields are marked *