Some thoughts about portability

I was reading through Kernigan and Pike's The Practice of Programming this weekend. It's a good book-- although, to be honest, I was already aware of a lot of the things they talked about.

One little gem I found in the book was a comment about portability. As the book points out, there are two ways you can write portable code, which they call the "union" approach and the "intersection" approach.

The union approach is to write compatibility shims over the platform-specific interfaces that each platform gives you. The intersection approach is to use only APIs that are provided by all the platforms you intend to support.

A lot of programmers intuitively reach for the union approach. The platform-specific APIs are often nicer and more powerful than the cross-platform ones. However, Kernighan and Pike favor the intersection approach. As they point out, it results in fewer lines of code, which translates into less that you need to test.

I have to admit, they are probably right. If you can, use the intersection approach to portability, and save yourself a lot of grief down the road.

The Third Way

Another approach is to use a library which hides the platform-specific stuff from you. One example of this is libev. Basically, if you have to deal with lots and lots of file descriptors in C, libev is what you want to use. Unlike epoll, which is Linux-specific, or kqueue, which is BSD-specific, libev works across many different platforms.

Of course, you have to be judicious about adding a new library to your project. In general, I tend to reject libraries that introduce a huge set of unecessary dependencies, are inefficient, or try to solve too many unrelated problems. The ideal library just does one thing, and does it very well.

Even well-written libraries have limitations. For example, libev doesn't perform very well on Microsoft Windows. This is not really the fault of the author, but just a reflection of the fact that file descriptors are second-class citizens on Windows. In order to get optimal performance on Windows, you would have to use completion ports, which have a totally different API.

So in summary: don't be too quick to write compatibility shims. First, see if you can make do with the APIs that are available to you on every platform. If you do decide you need the shims after all, see if anyone else has produced a good library to fill that role.