An Interface is More than Names and Arguments


If you claim to conform to an interface, it is not enough to follow the syntax, you must also be careful about the semantics.

I saw an example of this the other day, when I came across a custom vector class (different from std::vector) used for numerics. It provided much of the same interface provided by std::vector, among which, resize().

It looked something like this:

template <class T>
class Vec {
  (...)
  //! STL vector interface
  void resize(int size, T value);
}

That was however all the documentation that was available. Since it claimed to confirm to the stl::vector interface, I naturally assumed it meant the same thing.

Here is the documentation for std::vector::resize(size_type sz, T c):

If sz is smaller than the current vector size, the content is reduced to its first sz elements, the rest being dropped.

If sz is greater than the current vector size, the content is expanded by inserting at the end as many copies of c as needed to reach a size of sz elements.

Just to be sure, I had a look at the implementation of Vec::resize(int size, T value), and what do you know:

template <class T>
  //What was going on, conceptually:
  void Vec::resize(int size, T value) {
    _v.resize(0);
    _v.assign(size, value);
  }

See the difference? Vec::resize() drops and initializes all elements, whereas vector::resize() only initializes any extra elements. This might be dangerous if you for instance port from Vec to vector and rely on all elements to be reinitialized by resize(), or if you port from vector to Vec and rely on resize() to keep your old values.

So when claiming to confirm to an interface, make sure to not only adhere to the signature, but also to the semantics.

Make APIs hard to use incorrectly


So far I have written a couple of posts about bad practices I have found in other peoples code. Todays blunder is however one I was about to commit myself. A library I was extending had this (as usual, simplified) function:

void Library::write(const char* c, int i);

This was however not always a safe thing to do, so I found all the callers had wrapped it with almost identical code to protect it. I decided to add a method to the library, doing exactly the same thing, but with the added safety-wrapping. Fortunately, I didn’t even need to change the signature, so I could easily make a new function

void Library::writeSafely(const char* c, int i) {
    safety();
    write(c,i);
    more_safety();
}

and change all the callers to use this new function. Luckily, we still had control of all the users of this API, so this was no big deal. Can you spot my mistake?

The title is kind of revealing, hinting at the API design best practice “Make Interfaces Easy to Use Correctly and Hard to Use Incorrectly”, which for instance Scott Meyers mentions both in 97 Things Every Programmer Should Know (Chapter 55) and Effective C++ (Item 18).

If you need to provide both a safe and an unsafe version of a function, make the safe version the easiest to use. In this case, instead of just adding the new function, I renamed the old function write_raw(), and made a new, safe function write() that wrapped write_raw().

In a few months, someone unfamiliar with this API will come along looking to write something, and will most certainly use write() after skimming through the API. He will now be defaulting to the safe version, if he wants raw access he will need to dig a bit further.

And that someone might just as well be myself.