API granularity
4/Dec 2019
I’d be the first to admit I don’t have much experience designing public APIs. I typically work on code that’s fairly specific to the game we’re making and while some of it is expected to be reused, our potential user pool is very limited, we’re still talking just one team, so <40 people and definitely not thousands. I’m still successfully using some little utilities/helpers I designed/coded 10+ years ago, but every now and then I run into a situation where decisions taken back then catch up with me and force to rewrite the API.
Enter MemTracer. It’s almost 15 years old, but for the most part still serves its purpose. I can’t help but notice however, that the API “granularity” is waaaay too fine. I exposed (and expected users to implement) every single socket function for example (create/accept/listen/close/etc). There is two reasons for this: a) I simply didn’t know any better back then and b) it was mostly designed using a bottom-up methodology. I tried to get it up and running first without thinking too much about how is it going to be used. Recently, I was trying to port it to yet another platform and eventually got very fed up with all the wrappers I had to implement only to call them once. What the MemTracer thread really needs is just 1 thing: to establish a connection with a host. Sometimes it might not even need sockets (or at least not use them directly… different platforms might expose other ways to establish a connection with a host PC). As long as we connect and get a handle we can use for writing, we’re good. We are not writing a socket library, after all, it’s a memory debugger.
It does not mean I removed code, it’s still there, just public API has fewer functions. MemTracer initialization used to call 4-5 different functions to create socket/listen/connect, now it’s all been replaced with a single call to MemTracer::Socket::EstablishConnection and everything inside is an implemention details. It returns an opaque handle we use for communication, but we know nothing about it (nor do we care). For details – here’s a link to Github CL.
We gained exactly nothing by exposing these individual functions, there was exactly 1 way/order of calling them. I probably would have avoided that trap even 15 years ago if I had designed the public API first (ie. write the client code, even if it doesn’t compile, add stubs, implement). Stating the obvious, but we should strive to make our libraries as easy to use as possible, any extra function users have to call is a hoop they have to jump through.