Marcelo's WebLog

Improving the world one entity at a time (now tweeting on @mlrdev)

API Design Rule: First, Don't Lie

API Design Rule: First, Don't Lie

  • Comments 5

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!

  • I agree... I have encountered such liar methods in code I have used in the past. I can remember a GetSomething() method that returned 'something', and as a side-effect wrote some file on disk...

    Well, in one case I was forced to lie: to work around an issue in System.Windows.Forms.Control.Invoke(), I had to override the GetBaseException() of the Exception class (more details here:  blurredbytes.blogspot.com/.../controlinvoke-and-exceptions-part-2.html ). I still feel guilty, though!

  • Read your blog post - good information to have! I always found exception propagation across threads to be a bit "glitchy", in that there are no good uniform ways to handle. I have hopes for System.AggregateException to get wider adoption in cases where you really want a wrapping exception, especially when other things may go wrong while moving that very exception around and so on.

  • I wholeheartedly agree!  You might have gone on to the next step and offer some suggestions for changing the naming conventions in your examples.

  • @dshoyt - fair enough - I agree there are some interesting things to explore there. In particular with the last one, where there are at least three ways of slicing it, and they shift "blame" around differently. Thanks for the suggestion! I'll be queuing this up for next week then :)

  • Quite subtle but very much valid in the present day and context.

Page 1 of 1 (5 items)