API Design Rule: First, Don't Lie

Today's post is about a very simple rule for API design: don't lie. This seems relatively straightforward, but there are a number of ways in which APIs may end up lying or misleading their consumers.

What do I mean by lying? The obvious case is an API that doesn't do what it says it will. Sometimes you would think of this as a mis-named API.

public static void WriteToFile(Customer customer, string path)
{
  // Actually, write the orders of the customer to the file.
  // ...
}

The next case is an API with fine-print. These are APIs that have special cases that are not obvious and can easily trip you up if you don't understand them in depth. There are tons of these in the world, and I have been guilty of creating them just as much as the next developer - hopefully not as horrible as the following example.

public static void WriteToFile(Customer customer, string path)
{
  // Write the customer but only if we have permissions to the
// path; otherwise write to a temp file and set an environment
// variable to indicate we did this.
// ...
}

Another case is the API that does what you ask it to do, but also goes and does a bunch of things you didn't ask it to as well and that require more knowledge to understand what exactly is happening. The higher-level your API is, the more likely it is that a method name can't fully describe what's about to happen.

public static void WriteToFile(Customer customer, string path)
{
  // Write the customer to the file, and if it succeeds, compress
// the file and delete any temporary files that may have been
// left over from interrupted file writes in the past.
// ...
}

Finally, there are APIs that I call "teasing APIs". They promise more than they deliver; an example might be an API that takes an Expression argument, but only handles ConstantExpression values, for example.

public static void WriteToFile(Customer customer, string path)
{
  // Write the customer to the file but only handling the case where
// the customer is not a SpecialCustomer subtype; if so, throw
// an exception.
// ...
}

To recap and explain a bit why lying is bad.

  • Name your methods for what they do. Otherwise the code that uses it will be misleading and hard to follow, and readers will need to know specifically not to trust the method's name. Can't think of any reasons now why one would do this.
  • Do what you say you do. A method should keep its promise. If the method sometimes behaves differently, the consuming code needs to account for this subtlety, and if you forget to test for the special cases, you may very well forget to do so before releasing your product. Fine-print is sometimes unavoidable but should be fought hard.
  • Don't do what don't say you will. Keep your methods focused and don't "detour" to do related work, otherwise the effects may range from wasted effort to interfering with other code.
  • Don't tease. If you only handle specific cases of some problem, be explicit about that in your names and signatures. Otherwise you run the risk of testing only for the happy cases and releasing a product that will fail when put to broader uses.

Enjoy!