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 .
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:
- 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.
- 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.