My API Design Mantras

This morning I read an interesting article by Charlie Kindel: “Don’t build API’s”. He talks about some of the issues with customers using your API in ways that you never thought they would be using it, and how you forever are stuck with supporting these ‘almost unsupported’ use-cases. I wouldn’t go as far as saying you shouldn’t be building an API at all. After all if your product is an SDK, this is what you would have to do Smile.

I love working on API design and have been fortunate enough to be able to do this for several years now on a daily basis. I’ve gotten caught by similar issues that Kindel experienced as well. So over time, I have built myself a couple of mantras when I work on an API, and it generally is about how ‘tight’ or ‘loose’ you design your API. A very tight API has very few public methods and extensibility points, where a very loose API exposes everything and allows everything to be extended. My mantras are:

Unless there’s a solid and strong use-case for it:

    • …don’t make any methods, events or properties public.
    • …don’t allow people to create instances of a class (keep your constructor internal or private).
    • …seal your classes to prevent inheritance.

In other words: ‘private’ and ‘sealed’ are the keywords I think of first, next ‘internal’, next ‘protected’ (if class isn’t sealed), and lastly (after a lot of thought) ‘public’. ‘virtual’ comes even later. An example of this could be an event argument for an event that only your control will be raising. There’s rarely a reason to create a public constructor1. The class won’t be inherited from, so mark it sealed. Lastly the properties rarely will have any public setters either (the common use-case there is the .Handled property you can set to prevent bubbling). A great side-effect of tightening your API is that your API is smaller, leaner and meaner. The user of your API won’t have to browse a gazillion members to find the method he needs to use. It’ll be easier for him to understand your API, hit the things he’s supposed to hit, and stay out of the parts that could shoot himself in the foot.

I’m not saying you shouldn’t make anything public (it wouldn’t be an API if you didn’t). What I’m saying is that there needs to be a great and well-understood use-case for each and every one. As Kindel points out, you have to be VERY aware that any public method WILL be used in a way you didn’t think of. And the moment you made it public, you will have to support it forever.

So what I’m saying is that if I can’t think of a solid real-world use-case, making something publicly accessible wouldn’t be my first choice. You can always come up with random use-cases, but are they real-world scenarios? Are they best practices? Would the be a better way to achieve a certain use-case? If you can’t think of any, I would recommend keeping it tight, until that use-case shows up and you can get to understand it. Unless it saves a lot of customers a lot of time (which is essentially the purpose of an API), “Nice-to-have” features are unimportant. Focus on the “must-haves” first.

Any private and internal code you have is used in a finite set of use-cases2. You know the code that calls these methods, and they are in your full control. If a customer comes to you and wants access to these parts of the code, I generally first:

  1. Try to fully understand what the user is trying to achieve. Is there a better way / best practice? Is he really just trying to work around a feature that’s missing from your API, and if so would it be better to provide that instead of opening up an internal API? (generally internal methods are merely a set of helper/utility methods that helps supporting the public API). This is why it’s so important to fully understand the use-case.
  2. If you loosen existing internal parts your API, remember that so far this part of your code has only been used by you. When it was designed it might not have gone through the thought process that it requires for being used for any number of unknown use-cases. Using it wrong could very likely destabilize your code. Perhaps your code wasn’t originally designed for this type of use at all (we of course try, but everyone cuts corners now and then, or avoids unnecessary checks for performance reasons). Also do you need to do some MAJOR refactoring first? Is it worth the risk and is the demand large enough? Ie if there’s a huge risk and small demand, is there a workaround the customer can use as an alternative?

Let me say it again: You will have to support your public API forever! No more refactoring. No more major reworking to increase performance. No more major code cleanup. What you just did is now forever.

--------------------------------------------------------------------------------

1 In some unit-testing scenarios being able to create types are useful (luckily .NET lets you do this through reflection, and Visual Studio’s test suite helps you do it). Generally though you should be unit-testing your code, and your customer should just be testing their code.

2 Your internal API is also important - Keep things private unless ‘internal’ is really warranted, and try and make sure it can’t be misused - again as software evolves, your internal use might change and you will use your software in ways you didn’t originally think of - but at least here you’re not tied by ‘forever’ supporting it.

Comments (8) -

  • OK, so, basically, you say we should all program like we are using a modular programming language, not an object-oriented one? I mean, it is a valid point, but it still sounds like forgetting about everything an OO language offers.
  • I completely agree that an API should be well conceived, but sealing everything and even having no public constructors is throwing the baby out with the bathwater. I mean, can you imagine how much less fun it would be to, say, use the .NET framework if every object were designed this way? Sometimes you come across parts of it that are sealed, and it's nothing short of annoying.  So of course you just end up using reflection, or create instances without using the constructor, or some other hack or other to get around the artificial restriction. The problem is still there, and if anything, you've just made it worse while annoying the people you should be catering to: your power users.

    So I guess I would say while this will keep you safe from a few edge cases of people pushing the boundaries, in the end, instead of having people push the boundaries, you will have people frustrated by artificial limitations, or alternatively, people taking the screws off so they can get around your artificial restriction.

    Is that really better? Why not just let people use an object like an object, and assume the risks? Sure, design your API well, but don't treat your users like babies either.
  • I think that, it would be nice to have goooood manuals, just tu survive the api, and also there should be way more
    examples with MSDN and so on....
    Thanks
  • Emanuel and Jamie:
    I'm NOT saying you should seal everything and remove all constructors. As I said it it wouldn't be an API if you did. What I'm saying is that there should be a reason and well understood use case for doing that. Actually in most cases there is. But there are also reasons to not do that (why else would we have the private, internal and protected keywords?). I see too many APIs bloated with stuff that the dev doesn't need, and it complicated the API and takes more time to learn to use. Bugs are also way more prevalent.
  • The other reason to be careful in what you make public is to avoid having to make stuff like this part of your public documentation because you can't make it go away without breaking the code of anyone who was (ab)using it for something.

    "This class supports the .NET Framework infrastructure and is not intended to be used directly from your code.
    This type does nothing meaningful. "

    msdn.microsoft.com/.../...sfactory%28VS.80%29.aspx
  • Nicogis: FXCop is great and all, but it is just a helper tool, and can only guess and make suggestions. It's by no means a replacement for thinking your API design through.
  • Thanks Morten for sharing your thoughts on API design. I actually have a polar opposite point of view on this. I have been stumped too many times when extending third-party APIs and helping colleagues write unit tests to be able to support your point of view. Please don't clip the wings of your API users. I prefer the default to be public classes, virtual methods, and no sealed classes. Selectively, we can restrict our artifacts as needed.

    I do agree that a lot of thought must go into API design and the visibility of classes and methods although I find my views on the opposite end of the spectrum from yours. Thanks again.

Add comment