Snuffleupagus Oriented Programming
3 years, 11 months ago
In Episode 152, Casey explained more detail on API design and writing the usage code first. I was pretty surprised to hear this is not how most programmers design their APIs, I always assumed the bad APIs mostly came from other factors (perhaps I have too much faith.. or perhaps I'm a bit naive). Well perhaps that's not strictly true...I guess I have several points.
This post is long. Sorry! I will try and make it short by writing in authoritative sentences. Pretend I'm saying it in a questioning and exporatative tone, or maybe a squeaky voice. Whatever floats your goat.
It feels like the conflicting concerns I encounter are: the level of abstraction the API has and how closely the API ties in to a particular use. For ease of use, you want abstraction to be at a high level (so your usage code doesn't need to care about the implementation details). For flexibility, you want the API to expose enough to fit all the uses you need it to fill (you want your API be used more than once).
Understand the uses
The file loading API Casey made for the asset loader has nicely abstracted away lots of things that the asset system doesn't need to care about. That ties it somewhat to usage code that also doesn't care about those things.
Here we understand the uses really well, and even if we come up with a new thing that doesn't fit, we can change the API (or make a new one, if necessary). That change might break the existing usage code, or not. It's okay, we can fix that too.
So you start out as high level as you need and then you refine to provide enough flexibility to cover your uses.
Understand the uses you want to support with the API. That feeds into writing the usage code for those. Then that usage code feeds into the API (or APIs) you implement. And then you can iterate back and forth to fix any constraints.
So you have an API, it fits all your uses nicely. If you publish the API for other people to use (like a library or OS API) then there are some extra things to worry about.
There's a reasonable use of your API that you want to support that requires it to change. You could make the change the way you want, but it will break existing usage code. You could make a slightly different, more awkward, change that doesn't break existing code but gives you a slightly less nice API. You could go back in time and write the API differently so that you wouldn't have to make this choice.
Anticipate the uses?
You are defining your API and you've covered all your uses. Should you try to think up some hypothetical uses that you might want to support and add them to your API? A little more generality won't hurt, just a wafer-thin piece.
To be sure, you may have some reasonable insight into where the API might go, and without pandering to it you could make some zero-cost choices that don't unduly lock away those routes. But. And here, I have a sizeable but.
Some APIs can't decide what uses they want to cater for and are so generally applicable that they are not easy to use (or performant) for any reasonable thing you might want to achieve. They make too few assumptions about what you want to do. This makes them hard to understand, hard to use and generally just bad.
If you want your API to make sense, pin it the f**k down. Otherwise you too could end up with a loosely coupled masterpiece of gibberish. You may have heard that any configuaration file format eventually tends towards being a programming language. I find that an interesting observation, but probably not relevant. Forget I said it.
It's somewhat understandable (but maybe not forgiveable?) for widely used libraries (particularly APIs in OSes) to have APIs that end up hard to use/crufty, as they can be: widely used with very varied uses, usually want to remain back-compatible and not break existing code, and live on for a long time (where whole new things arise that are hard to forsee, like new technologies and architectures with completely different constraints.)
When you have control over your usage and API implementation code and when you are free to introduce breaking changes there should be enough freedom to solve these issues.
Understand the constraints
Platform layer APIs. When you have an API with different possibly implementations bound by potentially different constraints, you need to understand the constraints of all the target implementations (at some point).
It may be that an API that is fine on one platform, is difficult, inaccurate or slow when implemented on another platform due to differences in constraints between platforms. If you understand your target platforms (like understanding your uses) you can fix those problems.
(Aside: If anyone knows a decent way to get a time since epoch at millisecond granularity on Windows, let me know. My current implementation can go back in time. Which is useful if I need to go back an fix my API.)
If the API design could anticipate ALL the uses and write all the usage code first then that would solve a lot of API issues. The problems I'm describing come down to not exhaustively knowing/anticipating what ALL the possible usage code would be -- now and into the future. And complicated with not finding good ways to modify/fix bad APIs.
PS I swore in this post. I hardly ever swear. I know I bleeped it out. But I still feel guilty all the same.